diff --git a/src/chainspec/mod.rs b/src/chainspec/mod.rs index 3bcff1b2..39423220 100644 --- a/src/chainspec/mod.rs +++ b/src/chainspec/mod.rs @@ -804,9 +804,9 @@ impl From for BerachainChainSpec { mod tests { use super::*; use alloy_genesis::Genesis; - use alloy_primitives::address; + use alloy_primitives::{address, b256}; use jsonrpsee_core::__reexports::serde_json::json; - use reth_chainspec::ForkHash; + use reth_chainspec::{EthChainSpec, ForkHash}; #[test] fn test_builtin_genesis_deposit_contract() { @@ -1926,6 +1926,34 @@ mod tests { assert!(chain_spec.is_prague1_active_at_timestamp(1754496000)); } + #[test] + fn test_bepolia_genesis_hash_regression() { + let bepolia_path = + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/bepolia-genesis.json"); + let bepolia_json = std::fs::read_to_string(bepolia_path).unwrap(); + let genesis: Genesis = serde_json::from_str(&bepolia_json).unwrap(); + let chain_spec = BerachainChainSpec::from(genesis); + + assert_eq!( + chain_spec.genesis_hash(), + b256!("0x0207661de38f0e54ba91c8286096e72486784c79dc6a9681fc486b38335c042f") + ); + } + + #[test] + fn test_mainnet_genesis_hash_regression() { + let mainnet_path = + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/mainnet-genesis.json"); + let mainnet_json = std::fs::read_to_string(mainnet_path).unwrap(); + let genesis: Genesis = serde_json::from_str(&mainnet_json).unwrap(); + let chain_spec = BerachainChainSpec::from(genesis); + + assert_eq!( + chain_spec.genesis_hash(), + b256!("0xd57819422128da1c44339fc7956662378c17e2213e669b427ac91cd11dfcfb38") + ); + } + #[test] fn test_chain_uses_id_not_named() { let mut genesis = Genesis::default(); diff --git a/src/primitives/header.rs b/src/primitives/header.rs index ae8965b5..9e062e8a 100644 --- a/src/primitives/header.rs +++ b/src/primitives/header.rs @@ -947,4 +947,81 @@ mod tests { "Header with prev_proposer_pubkey should be larger when compressed" ); } + + mod regression { + use super::*; + use crate::test_utils::regression_fixtures::{ + holesky_berachain_header, holesky_berachain_header_with_proposer_pubkey, + }; + use alloy_primitives::{b256, hex}; + + // Sourced from Reth v1.11.4: + // crates/primitives/src/header.rs test_compact_header_holesky() + // Block #1947953 on Holesky testnet. + const HOLESKY_COMPACT: &[u8] = &hex!( + "81a121788605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a951dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347c6e2459991bfe27cca6d86722f35da23a1e4cb97edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b99934daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0edd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca0028900401db93101c9c38044094966982980574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f101080306000000aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1726574682f76312e302e302f6c696e7578" + ); + + // Self-generated snapshot: hash_slow() output for holesky_berachain_header(). + // Uses canonical RLP+keccak semantics for this fixed fixture. + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const HOLESKY_HASH: B256 = + b256!("0x2537b92b5139455368c19ec33a92e1ff0cf6b96e6fd8488bed4ed409d401ac9a"); + + // Self-generated snapshot: compress_to_buf() output of the same Holesky header + // with synthetic Berachain-specific fields (prev_proposer_pubkey, requests_hash). + // No external reference exists for these fields; this pins bera-reth's own format. + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const WITH_PROPOSER_COMPACT: &[u8] = &hex!( + "81a121f88605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a951dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347c6e2459991bfe27cca6d86722f35da23a1e4cb97edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b99934daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0edd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca0028900401db93101c9c38044094966982980574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f101080306000000aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c15203111111111111111111111111111111111111111111111111111111111111111130424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242726574682f76312e302e302f6c696e7578" + ); + + // Self-generated snapshot: hash_slow() output of the header with Berachain ext fields. + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const WITH_PROPOSER_HASH: B256 = + b256!("0xf822917a3ec331a4acc12c48325b1ecfd1f7771bb55dec41d648c365ee6855cb"); + + #[test] + fn compact_bitflag_layout() { + assert_eq!(CompactBerachainHeader::bitflag_encoded_bytes(), 4); + assert_eq!(BerachainHeaderExt::bitflag_encoded_bytes(), 1); + } + + #[test] + fn holesky_compact_matches_reth_v1_11_4() { + let header = holesky_berachain_header(); + let mut buf = Vec::new(); + header.compress_to_buf(&mut buf); + assert_eq!(buf.as_slice(), HOLESKY_COMPACT); + + let decompressed = BerachainHeader::decompress(HOLESKY_COMPACT).unwrap(); + assert_eq!(decompressed, header); + + let mut reencoded = Vec::new(); + decompressed.compress_to_buf(&mut reencoded); + assert_eq!(reencoded.as_slice(), HOLESKY_COMPACT); + } + + #[test] + fn holesky_hash_regression() { + assert_eq!(holesky_berachain_header().hash_slow(), HOLESKY_HASH); + } + + #[test] + fn prev_proposer_pubkey_compact_regression() { + let header = holesky_berachain_header_with_proposer_pubkey(); + let mut buf = Vec::new(); + header.compress_to_buf(&mut buf); + assert_eq!(buf.as_slice(), WITH_PROPOSER_COMPACT); + assert_eq!(BerachainHeader::decompress(WITH_PROPOSER_COMPACT).unwrap(), header); + } + + #[test] + fn prev_proposer_pubkey_hash_regression() { + assert_eq!( + holesky_berachain_header_with_proposer_pubkey().hash_slow(), + WITH_PROPOSER_HASH + ); + } + } } diff --git a/src/test_utils.rs b/src/test_utils.rs index dbb7f38b..cfbb21f5 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +pub mod regression_fixtures; + use crate::chainspec::BerachainChainSpec; use reth_cli::chainspec::parse_genesis; use std::sync::Arc; diff --git a/src/test_utils/regression_fixtures.rs b/src/test_utils/regression_fixtures.rs new file mode 100644 index 00000000..647cea2a --- /dev/null +++ b/src/test_utils/regression_fixtures.rs @@ -0,0 +1,126 @@ +use crate::primitives::header::{BerachainHeader, BlsPublicKey}; +use alloy_consensus::EMPTY_OMMER_ROOT_HASH; +use alloy_primitives::{Address, B64, Bytes, U256, address, b256, bloom, bytes}; + +pub fn holesky_berachain_header() -> BerachainHeader { + BerachainHeader { + parent_hash: b256!("0x8605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a95"), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: address!("0xc6e2459991bfe27cca6d86722f35da23a1e4cb97"), + state_root: b256!("0xedad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b9993"), + transactions_root: b256!( + "0x4daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee" + ), + receipts_root: b256!("0x1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0ed"), + withdrawals_root: Some(b256!( + "0xd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d" + )), + logs_bloom: bloom!( + "36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca002890040" + ), + difficulty: U256::ZERO, + number: 0x1db931, + gas_limit: 0x1c9c380, + gas_used: 0x440949, + timestamp: 0x66982980, + mix_hash: b256!("0x574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f1"), + nonce: B64::ZERO, + base_fee_per_gas: Some(0x8), + blob_gas_used: Some(0x60000), + excess_blob_gas: Some(0x0), + parent_beacon_block_root: Some(b256!( + "0xaa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1" + )), + requests_hash: None, + prev_proposer_pubkey: None, + extra_data: bytes!("726574682f76312e302e302f6c696e7578"), + } +} + +pub fn holesky_berachain_header_with_proposer_pubkey() -> BerachainHeader { + BerachainHeader { + prev_proposer_pubkey: Some(BlsPublicKey::from([0x42; 48])), + requests_hash: Some(b256!( + "0x1111111111111111111111111111111111111111111111111111111111111111" + )), + ..holesky_berachain_header() + } +} + +pub fn minimal_pol_tx() -> crate::transaction::PoLTx { + use alloy_primitives::ChainId; + crate::transaction::PoLTx { + chain_id: ChainId::from(80084u64), + from: Address::ZERO, + to: Address::from([1u8; 20]), + nonce: 42, + gas_limit: 21000, + gas_price: 1000000000u128, + input: Bytes::from("test data"), + } +} + +#[cfg(test)] +mod print_regression { + use super::*; + use crate::{ + chainspec::BerachainChainSpec, + test_utils::bepolia_chainspec, + transaction::{BerachainTxEnvelope, BerachainTxType, pol::create_pol_transaction}, + }; + use alloy_primitives::{Sealable, Sealed, U256}; + use reth_chainspec::EthChainSpec; + use reth_cli::chainspec::parse_genesis; + use reth_codecs::Compact; + use reth_db_api::table::Compress; + + #[test] + #[ignore = "manual regression values generator"] + fn print_regression_values() { + let holesky = holesky_berachain_header(); + let mut buf = Vec::new(); + holesky.compress_to_buf(&mut buf); + eprintln!("holesky_hash={:#x}", holesky.hash_slow()); + eprintln!("holesky_compact={}", alloy_primitives::hex::encode(&buf)); + + let with_pubkey = holesky_berachain_header_with_proposer_pubkey(); + let mut buf = Vec::new(); + with_pubkey.compress_to_buf(&mut buf); + eprintln!("with_pubkey_hash={:#x}", with_pubkey.hash_slow()); + eprintln!("with_pubkey_compact={}", alloy_primitives::hex::encode(&buf)); + + let pol = minimal_pol_tx(); + eprintln!("minimal_pol_hash={:#x}", pol.hash_slow()); + let mut buf = Vec::new(); + pol.to_compact(&mut buf); + eprintln!("minimal_pol_compact={}", alloy_primitives::hex::encode(&buf)); + + let envelope = BerachainTxEnvelope::Berachain(Sealed::new(pol.clone())); + let mut buf = Vec::new(); + envelope.compress_to_buf(&mut buf); + eprintln!("minimal_pol_envelope_compact={}", alloy_primitives::hex::encode(&buf)); + + let tx_type = BerachainTxType::Berachain; + let mut buf = Vec::new(); + let id = tx_type.to_compact(&mut buf); + eprintln!("berachain_tx_type_id={id}"); + eprintln!("berachain_tx_type_compact={}", alloy_primitives::hex::encode(&buf)); + + let chain_spec = bepolia_chainspec(); + let pubkey = BlsPublicKey::from([1u8; 48]); + let pol_prod = + match create_pol_transaction(chain_spec, pubkey, U256::from(10), 1000).unwrap() { + BerachainTxEnvelope::Berachain(sealed) => sealed, + _ => panic!("expected pol"), + }; + eprintln!("production_pol_hash={:#x}", pol_prod.hash()); + + let bepolia_json = include_str!("../../tests/fixtures/bepolia-genesis.json"); + let bepolia = BerachainChainSpec::from(parse_genesis(bepolia_json).unwrap()); + eprintln!("bepolia_genesis_hash={:#x}", bepolia.genesis_hash()); + + let mainnet_json = include_str!("../../tests/fixtures/mainnet-genesis.json"); + let mainnet = BerachainChainSpec::from(parse_genesis(mainnet_json).unwrap()); + eprintln!("mainnet_genesis_hash={:#x}", mainnet.genesis_hash()); + } +} diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 3387508d..538eed1a 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -1226,4 +1226,56 @@ mod pol_tx_rlp_tests { let result = PoLTx::rlp_decode(&mut malformed_rlp.as_slice()); assert!(matches!(result, Err(alloy_rlp::Error::InputTooShort))); } + + mod regression { + use super::*; + use crate::test_utils::regression_fixtures::minimal_pol_tx; + use alloy_primitives::{b256, hex}; + use reth_codecs::Compact; + use reth_db_api::table::Compress; + + // Self-generated snapshot: hash_slow() output of regression_fixtures::minimal_pol_tx(). + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const MINIMAL_POL_HASH: alloy_primitives::B256 = + b256!("0x0a7a43d18a263b300d6fea2dfaa45e05798bbd0b11ce2b20f3a7e269f873e59f"); + + // Self-generated snapshot: to_compact() output of regression_fixtures::minimal_pol_tx(). + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const MINIMAL_POL_COMPACT: &[u8] = &hex!( + "1342000138d4000000000000000000000000000000000000000001010101010101010101010101010101010101012a52083b9aca00746573742064617461" + ); + + // Self-generated snapshot: compress_to_buf() output of + // BerachainTxEnvelope::Berachain(Sealed::new(regression_fixtures::minimal_pol_tx())). + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const MINIMAL_POL_ENVELOPE_COMPACT: &[u8] = &hex!( + "06000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007e1342000138d4000000000000000000000000000000000000000001010101010101010101010101010101010101012a52083b9aca00746573742064617461" + ); + + #[test] + fn minimal_pol_tx_hash_regression() { + let pol_tx = minimal_pol_tx(); + assert_eq!(pol_tx.hash_slow(), MINIMAL_POL_HASH); + } + + #[test] + fn minimal_pol_tx_compact_regression() { + let pol_tx = minimal_pol_tx(); + let mut buf = Vec::new(); + pol_tx.to_compact(&mut buf); + assert_eq!(buf.as_slice(), MINIMAL_POL_COMPACT); + } + + #[test] + fn minimal_pol_envelope_compact_regression() { + let envelope = BerachainTxEnvelope::Berachain(Sealed::new(minimal_pol_tx())); + let mut buf = Vec::new(); + envelope.compress_to_buf(&mut buf); + assert_eq!(buf.as_slice(), MINIMAL_POL_ENVELOPE_COMPACT); + assert_eq!( + BerachainTxEnvelope::decompress(MINIMAL_POL_ENVELOPE_COMPACT).unwrap(), + envelope + ); + } + } } diff --git a/src/transaction/pol.rs b/src/transaction/pol.rs index b3fc5e62..6340a87a 100644 --- a/src/transaction/pol.rs +++ b/src/transaction/pol.rs @@ -84,10 +84,13 @@ pub fn validate_pol_transaction( mod tests { use super::*; use alloy_eips::eip7002::SYSTEM_ADDRESS; - use alloy_primitives::{U256, address}; + use alloy_primitives::{U256, address, b256}; use crate::test_utils::bepolia_chainspec; + const REGRESSION_BLOCK_NUMBER: u64 = 10; + const REGRESSION_BASE_FEE: u64 = 1000; + fn mock_bls_pubkey() -> BlsPublicKey { BlsPublicKey::from([1u8; 48]) } @@ -234,4 +237,61 @@ mod tests { "Identical PoL transactions should have identical hashes" ); } + + #[test] + fn test_pol_transaction_hash_regression() { + let chain_spec = bepolia_chainspec(); + let pubkey = mock_bls_pubkey(); + let pol_tx = match create_pol_transaction( + chain_spec, + pubkey, + U256::from(REGRESSION_BLOCK_NUMBER), + REGRESSION_BASE_FEE, + ) + .unwrap() + { + BerachainTxEnvelope::Berachain(sealed_tx) => sealed_tx, + _ => panic!("Expected PoL transaction"), + }; + + assert_eq!( + *pol_tx.hash(), + b256!("0x35b5cc4612e0e4ec54e339c189158098ef764de6229c7db0707d95c0850b5eb7") + ); + } + + #[test] + fn test_pol_transaction_compact_regression() { + use alloy_primitives::hex; + use reth_db::table::Decompress; + use reth_db_api::table::Compress; + + // Self-generated snapshot: compress_to_buf output for a production-like PoL tx + // using bepolia chain spec, block 10, base_fee 1000, pubkey [1u8; 48]. + // Same inputs as test_pol_transaction_hash_regression. + // Regenerate via: cargo test print_regression_values -- --ignored --nocapture + const PRODUCTION_POL_COMPACT: &[u8] = &hex!( + "0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028b52ffd23e4c3b413baed010094027e1324000138c5fffed2f19a79b026fb636a7c300bf5947df1139407610901c9c38003e860644a300106fc00023884e6f1eb031ef566f3de5587f9" + ); + + let chain_spec = bepolia_chainspec(); + let pubkey = mock_bls_pubkey(); + let sealed_pol = match create_pol_transaction( + chain_spec, + pubkey, + U256::from(REGRESSION_BLOCK_NUMBER), + REGRESSION_BASE_FEE, + ) + .unwrap() + { + BerachainTxEnvelope::Berachain(sealed) => sealed, + _ => panic!("Expected PoL transaction"), + }; + let envelope = BerachainTxEnvelope::Berachain(sealed_pol); + let mut buf = Vec::new(); + envelope.compress_to_buf(&mut buf); + assert_eq!(buf.as_slice(), PRODUCTION_POL_COMPACT); + + assert_eq!(BerachainTxEnvelope::decompress(PRODUCTION_POL_COMPACT).unwrap(), envelope); + } } diff --git a/src/transaction/txtype.rs b/src/transaction/txtype.rs index 94d87e1e..be23d409 100644 --- a/src/transaction/txtype.rs +++ b/src/transaction/txtype.rs @@ -186,4 +186,18 @@ mod tests { assert_eq!(decoded, expected_berachain_type); } } + + #[test] + fn test_berachain_tx_type_compact_regression() { + let tx_type = BerachainTxType::Berachain; + let mut buf = Vec::new(); + let identifier = tx_type.to_compact(&mut buf); + + assert_eq!(identifier, COMPACT_EXTENDED_IDENTIFIER_FLAG); + assert_eq!(buf.as_slice(), &[POL_TX_TYPE]); + + let (decoded, remaining) = BerachainTxType::from_compact(&buf, identifier); + assert_eq!(decoded, BerachainTxType::Berachain); + assert!(remaining.is_empty()); + } }