diff --git a/primitives/transaction/src/historic_transaction.rs b/primitives/transaction/src/historic_transaction.rs index 81ee4f9086..3a4327770c 100644 --- a/primitives/transaction/src/historic_transaction.rs +++ b/primitives/transaction/src/historic_transaction.rs @@ -10,13 +10,10 @@ use nimiq_hash::{Blake2bHash, Blake2bHasher, Hash, Hasher}; use nimiq_hash_derive::SerializeContent; use nimiq_keys::Address; use nimiq_mmr::hash::Hash as MMRHash; -use nimiq_primitives::{coin::Coin, networks::NetworkId, policy::Policy}; +use nimiq_primitives::{coin::Coin, networks::NetworkId}; use nimiq_serde::{Deserialize, Serialize}; -use crate::{ - inherent::Inherent, EquivocationLocator, ExecutedTransaction, - Transaction as BlockchainTransaction, -}; +use crate::{inherent::Inherent, EquivocationLocator, ExecutedTransaction}; /// The raw transaction hash is a type wrapper. /// This corresponds to the hash of the transaction without the execution result. @@ -294,29 +291,6 @@ impl HistoricTransaction { _ => self.executed_tx_hash().hash::().into(), } } - - /// Tries to convert an historic transaction into a regular transaction. This will work for all - /// historic transactions that wrap over regular transactions and reward inherents. - pub fn into_transaction(self) -> Result { - match self.data { - HistoricTransactionData::Basic(tx) => Ok(tx), - HistoricTransactionData::Reward(ev) => { - Ok(ExecutedTransaction::Ok(BlockchainTransaction::new_basic( - Policy::COINBASE_ADDRESS, - ev.reward_address, - ev.value, - Coin::ZERO, - self.block_number, - self.network_id, - ))) - } - HistoricTransactionData::Penalize(_) - | HistoricTransactionData::Jail(_) - | HistoricTransactionData::Equivocation(_) => { - Err(IntoTransactionError::NoBasicTransactionMapping) - } - } - } } impl MMRHash for HistoricTransaction { diff --git a/rpc-interface/src/types.rs b/rpc-interface/src/types.rs index 0d441e25e9..332fcfa6c4 100644 --- a/rpc-interface/src/types.rs +++ b/rpc-interface/src/types.rs @@ -273,7 +273,7 @@ impl Block { tx, block_number, micro_block.header.timestamp, - head_height, + Some(head_height), ) }) .collect(), @@ -526,7 +526,7 @@ impl ExecutedTransaction { transaction: nimiq_transaction::ExecutedTransaction, block_number: u32, timestamp: u64, - head_height: u32, + head_height: Option, ) -> Self { // We obtain an internal executed transaction // We need to grab the internal transaction and map it to the RPC transaction structure @@ -542,6 +542,47 @@ impl ExecutedTransaction { }, } } + pub fn from_reward_event( + ev: RewardEvent, + hash: Blake2bHash, + network: NetworkId, + block_number: u32, + timestamp: u64, + cur_block_height: Option, + ) -> ExecutedTransaction { + ExecutedTransaction { + transaction: Transaction::from_reward_event( + ev, + hash, + network, + block_number, + timestamp, + cur_block_height, + ), + execution_result: true, + } + } + pub fn try_from_historic_transaction( + tx: HistoricTransaction, + cur_block_height: Option, + ) -> Option { + Some(match tx.data { + HistoricTransactionData::Basic(inner) => { + Self::from_blockchain(inner, tx.block_number, tx.block_time, cur_block_height) + } + HistoricTransactionData::Reward(ref ev) => Self::from_reward_event( + ev.clone(), + tx.tx_hash().into(), + tx.network_id, + tx.block_number, + tx.block_time, + cur_block_height, + ), + HistoricTransactionData::Penalize(_) => return None, + HistoricTransactionData::Jail(_) => return None, + HistoricTransactionData::Equivocation(_) => return None, + }) + } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -582,13 +623,13 @@ impl Transaction { transaction: nimiq_transaction::Transaction, block_number: u32, timestamp: u64, - head_height: u32, + head_height: Option, ) -> Self { Transaction::from( transaction, Some(block_number), Some(timestamp), - Some(head_height), + head_height, ) } @@ -621,6 +662,35 @@ impl Transaction { network_id: transaction.network_id as u8, } } + + pub fn from_reward_event( + ev: RewardEvent, + hash: Blake2bHash, + network: NetworkId, + block_number: u32, + timestamp: u64, + cur_block_height: Option, + ) -> Transaction { + Transaction { + hash, + block_number: Some(block_number), + timestamp: Some(timestamp), + confirmations: cur_block_height.map(|h| h - block_number), + size: ev.serialized_size(), // TODO: changed + from: Policy::COINBASE_ADDRESS, + from_type: 0, + to: ev.reward_address.clone(), + to_type: 0, + value: ev.value, + fee: Coin::ZERO, + sender_data: vec![], + recipient_data: vec![], + flags: 0, + validity_start_height: block_number, + proof: vec![], + network_id: network as u8, + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/rpc-server/src/dispatchers/blockchain.rs b/rpc-server/src/dispatchers/blockchain.rs index c12fbf0dd2..c3c65ffc35 100644 --- a/rpc-server/src/dispatchers/blockchain.rs +++ b/rpc-server/src/dispatchers/blockchain.rs @@ -156,36 +156,26 @@ impl BlockchainInterface for BlockchainDispatcher { ) -> RPCResult { if let BlockchainReadProxy::Full(blockchain) = self.blockchain.read() { // Get all the historic transactions that correspond to this hash. - let mut historic_tx_vec = blockchain.history_store.get_hist_tx_by_hash(&hash, None); + let mut hist_txs = blockchain.history_store.get_hist_tx_by_hash(&hash, None); // Unpack the transaction or raise an error. - let historic_tx = match historic_tx_vec.len() { - 0 => { - return Err(Error::TransactionNotFound(hash)); - } - 1 => historic_tx_vec.pop().unwrap(), - _ => { - return Err(Error::MultipleTransactionsFound(hash)); - } - }; + if hist_txs.len() > 1 { + return Err(Error::MultipleTransactionsFound(hash)); + } + let hist_tx = hist_txs + .pop() + .ok_or_else(|| Error::TransactionNotFound(hash.clone()))?; // Convert the historic transaction into a regular transaction. This will also convert // reward inherents. - let block_number = historic_tx.block_number; - let timestamp = historic_tx.block_time; - - return match historic_tx.into_transaction() { - Ok(tx) => Ok(ExecutedTransaction::from_blockchain( - tx, - block_number, - timestamp, - blockchain.block_number(), - ) - .into()), - Err(_) => Err(Error::TransactionNotFound(hash)), - }; + Ok(ExecutedTransaction::try_from_historic_transaction( + hist_tx, + Some(blockchain.block_number()), + ) + .ok_or_else(|| Error::TransactionNotFound(hash.clone()))? + .into()) } else { - return Err(Error::NotSupportedForLightBlockchain); + Err(Error::NotSupportedForLightBlockchain) } } @@ -195,30 +185,21 @@ impl BlockchainInterface for BlockchainDispatcher { ) -> RPCResult, (), Self::Error> { if let BlockchainReadProxy::Full(blockchain) = self.blockchain.read() { // Get all the historic transactions that correspond to this block. - let historic_tx_vec = blockchain + let hist_txs = blockchain .history_store .get_block_transactions(block_number, None); - // Get the timestamp of the block from one of the historic transactions. This complicated - // setup is because we might not have any transactions. - let timestamp = historic_tx_vec.first().map(|x| x.block_time).unwrap_or(0); - - // Convert the historic transactions into regular transactions. This will also convert - // reward inherents. - let mut transactions = vec![]; - - for hist_tx in historic_tx_vec { - if let Ok(tx) = hist_tx.into_transaction() { - transactions.push(ExecutedTransaction::from_blockchain( - tx, - block_number, - timestamp, - blockchain.block_number(), - )); - } - } - - Ok(transactions.into()) + let cur_block_height = blockchain.block_number(); + let result: Vec<_> = hist_txs + .into_iter() + .filter_map(|hist_tx| { + ExecutedTransaction::try_from_historic_transaction( + hist_tx, + Some(cur_block_height), + ) + }) + .collect(); + Ok(result.into()) } else { Err(Error::NotSupportedForLightBlockchain) } @@ -265,25 +246,15 @@ impl BlockchainInterface for BlockchainDispatcher { // Search all micro blocks of the batch to find the transactions. let mut transactions = vec![]; + let cur_block_height = blockchain.block_number(); for i in first_block..=last_block { let hist_txs = blockchain.history_store.get_block_transactions(i, None); - - // Get the timestamp of the block from one of the historic transactions. This complicated - // setup is because we might not have any transactions. - let timestamp = hist_txs.first().map(|x| x.block_time).unwrap_or(0); - - // Convert the historic transactions into regular transactions. This will also convert - // reward inherents. - for hist_tx in hist_txs { - if let Ok(tx) = hist_tx.into_transaction() { - transactions.push(ExecutedTransaction::from_blockchain( - tx, - i, - timestamp, - blockchain.block_number(), - )); - } - } + transactions.extend(hist_txs.into_iter().filter_map(|hist_tx| { + ExecutedTransaction::try_from_historic_transaction( + hist_tx, + Some(cur_block_height), + ) + })); } Ok(transactions.into()) @@ -367,32 +338,25 @@ impl BlockchainInterface for BlockchainDispatcher { for hash in tx_hashes { // Get all the historic transactions that correspond to this hash. - let mut historic_tx_vec = blockchain.history_store.get_hist_tx_by_hash(&hash, None); + let mut hist_txs = blockchain.history_store.get_hist_tx_by_hash(&hash, None); // Unpack the transaction or raise an error. - let historic_tx = match historic_tx_vec.len() { - 0 => { - return Err(Error::TransactionNotFound(hash)); - } - 1 => historic_tx_vec.pop().unwrap(), - _ => { - return Err(Error::MultipleTransactionsFound(hash)); - } - }; + if hist_txs.len() > 1 { + return Err(Error::MultipleTransactionsFound(hash)); + } + let hist_tx = hist_txs + .pop() + .ok_or_else(|| Error::TransactionNotFound(hash.clone()))?; // Convert the historic transaction into a regular transaction. This will also convert // reward inherents. - let block_number = historic_tx.block_number; - let timestamp = historic_tx.block_time; - - if let Ok(tx) = historic_tx.into_transaction() { - txs.push(ExecutedTransaction::from_blockchain( - tx, - block_number, - timestamp, - blockchain.block_number(), - )); - } + txs.push( + ExecutedTransaction::try_from_historic_transaction( + hist_tx, + Some(blockchain.block_number()), + ) + .ok_or_else(|| Error::TransactionNotFound(hash.clone()))?, + ) } Ok(txs.into()) diff --git a/web-client/src/client/lib.rs b/web-client/src/client/lib.rs index 7e2ba287b3..fe74f364e9 100644 --- a/web-client/src/client/lib.rs +++ b/web-client/src/client/lib.rs @@ -622,10 +622,11 @@ impl Client { .into_iter() .next() .map(|hist_tx| { - PlainTransactionDetails::from_historic_transaction( - &hist_tx, + PlainTransactionDetails::try_from_historic_transaction( + hist_tx, self.inner.blockchain_head().block_number(), ) + .expect("no non-reward inherent") }) .ok_or_else(|| JsError::new("Transaction not found"))?; Ok(serde_wasm_bindgen::to_value(&details)?.into()) @@ -733,7 +734,8 @@ impl Client { let plain_tx_details: Vec<_> = transactions .into_iter() .map(|hist_tx| { - PlainTransactionDetails::from_historic_transaction(&hist_tx, current_height) + PlainTransactionDetails::try_from_historic_transaction(hist_tx, current_height) + .expect("no non-reward inherent") }) .collect(); @@ -1038,19 +1040,11 @@ impl Client { for hist_tx in hist_txs { let block_number = hist_tx.block_number; - let block_time = hist_tx.block_time; - - let exe_tx = hist_tx.into_transaction().unwrap(); - let tx = exe_tx.get_raw_transaction(); - - let details = PlainTransactionDetails::new( - &Transaction::from(tx.clone()), - TransactionState::Included, - Some(exe_tx.succeeded()), - Some(block_number), - Some(block_time), - Some(1), - ); + let details = PlainTransactionDetails::try_from_historic_transaction( + hist_tx, + block_number, + ) + .expect("no non-reward inherent"); if let Some(sender) = transaction_oneshots .borrow_mut() @@ -1059,21 +1053,24 @@ impl Client { let _ = sender.send(details.clone()); } + fn from_user(addr: &str) -> nimiq_keys::Address { + nimiq_keys::Address::from_user_friendly_address(addr).unwrap() + } + + let sender = from_user(&details.transaction.sender); + let recipient = from_user(&details.transaction.recipient); let staker_address = if let PlainTransactionRecipientData::AddStake(data) = &details.transaction.data { - Some( - nimiq_keys::Address::from_user_friendly_address(&data.staker) - .unwrap(), - ) + Some(from_user(&data.staker)) } else { None }; if let Ok(js_value) = serde_wasm_bindgen::to_value(&details) { for (listener, addresses) in transaction_listeners.borrow().values() { - if addresses.contains(&tx.sender) - || addresses.contains(&tx.recipient) + if addresses.contains(&sender) + || addresses.contains(&recipient) || if let Some(ref address) = staker_address { addresses.contains(address) } else { diff --git a/web-client/src/transaction.rs b/web-client/src/transaction.rs index 38a2a013e7..f4a65eddf6 100644 --- a/web-client/src/transaction.rs +++ b/web-client/src/transaction.rs @@ -6,8 +6,6 @@ use nimiq_keys::{PublicKey, Signature}; use nimiq_primitives::policy::Policy; use nimiq_primitives::{account::AccountType, coin::Coin, networks::NetworkId}; use nimiq_serde::{Deserialize, Serialize}; -#[cfg(feature = "client")] -use nimiq_transaction::historic_transaction::HistoricTransaction; use nimiq_transaction::{ account::{ htlc_contract::{ @@ -19,6 +17,11 @@ use nimiq_transaction::{ }, SignatureProof, TransactionFlags, TransactionFormat, }; +#[cfg(feature = "client")] +use nimiq_transaction::{ + historic_transaction::{HistoricTransaction, HistoricTransactionData, RewardEvent}, + ExecutedTransaction, +}; #[cfg(feature = "primitives")] use nimiq_transaction_builder::TransactionProofBuilder; #[cfg(feature = "client")] @@ -1019,6 +1022,41 @@ pub struct PlainTransaction { pub valid: bool, } +#[cfg(feature = "client")] +impl PlainTransaction { + pub fn from_reward_event( + ev: RewardEvent, + hash: Blake2bHash, + network: NetworkId, + block_number: u32, + ) -> PlainTransaction { + let size = ev.serialized_size(); + PlainTransaction { + transaction_hash: hash.to_hex(), + format: TransactionFormat::Basic, // TODO: bogus: not encoded as transaction on the block chain + sender: Address::from(Policy::COINBASE_ADDRESS).to_plain(), + sender_type: AccountType::Basic, + recipient: Address::from(ev.reward_address).to_plain(), + recipient_type: AccountType::Basic, + value: ev.value.into(), + fee: Coin::ZERO.into(), + fee_per_byte: 0.0, + validity_start_height: block_number, + network: network.to_string().to_lowercase(), + flags: 0, + sender_data: PlainTransactionSenderData::Raw(PlainRawData { + raw: String::from(""), + }), + data: PlainTransactionRecipientData::Raw(PlainRawData { + raw: String::from(""), + }), + proof: PlainTransactionProof::Empty(PlainEmptyProof {}), + size, + valid: true, + } + } +} + /// Describes the state of a transaction as known by the client. #[cfg(feature = "client")] #[derive(Clone, serde::Serialize, serde::Deserialize, Tsify)] @@ -1125,9 +1163,11 @@ impl PlainTransactionDetails { confirmations, } } - /// Creates a PlainTransactionDetails struct that can be serialized to JS from a native [HistoricTransaction]. - pub fn from_historic_transaction(hist_tx: &HistoricTransaction, current_block: u32) -> Self { + pub fn try_from_historic_transaction( + hist_tx: HistoricTransaction, + current_block: u32, + ) -> Option { let block_number = hist_tx.block_number; let block_time = hist_tx.block_time; @@ -1139,17 +1179,35 @@ impl PlainTransactionDetails { TransactionState::Included }; - let executed_transaction = hist_tx.clone().into_transaction().unwrap(); + let (succeeded, transaction) = match hist_tx.data { + HistoricTransactionData::Basic(ExecutedTransaction::Ok(inner)) => { + (true, Transaction::from(inner).to_plain_transaction()) + } + HistoricTransactionData::Basic(ExecutedTransaction::Err(inner)) => { + (false, Transaction::from(inner).to_plain_transaction()) + } + HistoricTransactionData::Reward(ref ev) => ( + true, + PlainTransaction::from_reward_event( + ev.clone(), + hist_tx.tx_hash().into(), + hist_tx.network_id, + hist_tx.block_number, + ), + ), + HistoricTransactionData::Penalize(_) => return None, + HistoricTransactionData::Jail(_) => return None, + HistoricTransactionData::Equivocation(_) => return None, + }; - Self { - transaction: Transaction::from(executed_transaction.get_raw_transaction().clone()) - .to_plain_transaction(), + Some(PlainTransactionDetails { + transaction, state, - execution_result: Some(executed_transaction.succeeded()), + execution_result: Some(succeeded), block_height: Some(block_number), timestamp: Some(block_time), confirmations: Some(current_block - block_number + 1), - } + }) } }