fix(rpc): Check that mempool transactions are valid for the state's chain info in getblocktemplate (#6416)

* check last seen tip hash from mempool in fetch_mempool_transactions()

* Moves last_seen_tip_hash to ActiveState

* fixes tests and tests fixes

* continues to the next iteration of the loop to make fresh state and mempool requests if called with a long poll id

* Update zebra-rpc/src/methods/get_block_template_rpcs.rs

Co-authored-by: teor <teor@riseup.net>

* adds allow[unused_variable) for linter

* expects a chain tip when not(test)

* Apply suggestions from code review

Co-authored-by: teor <teor@riseup.net>

* Addresses comments in code review

* - Removes second call to `last_tip_change()`

- Fixes tests using enabled mempool

* Adds note about chain tip action requirement to test method `enable()`

* updates doc comment

* Update zebrad/src/components/mempool.rs

Co-authored-by: teor <teor@riseup.net>

* fixes test

---------

Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
Arya 2023-04-03 19:22:07 -04:00 committed by GitHub
parent 4fcb5b9de9
commit d7842bd467
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 283 additions and 171 deletions

View File

@ -103,7 +103,13 @@ pub enum Response {
// TODO: make the Transactions response return VerifiedUnminedTx,
// and remove the FullTransactions variant
#[cfg(feature = "getblocktemplate-rpcs")]
FullTransactions(Vec<VerifiedUnminedTx>),
FullTransactions {
/// All [`VerifiedUnminedTx`]s in the mempool
transactions: Vec<VerifiedUnminedTx>,
/// Last seen chain tip hash by mempool service
last_seen_tip_hash: zebra_chain::block::Hash,
},
/// Returns matching cached rejected [`UnminedTxId`]s from the mempool,
RejectedTransactionIds(HashSet<UnminedTxId>),

View File

@ -798,7 +798,10 @@ where
match response {
#[cfg(feature = "getblocktemplate-rpcs")]
mempool::Response::FullTransactions(mut transactions) => {
mempool::Response::FullTransactions {
mut transactions,
last_seen_tip_hash: _,
} => {
// Sort transactions in descending order by fee/size, using hash in serialized byte order as a tie-breaker
transactions.sort_by_cached_key(|tx| {
// zcashd uses modified fee here but Zebra doesn't currently

View File

@ -448,8 +448,14 @@ where
.as_ref()
.and_then(get_block_template::JsonParameters::block_proposal_data)
{
return validate_block_proposal(self.chain_verifier.clone(), block_proposal_bytes)
.boxed();
return validate_block_proposal(
self.chain_verifier.clone(),
block_proposal_bytes,
network,
latest_chain_tip,
sync_status,
)
.boxed();
}
// To implement long polling correctly, we split this RPC into multiple phases.
@ -505,7 +511,15 @@ where
//
// Optional TODO:
// - add a `MempoolChange` type with an `async changed()` method (like `ChainTip`)
let mempool_txs = fetch_mempool_transactions(mempool.clone()).await?;
let Some(mempool_txs) =
fetch_mempool_transactions(mempool.clone(), chain_tip_and_local_time.tip_hash)
.await?
// If the mempool and state responses are out of sync:
// - if we are not long polling, omit mempool transactions from the template,
// - if we are long polling, continue to the next iteration of the loop to make fresh state and mempool requests.
.or_else(|| client_long_poll_id.is_none().then(Vec::new)) else {
continue;
};
// - Long poll ID calculation
let server_long_poll_id = LongPollInput::new(

View File

@ -97,9 +97,12 @@ pub fn check_miner_address(
/// usual acceptance rules (except proof-of-work).
///
/// Returns a `getblocktemplate` [`Response`].
pub async fn validate_block_proposal<ChainVerifier>(
pub async fn validate_block_proposal<ChainVerifier, Tip, SyncStatus>(
mut chain_verifier: ChainVerifier,
block_proposal_bytes: Vec<u8>,
network: Network,
latest_chain_tip: Tip,
sync_status: SyncStatus,
) -> Result<Response>
where
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
@ -107,7 +110,11 @@ where
+ Send
+ Sync
+ 'static,
Tip: ChainTip + Clone + Send + Sync + 'static,
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
{
check_synced_to_tip(network, latest_chain_tip, sync_status)?;
let block: Block = match block_proposal_bytes.zcash_deserialize_into() {
Ok(block) => block,
Err(parse_error) => {
@ -231,11 +238,15 @@ where
Ok(chain_info)
}
/// Returns the transactions that are currently in `mempool`.
/// Returns the transactions that are currently in `mempool`, or None if the
/// `last_seen_tip_hash` from the mempool response doesn't match the tip hash from the state.
///
/// You should call `check_synced_to_tip()` before calling this function.
/// If the mempool is inactive because Zebra is not synced to the tip, returns no transactions.
pub async fn fetch_mempool_transactions<Mempool>(mempool: Mempool) -> Result<Vec<VerifiedUnminedTx>>
pub async fn fetch_mempool_transactions<Mempool>(
mempool: Mempool,
chain_tip_hash: block::Hash,
) -> Result<Option<Vec<VerifiedUnminedTx>>>
where
Mempool: Service<
mempool::Request,
@ -253,11 +264,15 @@ where
data: None,
})?;
if let mempool::Response::FullTransactions(transactions) = response {
Ok(transactions)
} else {
let mempool::Response::FullTransactions {
transactions,
last_seen_tip_hash,
} = response else {
unreachable!("unmatched response to a mempool::FullTransactions request")
}
};
// Check that the mempool and state were in sync when we made the requests
Ok((last_seen_tip_hash == chain_tip_hash).then_some(transactions))
}
// - Response processing

View File

@ -386,7 +386,10 @@ proptest! {
mempool
.expect_request(mempool::Request::FullTransactions)
.await?
.respond(mempool::Response::FullTransactions(transactions));
.respond(mempool::Response::FullTransactions {
transactions,
last_seen_tip_hash: [0; 32].into(),
});
expected_response
};

View File

@ -176,7 +176,10 @@ async fn test_rpc_response_data_for_network(network: Network) {
let mempool_req = mempool
.expect_request_that(|request| matches!(request, mempool::Request::FullTransactions))
.map(|responder| {
responder.respond(mempool::Response::FullTransactions(vec![]));
responder.respond(mempool::Response::FullTransactions {
transactions: vec![],
last_seen_tip_hash: blocks[blocks.len() - 1].hash(),
});
});
#[cfg(not(feature = "getblocktemplate-rpcs"))]

View File

@ -219,7 +219,11 @@ pub async fn test_responses<State, ReadState>(
mempool
.expect_request(mempool::Request::FullTransactions)
.await
.respond(mempool::Response::FullTransactions(vec![]));
.respond(mempool::Response::FullTransactions {
transactions: vec![],
// tip hash needs to match chain info for long poll requests
last_seen_tip_hash: fake_tip_hash,
});
}
};

View File

@ -1167,6 +1167,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION},
chain_sync_status::MockSyncStatus,
serialization::DateTime32,
transaction::VerifiedUnminedTx,
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
};
use zebra_consensus::MAX_BLOCK_SIGOPS;
@ -1190,7 +1191,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut read_state = MockService::build().for_unit_tests();
let read_state = MockService::build().for_unit_tests();
let chain_verifier = MockService::build().for_unit_tests();
let mut mock_sync_status = MockSyncStatus::default();
@ -1238,36 +1239,43 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
);
// Fake the ChainInfo response
let mock_read_state_request_handler = async move {
read_state
.expect_request_that(|req| matches!(req, ReadRequest::ChainInfo))
.await
.respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo {
expected_difficulty: fake_difficulty,
tip_height: fake_tip_height,
tip_hash: fake_tip_hash,
cur_time: fake_cur_time,
min_time: fake_min_time,
max_time: fake_max_time,
history_tree: fake_history_tree(Mainnet),
}));
let make_mock_read_state_request_handler = || {
let mut read_state = read_state.clone();
async move {
read_state
.expect_request_that(|req| matches!(req, ReadRequest::ChainInfo))
.await
.respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo {
expected_difficulty: fake_difficulty,
tip_height: fake_tip_height,
tip_hash: fake_tip_hash,
cur_time: fake_cur_time,
min_time: fake_min_time,
max_time: fake_max_time,
history_tree: fake_history_tree(Mainnet),
}));
}
};
let mock_mempool_request_handler = {
let make_mock_mempool_request_handler = |transactions, last_seen_tip_hash| {
let mut mempool = mempool.clone();
async move {
mempool
.expect_request(mempool::Request::FullTransactions)
.await
.respond(mempool::Response::FullTransactions(vec![]));
.respond(mempool::Response::FullTransactions {
transactions,
last_seen_tip_hash,
});
}
};
let get_block_template_fut = get_block_template_rpc.get_block_template(None);
let (get_block_template, ..) = tokio::join!(
get_block_template_fut,
mock_mempool_request_handler,
mock_read_state_request_handler,
make_mock_mempool_request_handler(vec![], fake_tip_hash),
make_mock_read_state_request_handler(),
);
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
@ -1324,8 +1332,6 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
Amount::<NonNegative>::zero()
);
mempool.expect_no_requests().await;
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(200));
let get_block_template_sync_error = get_block_template_rpc
.get_block_template(None)
@ -1400,6 +1406,53 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
get_block_template_sync_error.code,
ErrorCode::ServerError(-10)
);
// Try getting mempool transactions with a different tip hash
let tx = Arc::new(Transaction::V1 {
inputs: vec![],
outputs: vec![],
lock_time: transaction::LockTime::unlocked(),
});
let unmined_tx = UnminedTx {
transaction: tx.clone(),
id: tx.unmined_id(),
size: tx.zcash_serialized_size(),
conventional_fee: 0.try_into().unwrap(),
};
let verified_unmined_tx = VerifiedUnminedTx {
transaction: unmined_tx,
miner_fee: 0.try_into().unwrap(),
legacy_sigop_count: 0,
unpaid_actions: 0,
fee_weight_ratio: 1.0,
};
let next_fake_tip_hash =
Hash::from_hex("0000000000b6a5024aa412120b684a509ba8fd57e01de07bc2a84e4d3719a9f1").unwrap();
mock_sync_status.set_is_close_to_tip(true);
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(0));
let (get_block_template, ..) = tokio::join!(
get_block_template_rpc.get_block_template(None),
make_mock_mempool_request_handler(vec![verified_unmined_tx], next_fake_tip_hash),
make_mock_read_state_request_handler(),
);
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
.expect("unexpected error in getblocktemplate RPC call") else {
panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response")
};
// mempool transactions should be omitted if the tip hash in the GetChainInfo response from the state
// does not match the `last_seen_tip_hash` in the FullTransactions response from the mempool.
assert!(get_block_template.transactions.is_empty());
mempool.expect_no_requests().await;
}
#[cfg(feature = "getblocktemplate-rpcs")]

