From 2138a8b587ca7dc985a23b20d346dbf6b60acc35 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:11:19 +0200 Subject: [PATCH] feat(primitives): add `Receipts` to encapsulate `Vec>` (#4626) Co-authored-by: Matthias Seitz --- bin/reth/src/init.rs | 6 +- crates/payload/basic/src/lib.rs | 11 ++- crates/primitives/src/lib.rs | 2 +- crates/primitives/src/prune/part.rs | 3 + crates/primitives/src/receipt.rs | 97 ++++++++++++++++++- crates/revm/src/processor.rs | 29 ++---- crates/rpc/rpc/src/eth/api/pending_block.rs | 10 +- .../bundle_state_with_receipts.rs | 47 ++++----- crates/storage/provider/src/chain.rs | 6 +- .../src/providers/database/provider.rs | 2 +- .../storage/provider/src/test_utils/blocks.rs | 10 +- 11 files changed, 153 insertions(+), 70 deletions(-) diff --git a/bin/reth/src/init.rs b/bin/reth/src/init.rs index 352c8cf38..0b186e9b6 100644 --- a/bin/reth/src/init.rs +++ b/bin/reth/src/init.rs @@ -6,7 +6,9 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, }; use reth_interfaces::{db::DatabaseError, RethError}; -use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, StorageEntry, H256, U256}; +use reth_primitives::{ + stage::StageId, Account, Bytecode, ChainSpec, Receipts, StorageEntry, H256, U256, +}; use reth_provider::{ bundle_state::{BundleStateInit, RevertsInit}, BundleStateWithReceipts, DatabaseProviderRW, HashingWriter, HistoryWriter, OriginalValuesKnown, @@ -145,7 +147,7 @@ pub fn insert_genesis_state( state_init, all_reverts_init, contracts.into_iter().collect(), - vec![], + Receipts::new(), 0, ); diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index e7d7abfe1..792cbe3fb 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -25,7 +25,7 @@ use reth_primitives::{ EMPTY_WITHDRAWALS, ETHEREUM_BLOCK_GAS_LIMIT, RETH_CLIENT_VERSION, SLOT_DURATION, }, proofs, Block, BlockNumberOrTag, ChainSpec, Header, IntoRecoveredTransaction, Receipt, - SealedBlock, Withdrawal, EMPTY_OMMER_ROOT, H256, U256, + Receipts, SealedBlock, Withdrawal, EMPTY_OMMER_ROOT, H256, U256, }; use reth_provider::{BlockReaderIdExt, BlockSource, BundleStateWithReceipts, StateProviderFactory}; use reth_revm::{ @@ -787,7 +787,11 @@ where // 4788 contract call db.merge_transitions(BundleRetention::PlainState); - let bundle = BundleStateWithReceipts::new(db.take_bundle(), vec![receipts], block_number); + let bundle = BundleStateWithReceipts::new( + db.take_bundle(), + Receipts::from_vec(vec![receipts]), + block_number, + ); let receipts_root = bundle.receipts_root_slow(block_number).expect("Number is in range"); let logs_bloom = bundle.block_logs_bloom(block_number).expect("Number is in range"); @@ -907,7 +911,8 @@ where db.merge_transitions(BundleRetention::PlainState); // calculate the state root - let bundle_state = BundleStateWithReceipts::new(db.take_bundle(), vec![], block_number); + let bundle_state = + BundleStateWithReceipts::new(db.take_bundle(), Receipts::new(), block_number); let state_root = state.state_root(&bundle_state)?; let header = Header { diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index bc1340cb8..45a29bee3 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -83,7 +83,7 @@ pub use prune::{ PruneBatchSizes, PruneCheckpoint, PruneMode, PruneModes, PrunePart, PrunePartError, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE, }; -pub use receipt::{Receipt, ReceiptWithBloom, ReceiptWithBloomRef}; +pub use receipt::{Receipt, ReceiptWithBloom, ReceiptWithBloomRef, Receipts}; pub use revm_primitives::JumpMap; pub use serde_helper::JsonU256; pub use storage::StorageEntry; diff --git a/crates/primitives/src/prune/part.rs b/crates/primitives/src/prune/part.rs index 7d1139c25..d1feb8995 100644 --- a/crates/primitives/src/prune/part.rs +++ b/crates/primitives/src/prune/part.rs @@ -26,6 +26,9 @@ pub enum PrunePartError { /// Invalid configuration of a prune part. #[error("The configuration provided for {0} is invalid.")] Configuration(PrunePart), + /// Receipts have been pruned + #[error("Receipts have been pruned")] + ReceiptsPruned, } #[cfg(test)] diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 57cbdcc6d..7b4f2740f 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -1,12 +1,16 @@ use crate::{ bloom::logs_bloom, compression::{RECEIPT_COMPRESSOR, RECEIPT_DECOMPRESSOR}, - Bloom, Log, TxType, + proofs::calculate_receipt_root_ref, + Bloom, Log, PrunePartError, TxType, H256, }; use bytes::{Buf, BufMut, BytesMut}; use reth_codecs::{main_codec, Compact, CompactZstd}; use reth_rlp::{length_of_length, Decodable, Encodable}; -use std::cmp::Ordering; +use std::{ + cmp::Ordering, + ops::{Deref, DerefMut}, +}; /// Receipt containing result of transaction execution. #[main_codec(zstd)] @@ -44,6 +48,95 @@ impl Receipt { } } +/// A collection of receipts organized as a two-dimensional vector. +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct Receipts { + /// A two-dimensional vector of optional `Receipt` instances. + pub receipt_vec: Vec>>, +} + +impl Receipts { + /// Create a new `Receipts` instance with an empty vector. + pub fn new() -> Self { + Self { receipt_vec: vec![] } + } + + /// Create a new `Receipts` instance from an existing vector. + pub fn from_vec(vec: Vec>>) -> Self { + Self { receipt_vec: vec } + } + + /// Returns the length of the `Receipts` vector. + pub fn len(&self) -> usize { + self.receipt_vec.len() + } + + /// Returns `true` if the `Receipts` vector is empty. + pub fn is_empty(&self) -> bool { + self.receipt_vec.is_empty() + } + + /// Push a new vector of receipts into the `Receipts` collection. + pub fn push(&mut self, receipts: Vec>) { + self.receipt_vec.push(receipts); + } + + /// Retrieves the receipt root for all recorded receipts from index. + pub fn root_slow(&self, index: usize) -> Option { + Some(calculate_receipt_root_ref( + &self.receipt_vec[index].iter().map(Option::as_ref).collect::>>()?, + )) + } + + /// Retrieves gas spent by transactions as a vector of tuples (transaction index, gas used). + pub fn gas_spent_by_tx(&self) -> Result, PrunePartError> { + self.last() + .map(|block_r| { + block_r + .iter() + .enumerate() + .map(|(id, tx_r)| { + if let Some(receipt) = tx_r.as_ref() { + Ok((id as u64, receipt.cumulative_gas_used)) + } else { + Err(PrunePartError::ReceiptsPruned) + } + }) + .collect::, PrunePartError>>() + }) + .unwrap_or(Ok(vec![])) + } +} + +impl Deref for Receipts { + type Target = Vec>>; + + fn deref(&self) -> &Self::Target { + &self.receipt_vec + } +} + +impl DerefMut for Receipts { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.receipt_vec + } +} + +impl IntoIterator for Receipts { + type Item = Vec>; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.receipt_vec.into_iter() + } +} + +impl FromIterator>> for Receipts { + fn from_iter>>>(iter: I) -> Self { + Self::from_vec(iter.into_iter().collect()) + } +} + impl From for ReceiptWithBloom { fn from(receipt: Receipt) -> Self { let bloom = receipt.bloom_slow(); diff --git a/crates/revm/src/processor.rs b/crates/revm/src/processor.rs index 3fa89ac21..d66d44879 100644 --- a/crates/revm/src/processor.rs +++ b/crates/revm/src/processor.rs @@ -12,8 +12,8 @@ use reth_interfaces::{ }; use reth_primitives::{ Address, Block, BlockNumber, Bloom, ChainSpec, Hardfork, Header, PruneMode, PruneModes, - PrunePartError, Receipt, ReceiptWithBloom, TransactionSigned, H256, MINIMUM_PRUNING_DISTANCE, - U256, + PrunePartError, Receipt, ReceiptWithBloom, Receipts, TransactionSigned, H256, + MINIMUM_PRUNING_DISTANCE, U256, }; use reth_provider::{ BlockExecutor, BlockExecutorStats, BundleStateWithReceipts, PrunableBlockExecutor, @@ -57,7 +57,7 @@ pub struct EVMProcessor<'a> { /// The inner vector stores receipts ordered by transaction number. /// /// If receipt is None it means it is pruned. - receipts: Vec>>, + receipts: Receipts, /// First block will be initialized to `None` /// and be set to the block number of first block executed. first_block: Option, @@ -86,7 +86,7 @@ impl<'a> EVMProcessor<'a> { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()), - receipts: Vec::new(), + receipts: Receipts::new(), first_block: None, tip: None, prune_modes: PruneModes::none(), @@ -119,7 +119,7 @@ impl<'a> EVMProcessor<'a> { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()), - receipts: Vec::new(), + receipts: Receipts::new(), first_block: None, tip: None, prune_modes: PruneModes::none(), @@ -352,24 +352,7 @@ impl<'a> EVMProcessor<'a> { return Err(BlockValidationError::BlockGasUsed { got: cumulative_gas_used, expected: block.gas_used, - gas_spent_by_tx: self - .receipts - .last() - .map(|block_r| { - block_r - .iter() - .enumerate() - .map(|(id, tx_r)| { - ( - id as u64, - tx_r.as_ref() - .expect("receipts have not been pruned") - .cumulative_gas_used, - ) - }) - .collect() - }) - .unwrap_or_default(), + gas_spent_by_tx: self.receipts.gas_spent_by_tx()?, } .into()) } diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc/src/eth/api/pending_block.rs index 1e3b1f807..819d08de8 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc/src/eth/api/pending_block.rs @@ -4,8 +4,8 @@ use crate::eth::error::{EthApiError, EthResult}; use core::fmt::Debug; use reth_primitives::{ constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE}, - proofs, Block, ChainSpec, Header, IntoRecoveredTransaction, Receipt, SealedBlock, SealedHeader, - EMPTY_OMMER_ROOT, H256, U256, + proofs, Block, ChainSpec, Header, IntoRecoveredTransaction, Receipt, Receipts, SealedBlock, + SealedHeader, EMPTY_OMMER_ROOT, H256, U256, }; use reth_provider::{BundleStateWithReceipts, ChainSpecProvider, StateProviderFactory}; use reth_revm::{ @@ -187,7 +187,11 @@ impl PendingBlockEnv { // merge all transitions into bundle state. db.merge_transitions(BundleRetention::PlainState); - let bundle = BundleStateWithReceipts::new(db.take_bundle(), vec![receipts], block_number); + let bundle = BundleStateWithReceipts::new( + db.take_bundle(), + Receipts::from_vec(vec![receipts]), + block_number, + ); let receipts_root = bundle.receipts_root_slow(block_number).expect("Block is present"); let logs_bloom = bundle.block_logs_bloom(block_number).expect("Block is present"); diff --git a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs index 5936cb940..29ef1dad0 100644 --- a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs +++ b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs @@ -5,8 +5,8 @@ use reth_db::{ }; use reth_interfaces::db::DatabaseError; use reth_primitives::{ - bloom::logs_bloom, keccak256, proofs::calculate_receipt_root_ref, Account, Address, - BlockNumber, Bloom, Bytecode, Log, Receipt, StorageEntry, H256, U256, + bloom::logs_bloom, keccak256, Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, + Receipts, StorageEntry, H256, U256, }; use reth_revm_primitives::{ db::states::BundleState, into_reth_acc, into_revm_acc, primitives::AccountInfo, @@ -30,8 +30,8 @@ pub struct BundleStateWithReceipts { /// Outer vector stores receipts for each block sequentially. /// The inner vector stores receipts ordered by transaction number. /// - /// If receipt is None it means it is pruned. - receipts: Vec>>, + /// If receipt is None it means it is pruned. + receipts: Receipts, /// First block of bundle state. first_block: BlockNumber, } @@ -48,11 +48,7 @@ pub type RevertsInit = HashMap> impl BundleStateWithReceipts { /// Create Bundle State. - pub fn new( - bundle: BundleState, - receipts: Vec>>, - first_block: BlockNumber, - ) -> Self { + pub fn new(bundle: BundleState, receipts: Receipts, first_block: BlockNumber) -> Self { Self { bundle, receipts, first_block } } @@ -61,7 +57,7 @@ impl BundleStateWithReceipts { state_init: BundleStateInit, revert_init: RevertsInit, contracts_init: Vec<(H256, Bytecode)>, - receipts: Vec>>, + receipts: Receipts, first_block: BlockNumber, ) -> Self { // sort reverts by block number @@ -167,7 +163,7 @@ impl BundleStateWithReceipts { /// # Example /// /// ``` - /// use reth_primitives::{Account, U256}; + /// use reth_primitives::{Account, U256, Receipts}; /// use reth_provider::BundleStateWithReceipts; /// use reth_db::{test_utils::create_test_rw_db, database::Database}; /// use std::collections::HashMap; @@ -187,7 +183,7 @@ impl BundleStateWithReceipts { /// )]), /// HashMap::from([]), /// vec![], - /// vec![], + /// Receipts::new(), /// 0, /// ); /// @@ -240,14 +236,11 @@ impl BundleStateWithReceipts { /// Note: this function calculated Bloom filters for every receipt and created merkle trees /// of receipt. This is a expensive operation. pub fn receipts_root_slow(&self, block_number: BlockNumber) -> Option { - let index = self.block_number_to_index(block_number)?; - let block_receipts = - self.receipts[index].iter().map(Option::as_ref).collect::>>()?; - Some(calculate_receipt_root_ref(&block_receipts)) + self.receipts.root_slow(self.block_number_to_index(block_number)?) } /// Return reference to receipts. - pub fn receipts(&self) -> &Vec>> { + pub fn receipts(&self) -> &Receipts { &self.receipts } @@ -325,7 +318,7 @@ impl BundleStateWithReceipts { // split is done as [0, num) and [num, len] let (_, this) = self.receipts.split_at(num_of_detached_block as usize); - self.receipts = this.to_vec().clone(); + self.receipts = Receipts::from_vec(this.to_vec().clone()); self.bundle.take_n_reverts(num_of_detached_block as usize); self.first_block = block_number + 1; @@ -340,7 +333,7 @@ impl BundleStateWithReceipts { /// In most cases this would be true. pub fn extend(&mut self, other: Self) { self.bundle.extend(other.bundle); - self.receipts.extend(other.receipts); + self.receipts.extend(other.receipts.receipt_vec); } /// Write bundle state to database. @@ -393,7 +386,7 @@ mod tests { transaction::DbTx, DatabaseEnv, }; - use reth_primitives::{Address, Receipt, StorageEntry, H256, MAINNET, U256}; + use reth_primitives::{Address, Receipt, Receipts, StorageEntry, H256, MAINNET, U256}; use reth_revm_primitives::{into_reth_acc, primitives::HashMap}; use revm::{ db::{ @@ -612,7 +605,7 @@ mod tests { state.merge_transitions(BundleRetention::Reverts); - BundleStateWithReceipts::new(state.take_bundle(), Vec::new(), 1) + BundleStateWithReceipts::new(state.take_bundle(), Receipts::new(), 1) .write_to_db(provider.tx_ref(), OriginalValuesKnown::Yes) .expect("Could not write bundle state to DB"); @@ -712,7 +705,7 @@ mod tests { )])); state.merge_transitions(BundleRetention::Reverts); - BundleStateWithReceipts::new(state.take_bundle(), Vec::new(), 2) + BundleStateWithReceipts::new(state.take_bundle(), Receipts::new(), 2) .write_to_db(provider.tx_ref(), OriginalValuesKnown::Yes) .expect("Could not write bundle state to DB"); @@ -779,7 +772,7 @@ mod tests { }, )])); init_state.merge_transitions(BundleRetention::Reverts); - BundleStateWithReceipts::new(init_state.take_bundle(), Vec::new(), 0) + BundleStateWithReceipts::new(init_state.take_bundle(), Receipts::new(), 0) .write_to_db(provider.tx_ref(), OriginalValuesKnown::Yes) .expect("Could not write init bundle state to DB"); @@ -926,7 +919,7 @@ mod tests { let bundle = state.take_bundle(); - BundleStateWithReceipts::new(bundle, Vec::new(), 1) + BundleStateWithReceipts::new(bundle, Receipts::new(), 1) .write_to_db(provider.tx_ref(), OriginalValuesKnown::Yes) .expect("Could not write bundle state to DB"); @@ -1092,7 +1085,7 @@ mod tests { }, )])); init_state.merge_transitions(BundleRetention::Reverts); - BundleStateWithReceipts::new(init_state.take_bundle(), Vec::new(), 0) + BundleStateWithReceipts::new(init_state.take_bundle(), Receipts::new(), 0) .write_to_db(provider.tx_ref(), OriginalValuesKnown::Yes) .expect("Could not write init bundle state to DB"); @@ -1139,7 +1132,7 @@ mod tests { // Commit block #1 changes to the database. state.merge_transitions(BundleRetention::Reverts); - BundleStateWithReceipts::new(state.take_bundle(), Vec::new(), 1) + BundleStateWithReceipts::new(state.take_bundle(), Receipts::new(), 1) .write_to_db(provider.tx_ref(), OriginalValuesKnown::Yes) .expect("Could not write bundle state to DB"); @@ -1171,7 +1164,7 @@ mod tests { fn revert_to_indices() { let base = BundleStateWithReceipts { bundle: BundleState::default(), - receipts: vec![vec![Some(Receipt::default()); 2]; 7], + receipts: Receipts::from_vec(vec![vec![Some(Receipt::default()); 2]; 7]), first_block: 10, }; diff --git a/crates/storage/provider/src/chain.rs b/crates/storage/provider/src/chain.rs index bb97c25f2..59940b1df 100644 --- a/crates/storage/provider/src/chain.rs +++ b/crates/storage/provider/src/chain.rs @@ -360,7 +360,7 @@ pub enum ChainSplit { #[cfg(test)] mod tests { use super::*; - use reth_primitives::{H160, H256}; + use reth_primitives::{Receipts, H160, H256}; use reth_revm_primitives::{ db::BundleState, primitives::{AccountInfo, HashMap}, @@ -406,7 +406,7 @@ mod tests { vec![vec![(H160([2; 20]), None, vec![])]], vec![], ), - vec![vec![]], + Receipts::from_vec(vec![vec![]]), 1, ); @@ -416,7 +416,7 @@ mod tests { vec![vec![(H160([3; 20]), None, vec![])]], vec![], ), - vec![vec![]], + Receipts::from_vec(vec![vec![]]), 2, ); diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index d5066c657..ebff42dad 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -368,7 +368,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { state, reverts, Vec::new(), - receipts, + reth_primitives::Receipts::from_vec(receipts), start_block_number, )) } diff --git a/crates/storage/provider/src/test_utils/blocks.rs b/crates/storage/provider/src/test_utils/blocks.rs index e978e979c..1ed514ebd 100644 --- a/crates/storage/provider/src/test_utils/blocks.rs +++ b/crates/storage/provider/src/test_utils/blocks.rs @@ -3,7 +3,7 @@ use crate::{BundleStateWithReceipts, DatabaseProviderRW}; use reth_db::{database::Database, models::StoredBlockBodyIndices, tables}; use reth_primitives::{ - hex_literal::hex, Account, BlockNumber, Bytes, Header, Log, Receipt, SealedBlock, + hex_literal::hex, Account, BlockNumber, Bytes, Header, Log, Receipt, Receipts, SealedBlock, SealedBlockWithSenders, StorageEntry, TxType, Withdrawal, H160, H256, U256, }; use reth_rlp::Decodable; @@ -129,7 +129,7 @@ fn block1(number: BlockNumber) -> (SealedBlockWithSenders, BundleStateWithReceip ]), )]), vec![], - vec![vec![Some(Receipt { + Receipts::from_vec(vec![vec![Some(Receipt { tx_type: TxType::EIP2930, success: true, cumulative_gas_used: 300, @@ -138,7 +138,7 @@ fn block1(number: BlockNumber) -> (SealedBlockWithSenders, BundleStateWithReceip topics: vec![H256::from_low_u64_be(1), H256::from_low_u64_be(2)], data: Bytes::default(), }], - })]], + })]]), number, ); @@ -185,7 +185,7 @@ fn block2( )]), )]), vec![], - vec![vec![Some(Receipt { + Receipts::from_vec(vec![vec![Some(Receipt { tx_type: TxType::EIP1559, success: false, cumulative_gas_used: 400, @@ -194,7 +194,7 @@ fn block2( topics: vec![H256::from_low_u64_be(3), H256::from_low_u64_be(4)], data: Bytes::default(), }], - })]], + })]]), number, ); (SealedBlockWithSenders { block, senders: vec![H160([0x31; 20])] }, bundle)