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:
parent
4fcb5b9de9
commit
d7842bd467
|
@ -103,7 +103,13 @@ pub enum Response {
|
||||||
// TODO: make the Transactions response return VerifiedUnminedTx,
|
// TODO: make the Transactions response return VerifiedUnminedTx,
|
||||||
// and remove the FullTransactions variant
|
// and remove the FullTransactions variant
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[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,
|
/// Returns matching cached rejected [`UnminedTxId`]s from the mempool,
|
||||||
RejectedTransactionIds(HashSet<UnminedTxId>),
|
RejectedTransactionIds(HashSet<UnminedTxId>),
|
||||||
|
|
|
@ -798,7 +798,10 @@ where
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[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
|
// Sort transactions in descending order by fee/size, using hash in serialized byte order as a tie-breaker
|
||||||
transactions.sort_by_cached_key(|tx| {
|
transactions.sort_by_cached_key(|tx| {
|
||||||
// zcashd uses modified fee here but Zebra doesn't currently
|
// zcashd uses modified fee here but Zebra doesn't currently
|
||||||
|
|
|
@ -448,8 +448,14 @@ where
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(get_block_template::JsonParameters::block_proposal_data)
|
.and_then(get_block_template::JsonParameters::block_proposal_data)
|
||||||
{
|
{
|
||||||
return validate_block_proposal(self.chain_verifier.clone(), block_proposal_bytes)
|
return validate_block_proposal(
|
||||||
.boxed();
|
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.
|
// To implement long polling correctly, we split this RPC into multiple phases.
|
||||||
|
@ -505,7 +511,15 @@ where
|
||||||
//
|
//
|
||||||
// Optional TODO:
|
// Optional TODO:
|
||||||
// - add a `MempoolChange` type with an `async changed()` method (like `ChainTip`)
|
// - 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
|
// - Long poll ID calculation
|
||||||
let server_long_poll_id = LongPollInput::new(
|
let server_long_poll_id = LongPollInput::new(
|
||||||
|
|
|
@ -97,9 +97,12 @@ pub fn check_miner_address(
|
||||||
/// usual acceptance rules (except proof-of-work).
|
/// usual acceptance rules (except proof-of-work).
|
||||||
///
|
///
|
||||||
/// Returns a `getblocktemplate` [`Response`].
|
/// Returns a `getblocktemplate` [`Response`].
|
||||||
pub async fn validate_block_proposal<ChainVerifier>(
|
pub async fn validate_block_proposal<ChainVerifier, Tip, SyncStatus>(
|
||||||
mut chain_verifier: ChainVerifier,
|
mut chain_verifier: ChainVerifier,
|
||||||
block_proposal_bytes: Vec<u8>,
|
block_proposal_bytes: Vec<u8>,
|
||||||
|
network: Network,
|
||||||
|
latest_chain_tip: Tip,
|
||||||
|
sync_status: SyncStatus,
|
||||||
) -> Result<Response>
|
) -> Result<Response>
|
||||||
where
|
where
|
||||||
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
|
ChainVerifier: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
|
||||||
|
@ -107,7 +110,11 @@ where
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ '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() {
|
let block: Block = match block_proposal_bytes.zcash_deserialize_into() {
|
||||||
Ok(block) => block,
|
Ok(block) => block,
|
||||||
Err(parse_error) => {
|
Err(parse_error) => {
|
||||||
|
@ -231,11 +238,15 @@ where
|
||||||
Ok(chain_info)
|
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.
|
/// 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.
|
/// 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
|
where
|
||||||
Mempool: Service<
|
Mempool: Service<
|
||||||
mempool::Request,
|
mempool::Request,
|
||||||
|
@ -253,11 +264,15 @@ where
|
||||||
data: None,
|
data: None,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let mempool::Response::FullTransactions(transactions) = response {
|
let mempool::Response::FullTransactions {
|
||||||
Ok(transactions)
|
transactions,
|
||||||
} else {
|
last_seen_tip_hash,
|
||||||
|
} = response else {
|
||||||
unreachable!("unmatched response to a mempool::FullTransactions request")
|
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
|
// - Response processing
|
||||||
|
|
|
@ -386,7 +386,10 @@ proptest! {
|
||||||
mempool
|
mempool
|
||||||
.expect_request(mempool::Request::FullTransactions)
|
.expect_request(mempool::Request::FullTransactions)
|
||||||
.await?
|
.await?
|
||||||
.respond(mempool::Response::FullTransactions(transactions));
|
.respond(mempool::Response::FullTransactions {
|
||||||
|
transactions,
|
||||||
|
last_seen_tip_hash: [0; 32].into(),
|
||||||
|
});
|
||||||
|
|
||||||
expected_response
|
expected_response
|
||||||
};
|
};
|
||||||
|
|
|
@ -176,7 +176,10 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
||||||
let mempool_req = mempool
|
let mempool_req = mempool
|
||||||
.expect_request_that(|request| matches!(request, mempool::Request::FullTransactions))
|
.expect_request_that(|request| matches!(request, mempool::Request::FullTransactions))
|
||||||
.map(|responder| {
|
.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"))]
|
#[cfg(not(feature = "getblocktemplate-rpcs"))]
|
||||||
|
|
|
@ -219,7 +219,11 @@ pub async fn test_responses<State, ReadState>(
|
||||||
mempool
|
mempool
|
||||||
.expect_request(mempool::Request::FullTransactions)
|
.expect_request(mempool::Request::FullTransactions)
|
||||||
.await
|
.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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1167,6 +1167,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
||||||
block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION},
|
block::{Hash, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION},
|
||||||
chain_sync_status::MockSyncStatus,
|
chain_sync_status::MockSyncStatus,
|
||||||
serialization::DateTime32,
|
serialization::DateTime32,
|
||||||
|
transaction::VerifiedUnminedTx,
|
||||||
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
|
work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256},
|
||||||
};
|
};
|
||||||
use zebra_consensus::MAX_BLOCK_SIGOPS;
|
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 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 chain_verifier = MockService::build().for_unit_tests();
|
||||||
|
|
||||||
let mut mock_sync_status = MockSyncStatus::default();
|
let mut mock_sync_status = MockSyncStatus::default();
|
||||||
|
@ -1238,36 +1239,43 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fake the ChainInfo response
|
// Fake the ChainInfo response
|
||||||
let mock_read_state_request_handler = async move {
|
let make_mock_read_state_request_handler = || {
|
||||||
read_state
|
let mut read_state = read_state.clone();
|
||||||
.expect_request_that(|req| matches!(req, ReadRequest::ChainInfo))
|
|
||||||
.await
|
async move {
|
||||||
.respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo {
|
read_state
|
||||||
expected_difficulty: fake_difficulty,
|
.expect_request_that(|req| matches!(req, ReadRequest::ChainInfo))
|
||||||
tip_height: fake_tip_height,
|
.await
|
||||||
tip_hash: fake_tip_hash,
|
.respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo {
|
||||||
cur_time: fake_cur_time,
|
expected_difficulty: fake_difficulty,
|
||||||
min_time: fake_min_time,
|
tip_height: fake_tip_height,
|
||||||
max_time: fake_max_time,
|
tip_hash: fake_tip_hash,
|
||||||
history_tree: fake_history_tree(Mainnet),
|
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();
|
let mut mempool = mempool.clone();
|
||||||
async move {
|
async move {
|
||||||
mempool
|
mempool
|
||||||
.expect_request(mempool::Request::FullTransactions)
|
.expect_request(mempool::Request::FullTransactions)
|
||||||
.await
|
.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_fut = get_block_template_rpc.get_block_template(None);
|
||||||
let (get_block_template, ..) = tokio::join!(
|
let (get_block_template, ..) = tokio::join!(
|
||||||
get_block_template_fut,
|
get_block_template_fut,
|
||||||
mock_mempool_request_handler,
|
make_mock_mempool_request_handler(vec![], fake_tip_hash),
|
||||||
mock_read_state_request_handler,
|
make_mock_read_state_request_handler(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let get_block_template::Response::TemplateMode(get_block_template) = get_block_template
|
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()
|
Amount::<NonNegative>::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
mempool.expect_no_requests().await;
|
|
||||||
|
|
||||||
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(200));
|
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(200));
|
||||||
let get_block_template_sync_error = get_block_template_rpc
|
let get_block_template_sync_error = get_block_template_rpc
|
||||||
.get_block_template(None)
|
.get_block_template(None)
|
||||||
|
@ -1400,6 +1406,53 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
||||||
get_block_template_sync_error.code,
|
get_block_template_sync_error.code,
|
||||||
ErrorCode::ServerError(-10)
|
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")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
|
|
@ -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`.
|
/// Returns a [`Grow`] based on `block`.
|
||||||
pub(crate) fn grow_with(block: ChainTipBlock) -> Self {
|
pub(crate) fn grow_with(block: ChainTipBlock) -> Self {
|
||||||
Grow { block }
|
Grow { block }
|
||||||
|
|
|
@ -31,7 +31,9 @@ use tokio::sync::watch;
|
||||||
use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service};
|
use tower::{buffer::Buffer, timeout::Timeout, util::BoxService, Service};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block::Height, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip,
|
block::{self, Height},
|
||||||
|
chain_sync_status::ChainSyncStatus,
|
||||||
|
chain_tip::ChainTip,
|
||||||
transaction::UnminedTxId,
|
transaction::UnminedTxId,
|
||||||
};
|
};
|
||||||
use zebra_consensus::{error::TransactionError, transaction};
|
use zebra_consensus::{error::TransactionError, transaction};
|
||||||
|
@ -104,6 +106,11 @@ enum ActiveState {
|
||||||
|
|
||||||
/// The transaction download and verify stream.
|
/// The transaction download and verify stream.
|
||||||
tx_downloads: Pin<Box<InboundTxDownloads>>,
|
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 {
|
ActiveState::Enabled {
|
||||||
storage,
|
storage,
|
||||||
tx_downloads,
|
tx_downloads,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let mut transactions = Vec::new();
|
let mut transactions = Vec::new();
|
||||||
|
|
||||||
|
@ -205,7 +213,7 @@ impl Mempool {
|
||||||
|
|
||||||
// Make sure `is_enabled` is accurate.
|
// Make sure `is_enabled` is accurate.
|
||||||
// Otherwise, it is only updated in `poll_ready`, right before each service call.
|
// Otherwise, it is only updated in `poll_ready`, right before each service call.
|
||||||
service.update_state();
|
service.update_state(None);
|
||||||
|
|
||||||
(service, transaction_receiver)
|
(service, transaction_receiver)
|
||||||
}
|
}
|
||||||
|
@ -241,41 +249,47 @@ impl Mempool {
|
||||||
/// Update the mempool state (enabled / disabled) depending on how close to
|
/// Update the mempool state (enabled / disabled) depending on how close to
|
||||||
/// the tip is the synchronization, including side effects to state changes.
|
/// 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.
|
/// 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();
|
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 {
|
match (is_close_to_tip, self.is_enabled(), tip_action) {
|
||||||
// the active state is up to date
|
// the active state is up to date, or there is no tip action to activate the mempool
|
||||||
return false;
|
(false, false, _) | (true, true, _) | (true, false, None) => return false,
|
||||||
}
|
|
||||||
|
|
||||||
// Update enabled / disabled state
|
// Enable state - there should be a chain tip when Zebra is close to the network tip
|
||||||
if is_close_to_tip {
|
(true, false, Some(tip_action)) => {
|
||||||
info!(
|
let (last_seen_tip_hash, tip_height) = tip_action.best_tip_hash_and_height();
|
||||||
tip_height = ?self.latest_chain_tip.best_tip_height(),
|
|
||||||
"activating mempool: Zebra is close to the tip"
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx_downloads = Box::pin(TxDownloads::new(
|
info!(?tip_height, "activating mempool: Zebra is close to the tip");
|
||||||
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"
|
|
||||||
);
|
|
||||||
|
|
||||||
// This drops the previous ActiveState::Enabled, cancelling its download tasks.
|
let tx_downloads = Box::pin(TxDownloads::new(
|
||||||
// We don't preserve the previous transactions, because we are syncing lots of blocks.
|
Timeout::new(self.outbound.clone(), TRANSACTION_DOWNLOAD_TIMEOUT),
|
||||||
self.active_state = ActiveState::Disabled
|
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
|
true
|
||||||
}
|
}
|
||||||
|
@ -307,7 +321,8 @@ impl Service<Request> for Mempool {
|
||||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
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...");
|
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(()));
|
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.
|
// Clear the mempool and cancel downloads if there has been a chain tip reset.
|
||||||
//
|
//
|
||||||
// But if the mempool was just freshly enabled,
|
// But if the mempool was just freshly enabled,
|
||||||
|
@ -341,7 +354,7 @@ impl Service<Request> for Mempool {
|
||||||
std::mem::drop(previous_state);
|
std::mem::drop(previous_state);
|
||||||
|
|
||||||
// Re-initialise an empty 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.
|
// 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.
|
// 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 {
|
if let ActiveState::Enabled {
|
||||||
storage,
|
storage,
|
||||||
tx_downloads,
|
tx_downloads,
|
||||||
|
last_seen_tip_hash,
|
||||||
} = &mut self.active_state
|
} = &mut self.active_state
|
||||||
{
|
{
|
||||||
// Collect inserted transaction ids.
|
// Collect inserted transaction ids.
|
||||||
|
@ -413,6 +427,7 @@ impl Service<Request> for Mempool {
|
||||||
// Handle best chain tip changes
|
// Handle best chain tip changes
|
||||||
if let Some(TipAction::Grow { block }) = tip_action {
|
if let Some(TipAction::Grow { block }) = tip_action {
|
||||||
tracing::trace!(block_height = ?block.height, "handling blocks added to tip");
|
tracing::trace!(block_height = ?block.height, "handling blocks added to tip");
|
||||||
|
*last_seen_tip_hash = block.hash;
|
||||||
|
|
||||||
// Cancel downloads/verifications/storage of transactions
|
// Cancel downloads/verifications/storage of transactions
|
||||||
// with the same mined IDs as recently mined transactions.
|
// with the same mined IDs as recently mined transactions.
|
||||||
|
@ -467,6 +482,10 @@ impl Service<Request> for Mempool {
|
||||||
ActiveState::Enabled {
|
ActiveState::Enabled {
|
||||||
storage,
|
storage,
|
||||||
tx_downloads,
|
tx_downloads,
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
last_seen_tip_hash,
|
||||||
|
#[cfg(not(feature = "getblocktemplate-rpcs"))]
|
||||||
|
last_seen_tip_hash: _,
|
||||||
} => match req {
|
} => match req {
|
||||||
// Queries
|
// Queries
|
||||||
Request::TransactionIds => {
|
Request::TransactionIds => {
|
||||||
|
@ -509,11 +528,16 @@ impl Service<Request> for Mempool {
|
||||||
Request::FullTransactions => {
|
Request::FullTransactions => {
|
||||||
trace!(?req, "got mempool request");
|
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) => {
|
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
|
// We can't return an error since that will cause a disconnection
|
||||||
// by the peer connection handler. Therefore, return successful
|
// by the peer connection handler. Therefore, return successful
|
||||||
// empty responses.
|
// empty responses.
|
||||||
|
|
||||||
let resp = match req {
|
let resp = match req {
|
||||||
// Return empty responses for queries.
|
// Return empty responses for queries.
|
||||||
Request::TransactionIds => Response::TransactionIds(Default::default()),
|
Request::TransactionIds => Response::TransactionIds(Default::default()),
|
||||||
|
@ -566,7 +591,12 @@ impl Service<Request> for Mempool {
|
||||||
Request::TransactionsById(_) => Response::Transactions(Default::default()),
|
Request::TransactionsById(_) => Response::Transactions(Default::default()),
|
||||||
Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()),
|
Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()),
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[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(_) => {
|
Request::RejectedTransactionIds(_) => {
|
||||||
Response::RejectedTransactionIds(Default::default())
|
Response::RejectedTransactionIds(Default::default())
|
||||||
|
@ -590,6 +620,7 @@ impl Service<Request> for Mempool {
|
||||||
Response::CheckedForVerifiedTransactions
|
Response::CheckedForVerifiedTransactions
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async move { Ok(resp) }.boxed()
|
async move { Ok(resp) }.boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@ impl Mempool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable the mempool by pretending the synchronization is close to the tip.
|
/// 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) {
|
pub async fn enable(&mut self, recent_syncs: &mut RecentSyncLengths) {
|
||||||
// Pretend we're close to tip
|
// Pretend we're close to tip
|
||||||
SyncStatus::sync_close_to_tip(recent_syncs);
|
SyncStatus::sync_close_to_tip(recent_syncs);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Randomised property tests for the mempool.
|
//! Randomised property tests for the mempool.
|
||||||
|
|
||||||
use std::{env, fmt};
|
use std::{env, fmt, sync::Arc};
|
||||||
|
|
||||||
use proptest::{collection::vec, prelude::*};
|
use proptest::{collection::vec, prelude::*};
|
||||||
use proptest_derive::Arbitrary;
|
use proptest_derive::Arbitrary;
|
||||||
|
@ -10,15 +10,17 @@ use tokio::time;
|
||||||
use tower::{buffer::Buffer, util::BoxService};
|
use tower::{buffer::Buffer, util::BoxService};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
block,
|
block::{self, Block},
|
||||||
fmt::DisplayToDebug,
|
fmt::DisplayToDebug,
|
||||||
parameters::{Network, NetworkUpgrade},
|
parameters::{Network, NetworkUpgrade},
|
||||||
|
serialization::ZcashDeserializeInto,
|
||||||
transaction::VerifiedUnminedTx,
|
transaction::VerifiedUnminedTx,
|
||||||
};
|
};
|
||||||
use zebra_consensus::{error::TransactionError, transaction as tx};
|
use zebra_consensus::{error::TransactionError, transaction as tx};
|
||||||
use zebra_network as zn;
|
use zebra_network as zn;
|
||||||
use zebra_state::{self as zs, ChainTipBlock, ChainTipSender};
|
use zebra_state::{self as zs, ChainTipBlock, ChainTipSender};
|
||||||
use zebra_test::mock_service::{MockService, PropTestAssertion};
|
use zebra_test::mock_service::{MockService, PropTestAssertion};
|
||||||
|
use zs::FinalizedBlock;
|
||||||
|
|
||||||
use crate::components::{
|
use crate::components::{
|
||||||
mempool::{config::Config, Mempool},
|
mempool::{config::Config, Mempool},
|
||||||
|
@ -193,7 +195,7 @@ proptest! {
|
||||||
mut state_service,
|
mut state_service,
|
||||||
mut tx_verifier,
|
mut tx_verifier,
|
||||||
mut recent_syncs,
|
mut recent_syncs,
|
||||||
_chain_tip_sender,
|
mut chain_tip_sender,
|
||||||
) = setup(network);
|
) = setup(network);
|
||||||
|
|
||||||
time::pause();
|
time::pause();
|
||||||
|
@ -217,6 +219,9 @@ proptest! {
|
||||||
// This time a call to `poll_ready` should clear the storage.
|
// This time a call to `poll_ready` should clear the storage.
|
||||||
mempool.dummy_call().await;
|
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.
|
// Enable the mempool again so the storage can be accessed.
|
||||||
mempool.enable(&mut recent_syncs).await;
|
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.
|
/// Create a new [`Mempool`] instance using mocked services.
|
||||||
fn setup(
|
fn setup(
|
||||||
network: Network,
|
network: Network,
|
||||||
|
@ -247,7 +268,8 @@ fn setup(
|
||||||
let tx_verifier = MockService::build().for_prop_tests();
|
let tx_verifier = MockService::build().for_prop_tests();
|
||||||
|
|
||||||
let (sync_status, recent_syncs) = SyncStatus::new();
|
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(
|
let (mempool, _transaction_receiver) = Mempool::new(
|
||||||
&Config {
|
&Config {
|
||||||
|
@ -262,6 +284,9 @@ fn setup(
|
||||||
chain_tip_change,
|
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,
|
mempool,
|
||||||
peer_set,
|
peer_set,
|
||||||
|
|
|
@ -44,7 +44,7 @@ async fn mempool_service_basic_single() -> Result<(), Report> {
|
||||||
let network = Network::Mainnet;
|
let network = Network::Mainnet;
|
||||||
|
|
||||||
// get the genesis block transactions from the Zcash blockchain.
|
// 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
|
let genesis_transaction = unmined_transactions
|
||||||
.next()
|
.next()
|
||||||
.expect("Missing genesis transaction");
|
.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 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) =
|
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
|
// Enable the mempool
|
||||||
service.enable(&mut recent_syncs).await;
|
service.enable(&mut recent_syncs).await;
|
||||||
|
@ -187,7 +187,7 @@ async fn mempool_queue_single() -> Result<(), Report> {
|
||||||
let network = Network::Mainnet;
|
let network = Network::Mainnet;
|
||||||
|
|
||||||
// Get transactions to use in the test
|
// 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<_>>();
|
let mut transactions = unmined_transactions.collect::<Vec<_>>();
|
||||||
// Split unmined_transactions into:
|
// Split unmined_transactions into:
|
||||||
// [transactions..., new_tx]
|
// [transactions..., new_tx]
|
||||||
|
@ -203,7 +203,7 @@ async fn mempool_queue_single() -> Result<(), Report> {
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
let (mut service, _peer_set, _state_service, _chain_tip_change, _tx_verifier, mut recent_syncs) =
|
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
|
// Enable the mempool
|
||||||
service.enable(&mut recent_syncs).await;
|
service.enable(&mut recent_syncs).await;
|
||||||
|
@ -277,10 +277,10 @@ async fn mempool_service_disabled() -> Result<(), Report> {
|
||||||
let network = Network::Mainnet;
|
let network = Network::Mainnet;
|
||||||
|
|
||||||
let (mut service, _peer_set, _state_service, _chain_tip_change, _tx_verifier, mut recent_syncs) =
|
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.
|
// 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
|
let genesis_transaction = unmined_transactions
|
||||||
.next()
|
.next()
|
||||||
.expect("Missing genesis transaction");
|
.expect("Missing genesis transaction");
|
||||||
|
@ -398,41 +398,12 @@ async fn mempool_cancel_mined() -> Result<(), Report> {
|
||||||
mut chain_tip_change,
|
mut chain_tip_change,
|
||||||
_tx_verifier,
|
_tx_verifier,
|
||||||
mut recent_syncs,
|
mut recent_syncs,
|
||||||
) = setup(network, u64::MAX).await;
|
) = setup(network, u64::MAX, true).await;
|
||||||
|
|
||||||
// Enable the mempool
|
// Enable the mempool
|
||||||
mempool.enable(&mut recent_syncs).await;
|
mempool.enable(&mut recent_syncs).await;
|
||||||
assert!(mempool.is_enabled());
|
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
|
// Query the mempool to make it poll chain_tip_change
|
||||||
mempool.dummy_call().await;
|
mempool.dummy_call().await;
|
||||||
|
|
||||||
|
@ -542,41 +513,12 @@ async fn mempool_cancel_downloads_after_network_upgrade() -> Result<(), Report>
|
||||||
mut chain_tip_change,
|
mut chain_tip_change,
|
||||||
_tx_verifier,
|
_tx_verifier,
|
||||||
mut recent_syncs,
|
mut recent_syncs,
|
||||||
) = setup(network, u64::MAX).await;
|
) = setup(network, u64::MAX, true).await;
|
||||||
|
|
||||||
// Enable the mempool
|
// Enable the mempool
|
||||||
mempool.enable(&mut recent_syncs).await;
|
mempool.enable(&mut recent_syncs).await;
|
||||||
assert!(mempool.is_enabled());
|
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
|
// Queue transaction from block 2 for download
|
||||||
let txid = block2.transactions[0].unmined_id();
|
let txid = block2.transactions[0].unmined_id();
|
||||||
let response = mempool
|
let response = mempool
|
||||||
|
@ -658,7 +600,7 @@ async fn mempool_failed_verification_is_rejected() -> Result<(), Report> {
|
||||||
_chain_tip_change,
|
_chain_tip_change,
|
||||||
mut tx_verifier,
|
mut tx_verifier,
|
||||||
mut recent_syncs,
|
mut recent_syncs,
|
||||||
) = setup(network, u64::MAX).await;
|
) = setup(network, u64::MAX, true).await;
|
||||||
|
|
||||||
// Get transactions to use in the test
|
// Get transactions to use in the test
|
||||||
let mut unmined_transactions = unmined_transactions_in_blocks(1..=2, network);
|
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,
|
_chain_tip_change,
|
||||||
_tx_verifier,
|
_tx_verifier,
|
||||||
mut recent_syncs,
|
mut recent_syncs,
|
||||||
) = setup(network, u64::MAX).await;
|
) = setup(network, u64::MAX, true).await;
|
||||||
|
|
||||||
// Get transactions to use in the test
|
// Get transactions to use in the test
|
||||||
let mut unmined_transactions = unmined_transactions_in_blocks(1..=2, network);
|
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> {
|
async fn mempool_reverifies_after_tip_change() -> Result<(), Report> {
|
||||||
let network = Network::Mainnet;
|
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
|
let block1: Arc<Block> = zebra_test::vectors::BLOCK_MAINNET_1_BYTES
|
||||||
.zcash_deserialize_into()
|
.zcash_deserialize_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -821,31 +760,12 @@ async fn mempool_reverifies_after_tip_change() -> Result<(), Report> {
|
||||||
mut chain_tip_change,
|
mut chain_tip_change,
|
||||||
mut tx_verifier,
|
mut tx_verifier,
|
||||||
mut recent_syncs,
|
mut recent_syncs,
|
||||||
) = setup(network, u64::MAX).await;
|
) = setup(network, u64::MAX, true).await;
|
||||||
|
|
||||||
// Enable the mempool
|
// Enable the mempool
|
||||||
mempool.enable(&mut recent_syncs).await;
|
mempool.enable(&mut recent_syncs).await;
|
||||||
assert!(mempool.is_enabled());
|
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
|
// Queue transaction from block 3 for download
|
||||||
let tx = block3.transactions[0].clone();
|
let tx = block3.transactions[0].clone();
|
||||||
let txid = block3.transactions[0].unmined_id();
|
let txid = block3.transactions[0].unmined_id();
|
||||||
|
@ -985,6 +905,7 @@ async fn mempool_reverifies_after_tip_change() -> Result<(), Report> {
|
||||||
async fn setup(
|
async fn setup(
|
||||||
network: Network,
|
network: Network,
|
||||||
tx_cost_limit: u64,
|
tx_cost_limit: u64,
|
||||||
|
should_commit_genesis_block: bool,
|
||||||
) -> (
|
) -> (
|
||||||
Mempool,
|
Mempool,
|
||||||
MockPeerSet,
|
MockPeerSet,
|
||||||
|
@ -997,9 +918,9 @@ async fn setup(
|
||||||
|
|
||||||
// UTXO verification doesn't matter here.
|
// UTXO verification doesn't matter here.
|
||||||
let state_config = StateConfig::ephemeral();
|
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);
|
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();
|
let tx_verifier = MockService::build().for_unit_tests();
|
||||||
|
|
||||||
|
@ -1018,6 +939,29 @@ async fn setup(
|
||||||
chain_tip_change.clone(),
|
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,
|
mempool,
|
||||||
peer_set,
|
peer_set,
|
||||||
|
|
Loading…
Reference in New Issue