View File

@ -666,6 +666,15 @@ impl TipAction {
}
}
/// Returns the block hash and height of this tip action,
/// regardless of the underlying variant.
pub fn best_tip_hash_and_height(&self) -> (block::Hash, block::Height) {
match self {
Grow { block } => (block.hash, block.height),
Reset { hash, height } => (*hash, *height),
}
}
/// Returns a [`Grow`] based on `block`.
pub(crate) fn grow_with(block: ChainTipBlock) -> Self {
Grow { block }

View File

@ -31,7 +31,9 @@ use tokio::sync::watch;
use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service};
use zebra_chain::{
block::Height, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip,
block::{self, Height},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
transaction::UnminedTxId,
};
use zebra_consensus::{error::TransactionError, transaction};
@ -104,6 +106,11 @@ enum ActiveState {
/// The transaction download and verify stream.
tx_downloads: Pin<Box<InboundTxDownloads>>,
/// Last seen chain tip hash that mempool transactions have been verified against.
///
/// In some tests, this is initialized to the latest chain tip, then updated in `poll_ready()` before each request.
last_seen_tip_hash: block::Hash,
},
}
@ -120,6 +127,7 @@ impl ActiveState {
ActiveState::Enabled {
storage,
tx_downloads,
..
} => {
let mut transactions = Vec::new();
@ -205,7 +213,7 @@ impl Mempool {
// Make sure `is_enabled` is accurate.
// Otherwise, it is only updated in `poll_ready`, right before each service call.
service.update_state();
service.update_state(None);
(service, transaction_receiver)
}
@ -241,41 +249,47 @@ impl Mempool {
/// Update the mempool state (enabled / disabled) depending on how close to
/// the tip is the synchronization, including side effects to state changes.
///
/// Accepts an optional [`TipAction`] for setting the `last_seen_tip_hash` field
/// when enabling the mempool state, it will not enable the mempool if this is None.
///
/// Returns `true` if the state changed.
fn update_state(&mut self) -> bool {
fn update_state(&mut self, tip_action: Option<&TipAction>) -> bool {
let is_close_to_tip = self.sync_status.is_close_to_tip() || self.is_enabled_by_debug();
if self.is_enabled() == is_close_to_tip {
// the active state is up to date
return false;
}
match (is_close_to_tip, self.is_enabled(), tip_action) {
// the active state is up to date, or there is no tip action to activate the mempool
(false, false, _) | (true, true, _) | (true, false, None) => return false,
// Update enabled / disabled state
if is_close_to_tip {
info!(
tip_height = ?self.latest_chain_tip.best_tip_height(),
"activating mempool: Zebra is close to the tip"
);
// Enable state - there should be a chain tip when Zebra is close to the network tip
(true, false, Some(tip_action)) => {
let (last_seen_tip_hash, tip_height) = tip_action.best_tip_hash_and_height();
let tx_downloads = Box::pin(TxDownloads::new(
Timeout::new(self.outbound.clone(), TRANSACTION_DOWNLOAD_TIMEOUT),
Timeout::new(self.tx_verifier.clone(), TRANSACTION_VERIFY_TIMEOUT),
self.state.clone(),
));
self.active_state = ActiveState::Enabled {
storage: storage::Storage::new(&self.config),
tx_downloads,
};
} else {
info!(
tip_height = ?self.latest_chain_tip.best_tip_height(),
"deactivating mempool: Zebra is syncing lots of blocks"
);
info!(?tip_height, "activating mempool: Zebra is close to the tip");
// This drops the previous ActiveState::Enabled, cancelling its download tasks.
// We don't preserve the previous transactions, because we are syncing lots of blocks.
self.active_state = ActiveState::Disabled
}
let tx_downloads = Box::pin(TxDownloads::new(
Timeout::new(self.outbound.clone(), TRANSACTION_DOWNLOAD_TIMEOUT),
Timeout::new(self.tx_verifier.clone(), TRANSACTION_VERIFY_TIMEOUT),
self.state.clone(),
));
self.active_state = ActiveState::Enabled {
storage: storage::Storage::new(&self.config),
tx_downloads,
last_seen_tip_hash,
};
}
// Disable state
(false, true, _) => {
info!(
tip_height = ?self.latest_chain_tip.best_tip_height(),
"deactivating mempool: Zebra is syncing lots of blocks"
);
// This drops the previous ActiveState::Enabled, cancelling its download tasks.
// We don't preserve the previous transactions, because we are syncing lots of blocks.
self.active_state = ActiveState::Disabled;
}
};
true
}
@ -307,7 +321,8 @@ impl Service<Request> for Mempool {
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let is_state_changed = self.update_state();
let tip_action = self.chain_tip_change.last_tip_change();
let is_state_changed = self.update_state(tip_action.as_ref());
tracing::trace!(is_enabled = ?self.is_enabled(), ?is_state_changed, "started polling the mempool...");
@ -317,8 +332,6 @@ impl Service<Request> for Mempool {
return Poll::Ready(Ok(()));
}
let tip_action = self.chain_tip_change.last_tip_change();
// Clear the mempool and cancel downloads if there has been a chain tip reset.
//
// But if the mempool was just freshly enabled,
@ -341,7 +354,7 @@ impl Service<Request> for Mempool {
std::mem::drop(previous_state);
// Re-initialise an empty state.
self.update_state();
self.update_state(tip_action.as_ref());
// Re-verify the transactions that were pending or valid at the previous tip.
// This saves us the time and data needed to re-download them.
@ -364,6 +377,7 @@ impl Service<Request> for Mempool {
if let ActiveState::Enabled {
storage,
tx_downloads,
last_seen_tip_hash,
} = &mut self.active_state
{
// Collect inserted transaction ids.
@ -413,6 +427,7 @@ impl Service<Request> for Mempool {
// Handle best chain tip changes
if let Some(TipAction::Grow { block }) = tip_action {
tracing::trace!(block_height = ?block.height, "handling blocks added to tip");
*last_seen_tip_hash = block.hash;
// Cancel downloads/verifications/storage of transactions
// with the same mined IDs as recently mined transactions.
@ -467,6 +482,10 @@ impl Service<Request> for Mempool {
ActiveState::Enabled {
storage,
tx_downloads,
#[cfg(feature = "getblocktemplate-rpcs")]
last_seen_tip_hash,
#[cfg(not(feature = "getblocktemplate-rpcs"))]
last_seen_tip_hash: _,
} => match req {
// Queries
Request::TransactionIds => {
@ -509,11 +528,16 @@ impl Service<Request> for Mempool {
Request::FullTransactions => {
trace!(?req, "got mempool request");
let res: Vec<_> = storage.full_transactions().cloned().collect();
let transactions: Vec<_> = storage.full_transactions().cloned().collect();
trace!(?req, res_count = ?res.len(), "answered mempool request");
trace!(?req, transactions_count = ?transactions.len(), "answered mempool request");
async move { Ok(Response::FullTransactions(res)) }.boxed()
let response = Response::FullTransactions {
transactions,
last_seen_tip_hash: *last_seen_tip_hash,
};
async move { Ok(response) }.boxed()
}
Request::RejectedTransactionIds(ref ids) => {
@ -559,6 +583,7 @@ impl Service<Request> for Mempool {
// We can't return an error since that will cause a disconnection
// by the peer connection handler. Therefore, return successful
// empty responses.
let resp = match req {
// Return empty responses for queries.
Request::TransactionIds => Response::TransactionIds(Default::default()),
@ -566,7 +591,12 @@ impl Service<Request> for Mempool {
Request::TransactionsById(_) => Response::Transactions(Default::default()),
Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()),
#[cfg(feature = "getblocktemplate-rpcs")]
Request::FullTransactions => Response::FullTransactions(Default::default()),
Request::FullTransactions => {
return async move {
Err("mempool is not active: wait for Zebra to sync to the tip".into())
}
.boxed()
}
Request::RejectedTransactionIds(_) => {
Response::RejectedTransactionIds(Default::default())
@ -590,6 +620,7 @@ impl Service<Request> for Mempool {
Response::CheckedForVerifiedTransactions
}
};
async move { Ok(resp) }.boxed()
}
}

View File

@ -31,6 +31,8 @@ impl Mempool {
}
/// Enable the mempool by pretending the synchronization is close to the tip.
///
/// Requires a chain tip action to enable the mempool before the future resolves.
pub async fn enable(&mut self, recent_syncs: &mut RecentSyncLengths) {
// Pretend we're close to tip
SyncStatus::sync_close_to_tip(recent_syncs);

View File

@ -1,6 +1,6 @@
//! Randomised property tests for the mempool.
use std::{env, fmt};
use std::{env, fmt, sync::Arc};
use proptest::{collection::vec, prelude::*};
use proptest_derive::Arbitrary;
@ -10,15 +10,17 @@ use tokio::time;
use tower::{buffer::Buffer, util::BoxService};
use zebra_chain::{
block,
block::{self, Block},
fmt::DisplayToDebug,
parameters::{Network, NetworkUpgrade},
serialization::ZcashDeserializeInto,
transaction::VerifiedUnminedTx,
};
use zebra_consensus::{error::TransactionError, transaction as tx};
use zebra_network as zn;
use zebra_state::{self as zs, ChainTipBlock, ChainTipSender};
use zebra_test::mock_service::{MockService, PropTestAssertion};
use zs::FinalizedBlock;
use crate::components::{
mempool::{config::Config, Mempool},
@ -193,7 +195,7 @@ proptest! {
mut state_service,
mut tx_verifier,
mut recent_syncs,
_chain_tip_sender,
mut chain_tip_sender,
) = setup(network);
time::pause();
@ -217,6 +219,9 @@ proptest! {
// This time a call to `poll_ready` should clear the storage.
mempool.dummy_call().await;
// sends a new fake chain tip so that the mempool can be enabled
chain_tip_sender.set_finalized_tip(block1_chain_tip());
// Enable the mempool again so the storage can be accessed.
mempool.enable(&mut recent_syncs).await;
@ -231,6 +236,22 @@ proptest! {
}
}
fn genesis_chain_tip() -> Option<ChainTipBlock> {
zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES
.zcash_deserialize_into::<Arc<Block>>()
.map(FinalizedBlock::from)
.map(ChainTipBlock::from)
.ok()
}
fn block1_chain_tip() -> Option<ChainTipBlock> {
zebra_test::vectors::BLOCK_MAINNET_1_BYTES
.zcash_deserialize_into::<Arc<Block>>()
.map(FinalizedBlock::from)
.map(ChainTipBlock::from)
.ok()
}
/// Create a new [`Mempool`] instance using mocked services.
fn setup(
network: Network,
@ -247,7 +268,8 @@ fn setup(
let tx_verifier = MockService::build().for_prop_tests();
let (sync_status, recent_syncs) = SyncStatus::new();
let (chain_tip_sender, latest_chain_tip, chain_tip_change) = ChainTipSender::new(None, network);
let (mut chain_tip_sender, latest_chain_tip, chain_tip_change) =
ChainTipSender::new(None, network);
let (mempool, _transaction_receiver) = Mempool::new(
&Config {
@ -262,6 +284,9 @@ fn setup(
chain_tip_change,
);
// sends a fake chain tip so that the mempool can be enabled
chain_tip_sender.set_finalized_tip(genesis_chain_tip());
(
mempool,
peer_set,

View File

@ -44,7 +44,7 @@ async fn mempool_service_basic_single() -> Result<(), Report> {
let network = Network::Mainnet;
// get the genesis block transactions from the Zcash blockchain.
let mut unmined_transactions = unmined_transactions_in_blocks(..=10, network);
let mut unmined_transactions = unmined_transactions_in_blocks(1..=10, network);
let genesis_transaction = unmined_transactions
.next()
.expect("Missing genesis transaction");
@ -56,7 +56,7 @@ async fn mempool_service_basic_single() -> Result<(), Report> {
let cost_limit = more_transactions.iter().map(|tx| tx.cost()).sum();
let (mut service, _peer_set, _state_service, _chain_tip_change, _tx_verifier, mut recent_syncs) =
setup(network, cost_limit).await;
setup(network, cost_limit, true).await;
// Enable the mempool
service.enable(&mut recent_syncs).await;
@ -187,7 +187,7 @@ async fn mempool_queue_single() -> Result<(), Report> {
let network = Network::Mainnet;
// Get transactions to use in the test
let unmined_transactions = unmined_transactions_in_blocks(..=10, network);
let unmined_transactions = unmined_transactions_in_blocks(1..=10, network);
let mut transactions = unmined_transactions.collect::<Vec<_>>();
// Split unmined_transactions into:
// [transactions..., new_tx]
@ -203,7 +203,7 @@ async fn mempool_queue_single() -> Result<(), Report> {
.sum();
let (mut service, _peer_set, _state_service, _chain_tip_change, _tx_verifier, mut recent_syncs) =
setup(network, cost_limit).await;
setup(network, cost_limit, true).await;
// Enable the mempool
service.enable(&mut recent_syncs).await;
@ -277,10 +277,10 @@ async fn mempool_service_disabled() -> Result<(), Report> {
let network = Network::Mainnet;
let (mut service, _peer_set, _state_service, _chain_tip_change, _tx_verifier, mut recent_syncs) =
setup(network, u64::MAX).await;
setup(network, u64::MAX, true).await;
// get the genesis block transactions from the Zcash blockchain.
let mut unmined_transactions = unmined_transactions_in_blocks(..=10, network);
let mut unmined_transactions = unmined_transactions_in_blocks(1..=10, network);
let genesis_transaction = unmined_transactions
.next()
.expect("Missing genesis transaction");
@ -398,41 +398,12 @@ async fn mempool_cancel_mined() -> Result<(), Report> {
mut chain_tip_change,
_tx_verifier,
mut recent_syncs,
) = setup(network, u64::MAX).await;
) = setup(network, u64::MAX, true).await;
// Enable the mempool
mempool.enable(&mut recent_syncs).await;
assert!(mempool.is_enabled());
// Push the genesis block to the state
let genesis_block: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES
.zcash_deserialize_into()
.unwrap();
state_service
.ready()
.await
.unwrap()
.call(zebra_state::Request::CommitFinalizedBlock(
genesis_block.clone().into(),
))
.await
.unwrap();
// Wait for the chain tip update
if let Err(timeout_error) = timeout(
CHAIN_TIP_UPDATE_WAIT_LIMIT,
chain_tip_change.wait_for_tip_change(),
)
.await
.map(|change_result| change_result.expect("unexpected chain tip update failure"))
{
info!(
timeout = ?humantime_seconds(CHAIN_TIP_UPDATE_WAIT_LIMIT),
?timeout_error,
"timeout waiting for chain tip change after committing block"
);
}
// Query the mempool to make it poll chain_tip_change
mempool.dummy_call().await;
@ -542,41 +513,12 @@ async fn mempool_cancel_downloads_after_network_upgrade() -> Result<(), Report>
mut chain_tip_change,
_tx_verifier,
mut recent_syncs,
) = setup(network, u64::MAX).await;
) = setup(network, u64::MAX, true).await;
// Enable the mempool
mempool.enable(&mut recent_syncs).await;
assert!(mempool.is_enabled());
// Push the genesis block to the state
let genesis_block: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES
.zcash_deserialize_into()
.unwrap();
state_service
.ready()
.await
.unwrap()
.call(zebra_state::Request::CommitFinalizedBlock(
genesis_block.clone().into(),
))
.await
.unwrap();
// Wait for the chain tip update
if let Err(timeout_error) = timeout(
CHAIN_TIP_UPDATE_WAIT_LIMIT,
chain_tip_change.wait_for_tip_change(),
)
.await
.map(|change_result| change_result.expect("unexpected chain tip update failure"))
{
info!(
timeout = ?humantime_seconds(CHAIN_TIP_UPDATE_WAIT_LIMIT),
?timeout_error,
"timeout waiting for chain tip change after committing block"
);
}
// Queue transaction from block 2 for download
let txid = block2.transactions[0].unmined_id();
let response = mempool
@ -658,7 +600,7 @@ async fn mempool_failed_verification_is_rejected() -> Result<(), Report> {
_chain_tip_change,
mut tx_verifier,
mut recent_syncs,
) = setup(network, u64::MAX).await;
) = setup(network, u64::MAX, true).await;
// Get transactions to use in the test
let mut unmined_transactions = unmined_transactions_in_blocks(1..=2, network);
@ -733,7 +675,7 @@ async fn mempool_failed_download_is_not_rejected() -> Result<(), Report> {
_chain_tip_change,
_tx_verifier,
mut recent_syncs,
) = setup(network, u64::MAX).await;
) = setup(network, u64::MAX, true).await;
// Get transactions to use in the test
let mut unmined_transactions = unmined_transactions_in_blocks(1..=2, network);
@ -801,9 +743,6 @@ async fn mempool_failed_download_is_not_rejected() -> Result<(), Report> {
async fn mempool_reverifies_after_tip_change() -> Result<(), Report> {
let network = Network::Mainnet;
let genesis_block: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES
.zcash_deserialize_into()
.unwrap();
let block1: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
.zcash_deserialize_into()
.unwrap();
@ -821,31 +760,12 @@ async fn mempool_reverifies_after_tip_change() -> Result<(), Report> {
mut chain_tip_change,
mut tx_verifier,
mut recent_syncs,
) = setup(network, u64::MAX).await;
) = setup(network, u64::MAX, true).await;
// Enable the mempool
mempool.enable(&mut recent_syncs).await;
assert!(mempool.is_enabled());
// Push the genesis block to the state
state_service
.ready()
.await
.unwrap()
.call(zebra_state::Request::CommitFinalizedBlock(
genesis_block.clone().into(),
))
.await
.unwrap();
// Wait for the chain tip update without a timeout
// (skipping the chain tip change here may cause the test to
// pass without reverifying transactions for `TipAction::Grow`)
chain_tip_change
.wait_for_tip_change()
.await
.expect("unexpected chain tip update failure");
// Queue transaction from block 3 for download
let tx = block3.transactions[0].clone();
let txid = block3.transactions[0].unmined_id();
@ -985,6 +905,7 @@ async fn mempool_reverifies_after_tip_change() -> Result<(), Report> {
async fn setup(
network: Network,
tx_cost_limit: u64,
should_commit_genesis_block: bool,
) -> (
Mempool,
MockPeerSet,
@ -997,9 +918,9 @@ async fn setup(
// UTXO verification doesn't matter here.
let state_config = StateConfig::ephemeral();
let (state, _read_only_state_service, latest_chain_tip, chain_tip_change) =
let (state, _read_only_state_service, latest_chain_tip, mut chain_tip_change) =
zebra_state::init(state_config, network, Height::MAX, 0);
let state_service = ServiceBuilder::new().buffer(1).service(state);
let mut state_service = ServiceBuilder::new().buffer(1).service(state);
let tx_verifier = MockService::build().for_unit_tests();
@ -1018,6 +939,29 @@ async fn setup(
chain_tip_change.clone(),
);
if should_commit_genesis_block {
let genesis_block: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES
.zcash_deserialize_into()
.unwrap();
// Push the genesis block to the state
state_service
.ready()
.await
.unwrap()
.call(zebra_state::Request::CommitFinalizedBlock(
genesis_block.clone().into(),
))
.await
.unwrap();
// Wait for the chain tip update without a timeout
chain_tip_change
.wait_for_tip_change()
.await
.expect("unexpected chain tip update failure");
}
(
mempool,
peer_set,