diff --git a/Cargo.lock b/Cargo.lock index a9a7e07f..892ad9fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ checksum = "056f2c01b2aed86e15b43c47d109bfc8b82553dc34e66452875e51247ec31ab2" dependencies = [ "alloy-consensus", "alloy-core", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-genesis", "alloy-rpc-types", "alloy-serde", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.40" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4932d790c723181807738cf1ac68198ab581cd699545b155601332541ee47bd" +checksum = "dca4a1469a3e572e9ba362920ff145f5d0a00a3e71a64ddcb4a3659cf64c76a7" dependencies = [ "alloy-primitives 0.8.9", "num_enum", @@ -112,7 +112,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "705687d5bfd019fee57cf9e206b27b30a9a9617535d5590a02b171e813208f8e" dependencies = [ - "alloy-eips 0.4.2", + "alloy-eips", "alloy-primitives 0.8.9", "alloy-rlp", "alloy-serde", @@ -175,19 +175,6 @@ dependencies = [ "serde", ] -[[package]] -name = "alloy-eips" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76ecab54890cdea1e4808fc0891c7e6cfcf71fe1a9fe26810c7280ef768f4ed" -dependencies = [ - "alloy-primitives 0.7.7", - "alloy-rlp", - "c-kzg", - "once_cell", - "serde", -] - [[package]] name = "alloy-eips" version = "0.4.2" @@ -236,7 +223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "801492711d4392b2ccf5fc0bc69e299fa1aab15167d74dcaa9aab96a54f684bd" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-primitives 0.8.9", "alloy-serde", "serde", @@ -334,7 +321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413f4aa3ccf2c3e4234a047c5fa4727916d7daf25a89f9b765df0ba09784fd87" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-network-primitives", "alloy-primitives 0.8.9", "alloy-rlp", @@ -1576,7 +1563,6 @@ dependencies = [ "rayon", "reth-primitives", "reth-trie-common", - "revm-primitives 6.0.0", "serde", "serde_json", "simple-log", @@ -2210,6 +2196,7 @@ dependencies = [ "alloy-consensus", "alloy-eip2930", "alloy-primitives 0.8.9", + "alloy-rlp", "ethportal-api", "hex", "primitive-types", @@ -2218,6 +2205,7 @@ dependencies = [ "prost-wkt", "prost-wkt-types", "reth-primitives", + "reth-trie-common", "serde", "serde_json", "thiserror", @@ -2869,9 +2857,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -3003,9 +2991,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" +checksum = "a1f72d3e19488cf7d8ea52d2fc0f8754fc933398b337cd3cbdb28aaeb35159ef" dependencies = [ "console", "lazy_static", @@ -3424,9 +3412,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "a00419de735aac21d53b0de5ce2c03bd3627277cf471300f27ebc89f7d828047" [[package]] name = "libredox" @@ -3896,7 +3884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ea7162170c6f3cad8f67f4dd7108e3f78349fd553da5b8bebff1e7ef8f38896" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-primitives 0.8.9", "alloy-rlp", "alloy-serde", @@ -3912,7 +3900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323c65880e2561aa87f74f8af260fd15b9cc930c448c88a60ae95af86c88c634" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-network-primitives", "alloy-primitives 0.8.9", "alloy-rpc-types-eth", @@ -4628,9 +4616,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -4682,7 +4670,7 @@ version = "1.1.0" source = "git+https://github.com/paradigmxyz/reth?tag=v1.1.0#1ba631ba9581973e7c6cadeea92cfe1802aceb4a" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-genesis", "alloy-primitives 0.8.9", "alloy-trie", @@ -4725,7 +4713,7 @@ version = "1.1.0" source = "git+https://github.com/paradigmxyz/reth?tag=v1.1.0#1ba631ba9581973e7c6cadeea92cfe1802aceb4a" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-primitives 0.8.9", "alloy-rlp", "alloy-rpc-types", @@ -4743,7 +4731,7 @@ dependencies = [ "reth-primitives-traits", "reth-static-file-types", "reth-trie-common", - "revm-primitives 10.0.0", + "revm-primitives", "secp256k1", "serde", "zstd", @@ -4755,7 +4743,7 @@ version = "1.1.0" source = "git+https://github.com/paradigmxyz/reth?tag=v1.1.0#1ba631ba9581973e7c6cadeea92cfe1802aceb4a" dependencies = [ "alloy-consensus", - "alloy-eips 0.4.2", + "alloy-eips", "alloy-genesis", "alloy-primitives 0.8.9", "alloy-rlp", @@ -4764,7 +4752,7 @@ dependencies = [ "derive_more 1.0.0", "modular-bitfield", "reth-codecs", - "revm-primitives 10.0.0", + "revm-primitives", "roaring", "serde", ] @@ -4796,29 +4784,7 @@ dependencies = [ "nybbles", "reth-codecs", "reth-primitives-traits", - "revm-primitives 10.0.0", - "serde", -] - -[[package]] -name = "revm-primitives" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b518f536bacee396eb28a43f0984b25b2cd80f052ba4f2e794d554d711c13f33" -dependencies = [ - "alloy-eips 0.1.4", - "alloy-primitives 0.7.7", - "auto_impl", - "bitflags 2.6.0", - "bitvec", - "c-kzg", - "cfg-if", - "derive_more 0.99.18", - "dyn-clone", - "enumn", - "hashbrown 0.14.5", - "hex", - "once_cell", + "revm-primitives", "serde", ] @@ -5036,9 +5002,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -5049,9 +5015,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.15" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "aws-lc-rs", "log", @@ -5285,9 +5251,9 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -5313,9 +5279,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.213" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", diff --git a/crates/firehose-protos/Cargo.toml b/crates/firehose-protos/Cargo.toml index 7410c4af..710d8176 100644 --- a/crates/firehose-protos/Cargo.toml +++ b/crates/firehose-protos/Cargo.toml @@ -11,12 +11,14 @@ path = "src/lib.rs" alloy-consensus.workspace = true alloy-eip2930.workspace = true alloy-primitives.workspace = true +alloy-rlp.workspace = true ethportal-api.workspace = true primitive-types.workspace = true prost.workspace = true prost-wkt.workspace = true prost-wkt-types.workspace = true reth-primitives.workspace = true +reth-trie-common.workspace = true serde.workspace = true thiserror.workspace = true tonic.workspace = true diff --git a/crates/firehose-protos/src/error.rs b/crates/firehose-protos/src/error.rs index 30e9b2b0..4bb40a28 100644 --- a/crates/firehose-protos/src/error.rs +++ b/crates/firehose-protos/src/error.rs @@ -31,9 +31,15 @@ pub enum ProtosError { #[error("Invalid trace signature {0:?} component: {1}")] InvalidTraceSignature(EcdsaComponent, String), + #[error("Invalid transaction receipt logs bloom: {0}")] + InvalidTransactionReceiptLogsBloom(String), + #[error("KzgCommitmentInvalid")] KzgCommitmentInvalid, + #[error("MissingBlockHeader")] + MissingBlockHeader, + #[error("Null attestation data")] NullAttestationData, @@ -70,6 +76,9 @@ pub enum ProtosError { #[error("Transaction missing call")] TransactionMissingCall, + #[error("Transaction trace missing receipt")] + TransactionTraceMissingReceipt, + #[error("TxTypeConversionError")] TxTypeConversion, } diff --git a/crates/firehose-protos/src/ethereum_v2/eth_block.rs b/crates/firehose-protos/src/ethereum_v2/eth_block.rs index 4e12f437..d7da4b4b 100644 --- a/crates/firehose-protos/src/ethereum_v2/eth_block.rs +++ b/crates/firehose-protos/src/ethereum_v2/eth_block.rs @@ -1,9 +1,14 @@ -use super::Block; -use alloy_primitives::{hex, Address, Bloom, FixedBytes, Uint}; +use super::{ + transaction::{self, FullReceipt}, + Block, BlockHeader, +}; +use alloy_primitives::{hex, Address, Bloom, FixedBytes, Uint, B256}; +use alloy_rlp::Encodable; use ethportal_api::types::execution::header::Header; use prost::Message; use prost_wkt_types::Any; use reth_primitives::{proofs::calculate_transaction_root, TransactionSigned}; +use reth_trie_common::root::ordered_trie_root_with_encoder; use tracing::error; use crate::{ @@ -132,27 +137,134 @@ impl TryFrom for Block { } impl Block { + fn block_header(&self) -> Result<&BlockHeader, ProtosError> { + self.header.as_ref().ok_or(ProtosError::MissingBlockHeader) + } + + /// Returns an encoder function for [RLP-encoding]((https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp)) + /// full receipts based on the Byzantium fork block. + /// + /// This function generates an encoding strategy for receipts based on the block number: + /// - **Pre-Byzantium:** Encodes with a header including state root, cumulative gas, bloom filter, and logs. + /// - **Byzantium and later:** Encodes the inner receipt contents only. + /// + /// The encoder function returned takes a reference to a [`FullReceipt`] and a mutable buffer implementing + /// [`BufMut`], into which it writes the RLP-encoded data. + /// + /// # Arguments + /// + /// * `block` - Reference to the [`Block`] from which to derive the encoding strategy. + /// + /// # Returns + /// + /// A function that encodes a [`FullReceipt`] into an RLP format, writing the result to a mutable `Vec`. + /// + fn get_full_receipt_encoder(&self) -> fn(&FullReceipt, &mut Vec) { + if !self.is_pre_byzantium() { + // For Byzantium and later: only encode the inner receipt contents + |r: &FullReceipt, out: &mut Vec| r.receipt.encode_inner(out, false) + } else { + // Pre-Byzantium: encode header values and additional receipt data + |r: &FullReceipt, out: &mut Vec| { + r.rlp_header().encode(out); + r.state_root.as_slice().encode(out); + Encodable::encode(&r.receipt.receipt.cumulative_gas_used, out); + r.receipt.bloom.encode(out); + r.receipt.receipt.logs.encode(out); + } + } + } + + fn is_pre_byzantium(&self) -> bool { + const BYZANTIUM_FORK_BLOCK: u64 = 4_370_000; + + self.number < BYZANTIUM_FORK_BLOCK + } + + /// Calculates the trie root of the receipts for the current block. + /// + /// This method aggregates and encodes receipts derived from transaction traces to compute + /// the Merkle trie root, which represents the receipt root of the block. + /// + /// # Returns + /// + /// A [`Result`] containing the calculated root as `B256` if successful, or a [`ProtosError`] if + /// an error occurs during receipt processing. + /// + /// # Note + /// + /// The root is computed based on receipts derived from transaction traces associated with the block. + pub fn receipt_root(&self) -> Result { + let receipts = self + .transaction_traces + .iter() + .map(transaction::FullReceipt::try_from) + .collect::, _>>()?; + + let encoder = self.get_full_receipt_encoder(); + Ok(ordered_trie_root_with_encoder(&receipts, encoder)) + } + + /// Verifies the receipt root in a given block's header against a + /// computed receipt root from the block's body. + /// + /// # Arguments + /// + /// * `block` reference to the block which the root will be verified + pub fn receipt_root_is_verified(&self) -> bool { + let computed_root = match self.receipt_root() { + Ok(root) => root, + Err(e) => { + error!("Failed to calculate receipt root: {e}"); + return false; + } + }; + + let receipt_root = match self.block_header() { + Ok(header) => header.receipt_root.as_slice(), + Err(e) => { + error!("Missing block header: {e}"); + return false; + } + }; + + if computed_root.as_slice() != receipt_root { + error!( + "Mismatched Receipt Root: {} != {}", + hex::encode(computed_root), + hex::encode(receipt_root) + ); + return false; + } + + true + } + + fn transaction_root(&self) -> Result, ProtosError> { + let transactions = self + .transaction_traces + .iter() + .map(TransactionSigned::try_from) + .collect::, _>>()?; + + Ok(calculate_transaction_root(&transactions)) + } + /// Checks if the transaction root matches the block header's transactions root. /// Returns `true` if they match, `false` otherwise. pub fn transaction_root_is_verified(&self) -> bool { - let mut transactions: Vec = Vec::new(); - - for trace in &self.transaction_traces { - match trace.try_into() { - Ok(transaction) => transactions.push(transaction), - Err(e) => { - error!("Failed to convert transaction trace to transaction: {e}"); - return false; - } + let tx_root = match self.transaction_root() { + Ok(root) => root, + Err(e) => { + error!("Failed to calculate transaction root: {e}"); + return false; } - } - - let tx_root = calculate_transaction_root(&transactions); + }; - let block_header = match &self.header { - Some(header) => header, - None => { - error!("Missing block header"); + let block_header = match self.block_header() { + Ok(header) => header, + Err(e) => { + error!("Missing block header: {e}"); return false; } }; diff --git a/crates/firehose-protos/src/ethereum_v2/transaction.rs b/crates/firehose-protos/src/ethereum_v2/transaction.rs index 6a148f41..a5f306d5 100644 --- a/crates/firehose-protos/src/ethereum_v2/transaction.rs +++ b/crates/firehose-protos/src/ethereum_v2/transaction.rs @@ -1,13 +1,16 @@ use alloy_consensus::{TxEip1559, TxEip2930, TxLegacy}; use alloy_eip2930::{AccessList, AccessListItem}; use alloy_primitives::{ - hex, Address, Bytes, ChainId, FixedBytes, Parity, TxKind, Uint, U128, U256, + hex, Address, Bloom, Bytes, ChainId, FixedBytes, Parity, TxKind, Uint, U128, U256, +}; +use alloy_rlp::{Encodable, Header}; +use reth_primitives::{ + Receipt, ReceiptWithBloom, Signature, Transaction, TransactionSigned, TxType, }; -use reth_primitives::{Signature, Transaction, TransactionSigned, TxType}; use crate::error::ProtosError; -use super::{transaction_trace::Type, BigInt, CallType, TransactionTrace}; +use super::{transaction_trace::Type, BigInt, CallType, TransactionReceipt, TransactionTrace}; impl From for TxType { fn from(tx_type: Type) -> Self { @@ -241,6 +244,70 @@ impl TryFrom<&BigInt> for u128 { } } +pub struct FullReceipt { + pub receipt: ReceiptWithBloom, + pub state_root: Vec, +} + +impl TryFrom<&TransactionTrace> for FullReceipt { + type Error = ProtosError; + + fn try_from(trace: &TransactionTrace) -> Result { + let tx_type = trace.try_into()?; + let trace_receipt = trace + .receipt + .as_ref() + .ok_or(ProtosError::TransactionTraceMissingReceipt)?; + + let logs = trace_receipt + .logs + .iter() + .map(reth_primitives::Log::try_from) + .collect::, _>>()?; + + let receipt = Receipt { + success: trace.is_success(), + tx_type, + logs, + cumulative_gas_used: trace_receipt.cumulative_gas_used, + }; + + let bloom = Bloom::try_from(trace_receipt)?; + + Ok(Self { + receipt: ReceiptWithBloom { receipt, bloom }, + state_root: trace_receipt.state_root.to_vec(), + }) + } +} + +impl TryFrom<&TransactionReceipt> for Bloom { + type Error = ProtosError; + + fn try_from(receipt: &TransactionReceipt) -> Result { + let logs_bloom = receipt.logs_bloom.as_slice(); + logs_bloom + .try_into() + .map(|array: [u8; 256]| Bloom(FixedBytes(array))) + .map_err(|_| Self::Error::InvalidTransactionReceiptLogsBloom(hex::encode(logs_bloom))) + } +} + +impl FullReceipt { + /// Encodes receipt header using [RLP serialization](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp) + pub fn rlp_header(&self) -> Header { + let payload_length = self.state_root.as_slice().length() + + self.receipt.receipt.cumulative_gas_used.length() + + self.receipt.bloom.length() + + self.receipt.receipt.logs.length(); + + Header { + list: true, + payload_length, + } + } +} + #[cfg(test)] mod tests { use crate::ethereum_v2::Call; diff --git a/crates/flat-files-decoder/Cargo.toml b/crates/flat-files-decoder/Cargo.toml index afda89cc..ad3fde01 100644 --- a/crates/flat-files-decoder/Cargo.toml +++ b/crates/flat-files-decoder/Cargo.toml @@ -18,7 +18,6 @@ rand.workspace = true rayon.workspace = true reth-primitives.workspace = true reth-trie-common.workspace = true -revm-primitives.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true simple-log.workspace = true diff --git a/crates/flat-files-decoder/benches/stream_blocks.rs b/crates/flat-files-decoder/benches/stream_blocks.rs index f341982b..25b5fa69 100644 --- a/crates/flat-files-decoder/benches/stream_blocks.rs +++ b/crates/flat-files-decoder/benches/stream_blocks.rs @@ -4,10 +4,7 @@ use std::{ }; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use decoder::{ - dbin::{error::DbinFileError, DbinFile}, - receipts::check_receipt_root, -}; +use decoder::dbin::{error::DbinFileError, DbinFile}; use prost::Message; const ITERS_PER_FILE: usize = 10; @@ -138,7 +135,7 @@ fn read_decode_check_bench(c: &mut Criterion) { ) .unwrap(); b.iter(|| { - black_box(check_receipt_root(&block)).unwrap(); + black_box(block.receipt_root_is_verified()); }); } } diff --git a/crates/flat-files-decoder/src/dbin/mod.rs b/crates/flat-files-decoder/src/dbin/mod.rs index 25900422..3d776347 100644 --- a/crates/flat-files-decoder/src/dbin/mod.rs +++ b/crates/flat-files-decoder/src/dbin/mod.rs @@ -151,3 +151,169 @@ impl DbinFile { Ok(content) } } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn test_supported_version() { + // Create a DBIN file with version 0 (supported) + let version = 0x00; // Version 0 + let content_type = b"ETH"; // Example content type + let content_version = b"00"; // Example content version + + // Construct the DBIN file bytes for version 0 + let dbin_bytes = [ + b'd', + b'b', + b'i', + b'n', // Magic bytes + version, // File format version (0x00) + content_type[0], // First byte of content type + content_type[1], // Second byte of content type + content_type[2], // Third byte of content type + content_version[0], // First byte of content version + content_version[1], // Second byte of content version + ] + .iter() + .copied() + .collect::>(); + + let mut cursor = Cursor::new(dbin_bytes); + + let result = DbinFile::try_from_read(&mut cursor); + assert!( + result.is_ok(), + "Expected success for valid DBIN file with version 0" + ); + } + + #[test] + fn test_unsupported_version() { + // Create a DBIN file with an unsupported version (anything other than 0) + let unsupported_version = 0x01; // Version 1 is unsupported + let content_type = b"ETH"; // Example content type + let content_version = b"00"; // Example content version + + // Construct the DBIN file bytes for version 1 + let unsupported_dbin = [ + b'd', + b'b', + b'i', + b'n', // Magic bytes + unsupported_version, // Version (0x01) + content_type[0], // First byte of content type + content_type[1], // Second byte of content type + content_type[2], // Third byte of content type + content_version[0], // First byte of content version + content_version[1], // Second byte of content version + ] + .iter() + .copied() + .collect::>(); + + let mut cursor = Cursor::new(unsupported_dbin); + + let result = DbinFile::try_from_read(&mut cursor); + assert!( + result.is_err(), + "Expected error for DBIN file with unsupported version" + ); + assert_eq!( + result.err().unwrap().to_string(), + DbinFileError::UnsupportedDBINVersion.to_string() + ); + } + + #[test] + fn test_invalid_magic_bytes() { + let invalid_dbin = b"xxxx\x00ETH00"; // Invalid magic bytes + let mut cursor = Cursor::new(invalid_dbin); + + let result = DbinFile::try_from_read(&mut cursor); + assert!(result.is_err()); + assert_eq!( + result.err().unwrap().to_string(), + DbinFileError::StartOfNewDBINFile.to_string() + ); + } + + #[test] + fn test_read_valid_dbin_with_multiple_messages() { + let version = 0x00; // Version 0 + let content_type = b"ETH"; // Example content type + let content_version = b"00"; // Example content version + let message1 = b"message1"; // Example message 1 + let message2 = b"message2"; // Example message 2 + + // Construct a valid DBIN file with two messages + let mut dbin_bytes = vec![ + b'd', + b'b', + b'i', + b'n', // Magic bytes + version, // File format version (0x00) + content_type[0], // First byte of content type + content_type[1], // Second byte of content type + content_type[2], // Third byte of content type + content_version[0], // First byte of content version + content_version[1], // Second byte of content version + ]; + + // Append message 1 length (4 bytes) and message 1 content + dbin_bytes.extend_from_slice(&(message1.len() as u32).to_be_bytes()); + dbin_bytes.extend_from_slice(message1); + + // Append message 2 length (4 bytes) and message 2 content + dbin_bytes.extend_from_slice(&(message2.len() as u32).to_be_bytes()); + dbin_bytes.extend_from_slice(message2); + + let mut cursor = Cursor::new(dbin_bytes); + + let result = DbinFile::try_from_read(&mut cursor); + assert!( + result.is_ok(), + "Expected success for valid DBIN file with multiple messages" + ); + let dbin_file = result.unwrap(); + assert_eq!(dbin_file.messages.len(), 2); + assert_eq!(dbin_file.messages[0], message1.to_vec()); + assert_eq!(dbin_file.messages[1], message2.to_vec()); + } + + #[test] + fn test_read_zero_length_message() { + let version = 0x00; + let content_type = b"ETH"; + let content_version = b"00"; + + // Construct a valid DBIN file with a zero-length message + let mut dbin_bytes = vec![ + b'd', + b'b', + b'i', + b'n', // Magic bytes + version, // File format version (0x00) + content_type[0], // Content type + content_type[1], + content_type[2], + content_version[0], // Content version + content_version[1], + ]; + + dbin_bytes.extend_from_slice(&(0 as u32).to_be_bytes()); + + let mut cursor = Cursor::new(dbin_bytes); + + let result = DbinFile::try_from_read(&mut cursor); + assert!( + result.is_ok(), + "Expected success for valid DBIN file with zero-length message" + ); + let dbin_file = result.unwrap(); + assert_eq!(dbin_file.messages.len(), 1); + assert!(dbin_file.messages[0].is_empty()); + } +} diff --git a/crates/flat-files-decoder/src/error.rs b/crates/flat-files-decoder/src/error.rs index 2801b559..139162c8 100644 --- a/crates/flat-files-decoder/src/error.rs +++ b/crates/flat-files-decoder/src/error.rs @@ -1,7 +1,5 @@ use crate::dbin::error::DbinFileError; use crate::headers::error::BlockHeaderError; -use crate::receipts::error::ReceiptError; -// use crate::transactions::error::TransactionError; use thiserror::Error; use tokio::task::JoinError; @@ -17,8 +15,8 @@ pub enum DecodeError { BlockHeaderError(#[from] BlockHeaderError), #[error("Invalid Transaction Root")] TransactionRoot, - #[error("Invalid Receipt Root: {0}")] - ReceiptError(#[from] ReceiptError), + #[error("Invalid Receipt Root")] + ReceiptRoot, #[error("IO Error: {0}")] IoError(#[from] std::io::Error), #[error("Invalid content type: {0}")] diff --git a/crates/flat-files-decoder/src/lib.rs b/crates/flat-files-decoder/src/lib.rs index ed7754ba..e71a1c40 100644 --- a/crates/flat-files-decoder/src/lib.rs +++ b/crates/flat-files-decoder/src/lib.rs @@ -7,7 +7,6 @@ pub mod dbin; pub mod error; pub mod headers; -pub mod receipts; pub mod transactions; use crate::{error::DecodeError, headers::check_valid_header}; @@ -15,7 +14,6 @@ use dbin::DbinFile; use firehose_protos::ethereum_v2::Block; use headers::HeaderRecordWithNumber; use prost::Message; -use receipts::check_receipt_root; use simple_log::log; use std::{ fs::{self, File}, @@ -203,8 +201,11 @@ fn handle_block( check_valid_header(&block, headers_dir)?; } if block.number != 0 { - check_receipt_root(&block)?; + if !block.receipt_root_is_verified() { + return Err(DecodeError::ReceiptRoot); + } if !block.transaction_root_is_verified() { + eprintln!("it's this one!"); return Err(DecodeError::TransactionRoot); } } @@ -253,14 +254,19 @@ pub async fn stream_blocks( block_number = block.number as usize; let receipts_check_process = spawn_check(&block, |b| { - check_receipt_root(b).map_err(DecodeError::ReceiptError) + if b.receipt_root_is_verified() { + Ok(()) + } else { + Err(DecodeError::ReceiptRoot) + } }); let transactions_check_process = spawn_check(&block, |b| { - if !b.transaction_root_is_verified() { - Err(DecodeError::TransactionRoot) - } else { + if b.transaction_root_is_verified() { Ok(()) + } else { + eprintln!("it's this other one!"); + Err(DecodeError::TransactionRoot) } }); @@ -357,11 +363,7 @@ mod tests { block.balance_changes.pop(); - let result = check_receipt_root(&block); - matches!( - result, - Err(receipts::error::ReceiptError::MismatchedRoot(_, _)) - ); + assert!(!block.receipt_root_is_verified()); } #[test] @@ -404,6 +406,9 @@ mod tests { .expect("Failed to read file"); let result = handle_buf(&buffer, Decompression::None); + if let Err(e) = result { + panic!("handle_buf failed: {}", e); + } assert!(result.is_ok(), "handle_buf should complete successfully"); } diff --git a/crates/flat-files-decoder/src/receipts/error.rs b/crates/flat-files-decoder/src/receipts/error.rs deleted file mode 100644 index 2be2bbb8..00000000 --- a/crates/flat-files-decoder/src/receipts/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use firehose_protos::error::ProtosError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ReceiptError { - #[error("Invalid status")] - InvalidStatus, - #[error("Invalid address: {0}")] - InvalidAddress(String), - #[error("Invalid topic: {0}")] - InvalidTopic(String), - #[error("Invalid data: {0}")] - InvalidBloom(String), - #[error("Receipt root mismatch: {0} != {1}")] - MismatchedRoot(String, String), - #[error("Missing receipt root")] - MissingRoot, - #[error("Missing receipt")] - MissingReceipt, - #[error("Protos error: {0}")] - ProtosError(#[from] ProtosError), - #[error("TryFromSliceError: {0}")] - TryFromSliceError(#[from] std::array::TryFromSliceError), -} diff --git a/crates/flat-files-decoder/src/receipts/mod.rs b/crates/flat-files-decoder/src/receipts/mod.rs deleted file mode 100644 index f861a07e..00000000 --- a/crates/flat-files-decoder/src/receipts/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -pub mod error; -pub mod receipt; - -use crate::receipts::error::ReceiptError; -use crate::receipts::receipt::FullReceipt; -use alloy_primitives::B256; -use alloy_rlp::{Encodable, Header}; -use firehose_protos::ethereum_v2::Block; -use reth_trie_common::root::ordered_trie_root_with_encoder; -use revm_primitives::hex; - -const BYZANTINUM_FORK_BLOCK: u64 = 4_370_000; - -/// Verifies the receipt root in a given block's header against a -/// computed receipt root from the block's body. -/// -/// # Arguments -/// -/// * `block` reference to the block which the root will be verified -pub fn check_receipt_root(block: &Block) -> Result<(), ReceiptError> { - let computed_root = calc_receipt_root(block)?; - let receipt_root = match block.header { - Some(ref header) => header.receipt_root.as_slice(), - None => return Err(ReceiptError::MissingRoot), - }; - if computed_root.as_slice() != receipt_root { - return Err(ReceiptError::MismatchedRoot( - hex::encode(computed_root.as_slice()), - hex::encode(receipt_root), - )); - } - - Ok(()) -} - -/// Calculates the trie receipt root of a given block recepits -/// -/// It uses the traces to aggregate receipts from blocks -/// -/// # Arguments -/// -/// * `block` reference to the block which the root will be verified -fn calc_receipt_root(block: &Block) -> Result { - let mut receipts = Vec::new(); - - for trace in &block.transaction_traces { - receipts.push(FullReceipt::try_from(trace)?); - } - - let encoder = get_encoder(block); - - Ok(ordered_trie_root_with_encoder(&receipts, encoder)) -} - -/// Encodes full rceipts using [RLP serialization](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp) -/// -/// For blocks before the Byzantium fork, it uses a specific RLP encoding that includes the receipt's header length values, state root, -/// cumulative gas used, bloom filter, and logs. -/// For blocks at or after the Byzantium fork, it encodes the receipt's inner contents without the header. -/// -/// This function is useful for computing the trie root hash which in reth needs to be rlp encoded. -/// -/// # Arguments -/// -/// * `block` reference to the [`Block`] where [`FullReceipt`] will be extracted from -/// -/// # Returns -/// -/// a function that takes a refenrece to a [`FullReceipt`], -/// and a mutable reference to a type implementing the [`BufMut`]. -/// All the data from the receipts in written into the `BufMut` buffer - -fn get_encoder(block: &Block) -> fn(&FullReceipt, &mut Vec) { - if block.number >= BYZANTINUM_FORK_BLOCK { - |r: &FullReceipt, out: &mut Vec| r.receipt.encode_inner(out, false) - } else { - |r: &FullReceipt, out: &mut Vec| { - receipt_rlp_header(r).encode(out); - r.state_root.as_slice().encode(out); - r.receipt.receipt.cumulative_gas_used.encode(out); - r.receipt.bloom.encode(out); - r.receipt.receipt.logs.encode(out); - } - } -} - -/// Encodes receipt header using [RLP serialization](https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp) -fn receipt_rlp_header(receipt: &FullReceipt) -> Header { - let payload_length = receipt.state_root.as_slice().length() - + receipt.receipt.receipt.cumulative_gas_used.length() - + receipt.receipt.bloom.length() - + receipt.receipt.receipt.logs.length(); - - Header { - list: true, - payload_length, - } -} diff --git a/crates/flat-files-decoder/src/receipts/receipt.rs b/crates/flat-files-decoder/src/receipts/receipt.rs deleted file mode 100644 index bedd6840..00000000 --- a/crates/flat-files-decoder/src/receipts/receipt.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::receipts::error::ReceiptError; -use alloy_primitives::{Bloom, FixedBytes}; -use firehose_protos::ethereum_v2::TransactionTrace; -use reth_primitives::{Log, Receipt, ReceiptWithBloom}; -use revm_primitives::hex; - -pub(crate) struct FullReceipt { - pub receipt: ReceiptWithBloom, - pub state_root: Vec, -} - -impl TryFrom<&TransactionTrace> for FullReceipt { - type Error = ReceiptError; - - fn try_from(trace: &TransactionTrace) -> Result { - let success = trace.is_success(); - let tx_type = trace.try_into()?; - - let trace_receipt = match &trace.receipt { - Some(receipt) => receipt, - None => return Err(ReceiptError::MissingReceipt), - }; - let logs = trace_receipt - .logs - .iter() - .map(Log::try_from) - .collect::, _>>()?; - - let cumulative_gas_used = trace_receipt.cumulative_gas_used; - - let receipt = Receipt { - success, - tx_type, - logs, - cumulative_gas_used, - }; - - let bloom = map_bloom(&trace_receipt.logs_bloom)?; - - let receipt = ReceiptWithBloom { receipt, bloom }; - - let state_root = &trace_receipt.state_root; - - Ok(Self { - receipt, - state_root: state_root.to_vec(), - }) - } -} - -fn map_bloom(slice: &[u8]) -> Result { - if slice.len() == 256 { - let array: [u8; 256] = slice.try_into()?; - Ok(Bloom(FixedBytes(array))) - } else { - Err(ReceiptError::InvalidBloom(hex::encode(slice))) - } -}