From 820c35db9000e06182fafc539e2c83078aad48f8 Mon Sep 17 00:00:00 2001 From: arya2 Date: Wed, 9 Nov 2022 21:40:12 -0500 Subject: [PATCH 1/6] Uses BestChainUtxo to find utxos for mempool --- zebra-consensus/src/error.rs | 3 +++ zebra-consensus/src/transaction.rs | 11 ++++++++++- zebra-state/src/request.rs | 11 +++++++++++ zebra-state/src/response.rs | 6 +++++- zebra-state/src/service.rs | 1 + 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index 4e08620b729..f4ed073f3f2 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -177,6 +177,9 @@ pub enum TransactionError { #[error("must have at least one active orchard flag")] NotEnoughFlags, + + #[error("could not find an input UTXO in best chain")] + InputNotFound, } impl From for TransactionError { diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index c10111fb1a7..d8e6ab8a679 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -356,8 +356,9 @@ where // https://zips.z.cash/zip-0213#specification // Load spent UTXOs from state. + // TODO: Make this a method of `Request` and replace `tx.clone()` with `self.transaction()`? let (spent_utxos, spent_outputs) = - Self::spent_utxos(tx.clone(), req.known_utxos(), state).await?; + Self::spent_utxos(tx.clone(), req.known_utxos(), req.is_mempool(), state).await?; let cached_ffi_transaction = Arc::new(CachedFfiTransaction::new(tx.clone(), spent_outputs)); @@ -464,6 +465,7 @@ where async fn spent_utxos( tx: Arc, known_utxos: Arc>, + is_mempool: bool, state: Timeout, ) -> Result< ( @@ -481,6 +483,13 @@ where let utxo = if let Some(output) = known_utxos.get(outpoint) { tracing::trace!("UXTO in known_utxos, discarding query"); output.utxo.clone() + } else if is_mempool { + let query = state.clone().oneshot(zs::Request::BestChainUtxo(*outpoint)); + if let zebra_state::Response::BestChainUtxo(utxo) = query.await? { + utxo.ok_or(TransactionError::InputNotFound)? + } else { + unreachable!("BestChainUtxo always responds with Option") + } } else { let query = state .clone() diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 87831c0719f..25fca397677 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -458,6 +458,15 @@ pub enum Request { /// * [`Response::Transaction(None)`](Response::Transaction) otherwise. Transaction(transaction::Hash), + /// Looks up a UTXO identified by the given [`OutPoint`](transparent::OutPoint), + /// returning `None` immediately if it is unknown. + /// + /// Checks verified blocks in the finalized chain and the _best_ non-finalized chain. + /// + /// This request is purely informational, there is no guarantee that + /// the UTXO remains unspent in the best chain. + BestChainUtxo(transparent::OutPoint), + /// Looks up a block by hash or height in the current best chain. /// /// Returns @@ -545,6 +554,7 @@ impl Request { Request::Tip => "tip", Request::BlockLocator => "block_locator", Request::Transaction(_) => "transaction", + Request::BestChainUtxo { .. } => "best_chain_utxo", Request::Block(_) => "block", Request::FindBlockHashes { .. } => "find_block_hashes", Request::FindBlockHeaders { .. } => "find_block_headers", @@ -789,6 +799,7 @@ impl TryFrom for ReadRequest { Request::Block(hash_or_height) => Ok(ReadRequest::Block(hash_or_height)), Request::Transaction(tx_hash) => Ok(ReadRequest::Transaction(tx_hash)), + Request::BestChainUtxo(outpoint) => Ok(ReadRequest::BestChainUtxo(outpoint)), Request::BlockLocator => Ok(ReadRequest::BlockLocator), Request::FindBlockHashes { known_blocks, stop } => { diff --git a/zebra-state/src/response.rs b/zebra-state/src/response.rs index 68bfafda5ef..8ea09f1837c 100644 --- a/zebra-state/src/response.rs +++ b/zebra-state/src/response.rs @@ -36,6 +36,9 @@ pub enum Response { /// Response to [`Request::Transaction`] with the specified transaction. Transaction(Option>), + /// Response to [`Request::BestChainUtxo`] with the UTXO + BestChainUtxo(Option), + /// Response to [`Request::Block`] with the specified block. Block(Option>), @@ -132,6 +135,8 @@ impl TryFrom for Response { ReadResponse::Transaction(tx_and_height) => { Ok(Response::Transaction(tx_and_height.map(|(tx, _height)| tx))) } + ReadResponse::BestChainUtxo(utxo) => Ok(Response::BestChainUtxo(utxo)), + ReadResponse::AnyChainUtxo(_) => Err("ReadService does not track pending UTXOs. \ Manually unwrap the response, and handle pending UTXOs."), @@ -141,7 +146,6 @@ impl TryFrom for Response { ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)), ReadResponse::TransactionIdsForBlock(_) - | ReadResponse::BestChainUtxo(_) | ReadResponse::SaplingTree(_) | ReadResponse::OrchardTree(_) | ReadResponse::AddressBalance(_) diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index 1b457f24a76..b583d6198fb 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -1021,6 +1021,7 @@ impl Service for StateService { | Request::Tip | Request::BlockLocator | Request::Transaction(_) + | Request::BestChainUtxo(_) | Request::Block(_) | Request::FindBlockHashes { .. } | Request::FindBlockHeaders { .. } => { From 10b73bc271d45723d531d1df2a53c5ac84cdcb02 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 10 Nov 2022 14:55:35 -0500 Subject: [PATCH 2/6] adds missing input test --- zebra-chain/src/transaction/arbitrary.rs | 30 +++++++++----------- zebra-consensus/src/transaction/tests.rs | 35 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/zebra-chain/src/transaction/arbitrary.rs b/zebra-chain/src/transaction/arbitrary.rs index 8413aed4fcc..8098b51eaed 100644 --- a/zebra-chain/src/transaction/arbitrary.rs +++ b/zebra-chain/src/transaction/arbitrary.rs @@ -930,16 +930,7 @@ pub fn test_transactions( Network::Testnet => zebra_test::vectors::TESTNET_BLOCKS.iter(), }; - blocks.flat_map(|(&block_height, &block_bytes)| { - let block = block_bytes - .zcash_deserialize_into::() - .expect("block is structurally valid"); - - block - .transactions - .into_iter() - .map(move |transaction| (block::Height(block_height), transaction)) - }) + transactions_from_blocks(blocks) } /// Generate an iterator over fake V5 transactions. @@ -950,18 +941,23 @@ pub fn fake_v5_transactions_for_network<'b>( network: Network, blocks: impl DoubleEndedIterator + 'b, ) -> impl DoubleEndedIterator + 'b { - blocks.flat_map(move |(height, original_bytes)| { - let original_block = original_bytes + transactions_from_blocks(blocks) + .map(move |(height, transaction)| transaction_to_fake_v5(&transaction, network, height)) +} + +/// Generate an iterator over ([`block::Height`], [`Arc`]). +pub fn transactions_from_blocks<'a>( + blocks: impl DoubleEndedIterator + 'a, +) -> impl DoubleEndedIterator)> + 'a { + blocks.flat_map(|(&block_height, &block_bytes)| { + let block = block_bytes .zcash_deserialize_into::() .expect("block is structurally valid"); - original_block + block .transactions .into_iter() - .map(move |transaction| { - transaction_to_fake_v5(&transaction, network, block::Height(*height)) - }) - .map(Transaction::from) + .map(move |transaction| (block::Height(block_height), transaction)) }) } diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 8dff572221a..0a5a1f5b4fc 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -19,12 +19,15 @@ use zebra_chain::{ transaction::{ arbitrary::{ fake_v5_transactions_for_network, insert_fake_orchard_shielded_data, test_transactions, + transactions_from_blocks, }, Hash, HashType, JoinSplitData, LockTime, Transaction, }, transparent::{self, CoinbaseData}, }; +use zebra_test::mock_service::MockService; + use crate::error::TransactionError; use super::{check, Request, Verifier}; @@ -177,6 +180,38 @@ fn v5_transaction_with_no_inputs_fails_validation() { ); } +#[tokio::test] +async fn mempool_request_with_missing_input_is_rejected() { + let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests(); + let verifier = Verifier::new(Network::Mainnet, state.clone()); + + let (height, tx) = transactions_from_blocks(zebra_test::vectors::MAINNET_BLOCKS.iter()) + .find(|(_, tx)| !(tx.is_coinbase() || tx.inputs().is_empty())) + .expect("At least one non-coinbase transaction with transparent inputs in test vectors"); + + let expected_state_request = zebra_state::Request::BestChainUtxo(match tx.inputs()[0] { + transparent::Input::PrevOut { outpoint, .. } => outpoint, + transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"), + }); + + tokio::spawn(async move { + state + .expect_request(expected_state_request) + .await + .expect("verifier should call mock state service") + .respond(zebra_state::Response::BestChainUtxo(None)); + }); + + let verifier_response = verifier + .oneshot(Request::Mempool { + transaction: tx.into(), + height, + }) + .await; + + assert_eq!(verifier_response, Err(TransactionError::InputNotFound)); +} + #[test] fn v5_transaction_with_no_outputs_fails_validation() { let transaction = fake_v5_transactions_for_network( From 084602c7b0df1723e14db085d02645d0c1988b39 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 10 Nov 2022 15:56:16 -0500 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: teor --- zebra-consensus/src/error.rs | 4 ++-- zebra-consensus/src/transaction.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/error.rs b/zebra-consensus/src/error.rs index f4ed073f3f2..6a40f032dbf 100644 --- a/zebra-consensus/src/error.rs +++ b/zebra-consensus/src/error.rs @@ -178,8 +178,8 @@ pub enum TransactionError { #[error("must have at least one active orchard flag")] NotEnoughFlags, - #[error("could not find an input UTXO in best chain")] - InputNotFound, + #[error("could not find a mempool transaction input UTXO in the best chain")] + TransparentInputNotFound, } impl From for TransactionError { diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index d8e6ab8a679..f11ff7a82c8 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -480,6 +480,8 @@ where for input in inputs { if let transparent::Input::PrevOut { outpoint, .. } = input { tracing::trace!("awaiting outpoint lookup"); + // Currently, Zebra only supports known UTXOs in block transactions. + // But it might support them in the mempool in future. let utxo = if let Some(output) = known_utxos.get(outpoint) { tracing::trace!("UXTO in known_utxos, discarding query"); output.utxo.clone() From d4a4ff874ee6becc0f677f30795e23892277283a Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 10 Nov 2022 15:54:27 -0500 Subject: [PATCH 4/6] update other instances of the renamed InputNotFound error --- zebra-consensus/src/transaction.rs | 2 +- zebra-consensus/src/transaction/tests.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index f11ff7a82c8..07953fdbe9c 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -488,7 +488,7 @@ where } else if is_mempool { let query = state.clone().oneshot(zs::Request::BestChainUtxo(*outpoint)); if let zebra_state::Response::BestChainUtxo(utxo) = query.await? { - utxo.ok_or(TransactionError::InputNotFound)? + utxo.ok_or(TransactionError::TransparentInputNotFound)? } else { unreachable!("BestChainUtxo always responds with Option") } diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 0a5a1f5b4fc..3c94178d74a 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -209,7 +209,10 @@ async fn mempool_request_with_missing_input_is_rejected() { }) .await; - assert_eq!(verifier_response, Err(TransactionError::InputNotFound)); + assert_eq!( + verifier_response, + Err(TransactionError::TransparentInputNotFound) + ); } #[test] From 5b8eadc66efbebfb6a1cfc2f6180a82fe40b0ed5 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 10 Nov 2022 18:53:57 -0500 Subject: [PATCH 5/6] adds read::unspent_utxo fn --- zebra-consensus/src/transaction.rs | 8 +++++--- zebra-consensus/src/transaction/tests.rs | 4 ++-- zebra-state/src/request.rs | 18 +++++++----------- zebra-state/src/response.rs | 13 +++++-------- zebra-state/src/service.rs | 16 ++++++++++------ zebra-state/src/service/read.rs | 4 +++- zebra-state/src/service/read/block.rs | 16 ++++++++++++++++ 7 files changed, 48 insertions(+), 31 deletions(-) diff --git a/zebra-consensus/src/transaction.rs b/zebra-consensus/src/transaction.rs index 07953fdbe9c..55e6539ca91 100644 --- a/zebra-consensus/src/transaction.rs +++ b/zebra-consensus/src/transaction.rs @@ -486,11 +486,13 @@ where tracing::trace!("UXTO in known_utxos, discarding query"); output.utxo.clone() } else if is_mempool { - let query = state.clone().oneshot(zs::Request::BestChainUtxo(*outpoint)); - if let zebra_state::Response::BestChainUtxo(utxo) = query.await? { + let query = state + .clone() + .oneshot(zs::Request::UnspentBestChainUtxo(*outpoint)); + if let zebra_state::Response::UnspentBestChainUtxo(utxo) = query.await? { utxo.ok_or(TransactionError::TransparentInputNotFound)? } else { - unreachable!("BestChainUtxo always responds with Option") + unreachable!("UnspentBestChainUtxo always responds with Option") } } else { let query = state diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 3c94178d74a..4adcb9e91fa 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -189,7 +189,7 @@ async fn mempool_request_with_missing_input_is_rejected() { .find(|(_, tx)| !(tx.is_coinbase() || tx.inputs().is_empty())) .expect("At least one non-coinbase transaction with transparent inputs in test vectors"); - let expected_state_request = zebra_state::Request::BestChainUtxo(match tx.inputs()[0] { + let expected_state_request = zebra_state::Request::UnspentBestChainUtxo(match tx.inputs()[0] { transparent::Input::PrevOut { outpoint, .. } => outpoint, transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"), }); @@ -199,7 +199,7 @@ async fn mempool_request_with_missing_input_is_rejected() { .expect_request(expected_state_request) .await .expect("verifier should call mock state service") - .respond(zebra_state::Response::BestChainUtxo(None)); + .respond(zebra_state::Response::UnspentBestChainUtxo(None)); }); let verifier_response = verifier diff --git a/zebra-state/src/request.rs b/zebra-state/src/request.rs index 25fca397677..151bb769c39 100644 --- a/zebra-state/src/request.rs +++ b/zebra-state/src/request.rs @@ -462,10 +462,7 @@ pub enum Request { /// returning `None` immediately if it is unknown. /// /// Checks verified blocks in the finalized chain and the _best_ non-finalized chain. - /// - /// This request is purely informational, there is no guarantee that - /// the UTXO remains unspent in the best chain. - BestChainUtxo(transparent::OutPoint), + UnspentBestChainUtxo(transparent::OutPoint), /// Looks up a block by hash or height in the current best chain. /// @@ -554,7 +551,7 @@ impl Request { Request::Tip => "tip", Request::BlockLocator => "block_locator", Request::Transaction(_) => "transaction", - Request::BestChainUtxo { .. } => "best_chain_utxo", + Request::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo", Request::Block(_) => "block", Request::FindBlockHashes { .. } => "find_block_hashes", Request::FindBlockHeaders { .. } => "find_block_headers", @@ -623,10 +620,7 @@ pub enum ReadRequest { /// returning `None` immediately if it is unknown. /// /// Checks verified blocks in the finalized chain and the _best_ non-finalized chain. - /// - /// This request is purely informational, there is no guarantee that - /// the UTXO remains unspent in the best chain. - BestChainUtxo(transparent::OutPoint), + UnspentBestChainUtxo(transparent::OutPoint), /// Looks up a UTXO identified by the given [`OutPoint`](transparent::OutPoint), /// returning `None` immediately if it is unknown. @@ -760,7 +754,7 @@ impl ReadRequest { ReadRequest::Block(_) => "block", ReadRequest::Transaction(_) => "transaction", ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block", - ReadRequest::BestChainUtxo { .. } => "best_chain_utxo", + ReadRequest::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo", ReadRequest::AnyChainUtxo { .. } => "any_chain_utxo", ReadRequest::BlockLocator => "block_locator", ReadRequest::FindBlockHashes { .. } => "find_block_hashes", @@ -799,7 +793,9 @@ impl TryFrom for ReadRequest { Request::Block(hash_or_height) => Ok(ReadRequest::Block(hash_or_height)), Request::Transaction(tx_hash) => Ok(ReadRequest::Transaction(tx_hash)), - Request::BestChainUtxo(outpoint) => Ok(ReadRequest::BestChainUtxo(outpoint)), + Request::UnspentBestChainUtxo(outpoint) => { + Ok(ReadRequest::UnspentBestChainUtxo(outpoint)) + } Request::BlockLocator => Ok(ReadRequest::BlockLocator), Request::FindBlockHashes { known_blocks, stop } => { diff --git a/zebra-state/src/response.rs b/zebra-state/src/response.rs index 8ea09f1837c..71d9ff10576 100644 --- a/zebra-state/src/response.rs +++ b/zebra-state/src/response.rs @@ -36,8 +36,8 @@ pub enum Response { /// Response to [`Request::Transaction`] with the specified transaction. Transaction(Option>), - /// Response to [`Request::BestChainUtxo`] with the UTXO - BestChainUtxo(Option), + /// Response to [`Request::UnspentBestChainUtxo`] with the UTXO + UnspentBestChainUtxo(Option), /// Response to [`Request::Block`] with the specified block. Block(Option>), @@ -84,12 +84,9 @@ pub enum ReadResponse { /// The response to a `FindBlockHeaders` request. BlockHeaders(Vec), - /// The response to a `BestChainUtxo` request, from verified blocks in the + /// The response to a `UnspentBestChainUtxo` request, from verified blocks in the /// _best_ non-finalized chain, or the finalized chain. - /// - /// This response is purely informational, there is no guarantee that - /// the UTXO remains unspent in the best chain. - BestChainUtxo(Option), + UnspentBestChainUtxo(Option), /// The response to an `AnyChainUtxo` request, from verified blocks in /// _any_ non-finalized chain, or the finalized chain. @@ -135,7 +132,7 @@ impl TryFrom for Response { ReadResponse::Transaction(tx_and_height) => { Ok(Response::Transaction(tx_and_height.map(|(tx, _height)| tx))) } - ReadResponse::BestChainUtxo(utxo) => Ok(Response::BestChainUtxo(utxo)), + ReadResponse::UnspentBestChainUtxo(utxo) => Ok(Response::UnspentBestChainUtxo(utxo)), ReadResponse::AnyChainUtxo(_) => Err("ReadService does not track pending UTXOs. \ diff --git a/zebra-state/src/service.rs b/zebra-state/src/service.rs index b583d6198fb..0f45aa0f448 100644 --- a/zebra-state/src/service.rs +++ b/zebra-state/src/service.rs @@ -1021,7 +1021,7 @@ impl Service for StateService { | Request::Tip | Request::BlockLocator | Request::Transaction(_) - | Request::BestChainUtxo(_) + | Request::UnspentBestChainUtxo(_) | Request::Block(_) | Request::FindBlockHashes { .. } | Request::FindBlockHeaders { .. } => { @@ -1218,7 +1218,7 @@ impl Service for ReadStateService { } // Currently unused. - ReadRequest::BestChainUtxo(outpoint) => { + ReadRequest::UnspentBestChainUtxo(outpoint) => { let timer = CodeTimer::start(); let state = self.clone(); @@ -1228,17 +1228,21 @@ impl Service for ReadStateService { span.in_scope(move || { let utxo = state.non_finalized_state_receiver.with_watch_data( |non_finalized_state| { - read::utxo(non_finalized_state.best_chain(), &state.db, outpoint) + read::unspent_utxo( + non_finalized_state.best_chain(), + &state.db, + outpoint, + ) }, ); // The work is done in the future. - timer.finish(module_path!(), line!(), "ReadRequest::BestChainUtxo"); + timer.finish(module_path!(), line!(), "ReadRequest::UnspentBestChainUtxo"); - Ok(ReadResponse::BestChainUtxo(utxo)) + Ok(ReadResponse::UnspentBestChainUtxo(utxo)) }) }) - .map(|join_result| join_result.expect("panic in ReadRequest::BestChainUtxo")) + .map(|join_result| join_result.expect("panic in ReadRequest::UnspentBestChainUtxo")) .boxed() } diff --git a/zebra-state/src/service/read.rs b/zebra-state/src/service/read.rs index debff2fd3b2..b0579c83779 100644 --- a/zebra-state/src/service/read.rs +++ b/zebra-state/src/service/read.rs @@ -28,7 +28,9 @@ pub use address::{ utxo::{address_utxos, AddressUtxos, ADDRESS_HEIGHTS_FULL_RANGE}, }; -pub use block::{any_utxo, block, block_header, transaction, transaction_hashes_for_block, utxo}; +pub use block::{ + any_utxo, block, block_header, transaction, transaction_hashes_for_block, unspent_utxo, utxo, +}; #[cfg(feature = "getblocktemplate-rpcs")] pub use block::hash; diff --git a/zebra-state/src/service/read/block.rs b/zebra-state/src/service/read/block.rs index dfa9ba125d2..3cb69686d4c 100644 --- a/zebra-state/src/service/read/block.rs +++ b/zebra-state/src/service/read/block.rs @@ -141,6 +141,22 @@ where .or_else(|| db.utxo(&outpoint).map(|utxo| utxo.utxo)) } +/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists and is unspent in the +/// non-finalized `chain` or finalized `db`. +pub fn unspent_utxo( + chain: Option, + db: &ZebraDb, + outpoint: transparent::OutPoint, +) -> Option +where + C: AsRef, +{ + match chain { + Some(chain) if chain.as_ref().spent_utxos.contains(&outpoint) => None, + chain => utxo(chain, db, outpoint), + } +} + /// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in any chain /// in the `non_finalized_state`, or in the finalized `db`. /// From ece1be55707a70f32b9a18ec121d640f132dbcf9 Mon Sep 17 00:00:00 2001 From: arya2 Date: Thu, 10 Nov 2022 19:12:02 -0500 Subject: [PATCH 6/6] adds test for success case --- zebra-consensus/src/transaction/tests.rs | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/zebra-consensus/src/transaction/tests.rs b/zebra-consensus/src/transaction/tests.rs index 4adcb9e91fa..360b26ad81f 100644 --- a/zebra-consensus/src/transaction/tests.rs +++ b/zebra-consensus/src/transaction/tests.rs @@ -215,6 +215,57 @@ async fn mempool_request_with_missing_input_is_rejected() { ); } +#[tokio::test] +async fn mempool_request_with_present_input_is_accepted() { + let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests(); + let verifier = Verifier::new(Network::Mainnet, state.clone()); + + let height = NetworkUpgrade::Canopy + .activation_height(Network::Mainnet) + .expect("Canopy activation height is specified"); + let fund_height = (height - 1).expect("fake source fund block height is too small"); + let (input, output, known_utxos) = mock_transparent_transfer(fund_height, true, 0); + + // Create a non-coinbase V4 tx with the last valid expiry height. + let tx = Transaction::V4 { + inputs: vec![input], + outputs: vec![output], + lock_time: LockTime::unlocked(), + expiry_height: height, + joinsplit_data: None, + sapling_shielded_data: None, + }; + + let input_outpoint = match tx.inputs()[0] { + transparent::Input::PrevOut { outpoint, .. } => outpoint, + transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"), + }; + + tokio::spawn(async move { + state + .expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint)) + .await + .expect("verifier should call mock state service") + .respond(zebra_state::Response::UnspentBestChainUtxo( + known_utxos + .get(&input_outpoint) + .map(|utxo| utxo.utxo.clone()), + )); + }); + + let verifier_response = verifier + .oneshot(Request::Mempool { + transaction: tx.into(), + height, + }) + .await; + + assert!( + verifier_response.is_ok(), + "expected successful verification, got: {verifier_response:?}" + ); +} + #[test] fn v5_transaction_with_no_outputs_fails_validation() { let transaction = fake_v5_transactions_for_network(