diff --git a/Cargo.lock b/Cargo.lock index 34de63d9cde..8aee78eb055 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10341,6 +10341,7 @@ dependencies = [ "reth-network-peers", "reth-primitives-traits", "reth-scroll-forks", + "reth-scroll-primitives", "reth-trie-common", "scroll-alloy-hardforks", "serde", @@ -10548,6 +10549,8 @@ version = "1.6.0" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-json-rpc", + "alloy-network", "alloy-primitives", "alloy-rlp", "arbitrary", @@ -11834,6 +11837,7 @@ dependencies = [ "reth-evm", "reth-scroll-chainspec", "reth-scroll-evm", + "reth-scroll-primitives", "revm", "revm-scroll", "scroll-alloy-consensus", @@ -11860,6 +11864,7 @@ dependencies = [ "alloy-provider", "alloy-rpc-types-eth", "alloy-signer", + "reth-scroll-primitives", "scroll-alloy-consensus", "scroll-alloy-rpc-types", ] diff --git a/crates/scroll/alloy/evm/Cargo.toml b/crates/scroll/alloy/evm/Cargo.toml index 2c61be72148..3e0842ed578 100644 --- a/crates/scroll/alloy/evm/Cargo.toml +++ b/crates/scroll/alloy/evm/Cargo.toml @@ -27,6 +27,7 @@ revm-scroll = { workspace = true, default-features = false } # scroll scroll-alloy-consensus = { workspace = true, default-features = false } scroll-alloy-hardforks = { workspace = true, default-features = false } +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } # misc auto_impl = { workspace = true, default-features = false } diff --git a/crates/scroll/alloy/evm/src/system_caller.rs b/crates/scroll/alloy/evm/src/system_caller.rs index 83cd0033743..75bf4ffc4af 100644 --- a/crates/scroll/alloy/evm/src/system_caller.rs +++ b/crates/scroll/alloy/evm/src/system_caller.rs @@ -99,6 +99,7 @@ mod tests { use reth_evm::ConfigureEvm; use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpecBuilder}; use reth_scroll_evm::ScrollEvmConfig; + use reth_scroll_primitives::{ScrollBlock, ScrollHeader}; use revm::{ bytecode::Bytecode, database::{EmptyDBTyped, State}, @@ -146,7 +147,7 @@ mod tests { let block: Block = Block { header, body: BlockBody::default() }; // initiate the evm and apply the block hashes contract call. - let mut evm = evm_config.evm_for_block(state, &block.header); + let mut evm = evm_config.evm_for_block(state, &ScrollHeader { inner: block.header.clone() }); system_caller.apply_blockhashes_contract_call(block.parent_hash, &mut evm).unwrap(); // assert the storage slot remains unchanged. @@ -189,7 +190,7 @@ mod tests { gas_limit: 20_000_000, ..Default::default() }; - let block: Block = Block { header, body: BlockBody::default() }; + let block: Block = ScrollBlock { header: ScrollHeader { inner: header }, body: BlockBody::default() }; // initiate the evm and apply the block hashes contract call. let mut evm = evm_config.evm_for_block(state, &block.header); diff --git a/crates/scroll/alloy/network/Cargo.toml b/crates/scroll/alloy/network/Cargo.toml index 24449a64ec8..c095261a6e5 100644 --- a/crates/scroll/alloy/network/Cargo.toml +++ b/crates/scroll/alloy/network/Cargo.toml @@ -17,6 +17,9 @@ alloy-provider = { workspace = true, default-features = false } scroll-alloy-consensus = { workspace = true, default-features = false } scroll-alloy-rpc-types = { workspace = true, default-features = false } +# reth +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } + # alloy alloy-consensus = { workspace = true, default-features = false } alloy-network = { workspace = true, default-features = false } diff --git a/crates/scroll/chainspec/Cargo.toml b/crates/scroll/chainspec/Cargo.toml index 60298bef3e0..fff5200519c 100644 --- a/crates/scroll/chainspec/Cargo.toml +++ b/crates/scroll/chainspec/Cargo.toml @@ -21,6 +21,7 @@ reth-trie-common = { workspace = true, default-features = false } # scroll reth-scroll-forks = { workspace = true, default-features = false } +reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } scroll-alloy-hardforks = { workspace = true, default-features = false } # ethereum diff --git a/crates/scroll/chainspec/src/dev.rs b/crates/scroll/chainspec/src/dev.rs index 61bc0e52d62..7fce370d329 100644 --- a/crates/scroll/chainspec/src/dev.rs +++ b/crates/scroll/chainspec/src/dev.rs @@ -26,7 +26,7 @@ pub static SCROLL_DEV: LazyLock> = LazyLock::new(|| { ScrollChainSpec { inner: ChainSpec { chain: Chain::dev(), - genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis)), + genesis_header: SealedHeader::new_unhashed(make_genesis_header(&genesis).inner), genesis, paris_block_and_final_difficulty: Some((0, U256::from(0))), hardforks: DEV_HARDFORKS.clone(), diff --git a/crates/scroll/chainspec/src/lib.rs b/crates/scroll/chainspec/src/lib.rs index db91abe0aa9..87f609f1894 100644 --- a/crates/scroll/chainspec/src/lib.rs +++ b/crates/scroll/chainspec/src/lib.rs @@ -23,6 +23,7 @@ use reth_ethereum_forks::{ }; use reth_network_peers::NodeRecord; use reth_primitives_traits::SealedHeader; +use reth_scroll_primitives::ScrollHeader; use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; use alloy_eips::eip7840::BlobParams; @@ -203,7 +204,7 @@ impl ScrollChainSpec { if header.base_fee_per_gas.is_none() && feynman_active_at_genesis { header.base_fee_per_gas = Some(0); } - spec.inner.genesis_header = SealedHeader::new_unhashed(header); + spec.inner.genesis_header = SealedHeader::new_unhashed(header.inner); // Use Scroll's EIP-1559 params from Feynman onwards. spec.inner.base_fee_params = BaseFeeParamsKind::Variable( @@ -242,7 +243,7 @@ pub struct ScrollChainSpec { } impl EthChainSpec for ScrollChainSpec { - type Header = Header; + type Header = ScrollHeader; fn chain(&self) -> alloy_chains::Chain { self.inner.chain() @@ -276,8 +277,11 @@ impl EthChainSpec for ScrollChainSpec { Box::new(ChainSpec::display_hardforks(self)) } - fn genesis_header(&self) -> &Header { - self.inner.genesis_header() + fn genesis_header(&self) -> &ScrollHeader { + // Store as a static to avoid lifetime issues + use std::sync::OnceLock; + static GENESIS_HEADER: OnceLock = OnceLock::new(); + GENESIS_HEADER.get_or_init(|| ScrollHeader { inner: self.inner.genesis_header().clone() }) } fn genesis(&self) -> &Genesis { @@ -300,25 +304,27 @@ impl EthereumCapabilities for ScrollChainSpec { } } -fn make_genesis_header(genesis: &Genesis) -> Header { - Header { - gas_limit: genesis.gas_limit, - difficulty: genesis.difficulty, - nonce: genesis.nonce.into(), - extra_data: genesis.extra_data.clone(), - state_root: reth_trie_common::root::state_root_ref_unhashed(&genesis.alloc), - timestamp: genesis.timestamp, - mix_hash: genesis.mix_hash, - beneficiary: genesis.coinbase, - base_fee_per_gas: genesis - .base_fee_per_gas - .map(|b| b.try_into().expect("base fee should fit in u64")), - withdrawals_root: None, - parent_beacon_block_root: None, - blob_gas_used: None, - excess_blob_gas: None, - requests_hash: None, - ..Default::default() +fn make_genesis_header(genesis: &Genesis) -> ScrollHeader { + ScrollHeader { + inner: Header { + gas_limit: genesis.gas_limit, + difficulty: genesis.difficulty, + nonce: genesis.nonce.into(), + extra_data: genesis.extra_data.clone(), + state_root: reth_trie_common::root::state_root_ref_unhashed(&genesis.alloc), + timestamp: genesis.timestamp, + mix_hash: genesis.mix_hash, + beneficiary: genesis.coinbase, + base_fee_per_gas: genesis + .base_fee_per_gas + .map(|b| b.try_into().expect("base fee should fit in u64")), + withdrawals_root: None, + parent_beacon_block_root: None, + blob_gas_used: None, + excess_blob_gas: None, + requests_hash: None, + ..Default::default() + } } } diff --git a/crates/scroll/chainspec/src/scroll.rs b/crates/scroll/chainspec/src/scroll.rs index d3f1ea2c0af..f222b0415a2 100644 --- a/crates/scroll/chainspec/src/scroll.rs +++ b/crates/scroll/chainspec/src/scroll.rs @@ -21,7 +21,7 @@ pub static SCROLL_MAINNET: LazyLock> = LazyLock::new(|| { // TODO(scroll): migrate to Chain::scroll() (introduced in https://github.com/alloy-rs/chains/pull/112) when alloy-chains is bumped to version 0.1.48 chain: Chain::from_named(NamedChain::Scroll), genesis_header: SealedHeader::new( - make_genesis_header(&genesis), + make_genesis_header(&genesis).inner, SCROLL_MAINNET_GENESIS_HASH, ), genesis, diff --git a/crates/scroll/chainspec/src/scroll_sepolia.rs b/crates/scroll/chainspec/src/scroll_sepolia.rs index 2c152fff5f5..29aaaf8e37b 100644 --- a/crates/scroll/chainspec/src/scroll_sepolia.rs +++ b/crates/scroll/chainspec/src/scroll_sepolia.rs @@ -21,7 +21,7 @@ pub static SCROLL_SEPOLIA: LazyLock> = LazyLock::new(|| { // TODO(scroll): migrate to Chain::scroll_sepolia() (introduced in https://github.com/alloy-rs/chains/pull/112) when alloy-chains is bumped to version 0.1.48 chain: Chain::from_named(NamedChain::ScrollSepolia), genesis_header: SealedHeader::new( - make_genesis_header(&genesis), + make_genesis_header(&genesis).inner, SCROLL_SEPOLIA_GENESIS_HASH, ), genesis, diff --git a/crates/scroll/engine-primitives/src/payload/mod.rs b/crates/scroll/engine-primitives/src/payload/mod.rs index c7fd4817017..bd3818e3cbb 100644 --- a/crates/scroll/engine-primitives/src/payload/mod.rs +++ b/crates/scroll/engine-primitives/src/payload/mod.rs @@ -22,7 +22,7 @@ use reth_engine_primitives::EngineTypes; use reth_payload_primitives::{BuiltPayload, PayloadTypes}; use reth_primitives::{Block, BlockBody, Header}; use reth_primitives_traits::{NodePrimitives, SealedBlock}; -use reth_scroll_primitives::ScrollBlock; +use reth_scroll_primitives::{ScrollBlock, ScrollHeader, ScrollTransactionSigned}; use scroll_alloy_hardforks::ScrollHardforks; use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; @@ -99,14 +99,14 @@ impl PayloadTypes for ScrollPayloadTypes { /// Scroll implementation of the [`ExecutionPayload::try_into_block`], which will fail with /// [`PayloadError::ExtraData`] due to the Scroll blocks containing extra data for the Clique /// consensus. -pub fn try_into_block( +pub fn try_into_block( value: ExecutionData, chainspec: Arc, -) -> Result, PayloadError> { +) -> Result { let mut block = match value.payload { - ExecutionPayload::V1(payload) => try_payload_v1_to_block(payload, chainspec)?, - ExecutionPayload::V2(payload) => try_payload_v2_to_block(payload, chainspec)?, - ExecutionPayload::V3(payload) => try_payload_v3_to_block(payload, chainspec)?, + ExecutionPayload::V1(payload) => try_payload_v1_to_block(payload, &*chainspec)?, + ExecutionPayload::V2(payload) => try_payload_v2_to_block(payload, &*chainspec)?, + ExecutionPayload::V3(payload) => try_payload_v3_to_block(payload, &*chainspec)?, }; block.header.parent_beacon_block_root = value.sidecar.parent_beacon_block_root(); @@ -116,10 +116,10 @@ pub fn try_into_block( } /// Tries to convert an [`ExecutionPayloadV1`] to [`Block`]. -fn try_payload_v1_to_block( +fn try_payload_v1_to_block( payload: ExecutionPayloadV1, chainspec: CS, -) -> Result, PayloadError> { +) -> Result { // WARNING: It’s allowed for a base fee in EIP1559 to increase unbounded. We assume that // it will fit in an u64. This is not always necessarily true, although it is extremely // unlikely not to be the case, a u64 maximum would have 2^64 which equates to 18 ETH per @@ -137,7 +137,7 @@ fn try_payload_v1_to_block( .map(|tx| { let mut buf = tx.as_ref(); - let tx = T::decode_2718(&mut buf).map_err(alloy_rlp::Error::from)?; + let tx = ScrollTransactionSigned::decode_2718(&mut buf).map_err(alloy_rlp::Error::from)?; if !buf.is_empty() { return Err(alloy_rlp::Error::UnexpectedLength); @@ -153,39 +153,41 @@ fn try_payload_v1_to_block( buf.put_slice(item) }); - let header = Header { - parent_hash: payload.parent_hash, - beneficiary: payload.fee_recipient, - state_root: payload.state_root, - transactions_root, - receipts_root: payload.receipts_root, - withdrawals_root: None, - logs_bloom: payload.logs_bloom, - number: payload.block_number, - gas_limit: payload.gas_limit, - gas_used: payload.gas_used, - timestamp: payload.timestamp, - mix_hash: payload.prev_randao, - base_fee_per_gas: basefee, - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, - requests_hash: None, - extra_data: payload.extra_data, - // Defaults - ommers_hash: EMPTY_OMMER_ROOT_HASH, - difficulty: U256::ONE, - nonce: Default::default(), + let header = ScrollHeader { + inner: Header { + parent_hash: payload.parent_hash, + beneficiary: payload.fee_recipient, + state_root: payload.state_root, + transactions_root, + receipts_root: payload.receipts_root, + withdrawals_root: None, + logs_bloom: payload.logs_bloom, + number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + mix_hash: payload.prev_randao, + base_fee_per_gas: basefee, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + extra_data: payload.extra_data, + // Defaults + ommers_hash: EMPTY_OMMER_ROOT_HASH, + difficulty: U256::ONE, + nonce: Default::default(), + } }; Ok(Block { header, body: BlockBody { transactions, ..Default::default() } }) } /// Tries to convert an [`ExecutionPayloadV2`] to [`Block`]. -fn try_payload_v2_to_block( +fn try_payload_v2_to_block( payload: ExecutionPayloadV2, chainspec: CS, -) -> Result, PayloadError> { +) -> Result { // this performs the same conversion as the underlying V1 payload, but calculates the // withdrawals root and adds withdrawals let mut base_sealed_block = try_payload_v1_to_block(payload.payload_inner, chainspec)?; @@ -196,10 +198,10 @@ fn try_payload_v2_to_block( } /// Tries to convert an [`ExecutionPayloadV3`] to [`Block`]. -fn try_payload_v3_to_block( +fn try_payload_v3_to_block( payload: ExecutionPayloadV3, chainspec: CS, -) -> Result, PayloadError> { +) -> Result { // this performs the same conversion as the underlying V2 payload, but inserts the blob gas // used and excess blob gas let mut base_block = try_payload_v2_to_block(payload.payload_inner, chainspec)?; @@ -218,7 +220,6 @@ mod tests { use arbitrary::{Arbitrary, Unstructured}; use rand::Rng; use reth_scroll_chainspec::SCROLL_MAINNET; - use reth_scroll_primitives::ScrollTransactionSigned; #[test] fn test_can_convert_execution_v1_payload_into_block() -> eyre::Result<()> { @@ -247,7 +248,7 @@ mod tests { }); let execution_data = ExecutionData::new(execution_payload, Default::default()); - let _: Block = + let _: ScrollBlock = try_into_block(execution_data, SCROLL_MAINNET.clone())?; Ok(()) @@ -283,7 +284,7 @@ mod tests { }); let execution_data = ExecutionData::new(execution_payload, Default::default()); - let _: Block = + let _: ScrollBlock = try_into_block(execution_data, SCROLL_MAINNET.clone())?; Ok(()) @@ -323,7 +324,7 @@ mod tests { }); let execution_data = ExecutionData::new(execution_payload, Default::default()); - let _: Block = + let _: ScrollBlock = try_into_block(execution_data, SCROLL_MAINNET.clone())?; Ok(()) diff --git a/crates/scroll/evm/src/build.rs b/crates/scroll/evm/src/build.rs index 2645dad697b..12e13c9eb9d 100644 --- a/crates/scroll/evm/src/build.rs +++ b/crates/scroll/evm/src/build.rs @@ -5,8 +5,7 @@ use alloy_evm::block::{BlockExecutionError, BlockExecutorFactory}; use alloy_primitives::{logs_bloom, Address}; use reth_evm::execute::{BlockAssembler, BlockAssemblerInput}; use reth_execution_types::BlockExecutionResult; -use reth_primitives_traits::SignedTransaction; -use reth_scroll_primitives::ScrollReceipt; +use reth_scroll_primitives::{ScrollBlock, ScrollHeader, ScrollReceipt, ScrollTransactionSigned}; use scroll_alloy_evm::ScrollBlockExecutionCtx; use scroll_alloy_hardforks::ScrollHardforks; @@ -34,15 +33,15 @@ where ChainSpec: ScrollHardforks, F: for<'a> BlockExecutorFactory< ExecutionCtx<'a> = ScrollBlockExecutionCtx, - Transaction: SignedTransaction, + Transaction = ScrollTransactionSigned, Receipt = ScrollReceipt, >, { - type Block = alloy_consensus::Block; + type Block = ScrollBlock; fn assemble_block( &self, - input: BlockAssemblerInput<'_, '_, F>, + input: BlockAssemblerInput<'_, '_, F, ScrollHeader>, ) -> Result { let BlockAssemblerInput { evm_env, @@ -59,36 +58,42 @@ where let receipts_root = ScrollReceipt::calculate_receipt_root_no_memo(receipts); let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); - let header = Header { - parent_hash: ctx.parent_hash, - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: Address::ZERO, - state_root, - transactions_root, - receipts_root, - withdrawals_root: None, - logs_bloom, - timestamp: timestamp.to(), - mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), - nonce: BEACON_NONCE.into(), - base_fee_per_gas: self - .chain_spec - .is_curie_active_at_block(evm_env.block_env.number.to()) - .then_some(evm_env.block_env.basefee), - number: evm_env.block_env.number.to(), - gas_limit: evm_env.block_env.gas_limit, - difficulty: evm_env.block_env.difficulty, - gas_used: *gas_used, - extra_data: Default::default(), - parent_beacon_block_root: None, - blob_gas_used: None, - excess_blob_gas: None, - requests_hash: None, + let header = ScrollHeader { + inner: Header { + parent_hash: ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: Address::ZERO, + state_root, + transactions_root, + receipts_root, + withdrawals_root: None, + logs_bloom, + timestamp: timestamp.to(), + mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: self + .chain_spec + .is_curie_active_at_block(evm_env.block_env.number.to()) + .then_some(evm_env.block_env.basefee), + number: evm_env.block_env.number.to(), + gas_limit: evm_env.block_env.gas_limit, + difficulty: evm_env.block_env.difficulty, + gas_used: *gas_used, + extra_data: Default::default(), + parent_beacon_block_root: None, + blob_gas_used: None, + excess_blob_gas: None, + requests_hash: None, + } }; Ok(alloy_consensus::Block::new( header, - BlockBody { transactions, ommers: Default::default(), withdrawals: None }, + BlockBody { + transactions, + ommers: Default::default(), + withdrawals: None + }, )) } } diff --git a/crates/scroll/evm/src/config.rs b/crates/scroll/evm/src/config.rs index 4f8adc3f105..8dfedfb860a 100644 --- a/crates/scroll/evm/src/config.rs +++ b/crates/scroll/evm/src/config.rs @@ -1,16 +1,16 @@ use crate::{build::ScrollBlockAssembler, ScrollEvmConfig, ScrollNextBlockEnvAttributes}; use alloc::sync::Arc; -use alloy_consensus::{BlockHeader, Header}; +use alloy_consensus::BlockHeader; use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; use alloy_primitives::B256; use core::convert::Infallible; use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, EvmEnv, ExecutionCtxFor}; use reth_primitives_traits::{ - BlockTy, NodePrimitives, SealedBlock, SealedHeader, SignedTransaction, + BlockTy, NodePrimitives, SealedBlock, SealedHeader, }; use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; -use reth_scroll_primitives::ScrollReceipt; +use reth_scroll_primitives::{ScrollBlock, ScrollBlockBody, ScrollHeader, ScrollTransactionSigned, ScrollReceipt}; use revm::{ context::{BlockEnv, CfgEnv, TxEnv}, primitives::U256, @@ -28,13 +28,13 @@ where N: NodePrimitives< Receipt = R::Receipt, SignedTx = R::Transaction, - BlockHeader = Header, - BlockBody = alloy_consensus::BlockBody, - Block = alloy_consensus::Block, + BlockHeader = ScrollHeader, + BlockBody = ScrollBlockBody, + Block = ScrollBlock, >, ScrollTransactionIntoTxEnv: FromRecoveredTx + FromTxWithEncoded, - R: ScrollReceiptBuilder, + R: ScrollReceiptBuilder, P: ScrollPrecompilesFactory, Self: Send + Sync + Unpin + Clone + 'static, { @@ -184,7 +184,7 @@ mod tests { ); // curie - let curie_header = Header { number: 7096836, ..Default::default() }; + let curie_header = ScrollHeader { inner: Header { number: 7096836, ..Default::default() } }; // fill cfg env let env = config.evm_env(&curie_header); @@ -194,7 +194,7 @@ mod tests { assert_eq!(env.cfg_env.spec, ScrollSpecId::CURIE); // bernoulli - let bernoulli_header = Header { number: 5220340, ..Default::default() }; + let bernoulli_header = ScrollHeader { inner: Header { number: 5220340, ..Default::default() } }; // fill cfg env let env = config.evm_env(&bernoulli_header); @@ -204,7 +204,7 @@ mod tests { assert_eq!(env.cfg_env.spec, ScrollSpecId::BERNOULLI); // pre-bernoulli - let pre_bernoulli_header = Header { number: 0, ..Default::default() }; + let pre_bernoulli_header = ScrollHeader { inner: Header { number: 0, ..Default::default() } }; // fill cfg env let env = config.evm_env(&pre_bernoulli_header); @@ -222,7 +222,7 @@ mod tests { ); // curie header - let header = Header { + let header = ScrollHeader { inner: Header { number: 7096836, beneficiary: Address::random(), timestamp: 1719994277, @@ -230,7 +230,7 @@ mod tests { base_fee_per_gas: Some(155157341), gas_limit: 10000000, ..Default::default() - }; + }}; // fill block env let env = config.evm_env(&header); @@ -257,7 +257,7 @@ mod tests { ); // pre curie header - let header = Header { + let header = ScrollHeader { inner: Header { number: 7096835, beneficiary: Address::random(), timestamp: 1719994274, @@ -265,7 +265,7 @@ mod tests { base_fee_per_gas: None, gas_limit: 10000000, ..Default::default() - }; + }}; // curie block attributes let attributes = ScrollNextBlockEnvAttributes { diff --git a/crates/scroll/evm/src/execute.rs b/crates/scroll/evm/src/execute.rs index f37769fc9c0..32d82e0a42d 100644 --- a/crates/scroll/evm/src/execute.rs +++ b/crates/scroll/evm/src/execute.rs @@ -63,7 +63,7 @@ mod tests { use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SignedTransaction}; use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpec, ScrollChainSpecBuilder}; use reth_scroll_primitives::{ - ScrollBlock, ScrollPrimitives, ScrollReceipt, ScrollTransactionSigned, + ScrollBlock, ScrollHeader, ScrollPrimitives, ScrollReceipt, ScrollTransactionSigned }; use revm::{ bytecode::Bytecode, @@ -136,11 +136,13 @@ mod tests { let senders = transactions.iter().map(|t| t.recover_signer().unwrap()).collect(); RecoveredBlock::new_unhashed( Block { - header: Header { - number, - timestamp, - gas_limit: BLOCK_GAS_LIMIT, - ..Default::default() + header: ScrollHeader { + inner: Header { + number, + timestamp, + gas_limit: BLOCK_GAS_LIMIT, + ..Default::default() + }, }, body: BlockBody { transactions, ..Default::default() }, }, diff --git a/crates/scroll/node/src/node.rs b/crates/scroll/node/src/node.rs index 17832c91290..0ca6dd2a32c 100644 --- a/crates/scroll/node/src/node.rs +++ b/crates/scroll/node/src/node.rs @@ -14,7 +14,7 @@ use reth_node_builder::{ }; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_engine_primitives::ScrollEngineTypes; -use reth_scroll_primitives::ScrollPrimitives; +use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; use reth_trie_db::MerklePatriciaTrie; use scroll_alloy_network::Scroll; use std::sync::Arc; @@ -96,10 +96,10 @@ impl DebugNode for ScrollNode where N: FullNodeComponents, { - type RpcBlock = alloy_rpc_types_eth::Block; + type RpcBlock = ScrollBlock; fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> reth_node_api::BlockTy { - rpc_block.into_consensus() + rpc_block } fn local_payload_attributes_builder( diff --git a/crates/scroll/payload/src/builder.rs b/crates/scroll/payload/src/builder.rs index d0bf505d029..56a827e174b 100644 --- a/crates/scroll/payload/src/builder.rs +++ b/crates/scroll/payload/src/builder.rs @@ -29,7 +29,7 @@ use reth_revm::{cancelled::CancelOnDrop, database::StateProviderDatabase, db::St use reth_scroll_chainspec::{ChainConfig, ScrollChainConfig}; use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; use reth_scroll_evm::{ScrollBaseFeeProvider, ScrollNextBlockEnvAttributes}; -use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; +use reth_scroll_primitives::{ScrollHeader, ScrollPrimitives, ScrollTransactionSigned}; use reth_storage_api::{BaseFeeProvider, StateProvider, StateProviderFactory}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::context::{Block, BlockEnv}; @@ -189,7 +189,7 @@ where // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. fn build_empty_payload( &self, - config: PayloadConfig, + config: PayloadConfig, ) -> Result { let args = BuildArguments { config, @@ -353,7 +353,7 @@ pub struct ScrollPayloadBuilderCtx { /// The chainspec pub chain_spec: ChainSpec, /// How to build the payload. - pub config: PayloadConfig, + pub config: PayloadConfig, /// Marker to check whether the job has been cancelled. pub cancel: CancelOnDrop, /// The currently best payload. @@ -368,7 +368,7 @@ where { /// Returns the parent block the payload will be build on. #[allow(clippy::missing_const_for_fn)] - pub fn parent(&self) -> &SealedHeader { + pub fn parent(&self) -> &SealedHeader { &self.config.parent_header } diff --git a/crates/scroll/primitives/Cargo.toml b/crates/scroll/primitives/Cargo.toml index be21143bb6a..985a31c7e72 100644 --- a/crates/scroll/primitives/Cargo.toml +++ b/crates/scroll/primitives/Cargo.toml @@ -19,6 +19,8 @@ reth-zstd-compressors = { workspace = true, optional = true } # alloy alloy-consensus.workspace = true alloy-eips.workspace = true +alloy-json-rpc.workspace = true +alloy-network.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true diff --git a/crates/scroll/primitives/src/header/mod.rs b/crates/scroll/primitives/src/header/mod.rs new file mode 100644 index 00000000000..8f505945332 --- /dev/null +++ b/crates/scroll/primitives/src/header/mod.rs @@ -0,0 +1,319 @@ +use alloy_consensus::Header; +use alloy_primitives::{ + Address, BlockNumber, Bloom, Bytes, Sealable, B256, B64, U256, keccak256, +}; +use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable}; +use serde::{Deserialize, Serialize}; +use core::ops::{Deref, DerefMut}; + +#[cfg(feature = "reth-codec")] + +/// A wrapper around `alloy_consensus::Header` that excludes `extra_data` field when computing hash_slow. +/// +/// This is useful for Scroll where the `extra_data` field should not be included in the block hash +/// calculation for certain operations. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + Default, + RlpEncodable, + RlpDecodable, + Serialize, + Deserialize, +)] + +#[serde(rename_all = "camelCase")] +pub struct ScrollHeader { + /// The inner alloy consensus header + #[serde(flatten)] + pub inner: Header, +} + +impl ScrollHeader { + /// Create a new ScrollHeader from an Header + pub fn new(header: Header) -> Self { + Self { inner: header } + } + + /// Create a header for hashing by manually encoding all fields except extra_data + fn encode_for_hashing(&self) -> Vec { + let mut out = Vec::new(); + + // Manually encode each field in the correct order + self.inner.parent_hash.encode(&mut out); + self.inner.ommers_hash.encode(&mut out); + self.inner.beneficiary.encode(&mut out); + self.inner.state_root.encode(&mut out); + self.inner.transactions_root.encode(&mut out); + self.inner.receipts_root.encode(&mut out); + self.inner.logs_bloom.encode(&mut out); + self.inner.difficulty.encode(&mut out); + self.inner.number.encode(&mut out); + self.inner.gas_limit.encode(&mut out); + self.inner.gas_used.encode(&mut out); + self.inner.timestamp.encode(&mut out); + // Note: extra_data is intentionally excluded + self.inner.mix_hash.encode(&mut out); + self.inner.nonce.encode(&mut out); + + // Encode optional fields if present + if let Some(base_fee) = self.inner.base_fee_per_gas { + base_fee.encode(&mut out); + } + if let Some(withdrawals_root) = self.inner.withdrawals_root { + withdrawals_root.encode(&mut out); + } + if let Some(blob_gas_used) = self.inner.blob_gas_used { + blob_gas_used.encode(&mut out); + } + if let Some(excess_blob_gas) = self.inner.excess_blob_gas { + excess_blob_gas.encode(&mut out); + } + if let Some(parent_beacon_block_root) = self.inner.parent_beacon_block_root { + parent_beacon_block_root.encode(&mut out); + } + if let Some(requests_hash) = self.inner.requests_hash { + requests_hash.encode(&mut out); + } + + out + } +} + +impl From
for ScrollHeader { + fn from(header: Header) -> Self { + Self::new(header) + } +} + +impl From for Header { + fn from(header: ScrollHeader) -> Self { + header.inner + } +} + +impl AsRef
for ScrollHeader { + fn as_ref(&self) -> &Header { + &self.inner + } +} + +impl AsMut
for ScrollHeader { + fn as_mut(&mut self) -> &mut Header { + &mut self.inner + } +} + +impl Deref for ScrollHeader { + type Target = Header; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for ScrollHeader { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Sealable for ScrollHeader { + /// Hash the header excluding the extra_data field + fn hash_slow(&self) -> B256 { + let encoded = self.encode_for_hashing(); + keccak256(&encoded) + } +} + +impl alloy_consensus::BlockHeader for ScrollHeader { + fn parent_hash(&self) -> B256 { + self.inner.parent_hash() + } + + fn ommers_hash(&self) -> B256 { + self.inner.ommers_hash() + } + + fn beneficiary(&self) -> Address { + self.inner.beneficiary() + } + + fn state_root(&self) -> B256 { + self.inner.state_root() + } + + fn transactions_root(&self) -> B256 { + self.inner.transactions_root() + } + + fn receipts_root(&self) -> B256 { + self.inner.receipts_root() + } + + fn withdrawals_root(&self) -> Option { + self.inner.withdrawals_root() + } + + fn logs_bloom(&self) -> Bloom { + self.inner.logs_bloom() + } + + fn difficulty(&self) -> U256 { + self.inner.difficulty() + } + + fn number(&self) -> BlockNumber { + self.inner.number() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_used(&self) -> u64 { + self.inner.gas_used() + } + + fn timestamp(&self) -> u64 { + self.inner.timestamp() + } + + fn mix_hash(&self) -> Option { + self.inner.mix_hash() + } + + fn nonce(&self) -> Option { + self.inner.nonce() + } + + fn base_fee_per_gas(&self) -> Option { + self.inner.base_fee_per_gas() + } + + fn blob_gas_used(&self) -> Option { + self.inner.blob_gas_used() + } + + fn excess_blob_gas(&self) -> Option { + self.inner.excess_blob_gas() + } + + fn parent_beacon_block_root(&self) -> Option { + self.inner.parent_beacon_block_root() + } + + fn requests_hash(&self) -> Option { + self.inner.requests_hash() + } + + fn extra_data(&self) -> &Bytes { + self.inner.extra_data() + } +} + +// Additional trait implementations required by reth_primitives_traits::BlockHeader +impl reth_primitives_traits::block::header::BlockHeaderMut for ScrollHeader { + fn extra_data_mut(&mut self) -> &mut Bytes { + &mut self.inner.extra_data + } +} + +impl reth_primitives_traits::InMemorySize for ScrollHeader { + fn size(&self) -> usize { + self.inner.size() + } +} + +impl AsRef for ScrollHeader { + fn as_ref(&self) -> &Self { + self + } +} + +// Implement reth_primitives_traits::BlockHeader +impl reth_primitives_traits::BlockHeader for ScrollHeader {} + + + +// Implement Compact manually (like alloy_consensus::Header does) +#[cfg(feature = "reth-codec")] +impl reth_codecs::Compact for ScrollHeader { + fn to_compact(&self, buf: &mut B) -> usize + where + B: bytes::BufMut + AsMut<[u8]>, + { + // Delegate to the inner Header's Compact implementation + self.inner.to_compact(buf) + } + + fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { + let (header, remaining) = alloy_consensus::Header::from_compact(buf, len); + (Self::new(header), remaining) + } +} + +// Implement SerdeBincodeCompat for ScrollHeader +#[cfg(feature = "serde-bincode-compat")] +impl reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for ScrollHeader { + type BincodeRepr<'a> = alloy_consensus::serde_bincode_compat::Header<'a>; + + fn as_repr(&self) -> Self::BincodeRepr<'_> { + (&self.inner).into() + } + + fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { + Self::new(repr.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::Bytes; + + #[test] + fn test_hash_excludes_extra_data() { + // Create two identical headers with different extra_data + let mut header1 = Header::default(); + let mut header2 = Header::default(); + + header1.extra_data = Bytes::from("test_data_1"); + header2.extra_data = Bytes::from("test_data_2"); + + let scroll_header1 = ScrollHeader::new(header1.clone()); + let scroll_header2 = ScrollHeader::new(header2.clone()); + + // The hashes should be the same since extra_data is excluded + assert_eq!(scroll_header1.hash_slow(), scroll_header2.hash_slow()); + + // But the original alloy headers would have different hashes + assert_ne!(header1.hash_slow(), header2.hash_slow()); + } + + #[test] + fn test_different_fields_produce_different_hashes() { + let mut header1 = Header::default(); + let mut header2 = Header::default(); + + header1.number = 1; + header2.number = 2; + + let scroll_header1 = ScrollHeader::new(header1); + let scroll_header2 = ScrollHeader::new(header2); + + // Different numbers should produce different hashes + assert_ne!(scroll_header1.hash_slow(), scroll_header2.hash_slow()); + } +} + +// Implement HeaderResponse trait for ScrollHeader +impl alloy_network::primitives::HeaderResponse for ScrollHeader { + fn hash(&self) -> alloy_primitives::BlockHash { + self.hash_slow() + } +} + diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs index 232cfc9a79f..eabf5145f26 100644 --- a/crates/scroll/primitives/src/lib.rs +++ b/crates/scroll/primitives/src/lib.rs @@ -14,13 +14,16 @@ use once_cell as _; pub mod transaction; pub use transaction::{tx_type::ScrollTxType, ScrollTransactionSigned}; +pub mod header; +pub use header::ScrollHeader; + use reth_primitives_traits::Block; mod receipt; pub use receipt::ScrollReceipt; /// Scroll-specific block type. -pub type ScrollBlock = alloy_consensus::Block; +pub type ScrollBlock = alloy_consensus::Block; /// Scroll-specific block body type. pub type ScrollBlockBody = ::Body; @@ -32,7 +35,7 @@ pub struct ScrollPrimitives; #[cfg(feature = "serde-bincode-compat")] impl reth_primitives_traits::NodePrimitives for ScrollPrimitives { type Block = ScrollBlock; - type BlockHeader = alloy_consensus::Header; + type BlockHeader = ScrollHeader; type BlockBody = ScrollBlockBody; type SignedTx = ScrollTransactionSigned; type Receipt = ScrollReceipt; diff --git a/crates/scroll/rpc/src/eth/mod.rs b/crates/scroll/rpc/src/eth/mod.rs index 8084177efa2..d0a5089ca32 100644 --- a/crates/scroll/rpc/src/eth/mod.rs +++ b/crates/scroll/rpc/src/eth/mod.rs @@ -28,6 +28,19 @@ use reth_tasks::{ use scroll_alloy_network::Scroll; use std::{fmt, marker::PhantomData, sync::Arc}; +/// Custom header converter for Scroll headers +#[derive(Debug, Clone, Default)] +pub struct ScrollHeaderConverter; + +impl reth_rpc_convert::transaction::HeaderConverter for ScrollHeaderConverter { + fn convert_header(&self, header: reth_primitives_traits::SealedHeader, block_size: usize) -> alloy_rpc_types_eth::Header { + // Convert the sealed ScrollHeader to a sealed alloy_consensus::Header + let inner_sealed = reth_primitives_traits::SealedHeader::new_unhashed(header.inner.clone()); + // Use the default conversion for alloy_consensus::Header + reth_rpc_convert::transaction::FromConsensusHeader::::from_consensus_header(inner_sealed, block_size) + } +} + mod block; mod call; mod fee; @@ -300,7 +313,7 @@ pub type ScrollRpcConvert = RpcConverter< NetworkT, ::Evm, ScrollReceiptConverter, - (), + ScrollHeaderConverter, ScrollTxInfoMapper<::Provider>, >; @@ -391,6 +404,7 @@ where .. } = self; let rpc_converter = RpcConverter::new(ScrollReceiptConverter::default()) + .with_header_converter(ScrollHeaderConverter::default()) .with_mapper(ScrollTxInfoMapper::new(ctx.components.provider().clone())); let eth_api = ctx.eth_api_builder().with_rpc_converter(rpc_converter).build_inner();