diff --git a/zebra-node-services/src/mempool.rs b/zebra-node-services/src/mempool.rs index 4fe0c83a..e83132be 100644 --- a/zebra-node-services/src/mempool.rs +++ b/zebra-node-services/src/mempool.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; -use zebra_chain::transaction::{UnminedTx, UnminedTxId}; +use zebra_chain::transaction::{Hash, UnminedTx, UnminedTxId}; use crate::BoxError; @@ -29,6 +29,12 @@ pub enum Request { /// using a unique set of [`UnminedTxId`]s. TransactionsById(HashSet), + /// Query matching transactions in the mempool, + /// using a unique set of [`Hash`]s. Pre-V5 transactions are matched + /// directly; V5 transaction are matched just by the Hash, disregarding + /// the [`AuthDigest`]. + TransactionsByMinedId(HashSet), + /// Query matching cached rejected transaction IDs in the mempool, /// using a unique set of [`UnminedTxId`]s. RejectedTransactionIds(HashSet), @@ -74,7 +80,9 @@ pub enum Response { /// Returns matching transactions from the mempool. /// /// Since the [`TransactionsById`] request is unique, - /// the response transactions are also unique. + /// the response transactions are also unique. The same applies to + /// [`TransactionByMinedId`] requests, since the mempool does not allow + /// different transactions with different mined IDs. Transactions(Vec), /// Returns matching cached rejected transaction IDs from the mempool, diff --git a/zebrad/src/components/mempool.rs b/zebrad/src/components/mempool.rs index bdca55c1..555c7922 100644 --- a/zebrad/src/components/mempool.rs +++ b/zebrad/src/components/mempool.rs @@ -398,6 +398,10 @@ impl Service for Mempool { let res = storage.transactions_exact(ids).cloned().collect(); async move { Ok(Response::Transactions(res)) }.boxed() } + Request::TransactionsByMinedId(ids) => { + let res = storage.transactions_same_effects(ids).cloned().collect(); + async move { Ok(Response::Transactions(res)) }.boxed() + } Request::RejectedTransactionIds(ids) => { let res = storage.rejected_transactions(ids).collect(); async move { Ok(Response::RejectedTransactionIds(res)) }.boxed() @@ -431,6 +435,7 @@ impl Service for Mempool { // Empty Queries Request::TransactionIds => Response::TransactionIds(Default::default()), Request::TransactionsById(_) => Response::Transactions(Default::default()), + Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()), Request::RejectedTransactionIds(_) => { Response::RejectedTransactionIds(Default::default()) } diff --git a/zebrad/src/components/mempool/storage.rs b/zebrad/src/components/mempool/storage.rs index fb06c6cf..b7db9683 100644 --- a/zebrad/src/components/mempool/storage.rs +++ b/zebrad/src/components/mempool/storage.rs @@ -15,7 +15,7 @@ use std::{ use thiserror::Error; -use zebra_chain::transaction::{self, UnminedTx, UnminedTxId, VerifiedUnminedTx}; +use zebra_chain::transaction::{self, Hash, UnminedTx, UnminedTxId, VerifiedUnminedTx}; use self::{eviction_list::EvictionList, verified_set::VerifiedSet}; use super::{config, downloads::TransactionDownloadVerifyError, MempoolError}; @@ -338,6 +338,19 @@ impl Storage { .filter(move |tx| tx_ids.contains(&tx.id)) } + /// Returns the set of [`UnminedTx`]es with matching [`transaction::Hash`]es + /// in the mempool. + /// + /// This matches transactions with the same effects, regardless of [`AuthDigest`]. + pub fn transactions_same_effects( + &self, + tx_ids: HashSet, + ) -> impl Iterator { + self.verified + .transactions() + .filter(move |tx| tx_ids.contains(&tx.id.mined_id())) + } + /// Returns `true` if a transaction exactly matching an [`UnminedTxId`] is in /// the mempool. /// diff --git a/zebrad/src/components/mempool/tests/vector.rs b/zebrad/src/components/mempool/tests/vector.rs index c19bae41..9d9f1228 100644 --- a/zebrad/src/components/mempool/tests/vector.rs +++ b/zebrad/src/components/mempool/tests/vector.rs @@ -98,6 +98,30 @@ async fn mempool_service_basic_single() -> Result<(), Report> { // response of `Request::TransactionsById` assert_eq!(genesis_transaction.transaction, transactions[0]); + // Test `Request::TransactionsByMinedId` + // TODO: use a V5 tx to test if it's really matched by mined ID + let genesis_transactions_mined_hash_set = genesis_transaction_ids + .iter() + .map(|txid| txid.mined_id()) + .collect::>(); + let response = service + .ready() + .await + .unwrap() + .call(Request::TransactionsByMinedId( + genesis_transactions_mined_hash_set, + )) + .await + .unwrap(); + let transactions = match response { + Response::Transactions(transactions) => transactions, + _ => unreachable!("will never happen in this test"), + }; + + // Make sure the transaction from the blockchain test vector is the same as the + // response of `Request::TransactionsByMinedId` + assert_eq!(genesis_transaction.transaction, transactions[0]); + // Insert more transactions into the mempool storage. // This will cause the genesis transaction to be moved into rejected. // Skip the last (will be used later)