change(state): Check block and transaction Sprout anchors in parallel (#5742)

* parallelize anchors checks for blocks

* parallelize anchors checks for unmined_tx

* reverts par_iter in block_sapling_orchard_anchors_refer_to_final_treestates

* moves fetch_sprout_final_treestates out of rayon thread
This commit is contained in:
Arya 2022-12-01 06:35:12 -05:00 committed by GitHub
parent 8f9031880e
commit c838383fd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 65 additions and 35 deletions

View File

@ -3,6 +3,8 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use rayon::prelude::*;
use zebra_chain::{ use zebra_chain::{
block::{Block, Height}, block::{Block, Height},
sprout, sprout,
@ -406,21 +408,35 @@ pub(crate) fn block_sprout_anchors_refer_to_treestates(
"received sprout final treestate anchors", "received sprout final treestate anchors",
); );
block let check_tx_sprout_anchors = |(tx_index_in_block, transaction)| {
.transactions sprout_anchors_refer_to_treestates(
.iter() &sprout_final_treestates,
.enumerate() transaction,
.try_for_each(|(tx_index_in_block, transaction)| { transaction_hashes[tx_index_in_block],
sprout_anchors_refer_to_treestates( Some(tx_index_in_block),
&sprout_final_treestates, Some(height),
transaction, )?;
transaction_hashes[tx_index_in_block],
Some(tx_index_in_block),
Some(height),
)?;
Ok(()) Ok(())
}) };
// The overhead for a parallel iterator is unwarranted if sprout_final_treestates is empty
// because it will either return an error for the first transaction or only check that `joinsplit_data`
// is `None` for each transaction.
if sprout_final_treestates.is_empty() {
// The block has no valid sprout anchors
block
.transactions
.iter()
.enumerate()
.try_for_each(check_tx_sprout_anchors)
} else {
block
.transactions
.par_iter()
.enumerate()
.try_for_each(check_tx_sprout_anchors)
}
} }
/// Accepts a [`ZebraDb`], an optional [`Option<Arc<Chain>>`](Chain), and an [`UnminedTx`]. /// Accepts a [`ZebraDb`], an optional [`Option<Arc<Chain>>`](Chain), and an [`UnminedTx`].
@ -444,30 +460,44 @@ pub(crate) fn tx_anchors_refer_to_final_treestates(
None, None,
)?; )?;
let mut sprout_final_treestates = HashMap::new(); // If there are no sprout transactions in the block, avoid running a rayon scope
if unmined_tx.transaction.has_sprout_joinsplit_data() {
let mut sprout_final_treestates = HashMap::new();
fetch_sprout_final_treestates( fetch_sprout_final_treestates(
&mut sprout_final_treestates, &mut sprout_final_treestates,
finalized_state, finalized_state,
parent_chain, parent_chain,
&unmined_tx.transaction, &unmined_tx.transaction,
None, None,
None, None,
); );
tracing::trace!( let mut sprout_anchors_result = None;
sprout_final_treestate_count = ?sprout_final_treestates.len(), rayon::in_place_scope_fifo(|s| {
?sprout_final_treestates, // This check is expensive, because it updates a note commitment tree for each sprout JoinSplit.
"received sprout final treestate anchors", // Since we could be processing attacker-controlled mempool transactions, we need to run each one
); // in its own thread, separately from tokio's blocking I/O threads. And if we are under heavy load,
// we want verification to finish in order, so that later transactions can't delay earlier ones.
s.spawn_fifo(|_s| {
tracing::trace!(
sprout_final_treestate_count = ?sprout_final_treestates.len(),
?sprout_final_treestates,
"received sprout final treestate anchors",
);
sprout_anchors_refer_to_treestates( sprout_anchors_result = Some(sprout_anchors_refer_to_treestates(
&sprout_final_treestates, &sprout_final_treestates,
&unmined_tx.transaction, &unmined_tx.transaction,
unmined_tx.id.mined_id(), unmined_tx.id.mined_id(),
None, None,
None, None,
)?; ));
});
});
sprout_anchors_result.expect("scope has finished")?;
}
Ok(()) Ok(())
} }