diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 0ce28575d4..d601463d8b 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -49,7 +49,7 @@ serde_json.workspace = true sha2 = { version = "0.10.7", optional = true } tempfile = { workspace = true, optional = true } thiserror.workspace = true -zstd = { version = "0.12", features = ["experimental"] } +zstd = { version = "0.12", features = ["experimental"], optional = true } roaring = "0.10.2" cfg-if = "1.0.0" @@ -90,7 +90,7 @@ pprof = { workspace = true, features = ["flamegraph", "frame-pointer", "criterio secp256k1.workspace = true [features] -default = ["c-kzg"] +default = ["c-kzg", "zstd-codec"] asm-keccak = ["alloy-primitives/asm-keccak"] arbitrary = [ "revm-primitives/arbitrary", @@ -102,8 +102,12 @@ arbitrary = [ "dep:arbitrary", "dep:proptest", "dep:proptest-derive", + "zstd-codec" ] c-kzg = ["dep:c-kzg", "revm/c-kzg", "revm-primitives/c-kzg", "dep:sha2", "dep:tempfile"] +zstd-codec = [ + "dep:zstd" +] clap = ["dep:clap"] optimism = [ "reth-codecs/optimism", diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 8d9793a789..c19bd444ee 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -21,6 +21,7 @@ mod account; pub mod basefee; mod block; mod chain; +#[cfg(feature = "zstd-codec")] mod compression; pub mod constants; pub mod eip4844; @@ -58,6 +59,7 @@ pub use chain::{ ChainSpecBuilder, DisplayHardforks, ForkBaseFeeParams, ForkCondition, ForkTimestamps, NamedChain, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA, }; +#[cfg(feature = "zstd-codec")] pub use compression::*; pub use constants::{ DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH, diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 8448b309ef..6b158770f5 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -1,19 +1,21 @@ -use crate::{ - compression::{RECEIPT_COMPRESSOR, RECEIPT_DECOMPRESSOR}, - logs_bloom, Bloom, Bytes, Log, PruneSegmentError, TxType, B256, -}; +#[cfg(feature = "zstd-codec")] +use crate::compression::{RECEIPT_COMPRESSOR, RECEIPT_DECOMPRESSOR}; +use crate::{logs_bloom, Bloom, Bytes, Log, PruneSegmentError, TxType, B256}; use alloy_rlp::{length_of_length, Decodable, Encodable}; use bytes::{Buf, BufMut}; #[cfg(any(test, feature = "arbitrary"))] use proptest::strategy::Strategy; -use reth_codecs::{add_arbitrary_tests, main_codec, Compact, CompactZstd}; +#[cfg(feature = "zstd-codec")] +use reth_codecs::CompactZstd; +use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; use std::{ cmp::Ordering, ops::{Deref, DerefMut}, }; /// Receipt containing result of transaction execution. -#[main_codec(no_arbitrary, zstd)] +#[cfg_attr(feature = "zstd-codec", main_codec(no_arbitrary, zstd))] +#[cfg_attr(not(feature = "zstd-codec"), main_codec(no_arbitrary))] #[add_arbitrary_tests] #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Receipt { diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index e21044dc72..c1a3b0f00e 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -1,7 +1,7 @@ -use crate::{ - compression::{TRANSACTION_COMPRESSOR, TRANSACTION_DECOMPRESSOR}, - keccak256, Address, BlockHashOrNumber, Bytes, TxHash, B256, U256, -}; +#[cfg(any(feature = "arbitrary", feature = "zstd-codec"))] +use crate::compression::{TRANSACTION_COMPRESSOR, TRANSACTION_DECOMPRESSOR}; +use crate::{keccak256, Address, BlockHashOrNumber, Bytes, TxHash, B256, U256}; + use alloy_rlp::{ Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE, }; @@ -52,6 +52,7 @@ mod variant; #[cfg(feature = "optimism")] mod optimism; + #[cfg(feature = "optimism")] pub use optimism::TxDeposit; #[cfg(feature = "optimism")] @@ -884,6 +885,7 @@ impl TransactionSignedNoHash { } } +#[cfg(feature = "zstd-codec")] impl Compact for TransactionSignedNoHash { fn to_compact(self, buf: &mut B) -> usize where @@ -946,6 +948,64 @@ impl Compact for TransactionSignedNoHash { } } +#[cfg(not(feature = "zstd-codec"))] +impl Compact for TransactionSignedNoHash { + fn to_compact(self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + to_compact_ztd_unaware(self, buf) + } + + fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) { + from_compact_zstd_unaware(buf, _len) + } +} + +// Allowing dead code, as this function is extracted from behind feature, so it can be tested +#[allow(dead_code)] +fn to_compact_ztd_unaware(transaction: TransactionSignedNoHash, buf: &mut B) -> usize +where + B: bytes::BufMut + AsMut<[u8]>, +{ + let start = buf.as_mut().len(); + + // Placeholder for bitflags. + // The first byte uses 4 bits as flags: IsCompressed[1bit], TxType[2bits], Signature[1bit] + buf.put_u8(0); + + let sig_bit = transaction.signature.to_compact(buf) as u8; + let zstd_bit = false; + let tx_bits = transaction.transaction.to_compact(buf) as u8; + + // Replace bitflags with the actual values + buf.as_mut()[start] = sig_bit | (tx_bits << 1) | ((zstd_bit as u8) << 3); + + buf.as_mut().len() - start +} + +// Allowing dead code, as this function is extracted from behind feature, so it can be tested +#[allow(dead_code)] +fn from_compact_zstd_unaware(mut buf: &[u8], _len: usize) -> (TransactionSignedNoHash, &[u8]) { + // The first byte uses 4 bits as flags: IsCompressed[1], TxType[2], Signature[1] + let bitflags = buf.get_u8() as usize; + + let sig_bit = bitflags & 1; + let (signature, buf) = Signature::from_compact(buf, sig_bit); + + let zstd_bit = bitflags >> 3; + if zstd_bit != 0 { + panic!( + "zstd-codec feature is not enabled, cannot decode `TransactionSignedNoHash` with zstd flag" + ) + } + + let transaction_type = bitflags >> 1; + let (transaction, buf) = Transaction::from_compact(buf, transaction_type); + + (TransactionSignedNoHash { signature, transaction }, buf) +} + impl From for TransactionSigned { fn from(tx: TransactionSignedNoHash) -> Self { tx.with_hash() @@ -1405,7 +1465,7 @@ impl Decodable for TransactionSigned { /// header if the first byte is less than `0xf7`. fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { if buf.is_empty() { - return Err(RlpError::InputTooShort) + return Err(RlpError::InputTooShort); } // decode header @@ -1602,17 +1662,18 @@ mod tests { use crate::{ hex, sign_message, transaction::{ - signature::Signature, TransactionKind, TxEip1559, TxLegacy, - MIN_LENGTH_EIP1559_TX_ENCODED, MIN_LENGTH_EIP2930_TX_ENCODED, - MIN_LENGTH_EIP4844_TX_ENCODED, MIN_LENGTH_LEGACY_TX_ENCODED, - PARALLEL_SENDER_RECOVERY_THRESHOLD, + from_compact_zstd_unaware, signature::Signature, to_compact_ztd_unaware, + TransactionKind, TxEip1559, TxLegacy, MIN_LENGTH_EIP1559_TX_ENCODED, + MIN_LENGTH_EIP2930_TX_ENCODED, MIN_LENGTH_EIP4844_TX_ENCODED, + MIN_LENGTH_LEGACY_TX_ENCODED, PARALLEL_SENDER_RECOVERY_THRESHOLD, }, - Address, Bytes, Transaction, TransactionSigned, TransactionSignedEcRecovered, TxEip2930, - TxEip4844, B256, U256, + Address, Bytes, Transaction, TransactionSigned, TransactionSignedEcRecovered, + TransactionSignedNoHash, TxEip2930, TxEip4844, B256, U256, }; use alloy_primitives::{address, b256, bytes}; use alloy_rlp::{Decodable, Encodable, Error as RlpError}; use bytes::BytesMut; + use reth_codecs::Compact; use secp256k1::{KeyPair, Secp256k1}; use std::str::FromStr; @@ -1684,7 +1745,7 @@ mod tests { b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"), b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"), b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"), - b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549") + b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549"), ]) ); } @@ -1837,7 +1898,7 @@ mod tests { let hash: B256 = hex!("559fb34c4a7f115db26cbf8505389475caaab3df45f5c7a0faa4abfa3835306c").into(); let signer: Address = hex!("641c5d790f862a58ec7abcfd644c0442e9c201b3").into(); - let raw =hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2"); + let raw = hex!("f88b8212b085028fa6ae00830f424094aad593da0c8116ef7d2d594dd6a63241bccfc26c80a48318b64b000000000000000000000000641c5d790f862a58ec7abcfd644c0442e9c201b32aa0a6ef9e170bca5ffb7ac05433b13b7043de667fbb0b4a5e45d3b54fb2d6efcc63a0037ec2c05c3d60c5f5f78244ce0a3859e3a18a36c61efb061b383507d3ce19d2"); let mut pointer = raw.as_ref(); let tx = TransactionSigned::decode(&mut pointer).unwrap(); @@ -2036,4 +2097,96 @@ mod tests { TransactionSigned::decode(&mut &encoded[..]).unwrap(); } + + #[test] + fn transaction_signed_no_hash_zstd_codec() { + // will use same signature everywhere. + // We don't need signature to match tx, just decoded to the same signature + let signature = Signature { + odd_y_parity: false, + r: U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae") + .unwrap(), + s: U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18") + .unwrap(), + }; + + let inputs: Vec> = vec![ + vec![], + vec![0], + vec![255], + vec![1u8; 31], + vec![255u8; 31], + vec![1u8; 32], + vec![255u8; 32], + vec![1u8; 64], + vec![255u8; 64], + ]; + + for input in inputs { + let transaction = Transaction::Legacy(TxLegacy { + chain_id: Some(4u64), + nonce: 2, + gas_price: 1000000000, + gas_limit: 100000, + to: TransactionKind::Call( + Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap(), + ), + value: U256::from(1000000000000000u64), + input: Bytes::from(input), + }); + + let tx_signed_no_hash = TransactionSignedNoHash { signature, transaction }; + test_transaction_signed_to_from_compact(tx_signed_no_hash); + } + } + + fn test_transaction_signed_to_from_compact(tx_signed_no_hash: TransactionSignedNoHash) { + // zstd aware `to_compact` + let mut buff: Vec = Vec::new(); + let written_bytes = tx_signed_no_hash.clone().to_compact(&mut buff); + let (decoded, _) = TransactionSignedNoHash::from_compact(&buff, written_bytes); + assert_eq!(tx_signed_no_hash, decoded); + + // zstd unaware `to_compact`/`from_compact` + let mut buff: Vec = Vec::new(); + let written_bytes = to_compact_ztd_unaware(tx_signed_no_hash.clone(), &mut buff); + let (decoded_no_zstd, _something) = from_compact_zstd_unaware(&buff, written_bytes); + assert_eq!(tx_signed_no_hash, decoded_no_zstd); + + // zstd unaware `to_compact`, but decode with zstd awareness + let mut buff: Vec = Vec::new(); + let written_bytes = to_compact_ztd_unaware(tx_signed_no_hash.clone(), &mut buff); + let (decoded, _) = TransactionSignedNoHash::from_compact(&buff, written_bytes); + assert_eq!(tx_signed_no_hash, decoded); + } + + #[test] + #[should_panic( + expected = "zstd-codec feature is not enabled, cannot decode `TransactionSignedNoHash` with zstd flag" + )] + fn transaction_signed_zstd_encoded_no_zstd_decode() { + let signature = Signature { + odd_y_parity: false, + r: U256::from_str("0xeb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae") + .unwrap(), + s: U256::from_str("0x3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18") + .unwrap(), + }; + let transaction = Transaction::Legacy(TxLegacy { + chain_id: Some(4u64), + nonce: 2, + gas_price: 1000000000, + gas_limit: 100000, + to: TransactionKind::Call( + Address::from_str("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap(), + ), + value: U256::from(1000000000000000u64), + input: Bytes::from(vec![3u8; 64]), + }); + let tx_signed_no_hash = TransactionSignedNoHash { signature, transaction }; + + let mut buff: Vec = Vec::new(); + let written_bytes = tx_signed_no_hash.to_compact(&mut buff); + from_compact_zstd_unaware(&buff, written_bytes); + } }