From 0eea5522d737dac4cb09848b9b3517c1d5fdd08f Mon Sep 17 00:00:00 2001 From: File Large Date: Thu, 16 Oct 2025 16:27:04 +0200 Subject: [PATCH 1/4] perf: compact merkle tree for transaction placeholder proof --- crates/rbuilder-primitives/Cargo.toml | 4 + .../rbuilder-primitives/benches/ssz_proof.rs | 183 ++++++++++++++++++ .../src/mev_boost/ssz_roots.rs | 118 +++++------ crates/rbuilder/src/building/mod.rs | 41 ++-- 4 files changed, 281 insertions(+), 65 deletions(-) create mode 100644 crates/rbuilder-primitives/benches/ssz_proof.rs diff --git a/crates/rbuilder-primitives/Cargo.toml b/crates/rbuilder-primitives/Cargo.toml index 6bc4d18c2..207a3d434 100644 --- a/crates/rbuilder-primitives/Cargo.toml +++ b/crates/rbuilder-primitives/Cargo.toml @@ -65,3 +65,7 @@ ring = "0.17" [[bench]] name = "sha_pair" harness = false + +[[bench]] +name = "ssz_proof" +harness = false diff --git a/crates/rbuilder-primitives/benches/ssz_proof.rs b/crates/rbuilder-primitives/benches/ssz_proof.rs new file mode 100644 index 000000000..e2da7f701 --- /dev/null +++ b/crates/rbuilder-primitives/benches/ssz_proof.rs @@ -0,0 +1,183 @@ +use alloy_primitives::Bytes; +use criterion::{ + criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion, +}; +use proptest::{prelude::*, strategy::ValueTree as _, test_runner::TestRunner}; + +use impls::SszTransactionProof; + +criterion_main!(ssz_proof); +criterion_group!(ssz_proof, ssz_proof_bench); + +fn ssz_proof_bench(c: &mut Criterion) { + let mut group = c.benchmark_group("ssz_proof"); + + // Start with asserting equivalence of all implementations. + impls::assert_equivalence(); + + for num_txs in [100, 500, 1_000] { + let target = num_txs - 1; + + for tx_size in [128, 1_024] { + let mut runner = TestRunner::deterministic(); + let txs = generate_test_data(&mut runner, num_txs, tx_size); + + run_bench::(&mut group, &txs, target); + run_bench::(&mut group, &txs, target); + run_bench::(&mut group, &txs, target); + } + } +} + +fn run_bench( + group: &mut BenchmarkGroup<'_, WallTime>, + txs: &[Bytes], + target: usize, +) { + let tx_size = txs.first().unwrap().0.len(); + let id = format!( + "{} | num txs {} | tx size {} bytes", + T::description(), + txs.len(), + tx_size + ); + group.bench_function(id, |b| { + b.iter_with_setup( + || T::default(), + |mut gen| { + gen.generate(txs, target); + }, + ) + }); +} + +fn generate_test_data(runner: &mut TestRunner, num_txs: usize, tx_size: usize) -> Vec { + proptest::collection::vec(proptest::collection::vec(any::(), tx_size), num_txs) + .new_tree(runner) + .unwrap() + .current() + .into_iter() + .map(Bytes::from) + .collect::>() +} + +mod impls { + use super::*; + use alloy_primitives::{Bytes, B256}; + use rbuilder_primitives::mev_boost::ssz_roots::{ + sha_pair, tx_ssz_leaf_root, CompactSszTransactionTree, + }; + + const TREE_DEPTH: usize = 20; // log₂(MAX_TRANSACTIONS_PER_PAYLOAD) + const MAX_CHUNK_COUNT: usize = 1 << TREE_DEPTH; + + pub fn assert_equivalence() { + let num_txs = 100; + let proof_target = num_txs - 1; + let tx_size = 1_024; + let mut runner = TestRunner::deterministic(); + + let mut vanilla = VanillaSszTxProof::default(); + let mut vanilla_buf = VanillaBufferedSszTxProof::default(); + let mut compact = CompactSszTxProof::default(); + for _ in 0..100 { + let txs = generate_test_data(&mut runner, num_txs, tx_size); + let expected = vanilla.generate(&txs, proof_target); + assert_eq!(expected, vanilla_buf.generate(&txs, proof_target)); + assert_eq!(expected, compact.generate(&txs, proof_target)); + } + } + + pub trait SszTransactionProof: Default { + fn description() -> &'static str; + + fn generate(&mut self, txs: &[Bytes], target: usize) -> Vec; + } + + /// === VanillaSszTransactionProof === + #[derive(Default)] + pub struct VanillaSszTxProof; + + impl SszTransactionProof for VanillaSszTxProof { + fn description() -> &'static str { + "vanilla" + } + + fn generate(&mut self, txs: &[Bytes], target: usize) -> Vec { + vanilla_transaction_proof_ssz(txs, target, &mut Vec::new(), &mut Vec::new()) + } + } + + /// === VanillaBufferedSszTransactionProof === + #[derive(Default)] + pub struct VanillaBufferedSszTxProof { + current_buf: Vec, + next_buf: Vec, + } + + impl SszTransactionProof for VanillaBufferedSszTxProof { + fn description() -> &'static str { + "vanilla with buffers" + } + + fn generate(&mut self, txs: &[Bytes], target: usize) -> Vec { + vanilla_transaction_proof_ssz(txs, target, &mut self.current_buf, &mut self.next_buf) + } + } + + fn vanilla_transaction_proof_ssz( + txs: &[Bytes], + target: usize, + current_buf: &mut Vec, + next_buf: &mut Vec, + ) -> Vec { + current_buf.clear(); + for idx in 0..MAX_CHUNK_COUNT { + let leaf = txs.get(idx).map(tx_ssz_leaf_root).unwrap_or(B256::ZERO); + current_buf.insert(idx, leaf); + } + + let mut branch = Vec::new(); + let (current_level, next_level) = (current_buf, next_buf); + let mut current_index = target; + + for _level in 0..TREE_DEPTH { + let sibling_index = current_index ^ 1; + branch.push(current_level[sibling_index]); + + next_level.clear(); + for i in (0..current_level.len()).step_by(2) { + let left = current_level[i]; + let right = current_level[i + 1]; + next_level.push(sha_pair(&left, &right)); + } + + std::mem::swap(current_level, next_level); + current_index /= 2; + + if current_level.len() == 1 { + break; + } + } + + branch + } + + /// === CompactSszTxProof === + #[derive(Default)] + pub struct CompactSszTxProof; + + impl SszTransactionProof for CompactSszTxProof { + fn description() -> &'static str { + "compact" + } + + fn generate(&mut self, txs: &[Bytes], target: usize) -> Vec { + let mut leaves = Vec::with_capacity(txs.len()); + for tx in txs { + leaves.push(tx_ssz_leaf_root(tx)); + } + CompactSszTransactionTree::from_leaves(leaves).proof(target) + } + } +} diff --git a/crates/rbuilder-primitives/src/mev_boost/ssz_roots.rs b/crates/rbuilder-primitives/src/mev_boost/ssz_roots.rs index 97a72ba87..eb5d3029f 100644 --- a/crates/rbuilder-primitives/src/mev_boost/ssz_roots.rs +++ b/crates/rbuilder-primitives/src/mev_boost/ssz_roots.rs @@ -3,6 +3,7 @@ use alloy_primitives::{Address, Bytes, B256}; use sha2::{Digest, Sha256}; use ssz_types::{FixedVector, VariableList}; +use std::sync::LazyLock; use tree_hash::TreeHash as _; #[derive(tree_hash_derive::TreeHash)] @@ -62,74 +63,83 @@ pub fn calculate_transactions_root_ssz(transactions: &[Bytes]) -> B256 { const TREE_DEPTH: usize = 20; // log₂(MAX_TRANSACTIONS_PER_PAYLOAD) -const MAX_CHUNK_COUNT: usize = 1 << TREE_DEPTH; - -/// Generate SSZ proof for target transaction. -pub fn generate_transaction_proof_ssz(transactions: &[Bytes], target: usize) -> Vec { - generate_transaction_proof_ssz_with_buffers( - transactions, - target, - &mut Vec::new(), - &mut Vec::new(), - ) -} - -/// Generate SSZ proof for target transaction with reusable buffer. -pub fn generate_transaction_proof_ssz_with_buffers( - transactions: &[Bytes], - target: usize, - current_buf: &mut Vec, - next_buf: &mut Vec, -) -> Vec { - // Compute all leaf hashes and fill remaining slots with 0 hashes. - // SSZ always pads to the maximum possible size defined by the type - current_buf.clear(); - for idx in 0..MAX_CHUNK_COUNT { - let leaf = transactions - .get(idx) - .map(ssz_leaf_root) - .unwrap_or(B256::ZERO); - current_buf.insert(idx, leaf); +// Precompute HASHES[k] = hash of a full-zero subtree at level k. +static ZERO_SUBTREE: LazyLock<[B256; TREE_DEPTH + 1]> = LazyLock::new(|| { + let mut hashes = [B256::ZERO; TREE_DEPTH + 1]; + for lvl in 0..TREE_DEPTH { + hashes[lvl + 1] = sha_pair(&hashes[lvl], &hashes[lvl]); } + hashes +}); + +#[derive(Debug)] +pub struct CompactSszTransactionTree(Vec>); + +impl CompactSszTransactionTree { + /// Build a compact Merkle tree over `n = txs.len()` leaves. + /// Level 0 = leaves; Level k has len = ceil(prev_len/2). + /// Padding beyond n uses structural zeros Z[k]. + pub fn from_leaves(mut leaves: Vec) -> Self { + // Degenerate case: treat as single zero leaf so we still have a root + if leaves.is_empty() { + leaves.push(ZERO_SUBTREE[0]); + } - // Build the merkle tree bottom-up and collect the proof - let mut branch = Vec::new(); - let (current_level, next_level) = (current_buf, next_buf); - let mut current_index = target; - - // Build the complete tree to depth TREE_DEPTH (20 levels) - for _level in 0..TREE_DEPTH { - // Get the sibling at this level - let sibling_index = current_index ^ 1; - branch.push(current_level[sibling_index]); - - // Build next level up - next_level.clear(); - for i in (0..current_level.len()).step_by(2) { - let left = current_level[i]; - let right = current_level[i + 1]; - next_level.push(sha_pair(&left, &right)); + // Level 0: leaves + let mut levels: Vec> = Vec::new(); + levels.push(leaves); + + // Upper levels + for level in 0..TREE_DEPTH { + let prev = &levels[level]; + if prev.len() == 1 { + break; // reached root + } + let parents = prev.len().div_ceil(2); + let mut next = Vec::with_capacity(parents); + for i in 0..parents { + // NOTE: left node should always be set + let l = prev.get(2 * i).copied().unwrap_or(ZERO_SUBTREE[level]); + let r = prev.get(2 * i + 1).copied().unwrap_or(ZERO_SUBTREE[level]); + next.push(sha_pair(&l, &r)); + } + levels.push(next); } - std::mem::swap(current_level, next_level); - current_index /= 2; + Self(levels) + } - // Stop when we reach the root - if current_level.len() == 1 { - break; + pub fn proof(&self, target: usize) -> Vec { + let mut branch = Vec::with_capacity(TREE_DEPTH); + for level in 0..TREE_DEPTH { + if level >= self.0.len() || self.0[level].len() == 1 { + // Either level wasn't built or compact root reached - structural zero sibling. + branch.push(ZERO_SUBTREE[level]); + continue; + } + + let segment_index = target >> level; + let sibling_index = segment_index ^ 1; + let sibling = self.0[level] + .get(sibling_index) + .copied() + .unwrap_or(ZERO_SUBTREE[level]); // structural zero if beyond built range + branch.push(sibling); } - } - branch + branch + } } +/// Create the leaf root for transaction bytes. #[inline] -fn ssz_leaf_root(data: &Bytes) -> B256 { +pub fn tx_ssz_leaf_root(data: &[u8]) -> B256 { B256::from_slice(&BinaryTransaction::from(data.to_vec()).tree_hash_root()[..]) } +/// Compute a SHA-256 hash of the pair of 32 byte hashes. #[inline] -fn sha_pair(a: &B256, b: &B256) -> B256 { +pub fn sha_pair(a: &B256, b: &B256) -> B256 { let mut h = Sha256::new(); h.update(a); h.update(b); diff --git a/crates/rbuilder/src/building/mod.rs b/crates/rbuilder/src/building/mod.rs index 6cfec1915..9a92133d2 100644 --- a/crates/rbuilder/src/building/mod.rs +++ b/crates/rbuilder/src/building/mod.rs @@ -32,12 +32,15 @@ use alloy_primitives::{Address, BlockNumber, Bytes, B256, I256, U256}; use alloy_rlp::Encodable as _; use alloy_rpc_types_beacon::events::PayloadAttributesEvent; use cached_reads::{LocalCachedReads, SharedCachedReads}; +use derive_more::Deref; use eth_sparse_mpt::SparseTrieLocalCache; use evm::EthCachedEvmFactory; use jsonrpsee::core::Serialize; +use parking_lot::RwLock; use rbuilder_primitives::{ mev_boost::{ - ssz_roots::generate_transaction_proof_ssz, BidAdjustmentData, BidAdjustmentStateProofs, + ssz_roots::{tx_ssz_leaf_root, CompactSszTransactionTree}, + BidAdjustmentData, BidAdjustmentStateProofs, }, BlockSpace, Order, OrderId, SimValue, SimulatedOrder, TransactionSignedEcRecoveredWithBlobs, }; @@ -382,8 +385,13 @@ pub struct ThreadBlockBuildingContext { pub bloom_cache: ReceiptsDataCache, pub tx_root_cache: TransactionRootCache, pub root_hash_calculator: SparseTrieLocalCache, + pub tx_ssz_leaf_root_cache: TransactionSszLeafRootCache, } +/// The cache for keeping the computed SSZ leaf roots for the transactions. +#[derive(Clone, Default, Debug, Deref)] +pub struct TransactionSszLeafRootCache(Arc>>); + #[derive(Debug, Clone, Copy)] pub struct BlockBuildingConfig { pub sorting: Sorting, @@ -1150,16 +1158,6 @@ impl>(); - generate_transaction_proof_ssz(&encoded_txs, target) - }); let bid_adjustment_state_proofs = Self::generate_bid_adjustment_state_proofs(state, ctx, local_ctx) .inspect_err(|error| { @@ -1170,6 +1168,27 @@ impl *leaf, + None => { + buf.clear(); + tx.encode_2718(&mut buf); + let leaf_root = tx_ssz_leaf_root(&buf); + cache.write().insert(*tx.hash(), leaf_root); + leaf_root + } + }; + leaves.push(leaf); + } + + let target = self.executed_tx_infos.len().checked_sub(1).unwrap(); + CompactSszTransactionTree::from_leaves(leaves).proof(target) + }); let bid_adjustments = bid_adjustment_state_proofs .into_iter() .map(|(fee_payer, state_proofs)| { From a2b22be8f4d0a6b2330e0f8cab7c132e059f84fa Mon Sep 17 00:00:00 2001 From: File Large Date: Thu, 16 Oct 2025 16:41:48 +0200 Subject: [PATCH 2/4] fix bench --- crates/rbuilder-primitives/benches/ssz_proof.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/rbuilder-primitives/benches/ssz_proof.rs b/crates/rbuilder-primitives/benches/ssz_proof.rs index e2da7f701..362ff6346 100644 --- a/crates/rbuilder-primitives/benches/ssz_proof.rs +++ b/crates/rbuilder-primitives/benches/ssz_proof.rs @@ -133,7 +133,10 @@ mod impls { ) -> Vec { current_buf.clear(); for idx in 0..MAX_CHUNK_COUNT { - let leaf = txs.get(idx).map(tx_ssz_leaf_root).unwrap_or(B256::ZERO); + let leaf = txs + .get(idx) + .map(|tx| tx_ssz_leaf_root(&tx)) + .unwrap_or(B256::ZERO); current_buf.insert(idx, leaf); } From 10c0a8f3874627f0020603c2f92a8bea326ec911 Mon Sep 17 00:00:00 2001 From: File Large Date: Thu, 16 Oct 2025 19:50:18 +0200 Subject: [PATCH 3/4] move to bid_adjustments.rs --- .../rbuilder/src/building/bid_adjustments.rs | 125 +++++++++++++++++ crates/rbuilder/src/building/mod.rs | 129 ++---------------- 2 files changed, 138 insertions(+), 116 deletions(-) create mode 100644 crates/rbuilder/src/building/bid_adjustments.rs diff --git a/crates/rbuilder/src/building/bid_adjustments.rs b/crates/rbuilder/src/building/bid_adjustments.rs new file mode 100644 index 000000000..f75df5c5d --- /dev/null +++ b/crates/rbuilder/src/building/bid_adjustments.rs @@ -0,0 +1,125 @@ +use alloy_eips::Encodable2718 as _; +use alloy_primitives::{Address, B256}; +use rbuilder_primitives::mev_boost::{ + ssz_roots::{tx_ssz_leaf_root, CompactSszTransactionTree}, + BidAdjustmentStateProofs, +}; +use reth::revm::database::StateProviderDatabase; +use reth_primitives::TransactionSigned; +use revm::{database::BundleAccount, Database as _}; +use std::collections::{hash_map, HashMap, HashSet}; +use tracing::*; + +use crate::building::{ + cached_reads::CachedDB, BlockBuildingContext, BlockState, FinalizeError, + ThreadBlockBuildingContext, TransactionSszLeafRootCache, +}; + +/// Generate bid adjustment state proofs. +pub fn generate_bid_adjustment_state_proofs( + block_state: &mut BlockState, + ctx: &BlockBuildingContext, + local_ctx: &mut ThreadBlockBuildingContext, +) -> Result, FinalizeError> { + if ctx.adjustment_fee_payers.is_empty() { + return Ok(Default::default()); + } + + let builder_signer = &ctx.builder_signer; + let builder_address = builder_signer.address; + let fee_recipient_address = ctx.attributes.suggested_fee_recipient; + + let proof_targets = HashSet::from_iter( + [builder_address, fee_recipient_address] + .into_iter() + .chain(ctx.adjustment_fee_payers.clone()), + ); + + // Pre-load all proof targets that are missing from the bundle state. + // This is a requirement for accounts to become a part of the trie and be able to generate proofs for them. + let mut cachedb = CachedDB::new( + StateProviderDatabase::new(block_state.state_provider()), + &mut local_ctx.cached_reads, + &ctx.shared_cached_reads, + ); + for fee_payer in &ctx.adjustment_fee_payers { + if let hash_map::Entry::Vacant(entry) = + block_state.bundle_state_mut().state.entry(*fee_payer) + { + let account_info = cachedb + .basic(*fee_payer) + .map_err(|error| FinalizeError::Other(error.into()))?; + entry.insert(BundleAccount { + original_info: account_info.clone(), + info: account_info, + status: revm::database::AccountStatus::Loaded, + storage: Default::default(), + }); + } + } + + let mut account_proofs = + ctx.root_hasher + .account_proofs(block_state.bundle_state(), &proof_targets, local_ctx)?; + + let Some(builder_proof) = account_proofs.remove(&builder_address) else { + return Err(FinalizeError::Other(eyre::eyre!( + "account proof for builder {builder_address} is missing" + ))); + }; + let Some(fee_recipient_proof) = account_proofs.remove(&fee_recipient_address) else { + return Err(FinalizeError::Other(eyre::eyre!( + "account proof for proposer {fee_recipient_address} is missing" + ))); + }; + + let mut bid_adjustments = HashMap::default(); + for fee_payer_address in &ctx.adjustment_fee_payers { + let Some(fee_payer_proof) = account_proofs.remove(fee_payer_address) else { + error!( + %fee_payer_address, + "Fee payer proof is missing" + ); + continue; + }; + + bid_adjustments.insert( + *fee_payer_address, + BidAdjustmentStateProofs { + builder_address, + builder_proof: builder_proof.clone(), + fee_recipient_address, + fee_recipient_proof: fee_recipient_proof.clone(), + fee_payer_address: *fee_payer_address, + fee_payer_proof, + }, + ); + } + + Ok(bid_adjustments) +} + +/// Compute the SSZ transaction proof for bid adjustments. +pub fn compute_cl_placeholder_transaction_proof( + transactions: &[TransactionSigned], + cache: &TransactionSszLeafRootCache, +) -> Vec { + let mut buf = Vec::new(); + let mut leaves = Vec::with_capacity(transactions.len()); + for tx in transactions { + let leaf = match cache.read().get(tx.hash()) { + Some(leaf) => *leaf, + None => { + buf.clear(); + tx.encode_2718(&mut buf); + let leaf_root = tx_ssz_leaf_root(&buf); + cache.write().insert(*tx.hash(), leaf_root); + leaf_root + } + }; + leaves.push(leaf); + } + + let target = transactions.len().checked_sub(1).unwrap(); + CompactSszTransactionTree::from_leaves(leaves).proof(target) +} diff --git a/crates/rbuilder/src/building/mod.rs b/crates/rbuilder/src/building/mod.rs index 9a92133d2..3f5fb95a8 100644 --- a/crates/rbuilder/src/building/mod.rs +++ b/crates/rbuilder/src/building/mod.rs @@ -1,5 +1,7 @@ use crate::{ - building::cached_reads::CachedDB, + building::bid_adjustments::{ + compute_cl_placeholder_transaction_proof, generate_bid_adjustment_state_proofs, + }, live_builder::{ block_list_provider::BlockList, order_input::mempool_txs_detector::MempoolTxsDetector, payload_events::InternalPayloadId, @@ -25,7 +27,6 @@ use alloy_eips::{ eip7685::Requests, eip7840::BlobParams, merge::BEACON_NONCE, - Encodable2718, }; use alloy_evm::{block::system_calls::SystemCaller, env::EvmEnv, eth::eip6110}; use alloy_primitives::{Address, BlockNumber, Bytes, B256, I256, U256}; @@ -38,16 +39,12 @@ use evm::EthCachedEvmFactory; use jsonrpsee::core::Serialize; use parking_lot::RwLock; use rbuilder_primitives::{ - mev_boost::{ - ssz_roots::{tx_ssz_leaf_root, CompactSszTransactionTree}, - BidAdjustmentData, BidAdjustmentStateProofs, - }, - BlockSpace, Order, OrderId, SimValue, SimulatedOrder, TransactionSignedEcRecoveredWithBlobs, + mev_boost::BidAdjustmentData, BlockSpace, Order, OrderId, SimValue, SimulatedOrder, + TransactionSignedEcRecoveredWithBlobs, }; use reth::{ payload::PayloadId, primitives::{Block, SealedBlock}, - revm::database::StateProviderDatabase, }; use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks}; use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError}; @@ -60,14 +57,13 @@ use reth_primitives_traits::{proofs, Block as _}; use revm::{ context::BlockEnv, context_interface::{block::BlobExcessGasAndPrice, result::InvalidTransaction}, - database::{states::bundle_state::BundleRetention, BundleAccount}, + database::states::bundle_state::BundleRetention, primitives::hardfork::SpecId, - Database as _, }; use serde::Deserialize; use std::{ cell::LazyCell, - collections::{hash_map, HashMap, HashSet}, + collections::HashMap, hash::Hash, str::FromStr, sync::Arc, @@ -78,6 +74,7 @@ use time::OffsetDateTime; use tracing::{error, trace}; use tx_sim_cache::TxExecutionCache; +pub mod bid_adjustments; pub mod block_orders; pub mod builders; pub mod built_block_trace; @@ -1159,7 +1156,7 @@ impl *leaf, - None => { - buf.clear(); - tx.encode_2718(&mut buf); - let leaf_root = tx_ssz_leaf_root(&buf); - cache.write().insert(*tx.hash(), leaf_root); - leaf_root - } - }; - leaves.push(leaf); - } - - let target = self.executed_tx_infos.len().checked_sub(1).unwrap(); - CompactSszTransactionTree::from_leaves(leaves).proof(target) + compute_cl_placeholder_transaction_proof( + &block.body.transactions, + &local_ctx.tx_ssz_leaf_root_cache, + ) }); let bid_adjustments = bid_adjustment_state_proofs .into_iter() @@ -1234,91 +1216,6 @@ impl Result, FinalizeError> { - if ctx.adjustment_fee_payers.is_empty() { - return Ok(Default::default()); - } - - let builder_signer = &ctx.builder_signer; - let builder_address = builder_signer.address; - let fee_recipient_address = ctx.attributes.suggested_fee_recipient; - - let proof_targets = HashSet::from_iter( - [builder_address, fee_recipient_address] - .into_iter() - .chain(ctx.adjustment_fee_payers.clone()), - ); - - // Pre-load all proof targets that are missing from the bundle state. - // This is a requirement for accounts to become a part of the trie and be able to generate proofs for them. - let mut cachedb = CachedDB::new( - StateProviderDatabase::new(block_state.state_provider()), - &mut local_ctx.cached_reads, - &ctx.shared_cached_reads, - ); - for fee_payer in &ctx.adjustment_fee_payers { - if let hash_map::Entry::Vacant(entry) = - block_state.bundle_state_mut().state.entry(*fee_payer) - { - let account_info = cachedb - .basic(*fee_payer) - .map_err(|error| FinalizeError::Other(error.into()))?; - entry.insert(BundleAccount { - original_info: account_info.clone(), - info: account_info, - status: revm::database::AccountStatus::Loaded, - storage: Default::default(), - }); - } - } - - let mut account_proofs = ctx.root_hasher.account_proofs( - block_state.bundle_state(), - &proof_targets, - local_ctx, - )?; - - let Some(builder_proof) = account_proofs.remove(&builder_address) else { - return Err(FinalizeError::Other(eyre::eyre!( - "account proof for builder {builder_address} is missing" - ))); - }; - let Some(fee_recipient_proof) = account_proofs.remove(&fee_recipient_address) else { - return Err(FinalizeError::Other(eyre::eyre!( - "account proof for proposer {fee_recipient_address} is missing" - ))); - }; - - let mut bid_adjustments = HashMap::default(); - for fee_payer_address in &ctx.adjustment_fee_payers { - let Some(fee_payer_proof) = account_proofs.remove(fee_payer_address) else { - error!( - %fee_payer_address, - "Fee payer proof is missing" - ); - continue; - }; - - bid_adjustments.insert( - *fee_payer_address, - BidAdjustmentStateProofs { - builder_address, - builder_proof: builder_proof.clone(), - fee_recipient_address, - fee_recipient_proof: fee_recipient_proof.clone(), - fee_payer_address: *fee_payer_address, - fee_payer_proof, - }, - ); - } - - Ok(bid_adjustments) - } - /// Standard pre block ETH stuff + space allocation for rlp length pub fn pre_block_call( &mut self, From 84c5e1c61dd6e9d092fbd8d8d29f3de0ef946bd8 Mon Sep 17 00:00:00 2001 From: File Large Date: Thu, 16 Oct 2025 21:26:38 +0200 Subject: [PATCH 4/4] change to mutex --- .../rbuilder/src/building/bid_adjustments.rs | 19 +++++++++---------- crates/rbuilder/src/building/mod.rs | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/rbuilder/src/building/bid_adjustments.rs b/crates/rbuilder/src/building/bid_adjustments.rs index f75df5c5d..188e9ac1b 100644 --- a/crates/rbuilder/src/building/bid_adjustments.rs +++ b/crates/rbuilder/src/building/bid_adjustments.rs @@ -106,20 +106,19 @@ pub fn compute_cl_placeholder_transaction_proof( ) -> Vec { let mut buf = Vec::new(); let mut leaves = Vec::with_capacity(transactions.len()); + let mut cache = cache.lock(); for tx in transactions { - let leaf = match cache.read().get(tx.hash()) { - Some(leaf) => *leaf, - None => { - buf.clear(); - tx.encode_2718(&mut buf); - let leaf_root = tx_ssz_leaf_root(&buf); - cache.write().insert(*tx.hash(), leaf_root); - leaf_root - } + let leaf = if let Some(leaf) = cache.get(tx.hash()) { + *leaf + } else { + buf.clear(); + tx.encode_2718(&mut buf); + let leaf_root = tx_ssz_leaf_root(&buf); + cache.insert(*tx.hash(), leaf_root); + leaf_root }; leaves.push(leaf); } - let target = transactions.len().checked_sub(1).unwrap(); CompactSszTransactionTree::from_leaves(leaves).proof(target) } diff --git a/crates/rbuilder/src/building/mod.rs b/crates/rbuilder/src/building/mod.rs index 3f5fb95a8..7947b9b0e 100644 --- a/crates/rbuilder/src/building/mod.rs +++ b/crates/rbuilder/src/building/mod.rs @@ -37,7 +37,7 @@ use derive_more::Deref; use eth_sparse_mpt::SparseTrieLocalCache; use evm::EthCachedEvmFactory; use jsonrpsee::core::Serialize; -use parking_lot::RwLock; +use parking_lot::Mutex; use rbuilder_primitives::{ mev_boost::BidAdjustmentData, BlockSpace, Order, OrderId, SimValue, SimulatedOrder, TransactionSignedEcRecoveredWithBlobs, @@ -387,7 +387,7 @@ pub struct ThreadBlockBuildingContext { /// The cache for keeping the computed SSZ leaf roots for the transactions. #[derive(Clone, Default, Debug, Deref)] -pub struct TransactionSszLeafRootCache(Arc>>); +pub struct TransactionSszLeafRootCache(Arc>>); #[derive(Debug, Clone, Copy)] pub struct BlockBuildingConfig {