From a6b6ec8a23643220ef84b508a3c1b9d700a4a018 Mon Sep 17 00:00:00 2001 From: frisitano Date: Thu, 27 Mar 2025 18:36:10 +0700 Subject: [PATCH 1/7] feat: init payload builder --- Cargo.lock | 28 + crates/e2e-test-utils/src/transaction.rs | 11 + crates/scroll/chainspec/src/lib.rs | 7 + .../src/payload/attributes.rs | 7 + crates/scroll/node/Cargo.toml | 43 ++ crates/scroll/node/src/builder/payload.rs | 133 +++-- crates/scroll/node/src/lib.rs | 6 + crates/scroll/node/src/node.rs | 8 +- crates/scroll/node/src/test_utils.rs | 111 ++++ crates/scroll/node/tests/assets/genesis.json | 1 + crates/scroll/node/tests/e2e/main.rs | 4 + crates/scroll/node/tests/e2e/payload.rs | 23 + crates/scroll/payload/Cargo.toml | 58 +- crates/scroll/payload/src/builder.rs | 522 +++++++++++++++++- crates/scroll/payload/src/error.rs | 11 + crates/scroll/payload/src/lib.rs | 12 +- crates/scroll/payload/src/traits.rs | 29 + 17 files changed, 960 insertions(+), 54 deletions(-) create mode 100644 crates/scroll/node/src/test_utils.rs create mode 100644 crates/scroll/node/tests/assets/genesis.json create mode 100644 crates/scroll/node/tests/e2e/main.rs create mode 100644 crates/scroll/node/tests/e2e/payload.rs create mode 100644 crates/scroll/payload/src/error.rs create mode 100644 crates/scroll/payload/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index db1542dfd356..9e1bc4d9c7f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9835,29 +9835,39 @@ version = "1.3.4" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-genesis", + "alloy-network", "alloy-primitives", "alloy-rpc-types-engine", "eyre", + "futures", "reth-basic-payload-builder", "reth-chainspec", + "reth-db", + "reth-e2e-test-utils", "reth-eth-wire-types", "reth-evm", "reth-network", "reth-node-api", "reth-node-builder", + "reth-node-core", "reth-node-types", "reth-payload-builder", "reth-primitives", "reth-primitives-traits", "reth-provider", + "reth-revm", "reth-rpc-eth-types", + "reth-rpc-server-types", "reth-scroll-chainspec", "reth-scroll-consensus", "reth-scroll-engine-primitives", "reth-scroll-evm", + "reth-scroll-node", "reth-scroll-payload", "reth-scroll-primitives", "reth-scroll-rpc", + "reth-tasks", "reth-tracing", "reth-transaction-pool", "reth-trie-db", @@ -9866,6 +9876,7 @@ dependencies = [ "scroll-alloy-evm", "scroll-alloy-hardforks", "scroll-alloy-rpc-types-engine", + "serde_json", "thiserror 2.0.12", "tokio", ] @@ -9874,14 +9885,31 @@ dependencies = [ name = "reth-scroll-payload" version = "1.3.4" dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", "futures-util", "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-evm", + "reth-execution-types", "reth-payload-builder", "reth-payload-primitives", "reth-payload-util", "reth-primitives-traits", + "reth-revm", + "reth-scroll-chainspec", "reth-scroll-engine-primitives", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-storage-api", "reth-transaction-pool", + "revm", + "scroll-alloy-consensus", + "scroll-alloy-hardforks", + "thiserror 2.0.12", + "tracing", ] [[package]] diff --git a/crates/e2e-test-utils/src/transaction.rs b/crates/e2e-test-utils/src/transaction.rs index c0a5155cb708..8e0dca9743a0 100644 --- a/crates/e2e-test-utils/src/transaction.rs +++ b/crates/e2e-test-utils/src/transaction.rs @@ -26,6 +26,17 @@ impl TransactionTestContext { signed.encoded_2718().into() } + /// Crates a transfer with a nonce and signs it, returning bytes. + pub async fn transfer_tx_nonce_bytes( + chain_id: u64, + wallet: PrivateKeySigner, + nonce: u64, + ) -> Bytes { + let tx = tx(chain_id, 21000, None, None, nonce); + let signed = Self::sign_tx(wallet, tx).await; + signed.encoded_2718().into() + } + /// Creates a deployment transaction and signs it, returning an envelope. pub async fn deploy_tx( chain_id: u64, diff --git a/crates/scroll/chainspec/src/lib.rs b/crates/scroll/chainspec/src/lib.rs index c80fa51943b5..71dbe128f211 100644 --- a/crates/scroll/chainspec/src/lib.rs +++ b/crates/scroll/chainspec/src/lib.rs @@ -338,6 +338,13 @@ impl ScrollHardforks for ScrollChainSpec { } } +impl From for ScrollChainSpec { + fn from(value: ChainSpec) -> Self { + let genesis = value.genesis; + genesis.into() + } +} + impl From for ScrollChainSpec { fn from(genesis: Genesis) -> Self { let scroll_chain_info = ScrollConfigInfo::extract_from(&genesis); diff --git a/crates/scroll/engine-primitives/src/payload/attributes.rs b/crates/scroll/engine-primitives/src/payload/attributes.rs index dc1feb2855cc..593516eda790 100644 --- a/crates/scroll/engine-primitives/src/payload/attributes.rs +++ b/crates/scroll/engine-primitives/src/payload/attributes.rs @@ -1,6 +1,7 @@ //! Payload related types use alloc::vec::Vec; +use std::fmt::Debug; use alloy_eips::{eip2718::Decodable2718, eip4895::Withdrawals}; use alloy_primitives::{keccak256, Address, B256}; @@ -138,6 +139,12 @@ pub(crate) fn payload_id_scroll( PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length")) } +impl From for ScrollPayloadBuilderAttributes { + fn from(value: EthPayloadBuilderAttributes) -> Self { + Self { payload_attributes: value, ..Default::default() } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/scroll/node/Cargo.toml b/crates/scroll/node/Cargo.toml index 089d4f282176..424f64b3ccae 100644 --- a/crates/scroll/node/Cargo.toml +++ b/crates/scroll/node/Cargo.toml @@ -18,14 +18,18 @@ reth-chainspec.workspace = true reth-rpc-eth-types.workspace = true reth-eth-wire-types.workspace = true reth-evm = { workspace = true, features = ["scroll-alloy-traits"] } +reth-e2e-test-utils = { workspace = true, optional = true } reth-network.workspace = true reth-node-api.workspace = true +reth-node-core = { workspace = true, optional = true } reth-node-types.workspace = true reth-node-builder.workspace = true reth-payload-builder.workspace = true reth-primitives = { workspace = true, features = ["c-kzg"] } reth-primitives-traits.workspace = true reth-provider.workspace = true +reth-rpc-server-types = { workspace = true, optional = true } +reth-tasks = { workspace = true, optional = true} reth-tracing.workspace = true reth-transaction-pool.workspace = true reth-trie-db.workspace = true @@ -53,12 +57,29 @@ scroll-alloy-rpc-types-engine.workspace = true # alloy alloy-primitives.workspace = true +alloy-genesis = { workspace = true, optional = true } # misc eyre.workspace = true +serde_json = { workspace = true, optional = true } thiserror.workspace = true tokio.workspace = true +[dev-dependencies] +reth-scroll-node = { workspace = true, features = ["test-utils"] } +reth-db.workspace = true +reth-node-core.workspace = true +reth-node-builder = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-revm = { workspace = true, features = ["test-utils"] } +reth-tasks.workspace = true + +alloy-primitives.workspace = true +scroll-alloy-consensus.workspace = true +alloy-network.workspace = true +alloy-consensus.workspace = true +futures.workspace = true + [features] default = ["reth-codec"] reth-codec = [ @@ -70,3 +91,25 @@ skip-state-root-validation = [ "reth-provider/skip-state-root-validation", "reth-node-builder/skip-state-root-validation", ] +test-utils = [ + "dep:alloy-genesis", + "dep:reth-e2e-test-utils", + "dep:reth-node-core", + "dep:reth-rpc-server-types", + "dep:reth-tasks", + "dep:serde_json", + "reth-chainspec/test-utils", + "reth-evm/test-utils", + "reth-network/test-utils", + "reth-node-builder/test-utils", + "reth-payload-builder/test-utils", + "reth-primitives/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-scroll-payload/test-utils", + "reth-transaction-pool/test-utils", + "reth-trie-db/test-utils", + "reth-db/test-utils", + "reth-revm/test-utils", + "reth-scroll-node/test-utils" +] diff --git a/crates/scroll/node/src/builder/payload.rs b/crates/scroll/node/src/builder/payload.rs index 6a50b1e9aa7e..0344115f09ce 100644 --- a/crates/scroll/node/src/builder/payload.rs +++ b/crates/scroll/node/src/builder/payload.rs @@ -1,12 +1,11 @@ -use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; -use reth_node_builder::{components::PayloadServiceBuilder, BuilderContext, FullNodeTypes}; +use reth_node_api::{ConfigureEvm, PrimitivesTy}; +use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext, FullNodeTypes}; use reth_node_types::{NodeTypesWithEngine, TxTy}; -use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; -use reth_provider::CanonStateSubscriptions; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_engine_primitives::ScrollEngineTypes; +use reth_scroll_evm::ScrollEvmConfig; use reth_scroll_payload::ScrollPayloadTransactions; -use reth_scroll_primitives::ScrollPrimitives; +use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; /// Payload builder for Scroll. @@ -16,44 +15,106 @@ pub struct ScrollPayloadBuilder { pub best_transactions: Txs, } -impl PayloadServiceBuilder for ScrollPayloadBuilder +impl ScrollPayloadBuilder { + /// A helper method to initialize [`reth_optimism_payload_builder::OpPayloadBuilder`] with the + /// given EVM config. + pub fn build( + self, + evm_config: Evm, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result> + where + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = ScrollEngineTypes, + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, + Evm: ConfigureEvm>, + Txs: ScrollPayloadTransactions, + { + let payload_builder = reth_scroll_payload::ScrollPayloadBuilder::new( + pool, + evm_config, + ctx.provider().clone(), + ) + .with_transactions(self.best_transactions); + + Ok(payload_builder) + } +} + +// impl PayloadServiceBuilder for ScrollPayloadBuilder +// where +// Node: FullNodeTypes, +// Node::Types: NodeTypesWithEngine< +// Primitives = ScrollPrimitives, +// Engine = ScrollEngineTypes, +// ChainSpec = ScrollChainSpec, +// >, +// Pool: TransactionPool>> +// + Unpin +// + 'static, +// Txs: ScrollPayloadTransactions, +// { +// async fn spawn_payload_builder_service( +// self, +// ctx: &BuilderContext, +// _pool: Pool, +// ) -> eyre::Result::Engine>> { +// let payload_builder = reth_scroll_payload::ScrollPayloadBuilder::default(); + +// let conf = ctx.config().builder.clone(); + +// let payload_job_config = BasicPayloadJobGeneratorConfig::default() +// .interval(conf.interval) +// .deadline(conf.deadline) +// .max_payload_tasks(conf.max_payload_tasks); + +// let payload_generator = BasicPayloadJobGenerator::with_builder( +// ctx.provider().clone(), +// ctx.task_executor().clone(), +// payload_job_config, +// payload_builder, +// ); +// let (payload_service, payload_service_handle) = +// PayloadBuilderService::new(payload_generator, +// ctx.provider().canonical_state_stream()); + +// ctx.task_executor().spawn_critical("payload builder service", Box::pin(payload_service)); + +// Ok(payload_service_handle) +// } +// } + +impl PayloadBuilderBuilder for ScrollPayloadBuilder where - Node: FullNodeTypes, - Node::Types: NodeTypesWithEngine< - Primitives = ScrollPrimitives, - Engine = ScrollEngineTypes, - ChainSpec = ScrollChainSpec, + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = ScrollEngineTypes, + ChainSpec = ScrollChainSpec, + Primitives = ScrollPrimitives, + >, >, - Pool: TransactionPool>> + Pool: TransactionPool> + Unpin + 'static, Txs: ScrollPayloadTransactions, + ::Transaction: PoolTransaction, { - async fn spawn_payload_builder_service( + type PayloadBuilder = + reth_scroll_payload::ScrollPayloadBuilder; + + async fn build_payload_builder( self, ctx: &BuilderContext, - _pool: Pool, - ) -> eyre::Result::Engine>> { - let payload_builder = reth_scroll_payload::ScrollEmptyPayloadBuilder::default(); - - let conf = ctx.config().builder.clone(); - - let payload_job_config = BasicPayloadJobGeneratorConfig::default() - .interval(conf.interval) - .deadline(conf.deadline) - .max_payload_tasks(conf.max_payload_tasks); - - let payload_generator = BasicPayloadJobGenerator::with_builder( - ctx.provider().clone(), - ctx.task_executor().clone(), - payload_job_config, - payload_builder, - ); - let (payload_service, payload_service_handle) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor().spawn_critical("payload builder service", Box::pin(payload_service)); - - Ok(payload_service_handle) + pool: Pool, + ) -> eyre::Result { + self.build(ScrollEvmConfig::scroll(ctx.chain_spec()), ctx, pool) } } diff --git a/crates/scroll/node/src/lib.rs b/crates/scroll/node/src/lib.rs index f9156681d1e6..3e791bb4eb66 100644 --- a/crates/scroll/node/src/lib.rs +++ b/crates/scroll/node/src/lib.rs @@ -20,3 +20,9 @@ pub use pool::{ScrollNoopTransactionPool, ScrollPooledTransaction}; mod storage; pub use storage::ScrollStorage; + +/// Helpers for running test node instances. +#[cfg(feature = "test-utils")] +pub mod test_utils; + +pub use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; diff --git a/crates/scroll/node/src/node.rs b/crates/scroll/node/src/node.rs index 30060b317cfd..1d9e2eddfcb1 100644 --- a/crates/scroll/node/src/node.rs +++ b/crates/scroll/node/src/node.rs @@ -5,7 +5,7 @@ use crate::{ ScrollPayloadBuilder, ScrollPoolBuilder, ScrollStorage, }; use reth_node_builder::{ - components::ComponentsBuilder, + components::{BasicPayloadServiceBuilder, ComponentsBuilder}, node::{FullNodeTypes, NodeTypes, NodeTypesWithEngine}, Node, NodeAdapter, NodeComponentsBuilder, }; @@ -23,7 +23,7 @@ impl ScrollNode { pub fn components() -> ComponentsBuilder< Node, ScrollPoolBuilder, - ScrollPayloadBuilder, + BasicPayloadServiceBuilder, ScrollNetworkBuilder, ScrollExecutorBuilder, ScrollConsensusBuilder, @@ -40,7 +40,7 @@ impl ScrollNode { ComponentsBuilder::default() .node_types::() .pool(ScrollPoolBuilder) - .payload(ScrollPayloadBuilder::default()) + .payload(BasicPayloadServiceBuilder::new(ScrollPayloadBuilder::default())) .network(ScrollNetworkBuilder) .executor(ScrollExecutorBuilder) .consensus(ScrollConsensusBuilder) @@ -54,7 +54,7 @@ where type ComponentsBuilder = ComponentsBuilder< N, ScrollPoolBuilder, - ScrollPayloadBuilder, + BasicPayloadServiceBuilder, ScrollNetworkBuilder, ScrollExecutorBuilder, ScrollConsensusBuilder, diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs new file mode 100644 index 000000000000..fff07717fb04 --- /dev/null +++ b/crates/scroll/node/src/test_utils.rs @@ -0,0 +1,111 @@ +use crate::{ScrollBuiltPayload, ScrollNode as OtherScrollNode, ScrollPayloadBuilderAttributes}; +use alloy_genesis::Genesis; +use alloy_primitives::{Address, B256}; +use alloy_rpc_types_engine::PayloadAttributes; +use reth_e2e_test_utils::{ + node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, NodeBuilderHelper, + NodeHelperType, TmpDB, +}; +use reth_node_api::NodeTypesWithDBAdapter; +use reth_node_builder::{Node, NodeBuilder, NodeConfig, NodeHandle}; +use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}; +use reth_payload_builder::EthPayloadBuilderAttributes; +use reth_provider::providers::BlockchainProvider; +use reth_rpc_server_types::RpcModuleSelection; +use reth_scroll_chainspec::{ScrollChainSpec, ScrollChainSpecBuilder}; +use reth_tasks::TaskManager; +use reth_transaction_pool::PeerId; +use std::sync::Arc; +use tokio::sync::Mutex; + +/// Optimism Node Helper type +pub(crate) type ScrollNode = NodeHelperType< + OtherScrollNode, + BlockchainProvider>, +>; + +pub async fn setup_engine( + chain_spec: Arc, + is_dev: bool, +) -> eyre::Result<(ScrollNode, TaskManager, PeerId)> { + // Create a [`TaskManager`] to manage the tasks. + let tasks = TaskManager::current(); + let exec = tasks.executor(); + + // Define the network configuration with discovery disabled. + let network_config = NetworkArgs { + discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, + ..NetworkArgs::default() + }; + + // Create the node config + let node_config = NodeConfig::new(chain_spec.clone()) + .with_network(network_config.clone()) + .with_rpc(RpcServerArgs::default().with_http().with_http_api(RpcModuleSelection::All)) + .set_dev(is_dev); + + // Create the node for a bridge node that will bridge messages from the eth-wire protocol + // to the scroll-wire protocol. + let node = OtherScrollNode; + let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config.clone()) + .testing_node(exec.clone()) + .with_types_and_provider::>() + .with_components(node.components_builder()) + .with_add_ons(node.add_ons()) + .launch() + .await?; + let peer_id = *node.network.peer_id(); + let node = NodeTestContext::new(node, scroll_payload_attributes).await?; + + Ok((node, tasks, peer_id)) +} + +/// Creates the initial setup with `num_nodes` of the node config, started and connected. +pub async fn setup(is_dev: bool) -> eyre::Result<(ScrollNode, TaskManager, Wallet)> { + let genesis: Genesis = + serde_json::from_str(include_str!("../tests/assets/genesis.json")).unwrap(); + let chain_spec = + ScrollChainSpecBuilder::scroll_mainnet().genesis(genesis).build(Default::default()); + let chain_id = chain_spec.chain().into(); + let (node, task_manager, _peer_id) = setup_engine(Arc::new(chain_spec), is_dev).await.unwrap(); + Ok((node, task_manager, Wallet::default().with_chain_id(chain_id))) +} + +/// Advance the chain with sequential payloads returning them in the end. +pub async fn advance_chain( + length: usize, + node: &mut ScrollNode, + wallet: Arc>, +) -> eyre::Result> { + node.advance(length as u64, |_| { + let wallet = wallet.clone(); + Box::pin(async move { + let mut wallet = wallet.lock().await; + let tx_fut = TransactionTestContext::transfer_tx_nonce_bytes( + wallet.chain_id, + wallet.inner.clone(), + wallet.inner_nonce, + ); + wallet.inner_nonce += 1; + tx_fut.await + }) + }) + .await +} + +/// Helper function to create a new eth payload attributes +pub fn scroll_payload_attributes(timestamp: u64) -> ScrollPayloadBuilderAttributes { + let attributes = PayloadAttributes { + timestamp, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + }; + + ScrollPayloadBuilderAttributes { + payload_attributes: EthPayloadBuilderAttributes::new(B256::ZERO, attributes), + transactions: vec![], + no_tx_pool: false, + } +} diff --git a/crates/scroll/node/tests/assets/genesis.json b/crates/scroll/node/tests/assets/genesis.json new file mode 100644 index 000000000000..e59d90f4ff10 --- /dev/null +++ b/crates/scroll/node/tests/assets/genesis.json @@ -0,0 +1 @@ +{"config":{"chainId":8453,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"berlinBlock":0,"londonBlock":0,"arrowGlacierBlock":0,"grayGlacierBlock":0,"mergeNetsplitBlock":0,"bedrockBlock":0,"regolithTime":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"optimism":{"eip1559Elasticity":6,"eip1559Denominator":50}},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x1c9c380","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"balance":"0xd3c21bcecceda1000000"},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"balance":"0xd3c21bcecceda1000000"},"0x1cbd3b2770909d4e10f157cabc84c7264073c9ec":{"balance":"0xd3c21bcecceda1000000"},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"balance":"0xd3c21bcecceda1000000"},"0x2546bcd3c84621e976d8185a91a922ae77ecec30":{"balance":"0xd3c21bcecceda1000000"},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"balance":"0xd3c21bcecceda1000000"},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"balance":"0xd3c21bcecceda1000000"},"0x71be63f3384f5fb98995898a86b02fb2426c5788":{"balance":"0xd3c21bcecceda1000000"},"0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199":{"balance":"0xd3c21bcecceda1000000"},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"balance":"0xd3c21bcecceda1000000"},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"balance":"0xd3c21bcecceda1000000"},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"balance":"0xd3c21bcecceda1000000"},"0x9c41de96b2088cdc640c6182dfcf5491dc574a57":{"balance":"0xd3c21bcecceda1000000"},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"balance":"0xd3c21bcecceda1000000"},"0xbcd4042de499d14e55001ccbb24a551f3b954096":{"balance":"0xd3c21bcecceda1000000"},"0xbda5747bfd65f08deb54cb465eb87d40e51b197e":{"balance":"0xd3c21bcecceda1000000"},"0xcd3b766ccdd6ae721141f452c550ca635964ce71":{"balance":"0xd3c21bcecceda1000000"},"0xdd2fd4581271e230360230f9337d5c0430bf44c0":{"balance":"0xd3c21bcecceda1000000"},"0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097":{"balance":"0xd3c21bcecceda1000000"},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"balance":"0xd3c21bcecceda1000000"},"0xfabb0ac9d68b0b445fb7357272ff202c5651694a":{"balance":"0xd3c21bcecceda1000000"}},"number":"0x0"} \ No newline at end of file diff --git a/crates/scroll/node/tests/e2e/main.rs b/crates/scroll/node/tests/e2e/main.rs new file mode 100644 index 000000000000..29e39429c23d --- /dev/null +++ b/crates/scroll/node/tests/e2e/main.rs @@ -0,0 +1,4 @@ +#[allow(missing_docs)] +mod payload; + +const fn main() {} diff --git a/crates/scroll/node/tests/e2e/payload.rs b/crates/scroll/node/tests/e2e/payload.rs new file mode 100644 index 000000000000..3ce333c28f99 --- /dev/null +++ b/crates/scroll/node/tests/e2e/payload.rs @@ -0,0 +1,23 @@ +use futures::StreamExt; +use reth_scroll_node::test_utils::{advance_chain, setup}; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[tokio::test] +async fn can_sync() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (node, _tasks, wallet) = setup(true).await?; + let wallet = Arc::new(Mutex::new(wallet)); + + let tip: usize = 90; + let tip_index: usize = tip - 1; + let reorg_depth = 2; + + // On first node, create a chain up to block number 90a + let canonical_payload_chain = advance_chain(tip, &mut node, wallet.clone()).await?; + let canonical_chain = + canonical_payload_chain.iter().map(|p| p.block().hash()).collect::>(); + + Ok(()) +} diff --git a/crates/scroll/payload/Cargo.toml b/crates/scroll/payload/Cargo.toml index 0e871f5fea39..e5d5caa364f1 100644 --- a/crates/scroll/payload/Cargo.toml +++ b/crates/scroll/payload/Cargo.toml @@ -12,29 +12,69 @@ exclude.workspace = true workspace = true [dependencies] +#  alloy +alloy-rlp.workspace = true +alloy-consensus.workspace = true +alloy-primitives.workspace = true + +# scroll-alloy +scroll-alloy-hardforks.workspace = true +scroll-alloy-consensus.workspace = true + +#  revm +revm.workspace = true + # reth reth-basic-payload-builder.workspace = true +reth-chainspec.workspace = true +reth-chain-state.workspace = true +reth-evm.workspace = true +reth-execution-types.workspace = true reth-payload-builder = { workspace = true, optional = true } reth-payload-primitives.workspace = true reth-primitives-traits.workspace = true +reth-revm.workspace = true +reth-scroll-chainspec.workspace = true +reth-scroll-forks.workspace = true +reth-storage-api.workspace = true reth-transaction-pool.workspace = true reth-payload-util.workspace = true # scroll +reth-scroll-primitives.workspace = true reth-scroll-engine-primitives.workspace = true futures-util = { workspace = true, optional = true } +# misc +thiserror.workspace = true +tracing.workspace = true + [features] std = [ - "futures-util/std", - "reth-payload-primitives/std", - "reth-primitives-traits/std", - "reth-scroll-engine-primitives/std", + "futures-util/std", + "reth-payload-primitives/std", + "reth-primitives-traits/std", + "reth-scroll-engine-primitives/std", + "alloy-consensus/std", + "alloy-primitives/std", + "alloy-rlp/std", + "reth-chainspec/std", + "reth-evm/std", + "reth-execution-types/std", + "reth-revm/std", + "reth-storage-api/std", + "revm/std", + "thiserror/std", + "tracing/std" ] test-utils = [ - "dep:futures-util", - "dep:reth-payload-builder", - "reth-payload-builder/test-utils", - "reth-primitives-traits/test-utils", - "reth-transaction-pool/test-utils", + "dep:futures-util", + "dep:reth-payload-builder", + "reth-payload-builder/test-utils", + "reth-primitives-traits/test-utils", + "reth-transaction-pool/test-utils", + "reth-chain-state/test-utils", + "reth-chainspec/test-utils", + "reth-evm/test-utils", + "reth-revm/test-utils" ] diff --git a/crates/scroll/payload/src/builder.rs b/crates/scroll/payload/src/builder.rs index 57ec6ea45be6..2ce6da261a08 100644 --- a/crates/scroll/payload/src/builder.rs +++ b/crates/scroll/payload/src/builder.rs @@ -1,11 +1,36 @@ +//! Scroll's payload builder implementation. + +use super::{traits::ScrollPayloadPrimitives, ScrollPayloadBuilderError}; +use alloy_consensus::{Transaction, Typed2718}; +use alloy_primitives::{B256, U256}; +use alloy_rlp::Encodable; use core::fmt::Debug; use reth_basic_payload_builder::{ - BuildArguments, BuildOutcome, HeaderForPayload, PayloadBuilder, PayloadConfig, + is_better_payload, BuildArguments, BuildOutcome, BuildOutcomeKind, HeaderForPayload, + MissingPayloadBehaviour, PayloadBuilder, PayloadConfig, +}; +use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth_evm::{ + block::{BlockExecutionError, BlockValidationError}, + execute::{BlockBuilder, BlockBuilderOutcome, ProviderError}, + ConfigureEvm, Database, Evm, NextBlockEnvAttributes, }; -use reth_payload_primitives::PayloadBuilderError; -use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; +use reth_execution_types::ExecutionOutcome; +use reth_payload_builder::PayloadId; +use reth_payload_primitives::{PayloadBuilderAttributes, PayloadBuilderError}; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; +use reth_primitives_traits::{SealedHeader, SignedTransaction, TxTy}; +use reth_revm::{cancelled::CancelOnDrop, database::StateProviderDatabase, db::State}; use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; +use reth_scroll_primitives::{ScrollPrimitives, ScrollReceipt, ScrollTransactionSigned}; +use reth_storage_api::{StateProvider, StateProviderFactory}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; +use revm::context::{Block, BlockEnv}; +use scroll_alloy_hardforks::ScrollHardforks; +use std::{boxed::Box, sync::Arc, vec, vec::Vec}; + +const SCROLL_GAS_LIMIT_10M: u64 = 10_000_000; /// A type that implements [`PayloadBuilder`] by building empty payloads. #[derive(Debug, Default, Clone)] @@ -52,3 +77,494 @@ impl ScrollPayloadTransactions for () { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } } + +/// Scroll's payload builder. +#[derive(Debug, Clone)] +pub struct ScrollPayloadBuilder { + /// The type responsible for creating the evm. + pub evm_config: Evm, + /// Transaction pool. + pub pool: Pool, + /// Node client + pub client: Client, + /// The type responsible for yielding the best transactions to include in a payload. + pub best_transactions: Txs, +} + +impl ScrollPayloadBuilder { + /// Creates a new `ScrollPayloadBuilder`. + pub fn new(pool: Pool, evm_config: Evm, client: Client) -> Self { + Self { evm_config, pool, client, best_transactions: () } + } +} + +impl ScrollPayloadBuilder { + /// Configures the type responsible for yielding the transactions that should be included in the + /// payload. + pub fn with_transactions( + self, + best_transactions: T, + ) -> ScrollPayloadBuilder { + let Self { evm_config, pool, client, .. } = self; + ScrollPayloadBuilder { evm_config, pool, client, best_transactions } + } +} + +impl ScrollPayloadBuilder +where + Pool: TransactionPool>, + Client: StateProviderFactory + ChainSpecProvider, + Evm: ConfigureEvm, +{ + /// Constructs an Scroll payload from the transactions sent via the + /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in + /// the payload attributes, the transaction pool will be ignored and the only transactions + /// included in the payload will be those sent through the attributes. + /// + /// Given build arguments including an Scroll client, transaction pool, + /// and configuration, this function creates a transaction payload. Returns + /// a result indicating success with the payload or an error in case of failure. + fn build_payload<'a, Txs>( + &self, + args: BuildArguments, + best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + ) -> Result, PayloadBuilderError> + where + Txs: PayloadTransactions>, + { + let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; + + let ctx = ScrollPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + chain_spec: self.client.chain_spec(), + config, + cancel, + best_payload, + }; + + let builder = ScrollBuilder::new(best); + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(&state_provider); + + if ctx.attributes().no_tx_pool { + builder.build(state, &state_provider, ctx) + } else { + // sequencer mode we can reuse cachedreads from previous runs + builder.build(cached_reads.as_db_mut(state), &state_provider, ctx) + } + .map(|out| out.with_cached_reads(cached_reads)) + } +} + +/// Implementation of the [`PayloadBuilder`] trait for [`ScrollPayloadBuilder`]. +impl PayloadBuilder for ScrollPayloadBuilder +where + Client: + StateProviderFactory + ChainSpecProvider + Clone, + Pool: TransactionPool>, + Evm: ConfigureEvm, + Txs: ScrollPayloadTransactions, +{ + type Attributes = ScrollPayloadBuilderAttributes; + type BuiltPayload = ScrollBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + let pool = self.pool.clone(); + self.build_payload(args, |attrs| self.best_transactions.best_transactions(pool, attrs)) + } + + fn on_missing_payload( + &self, + _args: BuildArguments, + ) -> MissingPayloadBehaviour { + // we want to await the job that's already in progress because that should be returned as + // is, there's no benefit in racing another job + MissingPayloadBehaviour::AwaitInProgress + } + + // NOTE: this should only be used for testing purposes because this doesn't have access to L1 + // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. + fn build_empty_payload( + &self, + config: PayloadConfig, + ) -> Result { + let args = BuildArguments { + config, + cached_reads: Default::default(), + cancel: Default::default(), + best_payload: None, + }; + self.build_payload(args, |_| NoopPayloadTransactions::::default())? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) + } +} + +/// A builder for a new payload. +pub struct ScrollBuilder<'a, Txs> { + /// Yields the best transaction to include if transactions from the mempool are allowed. + best: Box Txs + 'a>, +} + +impl<'a, Txs> ScrollBuilder<'a, Txs> { + /// Creates a new [`ScrollBuilder`]. + pub fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { + Self { best: Box::new(best) } + } +} + +impl<'a, Txs> std::fmt::Debug for ScrollBuilder<'a, Txs> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ScrollBuilder").finish() + } +} + +impl ScrollBuilder<'_, Txs> { + /// Builds the payload on top of the state. + pub fn build( + self, + db: impl Database, + state_provider: impl StateProvider, + ctx: ScrollPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> + where + EvmConfig: + ConfigureEvm, + ChainSpec: EthChainSpec + ScrollHardforks, + Txs: PayloadTransactions>, + { + let Self { best } = self; + tracing::debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); + + let mut db = State::builder().with_database(db).with_bundle_update().build(); + + let mut builder = ctx.block_builder(&mut db)?; + + // 1. apply pre-execution changes + builder.apply_pre_execution_changes().map_err(|err| { + tracing::warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); + PayloadBuilderError::Internal(err.into()) + })?; + + // 2. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(&mut builder)?; + + // 3. if mem pool transactions are requested we execute them + if !ctx.attributes().no_tx_pool { + let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block())); + if ctx.execute_best_transactions(&mut info, &mut builder, best_txs)?.is_some() { + return Ok(BuildOutcomeKind::Cancelled) + } + + // check if the new payload is even more valuable + if !ctx.is_better_payload(info.total_fees) { + // can skip building the block + return Ok(BuildOutcomeKind::Aborted { fees: info.total_fees }) + } + } + + let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, block } = + builder.finish(state_provider)?; + + let sealed_block = Arc::new(block.sealed_block().clone()); + tracing::debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block"); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![execution_result.receipts], + block.number, + Vec::new(), + ); + + // create the executed block data + let executed: ExecutedBlockWithTrieUpdates = + ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { + recovered_block: Arc::new(block), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + trie: Arc::new(trie_updates), + }; + + let no_tx_pool = ctx.attributes().no_tx_pool; + + let payload = ScrollBuiltPayload::new(ctx.payload_id(), executed, info.total_fees); + + if no_tx_pool { + // if `no_tx_pool` is set only transactions from the payload attributes will be included + // in the payload. In other words, the payload is deterministic and we can + // freeze it once we've successfully built it. + Ok(BuildOutcomeKind::Freeze(payload)) + } else { + Ok(BuildOutcomeKind::Better { payload }) + } + } +} + +/// Container type that holds all necessities to build a new payload. +#[derive(Debug)] +pub struct ScrollPayloadBuilderCtx { + /// The type that knows how to perform system calls and configure the evm. + pub evm_config: Evm, + /// The chainspec + pub chain_spec: ChainSpec, + /// How to build the payload. + pub config: PayloadConfig, + /// Marker to check whether the job has been cancelled. + pub cancel: CancelOnDrop, + /// The currently best payload. + pub best_payload: Option, +} + +impl ScrollPayloadBuilderCtx +where + Evm: + ConfigureEvm, + ChainSpec: EthChainSpec + ScrollHardforks, +{ + /// Returns the parent block the payload will be build on. + #[allow(clippy::missing_const_for_fn)] + pub fn parent(&self) -> &SealedHeader { + &self.config.parent_header + } + + /// Returns the builder attributes. + pub const fn attributes(&self) -> &ScrollPayloadBuilderAttributes { + &self.config.attributes + } + + // Returns the extra data for the block. + // + // After holocene this extracts the extra data from the payload + // pub fn extra_data(&self) -> Result { + // if self.is_holocene_active() { + // self.attributes() + // .get_holocene_extra_data( + // self.chain_spec.base_fee_params_at_timestamp( + // self.attributes().payload_attributes.timestamp, + // ), + // ) + // .map_err(PayloadBuilderError::other) + // } else { + // Ok(Default::default()) + // } + // } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self, block_env: &BlockEnv) -> BestTransactionsAttributes { + BestTransactionsAttributes::new( + block_env.basefee, + block_env.blob_gasprice().map(|p| p as u64), + ) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + // Returns true if holocene is active for the payload. + // pub fn is_holocene_active(&self) -> bool { + // self.chain_spec.is_holocene_active_at_timestamp(self.attributes().timestamp()) + // } + + /// Returns true if the fees are higher than the previous payload. + pub fn is_better_payload(&self, total_fees: U256) -> bool { + is_better_payload(self.best_payload.as_ref(), total_fees) + } + + /// Prepares a [`BlockBuilder`] for the next block. + pub fn block_builder<'a, DB: Database>( + &'a self, + db: &'a mut State, + ) -> Result + 'a, PayloadBuilderError> { + self.evm_config + .builder_for_next_block( + db, + self.parent(), + NextBlockEnvAttributes { + timestamp: self.attributes().timestamp(), + suggested_fee_recipient: self.attributes().suggested_fee_recipient(), + prev_randao: self.attributes().prev_randao(), + gas_limit: SCROLL_GAS_LIMIT_10M, + parent_beacon_block_root: self.attributes().parent_beacon_block_root(), + withdrawals: None, + }, + ) + .map_err(PayloadBuilderError::other) + } +} + +impl ScrollPayloadBuilderCtx +where + Evm: ConfigureEvm, + ChainSpec: EthChainSpec + ScrollHardforks, +{ + /// Executes all sequencer transactions that are included in the payload attributes. + pub fn execute_sequencer_transactions( + &self, + builder: &mut impl BlockBuilder, + ) -> Result { + let mut info = ExecutionInfo::new(); + + for sequencer_tx in &self.attributes().transactions { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + ScrollPayloadBuilderError::BlobTransactionRejected, + )) + } + + // Convert the transaction to a [RecoveredTx]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx.value().try_clone_into_recovered().map_err(|_| { + PayloadBuilderError::other(ScrollPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + let gas_used = match builder.execute_transaction(sequencer_tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + tracing::trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used; + } + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + builder: &mut impl BlockBuilder, + mut best_txs: impl PayloadTransactions< + Transaction: PoolTransaction>, + >, + ) -> Result, PayloadBuilderError> { + let block_gas_limit = builder.evm_mut().block().gas_limit; + let base_fee = builder.evm_mut().block().basefee; + + while let Some(tx) = best_txs.next(()) { + let tx = tx.into_consensus(); + if info.is_tx_over_limits(tx.inner(), block_gas_limit) { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.is_l1_message() { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue + } + + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())) + } + + let gas_used = match builder.execute_transaction(tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + if error.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + tracing::trace!(target: "payload_builder", %error, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + tracing::trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + continue + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + info.cumulative_gas_used += gas_used; + info.cumulative_da_bytes_used += tx.length() as u64; + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + } + + Ok(None) + } +} + +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, + /// Withdrawal hash. + pub withdrawals_root: Option, + /// The transaction receipts. + pub receipts: Vec, + /// The block env used during execution. + pub block_env: BlockEnv, +} + +/// This acts as the container for executed transactions and its byproducts (receipts, gas used) +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All gas used so far + pub cumulative_gas_used: u64, + /// Estimated DA size + pub cumulative_da_bytes_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub fn new() -> Self { + Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO } + } + + /// Returns true if the transaction would exceed the block limits: + /// - block gas limit: ensures the transaction still fits into the block. + /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit + /// per tx. + /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the + /// maximum allowed DA limit per block. + pub fn is_tx_over_limits( + &self, + tx: &(impl Encodable + Transaction), + block_gas_limit: u64, + ) -> bool { + self.cumulative_gas_used + tx.gas_limit() > block_gas_limit + } +} diff --git a/crates/scroll/payload/src/error.rs b/crates/scroll/payload/src/error.rs new file mode 100644 index 000000000000..0dbbd321af00 --- /dev/null +++ b/crates/scroll/payload/src/error.rs @@ -0,0 +1,11 @@ +/// Scroll specific payload building errors. +#[derive(Debug, thiserror::Error)] +pub enum ScrollPayloadBuilderError { + /// Thrown when a transaction fails to convert to a + /// [`alloy_consensus::transaction::Recovered`]. + #[error("failed to convert deposit transaction to RecoveredTx")] + TransactionEcRecoverFailed, + /// Thrown when a blob transaction is included in a sequencer's block. + #[error("blob transaction included in sequencer block")] + BlobTransactionRejected, +} diff --git a/crates/scroll/payload/src/lib.rs b/crates/scroll/payload/src/lib.rs index ea5a8ebd67ec..2359a5ddb2cc 100644 --- a/crates/scroll/payload/src/lib.rs +++ b/crates/scroll/payload/src/lib.rs @@ -2,8 +2,16 @@ #![cfg_attr(not(feature = "std"), no_std)] -mod builder; -pub use builder::{ScrollEmptyPayloadBuilder, ScrollPayloadTransactions}; +#[cfg(not(feature = "std"))] +extern crate alloc as std; + +pub mod builder; +pub use builder::{ScrollEmptyPayloadBuilder, ScrollPayloadBuilder, ScrollPayloadTransactions}; + +mod error; +pub use error::ScrollPayloadBuilderError; + +mod traits; #[cfg(feature = "test-utils")] mod test_utils; diff --git a/crates/scroll/payload/src/traits.rs b/crates/scroll/payload/src/traits.rs new file mode 100644 index 000000000000..64b271b43625 --- /dev/null +++ b/crates/scroll/payload/src/traits.rs @@ -0,0 +1,29 @@ +use alloy_consensus::{BlockBody, Header}; +use reth_primitives_traits::{NodePrimitives, Receipt, SignedTransaction}; +use reth_scroll_primitives::transaction::signed::IsL1Message; + +/// Helper trait to encapsulate common bounds on [`NodePrimitives`] for Scroll payload builder. +pub trait ScrollPayloadPrimitives: + NodePrimitives< + Receipt: Receipt, + SignedTx = Self::_TX, + BlockHeader = Header, + BlockBody = BlockBody, +> +{ + /// Helper AT to bound [`NodePrimitives::Block`] type without causing bound cycle. + type _TX: SignedTransaction + IsL1Message; +} + +impl ScrollPayloadPrimitives for T +where + Tx: SignedTransaction + IsL1Message, + T: NodePrimitives< + SignedTx = Tx, + Receipt: Receipt, + BlockHeader = Header, + BlockBody = BlockBody, + >, +{ + type _TX = Tx; +} From c86a75b06af5d0a096e1a3dd19265b0d4fbab310 Mon Sep 17 00:00:00 2001 From: frisitano Date: Tue, 1 Apr 2025 01:40:32 +0700 Subject: [PATCH 2/7] feat: txpool --- Cargo.lock | 34 ++ Cargo.toml | 2 + crates/scroll/alloy/provider/Cargo.toml | 1 + .../alloy/provider/src/engine/provider.rs | 8 +- .../src/payload/attributes.rs | 23 +- .../engine-primitives/src/payload/built.rs | 79 ++- .../engine-primitives/src/payload/mod.rs | 13 +- crates/scroll/evm/src/l1.rs | 44 ++ crates/scroll/evm/src/lib.rs | 52 +- crates/scroll/hardforks/src/lib.rs | 4 +- crates/scroll/node/Cargo.toml | 16 +- crates/scroll/node/src/builder/pool.rs | 148 ++++- crates/scroll/node/src/lib.rs | 3 - crates/scroll/node/src/node.rs | 2 +- crates/scroll/node/src/pool.rs | 542 ------------------ crates/scroll/node/src/test_utils.rs | 67 +-- crates/scroll/node/tests/assets/genesis.json | 113 +++- crates/scroll/node/tests/e2e/payload.rs | 3 +- crates/scroll/payload/Cargo.toml | 3 +- crates/scroll/payload/src/builder.rs | 78 +-- crates/scroll/txpool/Cargo.toml | 51 ++ crates/scroll/txpool/src/lib.rs | 16 + crates/scroll/txpool/src/transaction.rs | 252 ++++++++ crates/scroll/txpool/src/validator.rs | 249 ++++++++ 24 files changed, 1093 insertions(+), 710 deletions(-) create mode 100644 crates/scroll/evm/src/l1.rs delete mode 100644 crates/scroll/node/src/pool.rs create mode 100644 crates/scroll/txpool/Cargo.toml create mode 100644 crates/scroll/txpool/src/lib.rs create mode 100644 crates/scroll/txpool/src/transaction.rs create mode 100644 crates/scroll/txpool/src/validator.rs diff --git a/Cargo.lock b/Cargo.lock index 9e1bc4d9c7f3..c1d158cc50e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9845,6 +9845,7 @@ dependencies = [ "reth-chainspec", "reth-db", "reth-e2e-test-utils", + "reth-engine-local", "reth-eth-wire-types", "reth-evm", "reth-network", @@ -9867,6 +9868,7 @@ dependencies = [ "reth-scroll-payload", "reth-scroll-primitives", "reth-scroll-rpc", + "reth-scroll-txpool", "reth-tasks", "reth-tracing", "reth-transaction-pool", @@ -9879,6 +9881,7 @@ dependencies = [ "serde_json", "thiserror 2.0.12", "tokio", + "tracing", ] [[package]] @@ -9986,6 +9989,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-scroll-txpool" +version = "1.3.4" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-eth", + "c-kzg", + "derive_more 2.0.1", + "futures-util", + "metrics", + "op-alloy-flz", + "parking_lot", + "reth-chain-state", + "reth-chainspec", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-scroll-chainspec", + "reth-scroll-evm", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-storage-api", + "reth-transaction-pool", + "revm-scroll", + "scroll-alloy-consensus", +] + [[package]] name = "reth-stages" version = "1.3.4" @@ -11141,6 +11174,7 @@ dependencies = [ "reth-scroll-engine-primitives", "reth-scroll-node", "reth-scroll-payload", + "reth-scroll-primitives", "reth-tasks", "reth-tracing", "reth-transaction-pool", diff --git a/Cargo.toml b/Cargo.toml index a013cdffa910..fb260c9ba44a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,6 +121,7 @@ members = [ "crates/scroll/node", "crates/scroll/payload", "crates/scroll/primitives", + "crates/scroll/txpool", "crates/scroll/rpc", "crates/scroll/trie", "crates/stages/api/", @@ -529,6 +530,7 @@ reth-scroll-payload = { path = "crates/scroll/payload" } reth-scroll-primitives = { path = "crates/scroll/primitives" } reth-scroll-rpc = { path = "crates/scroll/rpc" } reth-scroll-trie = { path = "crates/scroll/trie" } +reth-scroll-txpool = { path = "crates/scroll/txpool" } # TODO (scroll): point to crates.io/tag once the crate is published/a tag is created. poseidon-bn254 = { git = "https://github.com/scroll-tech/poseidon-bn254", rev = "526a64a", features = ["bn254"] } diff --git a/crates/scroll/alloy/provider/Cargo.toml b/crates/scroll/alloy/provider/Cargo.toml index 0ecd7d740a6c..ddc8aee11a64 100644 --- a/crates/scroll/alloy/provider/Cargo.toml +++ b/crates/scroll/alloy/provider/Cargo.toml @@ -44,6 +44,7 @@ reth-rpc-engine-api.workspace = true reth-scroll-engine-primitives.workspace = true reth-scroll-node.workspace = true reth-scroll-payload = { workspace = true, features = ["test-utils"] } +reth-scroll-primitives.workspace = true reth-scroll-chainspec.workspace = true reth-tasks.workspace = true reth-tracing.workspace = true diff --git a/crates/scroll/alloy/provider/src/engine/provider.rs b/crates/scroll/alloy/provider/src/engine/provider.rs index ce94360a1b5f..8d039b47dc1b 100644 --- a/crates/scroll/alloy/provider/src/engine/provider.rs +++ b/crates/scroll/alloy/provider/src/engine/provider.rs @@ -112,6 +112,7 @@ mod tests { }; use reth_scroll_node::ScrollEngineValidator; use reth_scroll_payload::NoopPayloadJobGenerator; + use reth_scroll_primitives::ScrollTransactionSigned; use reth_tasks::TokioTaskExecutor; use reth_transaction_pool::noop::NoopTransactionPool; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; @@ -120,12 +121,15 @@ mod tests { fn spawn_test_payload_service() -> PayloadBuilderHandle where T: PayloadTypes< - PayloadBuilderAttributes = ScrollPayloadBuilderAttributes, + PayloadBuilderAttributes = ScrollPayloadBuilderAttributes, BuiltPayload = ScrollBuiltPayload, > + 'static, { let (service, handle) = PayloadBuilderService::< - NoopPayloadJobGenerator, + NoopPayloadJobGenerator< + ScrollPayloadBuilderAttributes, + ScrollBuiltPayload, + >, futures_util::stream::Empty, T, >::new(Default::default(), futures_util::stream::empty()); diff --git a/crates/scroll/engine-primitives/src/payload/attributes.rs b/crates/scroll/engine-primitives/src/payload/attributes.rs index 593516eda790..c7bc33305754 100644 --- a/crates/scroll/engine-primitives/src/payload/attributes.rs +++ b/crates/scroll/engine-primitives/src/payload/attributes.rs @@ -10,22 +10,33 @@ use alloy_rpc_types_engine::PayloadId; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_payload_primitives::PayloadBuilderAttributes; use reth_primitives::transaction::WithEncoded; -use reth_scroll_primitives::ScrollTransactionSigned; use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; /// Scroll Payload Builder Attributes -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct ScrollPayloadBuilderAttributes { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ScrollPayloadBuilderAttributes { /// Inner ethereum payload builder attributes pub payload_attributes: EthPayloadBuilderAttributes, /// `NoTxPool` option for the generated payload pub no_tx_pool: bool, /// Decoded transactions and the original EIP-2718 encoded bytes as received in the payload /// attributes. - pub transactions: Vec>, + pub transactions: Vec>, } -impl PayloadBuilderAttributes for ScrollPayloadBuilderAttributes { +impl Default for ScrollPayloadBuilderAttributes { + fn default() -> Self { + Self { + payload_attributes: Default::default(), + no_tx_pool: false, + transactions: Default::default(), + } + } +} + +impl PayloadBuilderAttributes + for ScrollPayloadBuilderAttributes +{ type RpcPayloadAttributes = ScrollPayloadAttributes; type Error = alloy_rlp::Error; @@ -139,7 +150,7 @@ pub(crate) fn payload_id_scroll( PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length")) } -impl From for ScrollPayloadBuilderAttributes { +impl From for ScrollPayloadBuilderAttributes { fn from(value: EthPayloadBuilderAttributes) -> Self { Self { payload_attributes: value, ..Default::default() } } diff --git a/crates/scroll/engine-primitives/src/payload/built.rs b/crates/scroll/engine-primitives/src/payload/built.rs index 4ca6b08a4d39..1b7d4b112b63 100644 --- a/crates/scroll/engine-primitives/src/payload/built.rs +++ b/crates/scroll/engine-primitives/src/payload/built.rs @@ -1,7 +1,9 @@ //! Outcome of a Scroll block building task with payload attributes provided via the Engine API. use core::iter; +use std::sync::Arc; +use alloy_consensus::Block; use alloy_eips::eip7685::Requests; use alloy_primitives::U256; use alloy_rpc_types_engine::{ @@ -11,28 +13,32 @@ use alloy_rpc_types_engine::{ }; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_payload_primitives::BuiltPayload; -use reth_primitives_traits::SealedBlock; -use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; +use reth_primitives::NodePrimitives; +use reth_primitives_traits::{SealedBlock, SignedTransaction}; +use reth_scroll_primitives::ScrollPrimitives; /// Contains the built payload. #[derive(Debug, Clone, Default)] -pub struct ScrollBuiltPayload { +pub struct ScrollBuiltPayload { /// Identifier of the payload pub(crate) id: PayloadId, + /// Sealed block + pub(crate) block: Arc>, /// Block execution data for the payload - pub(crate) block: ExecutedBlockWithTrieUpdates, + pub(crate) executed_block: Option>, /// The fees of the block pub(crate) fees: U256, } -impl ScrollBuiltPayload { +impl ScrollBuiltPayload { /// Initializes the payload with the given initial block. pub const fn new( id: PayloadId, - block: ExecutedBlockWithTrieUpdates, + block: Arc>, + executed_block: Option>, fees: U256, ) -> Self { - Self { id, block, fees } + Self { id, block, executed_block, fees } } /// Returns the identifier of the payload. @@ -42,20 +48,25 @@ impl ScrollBuiltPayload { /// Returns the built block(sealed) #[allow(clippy::missing_const_for_fn)] - pub fn block(&self) -> &SealedBlock { - self.block.sealed_block() + pub fn block(&self) -> &SealedBlock { + &self.block } /// Fees of the block pub const fn fees(&self) -> U256 { self.fees } + + /// Converts the value into [`SealedBlock`]. + pub fn into_sealed_block(self) -> SealedBlock { + Arc::unwrap_or_clone(self.block) + } } -impl BuiltPayload for ScrollBuiltPayload { - type Primitives = ScrollPrimitives; +impl BuiltPayload for ScrollBuiltPayload { + type Primitives = N; - fn block(&self) -> &SealedBlock { + fn block(&self) -> &SealedBlock { self.block() } @@ -63,8 +74,8 @@ impl BuiltPayload for ScrollBuiltPayload { self.fees } - fn executed_block(&self) -> Option> { - Some(self.block.clone()) + fn executed_block(&self) -> Option> { + self.executed_block.clone() } fn requests(&self) -> Option { @@ -73,40 +84,50 @@ impl BuiltPayload for ScrollBuiltPayload { } // V1 engine_getPayloadV1 response -impl From for ExecutionPayloadV1 { - fn from(value: ScrollBuiltPayload) -> Self { +impl From> for ExecutionPayloadV1 +where + T: SignedTransaction, + N: NodePrimitives>, +{ + fn from(value: ScrollBuiltPayload) -> Self { Self::from_block_unchecked( value.block().hash(), - &value.block.into_sealed_block().into_block(), + &Arc::unwrap_or_clone(value.block).into_block(), ) } } // V2 engine_getPayloadV2 response -impl From for ExecutionPayloadEnvelopeV2 { - fn from(value: ScrollBuiltPayload) -> Self { +impl From> for ExecutionPayloadEnvelopeV2 +where + T: SignedTransaction, + N: NodePrimitives>, +{ + fn from(value: ScrollBuiltPayload) -> Self { let ScrollBuiltPayload { block, fees, .. } = value; - let block = block.into_sealed_block(); Self { block_value: fees, execution_payload: ExecutionPayloadFieldV2::from_block_unchecked( block.hash(), - &block.into_block(), + &Arc::unwrap_or_clone(block).into_block(), ), } } } -impl From for ExecutionPayloadEnvelopeV3 { - fn from(value: ScrollBuiltPayload) -> Self { +impl From> for ExecutionPayloadEnvelopeV3 +where + T: SignedTransaction, + N: NodePrimitives>, +{ + fn from(value: ScrollBuiltPayload) -> Self { let ScrollBuiltPayload { block, fees, .. } = value; - let block = block.into_sealed_block(); Self { execution_payload: ExecutionPayloadV3::from_block_unchecked( block.hash(), - &block.into_block(), + &Arc::unwrap_or_clone(block).into_block(), ), block_value: fees, // From the engine API spec: @@ -122,8 +143,12 @@ impl From for ExecutionPayloadEnvelopeV3 { } } } -impl From for ExecutionPayloadEnvelopeV4 { - fn from(value: ScrollBuiltPayload) -> Self { +impl From> for ExecutionPayloadEnvelopeV4 +where + T: SignedTransaction, + N: NodePrimitives>, +{ + fn from(value: ScrollBuiltPayload) -> Self { Self { envelope_inner: value.into(), execution_requests: Default::default() } } } diff --git a/crates/scroll/engine-primitives/src/payload/mod.rs b/crates/scroll/engine-primitives/src/payload/mod.rs index 18ef28efe0a5..4a0d7be5096b 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_payload_primitives::{BuiltPayload, PayloadTypes}; use reth_primitives::{Block, BlockBody, Header}; use reth_primitives_traits::{NodePrimitives, SealedBlock}; use reth_scroll_chainspec::ScrollChainSpec; -use reth_scroll_primitives::ScrollBlock; +use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; use scroll_alloy_hardforks::ScrollHardfork; use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; @@ -74,13 +74,16 @@ where /// A default payload type for [`ScrollEngineTypes`] #[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] #[non_exhaustive] -pub struct ScrollPayloadTypes; +pub struct ScrollPayloadTypes(core::marker::PhantomData); -impl PayloadTypes for ScrollPayloadTypes { +impl PayloadTypes for ScrollPayloadTypes +where + ScrollBuiltPayload: BuiltPayload>, +{ type ExecutionData = ExecutionData; - type BuiltPayload = ScrollBuiltPayload; + type BuiltPayload = ScrollBuiltPayload; type PayloadAttributes = ScrollPayloadAttributes; - type PayloadBuilderAttributes = ScrollPayloadBuilderAttributes; + type PayloadBuilderAttributes = ScrollPayloadBuilderAttributes; fn block_to_payload( block: SealedBlock< diff --git a/crates/scroll/evm/src/l1.rs b/crates/scroll/evm/src/l1.rs new file mode 100644 index 000000000000..40591639f9e3 --- /dev/null +++ b/crates/scroll/evm/src/l1.rs @@ -0,0 +1,44 @@ +use super::spec_id_at_timestamp_and_number; +use reth_evm::block::BlockExecutionError; +use revm_primitives::U256; +use revm_scroll::l1block::L1BlockInfo; +use scroll_alloy_hardforks::ScrollHardforks; + +/// An extension trait for [`L1BlockInfo`] that allows us to calculate the L1 cost of a transaction +/// based off of the chain spec's activated hardfork. +pub trait RethL1BlockInfo { + /// Forwards an L1 transaction calculation to revm and returns the gas cost. + /// + /// ### Takes + /// - `chain_spec`: The chain spec for the node. + /// - `timestamp`: The timestamp of the current block. + /// - `block`: The block number of the current block. + /// - `input`: The calldata of the transaction. + /// - `is_l1_message`: Whether or not the transaction is a l1 message. + fn l1_tx_data_fee( + &mut self, + chain_spec: impl ScrollHardforks, + timestamp: u64, + block: u64, + input: &[u8], + is_l1_message: bool, + ) -> Result; +} + +impl RethL1BlockInfo for L1BlockInfo { + fn l1_tx_data_fee( + &mut self, + chain_spec: impl ScrollHardforks, + timestamp: u64, + block_number: u64, + input: &[u8], + is_l1_message: bool, + ) -> Result { + if is_l1_message { + return Ok(U256::ZERO); + } + + let spec_id = spec_id_at_timestamp_and_number(timestamp, block_number, chain_spec); + Ok(self.calculate_tx_l1_cost(input, spec_id)) + } +} diff --git a/crates/scroll/evm/src/lib.rs b/crates/scroll/evm/src/lib.rs index 12586ab89967..cbc7f590f36a 100644 --- a/crates/scroll/evm/src/lib.rs +++ b/crates/scroll/evm/src/lib.rs @@ -11,6 +11,9 @@ mod config; pub use execute::{ScrollBlockExecutionInput, ScrollExecutorProvider}; mod execute; +mod l1; +pub use l1::RethL1BlockInfo; + pub use receipt::ScrollRethReceiptBuilder; mod receipt; @@ -83,26 +86,35 @@ impl ScrollEvmConfig ScrollSpecId { let chain_spec = self.chain_spec(); - if chain_spec - .scroll_fork_activation(ScrollHardfork::DarwinV2) - .active_at_timestamp_or_number(timestamp, number) || - chain_spec - .scroll_fork_activation(ScrollHardfork::Darwin) - .active_at_timestamp_or_number(timestamp, number) - { - ScrollSpecId::DARWIN - } else if chain_spec - .scroll_fork_activation(ScrollHardfork::Curie) - .active_at_timestamp_or_number(timestamp, number) - { - ScrollSpecId::CURIE - } else if chain_spec - .scroll_fork_activation(ScrollHardfork::Bernoulli) + spec_id_at_timestamp_and_number(timestamp, number, chain_spec) + } +} + +/// Returns the spec id at the given timestamp and block number for the provided chain spec. +pub fn spec_id_at_timestamp_and_number( + timestamp: u64, + number: u64, + chain_spec: impl ScrollHardforks, +) -> ScrollSpecId { + if chain_spec + .scroll_fork_activation(ScrollHardfork::DarwinV2) + .active_at_timestamp_or_number(timestamp, number) || + chain_spec + .scroll_fork_activation(ScrollHardfork::Darwin) .active_at_timestamp_or_number(timestamp, number) - { - ScrollSpecId::BERNOULLI - } else { - ScrollSpecId::SHANGHAI - } + { + ScrollSpecId::DARWIN + } else if chain_spec + .scroll_fork_activation(ScrollHardfork::Curie) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::CURIE + } else if chain_spec + .scroll_fork_activation(ScrollHardfork::Bernoulli) + .active_at_timestamp_or_number(timestamp, number) + { + ScrollSpecId::BERNOULLI + } else { + ScrollSpecId::SHANGHAI } } diff --git a/crates/scroll/hardforks/src/lib.rs b/crates/scroll/hardforks/src/lib.rs index c0df0fec25d0..267833c4ae4f 100644 --- a/crates/scroll/hardforks/src/lib.rs +++ b/crates/scroll/hardforks/src/lib.rs @@ -3,7 +3,9 @@ #![doc = include_str!("../docs/hardforks.md")] use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition, Hardfork}; -use scroll_alloy_hardforks::ScrollHardfork; + +// Re-export scroll-alloy-hardforks types. +pub use scroll_alloy_hardforks::{ScrollHardfork, ScrollHardforks}; #[cfg(not(feature = "std"))] use once_cell::sync::Lazy as LazyLock; diff --git a/crates/scroll/node/Cargo.toml b/crates/scroll/node/Cargo.toml index 424f64b3ccae..de4abb5b1281 100644 --- a/crates/scroll/node/Cargo.toml +++ b/crates/scroll/node/Cargo.toml @@ -15,6 +15,8 @@ workspace = true # reth reth-basic-payload-builder.workspace = true reth-chainspec.workspace = true +reth-db = { workspace = true, features = ["scroll-alloy-traits"] } +reth-engine-local = { workspace = true, features = ["scroll-alloy-traits"] } reth-rpc-eth-types.workspace = true reth-eth-wire-types.workspace = true reth-evm = { workspace = true, features = ["scroll-alloy-traits"] } @@ -50,6 +52,7 @@ reth-scroll-evm.workspace = true reth-scroll-payload.workspace = true reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } reth-scroll-rpc.workspace = true +reth-scroll-txpool.workspace = true scroll-alloy-consensus.workspace = true scroll-alloy-evm.workspace = true scroll-alloy-hardforks.workspace = true @@ -62,6 +65,7 @@ alloy-genesis = { workspace = true, optional = true } # misc eyre.workspace = true serde_json = { workspace = true, optional = true } +tracing.workspace = true thiserror.workspace = true tokio.workspace = true @@ -81,11 +85,9 @@ alloy-consensus.workspace = true futures.workspace = true [features] -default = ["reth-codec"] +default = ["reth-codec", "scroll-alloy-traits"] reth-codec = [ - "reth-primitives/reth-codec", - "reth-primitives-traits/reth-codec", - "reth-scroll-primitives/reth-codec", + "reth-scroll-primitives/reth-codec" ] skip-state-root-validation = [ "reth-provider/skip-state-root-validation", @@ -113,3 +115,9 @@ test-utils = [ "reth-revm/test-utils", "reth-scroll-node/test-utils" ] +scroll-alloy-traits = [ + "reth-db/scroll-alloy-traits", + "reth-evm/scroll-alloy-traits", + "reth-primitives-traits/scroll-alloy-traits", + "reth-scroll-node/scroll-alloy-traits" +] diff --git a/crates/scroll/node/src/builder/pool.rs b/crates/scroll/node/src/builder/pool.rs index 8f1a1cb634bc..9d8e115d0b07 100644 --- a/crates/scroll/node/src/builder/pool.rs +++ b/crates/scroll/node/src/builder/pool.rs @@ -1,21 +1,141 @@ -use crate::pool::ScrollNoopTransactionPool; -use reth_node_builder::{components::PoolBuilder, BuilderContext, FullNodeTypes}; -use reth_node_types::NodeTypes; -use reth_scroll_chainspec::ScrollChainSpec; -use reth_scroll_primitives::ScrollPrimitives; +use alloy_consensus::{constants::EIP1559_TX_TYPE_ID, Transaction, Typed2718}; +use alloy_eips::{ + eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, + eip2718::Encodable2718, + eip2930::AccessList, + eip4844::{BlobAndProofV1, BlobTransactionSidecar, BlobTransactionValidationError}, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, B256, U256}; +use reth_eth_wire_types::HandleMempoolData; +use reth_node_api::{FullNodeTypes, NodeTypes}; +use reth_node_builder::{ + components::{PoolBuilder, PoolBuilderConfigOverrides}, + BuilderContext, TxTy, +}; +use reth_primitives::{kzg::KzgSettings, Recovered}; +use reth_primitives_traits::{ + transaction::error::TryFromRecoveredTransactionError, SignedTransaction, +}; +use reth_provider::CanonStateSubscriptions; +use reth_scroll_primitives::ScrollTransactionSigned; +use reth_scroll_txpool::{ScrollTransactionPool, ScrollTransactionValidator}; +use reth_transaction_pool::{ + blobstore::DiskFileBlobStore, error::PoolError, AllPoolTransactions, AllTransactionsEvents, + BestTransactions, BestTransactionsAttributes, BlobStoreError, BlockInfo, CoinbaseTipOrdering, + EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, GetPooledTransactionLimit, + NewBlobSidecar, NewTransactionEvent, PoolResult, PoolSize, PoolTransaction, + PropagatedTransactions, TransactionEvents, TransactionListenerKind, TransactionOrigin, + TransactionPool, TransactionValidationTaskExecutor, ValidPoolTransaction, +}; +use scroll_alloy_hardforks::ScrollHardforks; +use std::{collections::HashSet, sync::Arc}; +use tokio::sync::{mpsc, mpsc::Receiver}; -/// Pool builder for Scroll. -#[derive(Debug, Default, Clone, Copy)] -pub struct ScrollPoolBuilder; +/// A basic optimism transaction pool. +/// +/// This contains various settings that can be configured and take precedence over the node's +/// config. +#[derive(Debug, Clone)] +pub struct ScrollPoolBuilder { + /// Enforced overrides that are applied to the pool config. + pub pool_config_overrides: PoolBuilderConfigOverrides, -impl PoolBuilder for ScrollPoolBuilder + /// Marker for the pooled transaction type. + _pd: core::marker::PhantomData, +} + +impl Default for ScrollPoolBuilder { + fn default() -> Self { + Self { pool_config_overrides: Default::default(), _pd: Default::default() } + } +} + +impl ScrollPoolBuilder { + /// Sets the [`PoolBuilderConfigOverrides`] on the pool builder. + pub fn with_pool_config_overrides( + mut self, + pool_config_overrides: PoolBuilderConfigOverrides, + ) -> Self { + self.pool_config_overrides = pool_config_overrides; + self + } +} + +impl PoolBuilder for ScrollPoolBuilder where - Node: - FullNodeTypes>, + Node: FullNodeTypes>, + T: EthPoolTransaction>, { - type Pool = ScrollNoopTransactionPool; + type Pool = ScrollTransactionPool; + + async fn build_pool(self, ctx: &BuilderContext) -> eyre::Result { + let Self { pool_config_overrides, .. } = self; + let data_dir = ctx.config().datadir(); + let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; + + let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) + .no_eip4844() + .with_head_timestamp(ctx.head().timestamp) + .kzg_settings(ctx.kzg_settings()?) + .with_additional_tasks( + pool_config_overrides + .additional_validation_tasks + .unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks), + ) + .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()) + .map(|validator| { + ScrollTransactionValidator::new(validator) + // In --dev mode we can't require gas fees because we're unable to decode + // the L1 block info + .require_l1_data_gas_fee(!ctx.config().dev.dev) + }); + + let transaction_pool = reth_transaction_pool::Pool::new( + validator, + CoinbaseTipOrdering::default(), + blob_store, + pool_config_overrides.apply(ctx.pool_config()), + ); + tracing::info!(target: "reth::cli", "Transaction pool initialized"); + let transactions_path = data_dir.txpool_transactions(); + + // spawn txpool maintenance tasks + { + let pool = transaction_pool.clone(); + let chain_events = ctx.provider().canonical_state_stream(); + let client = ctx.provider().clone(); + let transactions_backup_config = + reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_backup(transactions_path); + + ctx.task_executor().spawn_critical_with_graceful_shutdown_signal( + "local transactions backup task", + |shutdown| { + reth_transaction_pool::maintain::backup_local_transactions_task( + shutdown, + pool.clone(), + transactions_backup_config, + ) + }, + ); + + // spawn the main maintenance task + ctx.task_executor().spawn_critical( + "txpool maintenance task", + reth_transaction_pool::maintain::maintain_transaction_pool_future( + client, + pool.clone(), + chain_events, + ctx.task_executor().clone(), + reth_transaction_pool::maintain::MaintainPoolConfig { + max_tx_lifetime: pool.config().max_queued_lifetime, + ..Default::default() + }, + ), + ); + tracing::debug!(target: "reth::cli", "Spawned txpool maintenance task"); + } - async fn build_pool(self, _ctx: &BuilderContext) -> eyre::Result { - Ok(ScrollNoopTransactionPool) + Ok(transaction_pool) } } diff --git a/crates/scroll/node/src/lib.rs b/crates/scroll/node/src/lib.rs index 3e791bb4eb66..6ce01c393954 100644 --- a/crates/scroll/node/src/lib.rs +++ b/crates/scroll/node/src/lib.rs @@ -15,9 +15,6 @@ pub use addons::{ScrollAddOns, ScrollAddOnsBuilder}; mod node; pub use node::ScrollNode; -mod pool; -pub use pool::{ScrollNoopTransactionPool, ScrollPooledTransaction}; - mod storage; pub use storage::ScrollStorage; diff --git a/crates/scroll/node/src/node.rs b/crates/scroll/node/src/node.rs index 1d9e2eddfcb1..428bbf57ca56 100644 --- a/crates/scroll/node/src/node.rs +++ b/crates/scroll/node/src/node.rs @@ -39,7 +39,7 @@ impl ScrollNode { { ComponentsBuilder::default() .node_types::() - .pool(ScrollPoolBuilder) + .pool(ScrollPoolBuilder::default()) .payload(BasicPayloadServiceBuilder::new(ScrollPayloadBuilder::default())) .network(ScrollNetworkBuilder) .executor(ScrollExecutorBuilder) diff --git a/crates/scroll/node/src/pool.rs b/crates/scroll/node/src/pool.rs deleted file mode 100644 index 06fcecd1d51d..000000000000 --- a/crates/scroll/node/src/pool.rs +++ /dev/null @@ -1,542 +0,0 @@ -use alloy_consensus::{constants::EIP1559_TX_TYPE_ID, Transaction, Typed2718}; -use alloy_eips::{ - eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, - eip2718::Encodable2718, - eip2930::AccessList, - eip4844::{BlobAndProofV1, BlobTransactionSidecar, BlobTransactionValidationError}, - eip7702::SignedAuthorization, -}; -use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, B256, U256}; -use reth_eth_wire_types::HandleMempoolData; -use reth_primitives::{kzg::KzgSettings, Recovered}; -use reth_primitives_traits::{ - transaction::error::TryFromRecoveredTransactionError, SignedTransaction, -}; -use reth_scroll_primitives::ScrollTransactionSigned; -use reth_transaction_pool::{ - error::PoolError, AllPoolTransactions, AllTransactionsEvents, BestTransactions, - BestTransactionsAttributes, BlobStoreError, BlockInfo, EthBlobTransactionSidecar, - EthPoolTransaction, EthPooledTransaction, GetPooledTransactionLimit, NewBlobSidecar, - NewTransactionEvent, PoolResult, PoolSize, PoolTransaction, PropagatedTransactions, - TransactionEvents, TransactionListenerKind, TransactionOrigin, TransactionPool, - ValidPoolTransaction, -}; -use std::{collections::HashSet, sync::Arc}; -use tokio::sync::{mpsc, mpsc::Receiver}; - -/// A [`TransactionPool`] implementation that does nothing for Scroll. -/// -/// All transactions are rejected and no events are emitted. -/// This type will never hold any transactions and is only useful for wiring components together -/// using the Scroll primitive types. -#[derive(Debug, Clone, Default)] -pub struct ScrollNoopTransactionPool; - -impl TransactionPool for ScrollNoopTransactionPool { - type Transaction = ScrollPooledTransaction; - - fn pool_size(&self) -> PoolSize { - Default::default() - } - - fn block_info(&self) -> BlockInfo { - BlockInfo { - block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M, - last_seen_block_hash: Default::default(), - last_seen_block_number: 0, - pending_basefee: 0, - pending_blob_fee: None, - } - } - - async fn add_transaction_and_subscribe( - &self, - _origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> PoolResult { - let hash = *transaction.hash(); - Err(PoolError::other(hash, Box::new(NoopInsertError::new(transaction)))) - } - - async fn add_transaction( - &self, - _origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> PoolResult { - let hash = *transaction.hash(); - Err(PoolError::other(hash, Box::new(NoopInsertError::new(transaction)))) - } - - async fn add_transactions( - &self, - _origin: TransactionOrigin, - transactions: Vec, - ) -> Vec> { - transactions - .into_iter() - .map(|transaction| { - let hash = *transaction.hash(); - Err(PoolError::other(hash, Box::new(NoopInsertError::new(transaction)))) - }) - .collect() - } - - fn transaction_event_listener(&self, _tx_hash: TxHash) -> Option { - None - } - - fn all_transactions_event_listener(&self) -> AllTransactionsEvents { - AllTransactionsEvents::new(mpsc::channel(1).1) - } - - fn pending_transactions_listener_for( - &self, - _kind: TransactionListenerKind, - ) -> Receiver { - mpsc::channel(1).1 - } - - fn new_transactions_listener(&self) -> Receiver> { - mpsc::channel(1).1 - } - - fn blob_transaction_sidecars_listener(&self) -> Receiver { - mpsc::channel(1).1 - } - - fn new_transactions_listener_for( - &self, - _kind: TransactionListenerKind, - ) -> Receiver> { - mpsc::channel(1).1 - } - - fn pooled_transaction_hashes(&self) -> Vec { - vec![] - } - - fn pooled_transaction_hashes_max(&self, _max: usize) -> Vec { - vec![] - } - - fn pooled_transactions(&self) -> Vec>> { - vec![] - } - - fn pooled_transactions_max( - &self, - _max: usize, - ) -> Vec>> { - vec![] - } - - fn get_pooled_transaction_elements( - &self, - _tx_hashes: Vec, - _limit: GetPooledTransactionLimit, - ) -> Vec<::Pooled> { - vec![] - } - - fn get_pooled_transaction_element( - &self, - _tx_hash: TxHash, - ) -> Option::Pooled>> { - None - } - - fn best_transactions( - &self, - ) -> Box>>> { - Box::new(std::iter::empty()) - } - - fn best_transactions_with_attributes( - &self, - _: BestTransactionsAttributes, - ) -> Box>>> { - Box::new(std::iter::empty()) - } - - fn pending_transactions(&self) -> Vec>> { - vec![] - } - - fn pending_transactions_max( - &self, - _max: usize, - ) -> Vec>> { - vec![] - } - - fn queued_transactions(&self) -> Vec>> { - vec![] - } - - fn all_transactions(&self) -> AllPoolTransactions { - AllPoolTransactions::default() - } - - fn remove_transactions( - &self, - _hashes: Vec, - ) -> Vec>> { - vec![] - } - - fn remove_transactions_and_descendants( - &self, - _hashes: Vec, - ) -> Vec>> { - vec![] - } - - fn remove_transactions_by_sender( - &self, - _sender: Address, - ) -> Vec>> { - vec![] - } - - fn retain_unknown(&self, _announcement: &mut A) - where - A: HandleMempoolData, - { - } - - fn get(&self, _tx_hash: &TxHash) -> Option>> { - None - } - - fn get_all(&self, _txs: Vec) -> Vec>> { - vec![] - } - - fn on_propagated(&self, _txs: PropagatedTransactions) {} - - fn get_transactions_by_sender( - &self, - _sender: Address, - ) -> Vec>> { - vec![] - } - - fn get_pending_transactions_with_predicate( - &self, - _predicate: impl FnMut(&ValidPoolTransaction) -> bool, - ) -> Vec>> { - vec![] - } - - fn get_pending_transactions_by_sender( - &self, - _sender: Address, - ) -> Vec>> { - vec![] - } - - fn get_queued_transactions_by_sender( - &self, - _sender: Address, - ) -> Vec>> { - vec![] - } - - fn get_highest_transaction_by_sender( - &self, - _sender: Address, - ) -> Option>> { - None - } - - fn get_highest_consecutive_transaction_by_sender( - &self, - _sender: Address, - _on_chain_nonce: u64, - ) -> Option>> { - None - } - - fn get_transaction_by_sender_and_nonce( - &self, - _sender: Address, - _nonce: u64, - ) -> Option>> { - None - } - - fn get_transactions_by_origin( - &self, - _origin: TransactionOrigin, - ) -> Vec>> { - vec![] - } - - fn get_pending_transactions_by_origin( - &self, - _origin: TransactionOrigin, - ) -> Vec>> { - vec![] - } - - fn unique_senders(&self) -> HashSet
{ - Default::default() - } - - fn get_blob( - &self, - _tx_hash: TxHash, - ) -> Result>, BlobStoreError> { - Ok(None) - } - - fn get_all_blobs( - &self, - _tx_hashes: Vec, - ) -> Result)>, BlobStoreError> { - Ok(vec![]) - } - - fn get_all_blobs_exact( - &self, - tx_hashes: Vec, - ) -> Result>, BlobStoreError> { - if tx_hashes.is_empty() { - return Ok(vec![]); - } - Err(BlobStoreError::MissingSidecar(tx_hashes[0])) - } - - fn get_blobs_for_versioned_hashes( - &self, - versioned_hashes: &[B256], - ) -> Result>, BlobStoreError> { - Ok(vec![None; versioned_hashes.len()]) - } -} - -/// A transaction that can be included in the [`ScrollNoopTransactionPool`]. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct ScrollPooledTransaction(EthPooledTransaction); - -impl ScrollPooledTransaction { - /// Returns a new [`ScrollPooledTransaction`]. - pub fn new(transaction: Recovered, encoded_length: usize) -> Self { - Self(EthPooledTransaction::new(transaction, encoded_length)) - } -} - -impl TryFrom> for ScrollPooledTransaction { - type Error = TryFromRecoveredTransactionError; - - fn try_from(tx: Recovered) -> Result { - // ensure we can handle the transaction type and its format - match tx.ty() { - 0..=EIP1559_TX_TYPE_ID => { - // supported - } - unsupported => { - // unsupported transaction type - return Err(TryFromRecoveredTransactionError::UnsupportedTransactionType( - unsupported, - )) - } - }; - - let encoded_length = tx.encode_2718_len(); - let transaction = Self::new(tx, encoded_length); - Ok(transaction) - } -} - -impl From for Recovered { - fn from(tx: ScrollPooledTransaction) -> Self { - tx.0.transaction - } -} - -impl From> for ScrollPooledTransaction { - fn from(tx: Recovered) -> Self { - let encoded_length = tx.encode_2718_len(); - let (tx, signer) = tx.into_parts(); - // no blob sidecar - let tx = Recovered::new_unchecked(tx.into(), signer); - Self::new(tx, encoded_length) - } -} - -impl PoolTransaction for ScrollPooledTransaction { - type TryFromConsensusError = TryFromRecoveredTransactionError; - - type Consensus = ScrollTransactionSigned; - - type Pooled = scroll_alloy_consensus::ScrollPooledTransaction; - - fn clone_into_consensus(&self) -> Recovered { - self.0.transaction().clone() - } - - fn into_consensus(self) -> Recovered { - self.0.transaction - } - - fn from_pooled(tx: Recovered) -> Self { - let encoded_len = tx.encode_2718_len(); - let tx = tx.map(|tx| tx.into()); - Self::new(tx, encoded_len) - } - - /// Returns hash of the transaction. - fn hash(&self) -> &TxHash { - self.0.transaction.tx_hash() - } - - /// Returns the Sender of the transaction. - fn sender(&self) -> Address { - self.0.transaction.signer() - } - - /// Returns a reference to the Sender of the transaction. - fn sender_ref(&self) -> &Address { - self.0.transaction.signer_ref() - } - - /// Returns the cost that this transaction is allowed to consume: - /// - /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. - /// For legacy transactions: `gas_price * gas_limit + tx_value`. - /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value + - /// max_blob_fee_per_gas * blob_gas_used`. - fn cost(&self) -> &U256 { - &self.0.cost - } - - /// Returns the length of the rlp encoded object - fn encoded_length(&self) -> usize { - self.0.encoded_length - } -} - -impl reth_primitives_traits::InMemorySize for ScrollPooledTransaction { - fn size(&self) -> usize { - self.0.size() - } -} - -impl Typed2718 for ScrollPooledTransaction { - fn ty(&self) -> u8 { - self.0.ty() - } -} - -impl Transaction for ScrollPooledTransaction { - fn chain_id(&self) -> Option { - self.0.chain_id() - } - - fn nonce(&self) -> u64 { - self.0.nonce() - } - - fn gas_limit(&self) -> u64 { - self.0.gas_limit() - } - - fn gas_price(&self) -> Option { - self.0.gas_price() - } - - fn max_fee_per_gas(&self) -> u128 { - self.0.max_fee_per_gas() - } - - fn max_priority_fee_per_gas(&self) -> Option { - self.0.max_priority_fee_per_gas() - } - - fn max_fee_per_blob_gas(&self) -> Option { - self.0.max_fee_per_blob_gas() - } - - fn priority_fee_or_price(&self) -> u128 { - self.0.priority_fee_or_price() - } - - fn effective_gas_price(&self, base_fee: Option) -> u128 { - self.0.effective_gas_price(base_fee) - } - - fn is_dynamic_fee(&self) -> bool { - self.0.is_dynamic_fee() - } - - fn kind(&self) -> TxKind { - self.0.kind() - } - - fn is_create(&self) -> bool { - self.0.is_create() - } - - fn value(&self) -> U256 { - self.0.value() - } - - fn input(&self) -> &Bytes { - self.0.input() - } - - fn access_list(&self) -> Option<&AccessList> { - self.0.access_list() - } - - fn blob_versioned_hashes(&self) -> Option<&[B256]> { - self.0.blob_versioned_hashes() - } - - fn authorization_list(&self) -> Option<&[SignedAuthorization]> { - self.0.authorization_list() - } -} - -impl EthPoolTransaction for ScrollPooledTransaction { - fn take_blob(&mut self) -> EthBlobTransactionSidecar { - EthBlobTransactionSidecar::None - } - - fn try_into_pooled_eip4844( - self, - _sidecar: Arc, - ) -> Option> { - None - } - - fn try_from_eip4844( - _tx: Recovered, - _sidecar: BlobTransactionSidecar, - ) -> Option { - None - } - - fn validate_blob( - &self, - _blob: &BlobTransactionSidecar, - _settings: &KzgSettings, - ) -> Result<(), BlobTransactionValidationError> { - Err(BlobTransactionValidationError::NotBlobTransaction(self.ty())) - } -} - -/// An error that contains the transaction that failed to be inserted into the noop pool. -#[derive(Debug, Clone, thiserror::Error)] -#[error("can't insert transaction into the noop pool that does nothing")] -struct NoopInsertError { - tx: ScrollPooledTransaction, -} - -impl NoopInsertError { - const fn new(tx: ScrollPooledTransaction) -> Self { - Self { tx } - } -} diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs index fff07717fb04..9f21b4d5ad9f 100644 --- a/crates/scroll/node/src/test_utils.rs +++ b/crates/scroll/node/src/test_utils.rs @@ -3,16 +3,17 @@ use alloy_genesis::Genesis; use alloy_primitives::{Address, B256}; use alloy_rpc_types_engine::PayloadAttributes; use reth_e2e_test_utils::{ - node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, NodeBuilderHelper, - NodeHelperType, TmpDB, + node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, NodeHelperType, + TmpDB, }; -use reth_node_api::NodeTypesWithDBAdapter; +use reth_node_api::{NodePrimitives, NodeTypes, NodeTypesWithDBAdapter}; use reth_node_builder::{Node, NodeBuilder, NodeConfig, NodeHandle}; use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_provider::providers::BlockchainProvider; use reth_rpc_server_types::RpcModuleSelection; use reth_scroll_chainspec::{ScrollChainSpec, ScrollChainSpecBuilder}; +use reth_scroll_primitives::ScrollPrimitives; use reth_tasks::TaskManager; use reth_transaction_pool::PeerId; use std::sync::Arc; @@ -24,51 +25,23 @@ pub(crate) type ScrollNode = NodeHelperType< BlockchainProvider>, >; -pub async fn setup_engine( - chain_spec: Arc, - is_dev: bool, -) -> eyre::Result<(ScrollNode, TaskManager, PeerId)> { - // Create a [`TaskManager`] to manage the tasks. - let tasks = TaskManager::current(); - let exec = tasks.executor(); - - // Define the network configuration with discovery disabled. - let network_config = NetworkArgs { - discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, - ..NetworkArgs::default() - }; - - // Create the node config - let node_config = NodeConfig::new(chain_spec.clone()) - .with_network(network_config.clone()) - .with_rpc(RpcServerArgs::default().with_http().with_http_api(RpcModuleSelection::All)) - .set_dev(is_dev); - - // Create the node for a bridge node that will bridge messages from the eth-wire protocol - // to the scroll-wire protocol. - let node = OtherScrollNode; - let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config.clone()) - .testing_node(exec.clone()) - .with_types_and_provider::>() - .with_components(node.components_builder()) - .with_add_ons(node.add_ons()) - .launch() - .await?; - let peer_id = *node.network.peer_id(); - let node = NodeTestContext::new(node, scroll_payload_attributes).await?; - - Ok((node, tasks, peer_id)) -} - /// Creates the initial setup with `num_nodes` of the node config, started and connected. -pub async fn setup(is_dev: bool) -> eyre::Result<(ScrollNode, TaskManager, Wallet)> { +pub async fn setup(num_nodes: usize) -> eyre::Result<(Vec, TaskManager, Wallet)> { let genesis: Genesis = serde_json::from_str(include_str!("../tests/assets/genesis.json")).unwrap(); - let chain_spec = - ScrollChainSpecBuilder::scroll_mainnet().genesis(genesis).build(Default::default()); - let chain_id = chain_spec.chain().into(); - let (node, task_manager, _peer_id) = setup_engine(Arc::new(chain_spec), is_dev).await.unwrap(); - Ok((node, task_manager, Wallet::default().with_chain_id(chain_id))) + println!("{:?}", genesis); + reth_e2e_test_utils::setup_engine( + num_nodes, + Arc::new( + ScrollChainSpecBuilder::scroll_mainnet() + .genesis(genesis) + .darwin_v2_activated() + .build(Default::default()), + ), + false, + scroll_payload_attributes::, + ) + .await } /// Advance the chain with sequential payloads returning them in the end. @@ -94,7 +67,9 @@ pub async fn advance_chain( } /// Helper function to create a new eth payload attributes -pub fn scroll_payload_attributes(timestamp: u64) -> ScrollPayloadBuilderAttributes { +pub fn scroll_payload_attributes( + timestamp: u64, +) -> ScrollPayloadBuilderAttributes { let attributes = PayloadAttributes { timestamp, prev_randao: B256::ZERO, diff --git a/crates/scroll/node/tests/assets/genesis.json b/crates/scroll/node/tests/assets/genesis.json index e59d90f4ff10..5c652d084a52 100644 --- a/crates/scroll/node/tests/assets/genesis.json +++ b/crates/scroll/node/tests/assets/genesis.json @@ -1 +1,112 @@ -{"config":{"chainId":8453,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"berlinBlock":0,"londonBlock":0,"arrowGlacierBlock":0,"grayGlacierBlock":0,"mergeNetsplitBlock":0,"bedrockBlock":0,"regolithTime":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"optimism":{"eip1559Elasticity":6,"eip1559Denominator":50}},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x1c9c380","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"balance":"0xd3c21bcecceda1000000"},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"balance":"0xd3c21bcecceda1000000"},"0x1cbd3b2770909d4e10f157cabc84c7264073c9ec":{"balance":"0xd3c21bcecceda1000000"},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"balance":"0xd3c21bcecceda1000000"},"0x2546bcd3c84621e976d8185a91a922ae77ecec30":{"balance":"0xd3c21bcecceda1000000"},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"balance":"0xd3c21bcecceda1000000"},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"balance":"0xd3c21bcecceda1000000"},"0x71be63f3384f5fb98995898a86b02fb2426c5788":{"balance":"0xd3c21bcecceda1000000"},"0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199":{"balance":"0xd3c21bcecceda1000000"},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"balance":"0xd3c21bcecceda1000000"},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"balance":"0xd3c21bcecceda1000000"},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"balance":"0xd3c21bcecceda1000000"},"0x9c41de96b2088cdc640c6182dfcf5491dc574a57":{"balance":"0xd3c21bcecceda1000000"},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"balance":"0xd3c21bcecceda1000000"},"0xbcd4042de499d14e55001ccbb24a551f3b954096":{"balance":"0xd3c21bcecceda1000000"},"0xbda5747bfd65f08deb54cb465eb87d40e51b197e":{"balance":"0xd3c21bcecceda1000000"},"0xcd3b766ccdd6ae721141f452c550ca635964ce71":{"balance":"0xd3c21bcecceda1000000"},"0xdd2fd4581271e230360230f9337d5c0430bf44c0":{"balance":"0xd3c21bcecceda1000000"},"0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097":{"balance":"0xd3c21bcecceda1000000"},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"balance":"0xd3c21bcecceda1000000"},"0xfabb0ac9d68b0b445fb7357272ff202c5651694a":{"balance":"0xd3c21bcecceda1000000"}},"number":"0x0"} \ No newline at end of file +{ + "config": { + "chainId": 8453, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "mergeNetsplitBlock": 0, + "bedrockBlock": 0, + "regolithTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "optimism": { + "eip1559Elasticity": 6, + "eip1559Denominator": 50 + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x1cbd3b2770909d4e10f157cabc84c7264073c9ec": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x2546bcd3c84621e976d8185a91a922ae77ecec30": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x71be63f3384f5fb98995898a86b02fb2426c5788": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x9c41de96b2088cdc640c6182dfcf5491dc574a57": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbcd4042de499d14e55001ccbb24a551f3b954096": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbda5747bfd65f08deb54cb465eb87d40e51b197e": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xcd3b766ccdd6ae721141f452c550ca635964ce71": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdd2fd4581271e230360230f9337d5c0430bf44c0": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xfabb0ac9d68b0b445fb7357272ff202c5651694a": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x5300000000000000000000000000000000000002": { + "balance": "0xd3c21bcecceda1000000", + "storage": { + "0x01": "0x000000000000000000000000000000000000000000000000000000003758e6b0", + "0x02": "0x0000000000000000000000000000000000000000000000000000000000000038", + "0x03": "0x000000000000000000000000000000000000000000000000000000003e95ba80", + "0x04": "0x0000000000000000000000005300000000000000000000000000000000000003", + "0x05": "0x000000000000000000000000000000000000000000000000000000008390c2c1", + "0x06": "0x00000000000000000000000000000000000000000000000000000069cf265bfe", + "0x07": "0x00000000000000000000000000000000000000000000000000000000168b9aa3" + } + } + }, + "number": "0x0" +} \ No newline at end of file diff --git a/crates/scroll/node/tests/e2e/payload.rs b/crates/scroll/node/tests/e2e/payload.rs index 3ce333c28f99..51049890a37f 100644 --- a/crates/scroll/node/tests/e2e/payload.rs +++ b/crates/scroll/node/tests/e2e/payload.rs @@ -7,7 +7,8 @@ use tokio::sync::Mutex; async fn can_sync() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (node, _tasks, wallet) = setup(true).await?; + let (mut node, _tasks, wallet) = setup(1).await?; + let mut node = node.pop().unwrap(); let wallet = Arc::new(Mutex::new(wallet)); let tip: usize = 90; diff --git a/crates/scroll/payload/Cargo.toml b/crates/scroll/payload/Cargo.toml index e5d5caa364f1..fbd246b4969c 100644 --- a/crates/scroll/payload/Cargo.toml +++ b/crates/scroll/payload/Cargo.toml @@ -30,7 +30,7 @@ reth-chainspec.workspace = true reth-chain-state.workspace = true reth-evm.workspace = true reth-execution-types.workspace = true -reth-payload-builder = { workspace = true, optional = true } +reth-payload-builder.workspace = true reth-payload-primitives.workspace = true reth-primitives-traits.workspace = true reth-revm.workspace = true @@ -69,7 +69,6 @@ std = [ ] test-utils = [ "dep:futures-util", - "dep:reth-payload-builder", "reth-payload-builder/test-utils", "reth-primitives-traits/test-utils", "reth-transaction-pool/test-utils", diff --git a/crates/scroll/payload/src/builder.rs b/crates/scroll/payload/src/builder.rs index 2ce6da261a08..58b876442325 100644 --- a/crates/scroll/payload/src/builder.rs +++ b/crates/scroll/payload/src/builder.rs @@ -20,10 +20,10 @@ use reth_execution_types::ExecutionOutcome; use reth_payload_builder::PayloadId; use reth_payload_primitives::{PayloadBuilderAttributes, PayloadBuilderError}; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; -use reth_primitives_traits::{SealedHeader, SignedTransaction, TxTy}; +use reth_primitives_traits::{NodePrimitives, SealedHeader, SignedTransaction, TxTy}; use reth_revm::{cancelled::CancelOnDrop, database::StateProviderDatabase, db::State}; use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; -use reth_scroll_primitives::{ScrollPrimitives, ScrollReceipt, ScrollTransactionSigned}; +use reth_scroll_primitives::{transaction::signed::IsL1Message, ScrollPrimitives}; use reth_storage_api::{StateProvider, StateProviderFactory}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::context::{Block, BlockEnv}; @@ -38,7 +38,8 @@ const SCROLL_GAS_LIMIT_10M: u64 = 10_000_000; pub struct ScrollEmptyPayloadBuilder; impl PayloadBuilder for ScrollEmptyPayloadBuilder { - type Attributes = ScrollPayloadBuilderAttributes; + type Attributes = + ScrollPayloadBuilderAttributes<::SignedTx>; type BuiltPayload = ScrollBuiltPayload; fn try_build( @@ -110,11 +111,12 @@ impl ScrollPayloadBuilder { } } -impl ScrollPayloadBuilder +impl ScrollPayloadBuilder where - Pool: TransactionPool>, + Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, - Evm: ConfigureEvm, + N: ScrollPayloadPrimitives, + Evm: ConfigureEvm, { /// Constructs an Scroll payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -126,11 +128,11 @@ where /// a result indicating success with the payload or an error in case of failure. fn build_payload<'a, Txs>( &self, - args: BuildArguments, + args: BuildArguments, ScrollBuiltPayload>, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - ) -> Result, PayloadBuilderError> + ) -> Result>, PayloadBuilderError> where - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, { let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; @@ -158,16 +160,17 @@ where } /// Implementation of the [`PayloadBuilder`] trait for [`ScrollPayloadBuilder`]. -impl PayloadBuilder for ScrollPayloadBuilder +impl PayloadBuilder for ScrollPayloadBuilder where Client: StateProviderFactory + ChainSpecProvider + Clone, - Pool: TransactionPool>, - Evm: ConfigureEvm, + Pool: TransactionPool>, + Evm: ConfigureEvm, Txs: ScrollPayloadTransactions, + N: ScrollPayloadPrimitives, { - type Attributes = ScrollPayloadBuilderAttributes; - type BuiltPayload = ScrollBuiltPayload; + type Attributes = ScrollPayloadBuilderAttributes; + type BuiltPayload = ScrollBuiltPayload; fn try_build( &self, @@ -225,17 +228,17 @@ impl<'a, Txs> std::fmt::Debug for ScrollBuilder<'a, Txs> { impl ScrollBuilder<'_, Txs> { /// Builds the payload on top of the state. - pub fn build( + pub fn build( self, db: impl Database, state_provider: impl StateProvider, ctx: ScrollPayloadBuilderCtx, - ) -> Result, PayloadBuilderError> + ) -> Result>, PayloadBuilderError> where - EvmConfig: - ConfigureEvm, + EvmConfig: ConfigureEvm, ChainSpec: EthChainSpec + ScrollHardforks, - Txs: PayloadTransactions>, + N: ScrollPayloadPrimitives, + Txs: PayloadTransactions>, { let Self { best } = self; tracing::debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); @@ -281,19 +284,23 @@ impl ScrollBuilder<'_, Txs> { ); // create the executed block data - let executed: ExecutedBlockWithTrieUpdates = - ExecutedBlockWithTrieUpdates { - block: ExecutedBlock { - recovered_block: Arc::new(block), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - }, - trie: Arc::new(trie_updates), - }; + let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { + recovered_block: Arc::new(block), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + trie: Arc::new(trie_updates), + }; let no_tx_pool = ctx.attributes().no_tx_pool; - let payload = ScrollBuiltPayload::new(ctx.payload_id(), executed, info.total_fees); + let payload = ScrollBuiltPayload::new( + ctx.payload_id(), + sealed_block, + Some(executed), + info.total_fees, + ); if no_tx_pool { // if `no_tx_pool` is set only transactions from the payload attributes will be included @@ -314,11 +321,11 @@ 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. - pub best_payload: Option, + pub best_payload: Option>, } impl ScrollPayloadBuilderCtx @@ -334,7 +341,7 @@ where } /// Returns the builder attributes. - pub const fn attributes(&self) -> &ScrollPayloadBuilderAttributes { + pub const fn attributes(&self) -> &ScrollPayloadBuilderAttributes> { &self.config.attributes } @@ -402,7 +409,8 @@ where impl ScrollPayloadBuilderCtx where - Evm: ConfigureEvm, + Evm: + ConfigureEvm, ChainSpec: EthChainSpec + ScrollHardforks, { /// Executes all sequencer transactions that are included in the payload attributes. @@ -526,13 +534,13 @@ where /// Holds the state after execution #[derive(Debug)] -pub struct ExecutedPayload { +pub struct ExecutedPayload { /// Tracked execution info pub info: ExecutionInfo, /// Withdrawal hash. pub withdrawals_root: Option, /// The transaction receipts. - pub receipts: Vec, + pub receipts: Vec, /// The block env used during execution. pub block_env: BlockEnv, } diff --git a/crates/scroll/txpool/Cargo.toml b/crates/scroll/txpool/Cargo.toml new file mode 100644 index 000000000000..f891b1d8aa53 --- /dev/null +++ b/crates/scroll/txpool/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "reth-scroll-txpool" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# ethereum +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types-eth.workspace = true + +# reth +reth-chainspec.workspace = true +reth-primitives-traits.workspace = true +reth-chain-state.workspace = true +reth-revm.workspace = true +reth-storage-api.workspace = true +reth-transaction-pool.workspace = true + +# revm +revm-scroll.workspace = true + +# optimism +scroll-alloy-consensus.workspace = true +op-alloy-flz.workspace = true +reth-scroll-evm.workspace = true +reth-scroll-forks.workspace = true +reth-scroll-primitives.workspace = true + +# metrics +reth-metrics.workspace = true +metrics.workspace = true + +# misc +c-kzg.workspace = true +derive_more.workspace = true +futures-util.workspace = true +parking_lot.workspace = true + +[dev-dependencies] +reth-scroll-chainspec.workspace = true +reth-provider = { workspace = true, features = ["test-utils"] } diff --git a/crates/scroll/txpool/src/lib.rs b/crates/scroll/txpool/src/lib.rs new file mode 100644 index 000000000000..b339807eeed5 --- /dev/null +++ b/crates/scroll/txpool/src/lib.rs @@ -0,0 +1,16 @@ +//! Transaction pool for Scroll node. + +mod transaction; +pub use transaction::ScrollPooledTransaction; + +mod validator; +pub use validator::ScrollTransactionValidator; + +use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; + +/// Type alias for default optimism transaction pool +pub type ScrollTransactionPool = Pool< + TransactionValidationTaskExecutor>, + CoinbaseTipOrdering, + S, +>; diff --git a/crates/scroll/txpool/src/transaction.rs b/crates/scroll/txpool/src/transaction.rs new file mode 100644 index 000000000000..68ea2eafc0f6 --- /dev/null +++ b/crates/scroll/txpool/src/transaction.rs @@ -0,0 +1,252 @@ +use alloy_consensus::{ + transaction::Recovered, BlobTransactionSidecar, BlobTransactionValidationError, Typed2718, +}; +use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use c_kzg::KzgSettings; +use core::fmt::Debug; +use reth_primitives_traits::{InMemorySize, SignedTransaction}; +use reth_scroll_primitives::ScrollTransactionSigned; +use reth_transaction_pool::{ + EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, PoolTransaction, +}; +use std::sync::{Arc, OnceLock}; + +/// Pool transaction for Scroll. +/// +/// This type wraps the actual transaction and caches values that are frequently used by the pool. +/// For payload building this lazily tracks values that are required during payload building: +/// - Estimated compressed size of this transaction +#[derive(Debug, Clone, derive_more::Deref)] +pub struct ScrollPooledTransaction< + Cons = ScrollTransactionSigned, + Pooled = scroll_alloy_consensus::ScrollPooledTransaction, +> { + #[deref] + inner: EthPooledTransaction, + /// The pooled transaction type. + _pd: core::marker::PhantomData, + + /// Cached EIP-2718 encoded bytes of the transaction, lazily computed. + encoded_2718: OnceLock, +} + +impl ScrollPooledTransaction { + /// Create new instance of [Self]. + pub fn new(transaction: Recovered, encoded_length: usize) -> Self { + Self { + inner: EthPooledTransaction::new(transaction, encoded_length), + _pd: core::marker::PhantomData, + encoded_2718: Default::default(), + } + } + + /// Returns lazily computed EIP-2718 encoded bytes of the transaction. + pub fn encoded_2718(&self) -> &Bytes { + self.encoded_2718.get_or_init(|| self.inner.transaction().encoded_2718().into()) + } +} + +impl PoolTransaction for ScrollPooledTransaction +where + Cons: SignedTransaction + From, + Pooled: SignedTransaction + TryFrom, +{ + type TryFromConsensusError = >::Error; + type Consensus = Cons; + type Pooled = Pooled; + + fn clone_into_consensus(&self) -> Recovered { + self.inner.transaction().clone() + } + + fn into_consensus(self) -> Recovered { + self.inner.transaction + } + + fn from_pooled(tx: Recovered) -> Self { + let encoded_len = tx.encode_2718_len(); + Self::new(tx.convert(), encoded_len) + } + + fn hash(&self) -> &TxHash { + self.inner.transaction.tx_hash() + } + + fn sender(&self) -> Address { + self.inner.transaction.signer() + } + + fn sender_ref(&self) -> &Address { + self.inner.transaction.signer_ref() + } + + fn cost(&self) -> &U256 { + &self.inner.cost + } + + fn encoded_length(&self) -> usize { + self.inner.encoded_length + } +} + +impl Typed2718 for ScrollPooledTransaction { + fn ty(&self) -> u8 { + self.inner.ty() + } +} + +impl InMemorySize for ScrollPooledTransaction { + fn size(&self) -> usize { + self.inner.size() + } +} + +impl alloy_consensus::Transaction for ScrollPooledTransaction +where + Cons: alloy_consensus::Transaction, + Pooled: Debug + Send + Sync + 'static, +{ + fn chain_id(&self) -> Option { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option { + self.inner.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.inner.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + self.inner.effective_gas_price(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.inner.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn is_create(&self) -> bool { + self.inner.is_create() + } + + fn value(&self) -> U256 { + self.inner.value() + } + + fn input(&self) -> &Bytes { + self.inner.input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } +} + +impl EthPoolTransaction for ScrollPooledTransaction +where + Cons: SignedTransaction + From, + Pooled: SignedTransaction + TryFrom, + >::Error: core::error::Error, +{ + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + EthBlobTransactionSidecar::None + } + + fn try_into_pooled_eip4844( + self, + _sidecar: Arc, + ) -> Option> { + None + } + + fn try_from_eip4844( + _tx: Recovered, + _sidecar: BlobTransactionSidecar, + ) -> Option { + None + } + + fn validate_blob( + &self, + _sidecar: &BlobTransactionSidecar, + _settings: &KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + Err(BlobTransactionValidationError::NotBlobTransaction(self.ty())) + } +} + +#[cfg(test)] +mod tests { + use crate::{ScrollPooledTransaction, ScrollTransactionValidator}; + use alloy_consensus::transaction::Recovered; + use alloy_eips::eip2718::Encodable2718; + use alloy_primitives::PrimitiveSignature as Signature; + use reth_provider::test_utils::MockEthProvider; + use reth_scroll_chainspec::SCROLL_MAINNET; + use reth_scroll_primitives::ScrollTransactionSigned; + use reth_transaction_pool::{ + blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, TransactionOrigin, + TransactionValidationOutcome, + }; + use scroll_alloy_consensus::{ScrollTypedTransaction, TxL1Message}; + #[test] + fn validate_optimism_transaction() { + let client = MockEthProvider::default().with_chain_spec(SCROLL_MAINNET.clone()); + let validator = EthTransactionValidatorBuilder::new(client) + .no_shanghai() + .no_cancun() + .build(InMemoryBlobStore::default()); + let validator = ScrollTransactionValidator::new(validator); + + let origin = TransactionOrigin::External; + let signer = Default::default(); + let deposit_tx = ScrollTypedTransaction::L1Message(TxL1Message::default()); + let signature = Signature::test_signature(); + let signed_tx = ScrollTransactionSigned::new_unhashed(deposit_tx, signature); + let signed_recovered = Recovered::new_unchecked(signed_tx, signer); + let len = signed_recovered.encode_2718_len(); + let pooled_tx: ScrollPooledTransaction = + ScrollPooledTransaction::new(signed_recovered, len); + let outcome = validator.validate_one(origin, pooled_tx); + + let err = match outcome { + TransactionValidationOutcome::Invalid(_, err) => err, + _ => panic!("Expected invalid transaction"), + }; + assert_eq!(err.to_string(), "transaction type not supported"); + } +} diff --git a/crates/scroll/txpool/src/validator.rs b/crates/scroll/txpool/src/validator.rs new file mode 100644 index 000000000000..c867a83262c0 --- /dev/null +++ b/crates/scroll/txpool/src/validator.rs @@ -0,0 +1,249 @@ +use alloy_consensus::BlockHeader; +use alloy_eips::Encodable2718; +use parking_lot::RwLock; +use reth_chainspec::ChainSpecProvider; +use reth_primitives_traits::{ + transaction::error::InvalidTransactionError, Block, GotExpected, SealedBlock, +}; +use reth_revm::database::StateProviderDatabase; +use reth_scroll_evm::{spec_id_at_timestamp_and_number, RethL1BlockInfo}; +use reth_scroll_forks::ScrollHardforks; +use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; +use reth_transaction_pool::{ + EthPoolTransaction, EthTransactionValidator, TransactionOrigin, TransactionValidationOutcome, + TransactionValidator, +}; +use revm_scroll::l1block::L1BlockInfo; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; + +/// Tracks additional infos for the current block. +#[derive(Debug, Default)] +pub struct ScrollL1BlockInfo { + /// The current L1 block info. + l1_block_info: RwLock, + /// Current block timestamp. + timestamp: AtomicU64, + /// Current block number. + number: AtomicU64, +} + +/// Validator for Optimism transactions. +#[derive(Debug, Clone)] +pub struct ScrollTransactionValidator { + /// The type that performs the actual validation. + inner: EthTransactionValidator, + /// Additional block info required for validation. + block_info: Arc, + /// If true, ensure that the transaction's sender has enough balance to cover the L1 gas fee + /// derived from the tracked L1 block info. + require_l1_data_gas_fee: bool, +} + +impl ScrollTransactionValidator { + /// Returns the configured chain spec + pub fn chain_spec(&self) -> Arc + where + Client: ChainSpecProvider, + { + self.inner.chain_spec() + } + + /// Returns the configured client + pub fn client(&self) -> &Client { + self.inner.client() + } + + /// Returns the current block timestamp. + fn block_timestamp(&self) -> u64 { + self.block_info.timestamp.load(Ordering::Relaxed) + } + + /// Returns the current block number. + fn block_number(&self) -> u64 { + self.block_info.number.load(Ordering::Relaxed) + } + + /// Whether to ensure that the transaction's sender has enough balance to also cover the L1 gas + /// fee. + pub fn require_l1_data_gas_fee(self, require_l1_data_gas_fee: bool) -> Self { + Self { require_l1_data_gas_fee, ..self } + } + + /// Returns whether this validator also requires the transaction's sender to have enough balance + /// to cover the L1 gas fee. + pub const fn requires_l1_data_gas_fee(&self) -> bool { + self.require_l1_data_gas_fee + } +} + +impl ScrollTransactionValidator +where + Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, + Tx: EthPoolTransaction, +{ + /// Create a new [`OpTransactionValidator`]. + pub fn new(inner: EthTransactionValidator) -> Self { + let this = Self::with_block_info(inner, ScrollL1BlockInfo::default()); + if let Ok(Some(block)) = + this.inner.client().block_by_number_or_tag(alloy_eips::BlockNumberOrTag::Latest) + { + // genesis block has no txs, so we can't extract L1 info, we set the block info to empty + // so that we will accept txs into the pool before the first block + if block.header().number() == 0 { + this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed); + this.block_info.number.store(block.header().number(), Ordering::Relaxed); + } else { + this.update_l1_block_info(block.header()); + } + } + + this + } + + /// Create a new [`ScrollTransactionValidator`] with the given [`ScrollL1BlockInfo`]. + pub fn with_block_info( + inner: EthTransactionValidator, + block_info: ScrollL1BlockInfo, + ) -> Self { + Self { inner, block_info: Arc::new(block_info), require_l1_data_gas_fee: true } + } + + /// Update the L1 block info for the given header and system transaction, if any. + pub fn update_l1_block_info(&self, header: &H) + where + H: BlockHeader, + { + self.block_info.timestamp.store(header.timestamp(), Ordering::Relaxed); + self.block_info.number.store(header.number(), Ordering::Relaxed); + + let provider = + self.client().state_by_block_number_or_tag(header.number().into()).expect("msg"); + let mut db = StateProviderDatabase::new(provider); + let spec_id = + spec_id_at_timestamp_and_number(header.timestamp(), header.number(), self.chain_spec()); + if let Ok(l1_block_info) = L1BlockInfo::try_fetch(&mut db, spec_id) { + *self.block_info.l1_block_info.write() = l1_block_info; + } + } + + /// Validates a single transaction. + /// + /// See also [`TransactionValidator::validate_transaction`] + /// + /// This behaves the same as [`EthTransactionValidator::validate_one`], but in addition, ensures + /// that the account has enough balance to cover the L1 gas cost. + pub fn validate_one( + &self, + origin: TransactionOrigin, + transaction: Tx, + ) -> TransactionValidationOutcome { + if transaction.is_eip4844() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::TxTypeNotSupported.into(), + ) + } + + let outcome = self.inner.validate_one(origin, transaction); + + if !self.requires_l1_data_gas_fee() { + // no need to check L1 gas fee + return outcome + } + + // ensure that the account has enough balance to cover the L1 gas cost + if let TransactionValidationOutcome::Valid { + balance, + state_nonce, + transaction: valid_tx, + propagate, + } = outcome + { + let mut l1_block_info = self.block_info.l1_block_info.read().clone(); + + let mut encoded = Vec::with_capacity(valid_tx.transaction().encoded_length()); + let tx = valid_tx.transaction().clone_into_consensus(); + tx.encode_2718(&mut encoded); + + let cost_addition = match l1_block_info.l1_tx_data_fee( + self.chain_spec(), + self.block_timestamp(), + self.block_number(), + &encoded, + false, + ) { + Ok(cost) => cost, + Err(err) => { + return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err)) + } + }; + let cost = valid_tx.transaction().cost().saturating_add(cost_addition); + + // Checks for max cost + if cost > balance { + return TransactionValidationOutcome::Invalid( + valid_tx.into_transaction(), + InvalidTransactionError::InsufficientFunds( + GotExpected { got: balance, expected: cost }.into(), + ) + .into(), + ) + } + + return TransactionValidationOutcome::Valid { + balance, + state_nonce, + transaction: valid_tx, + propagate, + } + } + + outcome + } + + /// Validates all given transactions. + /// + /// Returns all outcomes for the given transactions in the same order. + /// + /// See also [`Self::validate_one`] + pub fn validate_all( + &self, + transactions: Vec<(TransactionOrigin, Tx)>, + ) -> Vec> { + transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)).collect() + } +} + +impl TransactionValidator for ScrollTransactionValidator +where + Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, + Tx: EthPoolTransaction, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome { + self.validate_one(origin, transaction) + } + + async fn validate_transactions( + &self, + transactions: Vec<(TransactionOrigin, Self::Transaction)>, + ) -> Vec> { + self.validate_all(transactions) + } + + fn on_new_head_block(&self, new_tip_block: &SealedBlock) + where + B: Block, + { + self.inner.on_new_head_block(new_tip_block); + self.update_l1_block_info(new_tip_block.header()); + } +} From 6c2327a0d226352ef56da4da8db47793fe6c44aa Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 2 Apr 2025 12:40:36 +0700 Subject: [PATCH 3/7] land first stable scroll payload builder --- crates/scroll/evm/src/config.rs | 2 +- crates/scroll/node/Cargo.toml | 3 ++- crates/scroll/node/src/builder/engine.rs | 9 ++++++++ crates/scroll/node/src/test_utils.rs | 8 +++++--- crates/scroll/node/tests/e2e/payload.rs | 7 +------ crates/scroll/payload/src/builder.rs | 26 ------------------------ crates/scroll/payload/src/lib.rs | 2 +- crates/scroll/txpool/src/transaction.rs | 2 +- crates/scroll/txpool/src/validator.rs | 9 +++----- 9 files changed, 23 insertions(+), 45 deletions(-) diff --git a/crates/scroll/evm/src/config.rs b/crates/scroll/evm/src/config.rs index f9f66f2e5fbe..f7a5119a954f 100644 --- a/crates/scroll/evm/src/config.rs +++ b/crates/scroll/evm/src/config.rs @@ -129,7 +129,7 @@ where parent: &SealedHeader, _attributes: Self::NextBlockEnvCtx, ) -> ExecutionCtxFor<'_, Self> { - ScrollBlockExecutionCtx { parent_hash: parent.header().parent_hash() } + ScrollBlockExecutionCtx { parent_hash: parent.hash() } } } diff --git a/crates/scroll/node/Cargo.toml b/crates/scroll/node/Cargo.toml index de4abb5b1281..d577d788017a 100644 --- a/crates/scroll/node/Cargo.toml +++ b/crates/scroll/node/Cargo.toml @@ -119,5 +119,6 @@ scroll-alloy-traits = [ "reth-db/scroll-alloy-traits", "reth-evm/scroll-alloy-traits", "reth-primitives-traits/scroll-alloy-traits", - "reth-scroll-node/scroll-alloy-traits" + "reth-scroll-node/scroll-alloy-traits", + "reth-engine-local/scroll-alloy-traits" ] diff --git a/crates/scroll/node/src/builder/engine.rs b/crates/scroll/node/src/builder/engine.rs index 93d84578d459..959232a4fe07 100644 --- a/crates/scroll/node/src/builder/engine.rs +++ b/crates/scroll/node/src/builder/engine.rs @@ -87,6 +87,15 @@ impl PayloadValidator for ScrollEngineValidator { // First parse the block let mut block = try_into_block(payload, self.chainspec.clone())?; + // Seal the block and return if hashes match + let block_hash = block.hash_slow(); + if block_hash == expected_hash { + return block + .seal_unchecked(block_hash) + .try_recover() + .map_err(|err| NewPayloadError::Other(err.into())); + } + // Seal the block with the in-turn difficulty and return if hashes match block.header.difficulty = CLIQUE_IN_TURN_DIFFICULTY; let block_hash_in_turn = block.hash_slow(); diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs index 9f21b4d5ad9f..55348784d17a 100644 --- a/crates/scroll/node/src/test_utils.rs +++ b/crates/scroll/node/src/test_utils.rs @@ -26,10 +26,12 @@ pub(crate) type ScrollNode = NodeHelperType< >; /// Creates the initial setup with `num_nodes` of the node config, started and connected. -pub async fn setup(num_nodes: usize) -> eyre::Result<(Vec, TaskManager, Wallet)> { +pub async fn setup( + num_nodes: usize, + is_dev: bool, +) -> eyre::Result<(Vec, TaskManager, Wallet)> { let genesis: Genesis = serde_json::from_str(include_str!("../tests/assets/genesis.json")).unwrap(); - println!("{:?}", genesis); reth_e2e_test_utils::setup_engine( num_nodes, Arc::new( @@ -38,7 +40,7 @@ pub async fn setup(num_nodes: usize) -> eyre::Result<(Vec, TaskManag .darwin_v2_activated() .build(Default::default()), ), - false, + is_dev, scroll_payload_attributes::, ) .await diff --git a/crates/scroll/node/tests/e2e/payload.rs b/crates/scroll/node/tests/e2e/payload.rs index 51049890a37f..e338e805a62e 100644 --- a/crates/scroll/node/tests/e2e/payload.rs +++ b/crates/scroll/node/tests/e2e/payload.rs @@ -1,4 +1,3 @@ -use futures::StreamExt; use reth_scroll_node::test_utils::{advance_chain, setup}; use std::sync::Arc; use tokio::sync::Mutex; @@ -7,18 +6,14 @@ use tokio::sync::Mutex; async fn can_sync() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut node, _tasks, wallet) = setup(1).await?; + let (mut node, _tasks, wallet) = setup(3, false).await?; let mut node = node.pop().unwrap(); let wallet = Arc::new(Mutex::new(wallet)); let tip: usize = 90; - let tip_index: usize = tip - 1; - let reorg_depth = 2; // On first node, create a chain up to block number 90a let canonical_payload_chain = advance_chain(tip, &mut node, wallet.clone()).await?; - let canonical_chain = - canonical_payload_chain.iter().map(|p| p.block().hash()).collect::>(); Ok(()) } diff --git a/crates/scroll/payload/src/builder.rs b/crates/scroll/payload/src/builder.rs index 58b876442325..e54c29b62b50 100644 --- a/crates/scroll/payload/src/builder.rs +++ b/crates/scroll/payload/src/builder.rs @@ -32,32 +32,6 @@ use std::{boxed::Box, sync::Arc, vec, vec::Vec}; const SCROLL_GAS_LIMIT_10M: u64 = 10_000_000; -/// A type that implements [`PayloadBuilder`] by building empty payloads. -#[derive(Debug, Default, Clone)] -#[non_exhaustive] -pub struct ScrollEmptyPayloadBuilder; - -impl PayloadBuilder for ScrollEmptyPayloadBuilder { - type Attributes = - ScrollPayloadBuilderAttributes<::SignedTx>; - type BuiltPayload = ScrollBuiltPayload; - - fn try_build( - &self, - _args: BuildArguments, - ) -> Result, PayloadBuilderError> { - // we can't currently actually build a payload, so we mark the outcome as cancelled. - Ok(BuildOutcome::Cancelled) - } - - fn build_empty_payload( - &self, - _config: PayloadConfig>, - ) -> Result { - Ok(ScrollBuiltPayload::default()) - } -} - /// A type that returns the [`PayloadTransactions`] that should be included in the pool. pub trait ScrollPayloadTransactions: Clone + Send + Sync + Unpin + 'static { /// Returns an iterator that yields the transaction in the order they should get included in the diff --git a/crates/scroll/payload/src/lib.rs b/crates/scroll/payload/src/lib.rs index 2359a5ddb2cc..8189d6877b1f 100644 --- a/crates/scroll/payload/src/lib.rs +++ b/crates/scroll/payload/src/lib.rs @@ -6,7 +6,7 @@ extern crate alloc as std; pub mod builder; -pub use builder::{ScrollEmptyPayloadBuilder, ScrollPayloadBuilder, ScrollPayloadTransactions}; +pub use builder::{ScrollPayloadBuilder, ScrollPayloadTransactions}; mod error; pub use error::ScrollPayloadBuilderError; diff --git a/crates/scroll/txpool/src/transaction.rs b/crates/scroll/txpool/src/transaction.rs index 68ea2eafc0f6..dbf604da20dc 100644 --- a/crates/scroll/txpool/src/transaction.rs +++ b/crates/scroll/txpool/src/transaction.rs @@ -224,7 +224,7 @@ mod tests { }; use scroll_alloy_consensus::{ScrollTypedTransaction, TxL1Message}; #[test] - fn validate_optimism_transaction() { + fn validate_scroll_transaction() { let client = MockEthProvider::default().with_chain_spec(SCROLL_MAINNET.clone()); let validator = EthTransactionValidatorBuilder::new(client) .no_shanghai() diff --git a/crates/scroll/txpool/src/validator.rs b/crates/scroll/txpool/src/validator.rs index c867a83262c0..d0ef2804f58c 100644 --- a/crates/scroll/txpool/src/validator.rs +++ b/crates/scroll/txpool/src/validator.rs @@ -92,12 +92,9 @@ where { // genesis block has no txs, so we can't extract L1 info, we set the block info to empty // so that we will accept txs into the pool before the first block - if block.header().number() == 0 { - this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed); - this.block_info.number.store(block.header().number(), Ordering::Relaxed); - } else { - this.update_l1_block_info(block.header()); - } + this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed); + this.block_info.number.store(block.header().number(), Ordering::Relaxed); + this.update_l1_block_info(block.header()); } this From d8d82ffd3ad6c4c0731c176e0dc5c17914f0303f Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 2 Apr 2025 13:19:04 +0700 Subject: [PATCH 4/7] clean up --- .../alloy/provider/src/engine/provider.rs | 8 +-- .../src/payload/attributes.rs | 23 ++----- .../engine-primitives/src/payload/built.rs | 62 +++++++---------- .../engine-primitives/src/payload/mod.rs | 13 ++-- crates/scroll/node/Cargo.toml | 54 +++++++-------- crates/scroll/node/src/builder/pool.rs | 33 ++-------- crates/scroll/node/src/test_utils.rs | 19 ++---- crates/scroll/node/tests/e2e/main.rs | 3 +- crates/scroll/node/tests/e2e/payload.rs | 6 +- crates/scroll/payload/Cargo.toml | 46 ++++++------- crates/scroll/payload/src/builder.rs | 66 +++++++++---------- 11 files changed, 134 insertions(+), 199 deletions(-) diff --git a/crates/scroll/alloy/provider/src/engine/provider.rs b/crates/scroll/alloy/provider/src/engine/provider.rs index 8d039b47dc1b..ce94360a1b5f 100644 --- a/crates/scroll/alloy/provider/src/engine/provider.rs +++ b/crates/scroll/alloy/provider/src/engine/provider.rs @@ -112,7 +112,6 @@ mod tests { }; use reth_scroll_node::ScrollEngineValidator; use reth_scroll_payload::NoopPayloadJobGenerator; - use reth_scroll_primitives::ScrollTransactionSigned; use reth_tasks::TokioTaskExecutor; use reth_transaction_pool::noop::NoopTransactionPool; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; @@ -121,15 +120,12 @@ mod tests { fn spawn_test_payload_service() -> PayloadBuilderHandle where T: PayloadTypes< - PayloadBuilderAttributes = ScrollPayloadBuilderAttributes, + PayloadBuilderAttributes = ScrollPayloadBuilderAttributes, BuiltPayload = ScrollBuiltPayload, > + 'static, { let (service, handle) = PayloadBuilderService::< - NoopPayloadJobGenerator< - ScrollPayloadBuilderAttributes, - ScrollBuiltPayload, - >, + NoopPayloadJobGenerator, futures_util::stream::Empty, T, >::new(Default::default(), futures_util::stream::empty()); diff --git a/crates/scroll/engine-primitives/src/payload/attributes.rs b/crates/scroll/engine-primitives/src/payload/attributes.rs index c7bc33305754..e628aba91d98 100644 --- a/crates/scroll/engine-primitives/src/payload/attributes.rs +++ b/crates/scroll/engine-primitives/src/payload/attributes.rs @@ -10,33 +10,22 @@ use alloy_rpc_types_engine::PayloadId; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_payload_primitives::PayloadBuilderAttributes; use reth_primitives::transaction::WithEncoded; +use reth_scroll_primitives::ScrollTransactionSigned; use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; /// Scroll Payload Builder Attributes -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ScrollPayloadBuilderAttributes { +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct ScrollPayloadBuilderAttributes { /// Inner ethereum payload builder attributes pub payload_attributes: EthPayloadBuilderAttributes, /// `NoTxPool` option for the generated payload pub no_tx_pool: bool, /// Decoded transactions and the original EIP-2718 encoded bytes as received in the payload /// attributes. - pub transactions: Vec>, + pub transactions: Vec>, } -impl Default for ScrollPayloadBuilderAttributes { - fn default() -> Self { - Self { - payload_attributes: Default::default(), - no_tx_pool: false, - transactions: Default::default(), - } - } -} - -impl PayloadBuilderAttributes - for ScrollPayloadBuilderAttributes -{ +impl PayloadBuilderAttributes for ScrollPayloadBuilderAttributes { type RpcPayloadAttributes = ScrollPayloadAttributes; type Error = alloy_rlp::Error; @@ -150,7 +139,7 @@ pub(crate) fn payload_id_scroll( PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length")) } -impl From for ScrollPayloadBuilderAttributes { +impl From for ScrollPayloadBuilderAttributes { fn from(value: EthPayloadBuilderAttributes) -> Self { Self { payload_attributes: value, ..Default::default() } } diff --git a/crates/scroll/engine-primitives/src/payload/built.rs b/crates/scroll/engine-primitives/src/payload/built.rs index 1b7d4b112b63..e6c44c64eb71 100644 --- a/crates/scroll/engine-primitives/src/payload/built.rs +++ b/crates/scroll/engine-primitives/src/payload/built.rs @@ -3,7 +3,6 @@ use core::iter; use std::sync::Arc; -use alloy_consensus::Block; use alloy_eips::eip7685::Requests; use alloy_primitives::U256; use alloy_rpc_types_engine::{ @@ -13,29 +12,28 @@ use alloy_rpc_types_engine::{ }; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_payload_primitives::BuiltPayload; -use reth_primitives::NodePrimitives; -use reth_primitives_traits::{SealedBlock, SignedTransaction}; -use reth_scroll_primitives::ScrollPrimitives; +use reth_primitives_traits::SealedBlock; +use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; /// Contains the built payload. #[derive(Debug, Clone, Default)] -pub struct ScrollBuiltPayload { +pub struct ScrollBuiltPayload { /// Identifier of the payload pub(crate) id: PayloadId, /// Sealed block - pub(crate) block: Arc>, + pub(crate) block: Arc>, /// Block execution data for the payload - pub(crate) executed_block: Option>, + pub(crate) executed_block: Option>, /// The fees of the block pub(crate) fees: U256, } -impl ScrollBuiltPayload { +impl ScrollBuiltPayload { /// Initializes the payload with the given initial block. pub const fn new( id: PayloadId, - block: Arc>, - executed_block: Option>, + block: Arc>, + executed_block: Option>, fees: U256, ) -> Self { Self { id, block, executed_block, fees } @@ -48,7 +46,7 @@ impl ScrollBuiltPayload { /// Returns the built block(sealed) #[allow(clippy::missing_const_for_fn)] - pub fn block(&self) -> &SealedBlock { + pub fn block(&self) -> &SealedBlock { &self.block } @@ -58,15 +56,15 @@ impl ScrollBuiltPayload { } /// Converts the value into [`SealedBlock`]. - pub fn into_sealed_block(self) -> SealedBlock { + pub fn into_sealed_block(self) -> SealedBlock { Arc::unwrap_or_clone(self.block) } } -impl BuiltPayload for ScrollBuiltPayload { - type Primitives = N; +impl BuiltPayload for ScrollBuiltPayload { + type Primitives = ScrollPrimitives; - fn block(&self) -> &SealedBlock { + fn block(&self) -> &SealedBlock { self.block() } @@ -74,7 +72,7 @@ impl BuiltPayload for ScrollBuiltPayload { self.fees } - fn executed_block(&self) -> Option> { + fn executed_block(&self) -> Option> { self.executed_block.clone() } @@ -84,12 +82,8 @@ impl BuiltPayload for ScrollBuiltPayload { } // V1 engine_getPayloadV1 response -impl From> for ExecutionPayloadV1 -where - T: SignedTransaction, - N: NodePrimitives>, -{ - fn from(value: ScrollBuiltPayload) -> Self { +impl From for ExecutionPayloadV1 { + fn from(value: ScrollBuiltPayload) -> Self { Self::from_block_unchecked( value.block().hash(), &Arc::unwrap_or_clone(value.block).into_block(), @@ -98,12 +92,8 @@ where } // V2 engine_getPayloadV2 response -impl From> for ExecutionPayloadEnvelopeV2 -where - T: SignedTransaction, - N: NodePrimitives>, -{ - fn from(value: ScrollBuiltPayload) -> Self { +impl From for ExecutionPayloadEnvelopeV2 { + fn from(value: ScrollBuiltPayload) -> Self { let ScrollBuiltPayload { block, fees, .. } = value; Self { @@ -116,12 +106,8 @@ where } } -impl From> for ExecutionPayloadEnvelopeV3 -where - T: SignedTransaction, - N: NodePrimitives>, -{ - fn from(value: ScrollBuiltPayload) -> Self { +impl From for ExecutionPayloadEnvelopeV3 { + fn from(value: ScrollBuiltPayload) -> Self { let ScrollBuiltPayload { block, fees, .. } = value; Self { @@ -143,12 +129,8 @@ where } } } -impl From> for ExecutionPayloadEnvelopeV4 -where - T: SignedTransaction, - N: NodePrimitives>, -{ - fn from(value: ScrollBuiltPayload) -> Self { +impl From for ExecutionPayloadEnvelopeV4 { + fn from(value: ScrollBuiltPayload) -> Self { Self { envelope_inner: value.into(), execution_requests: Default::default() } } } diff --git a/crates/scroll/engine-primitives/src/payload/mod.rs b/crates/scroll/engine-primitives/src/payload/mod.rs index 4a0d7be5096b..18ef28efe0a5 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_payload_primitives::{BuiltPayload, PayloadTypes}; use reth_primitives::{Block, BlockBody, Header}; use reth_primitives_traits::{NodePrimitives, SealedBlock}; use reth_scroll_chainspec::ScrollChainSpec; -use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives}; +use reth_scroll_primitives::ScrollBlock; use scroll_alloy_hardforks::ScrollHardfork; use scroll_alloy_rpc_types_engine::ScrollPayloadAttributes; @@ -74,16 +74,13 @@ where /// A default payload type for [`ScrollEngineTypes`] #[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] #[non_exhaustive] -pub struct ScrollPayloadTypes(core::marker::PhantomData); +pub struct ScrollPayloadTypes; -impl PayloadTypes for ScrollPayloadTypes -where - ScrollBuiltPayload: BuiltPayload>, -{ +impl PayloadTypes for ScrollPayloadTypes { type ExecutionData = ExecutionData; - type BuiltPayload = ScrollBuiltPayload; + type BuiltPayload = ScrollBuiltPayload; type PayloadAttributes = ScrollPayloadAttributes; - type PayloadBuilderAttributes = ScrollPayloadBuilderAttributes; + type PayloadBuilderAttributes = ScrollPayloadBuilderAttributes; fn block_to_payload( block: SealedBlock< diff --git a/crates/scroll/node/Cargo.toml b/crates/scroll/node/Cargo.toml index d577d788017a..4a32b7d9b168 100644 --- a/crates/scroll/node/Cargo.toml +++ b/crates/scroll/node/Cargo.toml @@ -31,7 +31,7 @@ reth-primitives = { workspace = true, features = ["c-kzg"] } reth-primitives-traits.workspace = true reth-provider.workspace = true reth-rpc-server-types = { workspace = true, optional = true } -reth-tasks = { workspace = true, optional = true} +reth-tasks = { workspace = true, optional = true } reth-tracing.workspace = true reth-transaction-pool.workspace = true reth-trie-db.workspace = true @@ -87,38 +87,38 @@ futures.workspace = true [features] default = ["reth-codec", "scroll-alloy-traits"] reth-codec = [ - "reth-scroll-primitives/reth-codec" + "reth-scroll-primitives/reth-codec", ] skip-state-root-validation = [ "reth-provider/skip-state-root-validation", "reth-node-builder/skip-state-root-validation", ] test-utils = [ - "dep:alloy-genesis", - "dep:reth-e2e-test-utils", - "dep:reth-node-core", - "dep:reth-rpc-server-types", - "dep:reth-tasks", - "dep:serde_json", - "reth-chainspec/test-utils", - "reth-evm/test-utils", - "reth-network/test-utils", - "reth-node-builder/test-utils", - "reth-payload-builder/test-utils", - "reth-primitives/test-utils", - "reth-primitives-traits/test-utils", - "reth-provider/test-utils", - "reth-scroll-payload/test-utils", - "reth-transaction-pool/test-utils", - "reth-trie-db/test-utils", - "reth-db/test-utils", - "reth-revm/test-utils", - "reth-scroll-node/test-utils" + "dep:alloy-genesis", + "dep:reth-e2e-test-utils", + "dep:reth-node-core", + "dep:reth-rpc-server-types", + "dep:reth-tasks", + "dep:serde_json", + "reth-chainspec/test-utils", + "reth-evm/test-utils", + "reth-network/test-utils", + "reth-node-builder/test-utils", + "reth-payload-builder/test-utils", + "reth-primitives/test-utils", + "reth-primitives-traits/test-utils", + "reth-provider/test-utils", + "reth-scroll-payload/test-utils", + "reth-transaction-pool/test-utils", + "reth-trie-db/test-utils", + "reth-db/test-utils", + "reth-revm/test-utils", + "reth-scroll-node/test-utils", ] scroll-alloy-traits = [ - "reth-db/scroll-alloy-traits", - "reth-evm/scroll-alloy-traits", - "reth-primitives-traits/scroll-alloy-traits", - "reth-scroll-node/scroll-alloy-traits", - "reth-engine-local/scroll-alloy-traits" + "reth-db/scroll-alloy-traits", + "reth-evm/scroll-alloy-traits", + "reth-primitives-traits/scroll-alloy-traits", + "reth-scroll-node/scroll-alloy-traits", + "reth-engine-local/scroll-alloy-traits", ] diff --git a/crates/scroll/node/src/builder/pool.rs b/crates/scroll/node/src/builder/pool.rs index 9d8e115d0b07..0691fc669579 100644 --- a/crates/scroll/node/src/builder/pool.rs +++ b/crates/scroll/node/src/builder/pool.rs @@ -1,36 +1,16 @@ -use alloy_consensus::{constants::EIP1559_TX_TYPE_ID, Transaction, Typed2718}; -use alloy_eips::{ - eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M, - eip2718::Encodable2718, - eip2930::AccessList, - eip4844::{BlobAndProofV1, BlobTransactionSidecar, BlobTransactionValidationError}, - eip7702::SignedAuthorization, -}; -use alloy_primitives::{Address, Bytes, ChainId, TxHash, TxKind, B256, U256}; -use reth_eth_wire_types::HandleMempoolData; use reth_node_api::{FullNodeTypes, NodeTypes}; use reth_node_builder::{ components::{PoolBuilder, PoolBuilderConfigOverrides}, BuilderContext, TxTy, }; -use reth_primitives::{kzg::KzgSettings, Recovered}; -use reth_primitives_traits::{ - transaction::error::TryFromRecoveredTransactionError, SignedTransaction, -}; + use reth_provider::CanonStateSubscriptions; -use reth_scroll_primitives::ScrollTransactionSigned; use reth_scroll_txpool::{ScrollTransactionPool, ScrollTransactionValidator}; use reth_transaction_pool::{ - blobstore::DiskFileBlobStore, error::PoolError, AllPoolTransactions, AllTransactionsEvents, - BestTransactions, BestTransactionsAttributes, BlobStoreError, BlockInfo, CoinbaseTipOrdering, - EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, GetPooledTransactionLimit, - NewBlobSidecar, NewTransactionEvent, PoolResult, PoolSize, PoolTransaction, - PropagatedTransactions, TransactionEvents, TransactionListenerKind, TransactionOrigin, - TransactionPool, TransactionValidationTaskExecutor, ValidPoolTransaction, + blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPoolTransaction, + TransactionValidationTaskExecutor, }; use scroll_alloy_hardforks::ScrollHardforks; -use std::{collections::HashSet, sync::Arc}; -use tokio::sync::{mpsc, mpsc::Receiver}; /// A basic optimism transaction pool. /// @@ -102,7 +82,6 @@ where // spawn txpool maintenance tasks { - let pool = transaction_pool.clone(); let chain_events = ctx.provider().canonical_state_stream(); let client = ctx.provider().clone(); let transactions_backup_config = @@ -113,7 +92,7 @@ where |shutdown| { reth_transaction_pool::maintain::backup_local_transactions_task( shutdown, - pool.clone(), + transaction_pool.clone(), transactions_backup_config, ) }, @@ -124,11 +103,11 @@ where "txpool maintenance task", reth_transaction_pool::maintain::maintain_transaction_pool_future( client, - pool.clone(), + transaction_pool.clone(), chain_events, ctx.task_executor().clone(), reth_transaction_pool::maintain::MaintainPoolConfig { - max_tx_lifetime: pool.config().max_queued_lifetime, + max_tx_lifetime: transaction_pool.config().max_queued_lifetime, ..Default::default() }, ), diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs index 55348784d17a..184b7cf7eae6 100644 --- a/crates/scroll/node/src/test_utils.rs +++ b/crates/scroll/node/src/test_utils.rs @@ -3,19 +3,14 @@ use alloy_genesis::Genesis; use alloy_primitives::{Address, B256}; use alloy_rpc_types_engine::PayloadAttributes; use reth_e2e_test_utils::{ - node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, NodeHelperType, - TmpDB, + transaction::TransactionTestContext, wallet::Wallet, NodeHelperType, TmpDB, }; -use reth_node_api::{NodePrimitives, NodeTypes, NodeTypesWithDBAdapter}; -use reth_node_builder::{Node, NodeBuilder, NodeConfig, NodeHandle}; -use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}; +use reth_node_api::NodeTypesWithDBAdapter; + use reth_payload_builder::EthPayloadBuilderAttributes; use reth_provider::providers::BlockchainProvider; -use reth_rpc_server_types::RpcModuleSelection; -use reth_scroll_chainspec::{ScrollChainSpec, ScrollChainSpecBuilder}; -use reth_scroll_primitives::ScrollPrimitives; +use reth_scroll_chainspec::ScrollChainSpecBuilder; use reth_tasks::TaskManager; -use reth_transaction_pool::PeerId; use std::sync::Arc; use tokio::sync::Mutex; @@ -41,7 +36,7 @@ pub async fn setup( .build(Default::default()), ), is_dev, - scroll_payload_attributes::, + scroll_payload_attributes, ) .await } @@ -69,9 +64,7 @@ pub async fn advance_chain( } /// Helper function to create a new eth payload attributes -pub fn scroll_payload_attributes( - timestamp: u64, -) -> ScrollPayloadBuilderAttributes { +pub fn scroll_payload_attributes(timestamp: u64) -> ScrollPayloadBuilderAttributes { let attributes = PayloadAttributes { timestamp, prev_randao: B256::ZERO, diff --git a/crates/scroll/node/tests/e2e/main.rs b/crates/scroll/node/tests/e2e/main.rs index 29e39429c23d..7d12cfcf14f0 100644 --- a/crates/scroll/node/tests/e2e/main.rs +++ b/crates/scroll/node/tests/e2e/main.rs @@ -1,4 +1,5 @@ -#[allow(missing_docs)] +#![allow(missing_docs)] + mod payload; const fn main() {} diff --git a/crates/scroll/node/tests/e2e/payload.rs b/crates/scroll/node/tests/e2e/payload.rs index e338e805a62e..81e2bf4b45ad 100644 --- a/crates/scroll/node/tests/e2e/payload.rs +++ b/crates/scroll/node/tests/e2e/payload.rs @@ -6,14 +6,14 @@ use tokio::sync::Mutex; async fn can_sync() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut node, _tasks, wallet) = setup(3, false).await?; + let (mut node, _tasks, wallet) = setup(1, false).await?; let mut node = node.pop().unwrap(); let wallet = Arc::new(Mutex::new(wallet)); let tip: usize = 90; - // On first node, create a chain up to block number 90a - let canonical_payload_chain = advance_chain(tip, &mut node, wallet.clone()).await?; + // Create a chain of 90 blocks + let _canonical_payload_chain = advance_chain(tip, &mut node, wallet.clone()).await?; Ok(()) } diff --git a/crates/scroll/payload/Cargo.toml b/crates/scroll/payload/Cargo.toml index fbd246b4969c..bc6594b71dbd 100644 --- a/crates/scroll/payload/Cargo.toml +++ b/crates/scroll/payload/Cargo.toml @@ -51,29 +51,29 @@ tracing.workspace = true [features] std = [ - "futures-util/std", - "reth-payload-primitives/std", - "reth-primitives-traits/std", - "reth-scroll-engine-primitives/std", - "alloy-consensus/std", - "alloy-primitives/std", - "alloy-rlp/std", - "reth-chainspec/std", - "reth-evm/std", - "reth-execution-types/std", - "reth-revm/std", - "reth-storage-api/std", - "revm/std", - "thiserror/std", - "tracing/std" + "futures-util/std", + "reth-payload-primitives/std", + "reth-primitives-traits/std", + "reth-scroll-engine-primitives/std", + "alloy-consensus/std", + "alloy-primitives/std", + "alloy-rlp/std", + "reth-chainspec/std", + "reth-evm/std", + "reth-execution-types/std", + "reth-revm/std", + "reth-storage-api/std", + "revm/std", + "thiserror/std", + "tracing/std", ] test-utils = [ - "dep:futures-util", - "reth-payload-builder/test-utils", - "reth-primitives-traits/test-utils", - "reth-transaction-pool/test-utils", - "reth-chain-state/test-utils", - "reth-chainspec/test-utils", - "reth-evm/test-utils", - "reth-revm/test-utils" + "dep:futures-util", + "reth-payload-builder/test-utils", + "reth-primitives-traits/test-utils", + "reth-transaction-pool/test-utils", + "reth-chain-state/test-utils", + "reth-chainspec/test-utils", + "reth-evm/test-utils", + "reth-revm/test-utils", ] diff --git a/crates/scroll/payload/src/builder.rs b/crates/scroll/payload/src/builder.rs index e54c29b62b50..1e7b4b82f18c 100644 --- a/crates/scroll/payload/src/builder.rs +++ b/crates/scroll/payload/src/builder.rs @@ -6,8 +6,8 @@ use alloy_primitives::{B256, U256}; use alloy_rlp::Encodable; use core::fmt::Debug; use reth_basic_payload_builder::{ - is_better_payload, BuildArguments, BuildOutcome, BuildOutcomeKind, HeaderForPayload, - MissingPayloadBehaviour, PayloadBuilder, PayloadConfig, + is_better_payload, BuildArguments, BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, + PayloadBuilder, PayloadConfig, }; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; @@ -23,7 +23,7 @@ use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, Payloa use reth_primitives_traits::{NodePrimitives, SealedHeader, SignedTransaction, TxTy}; use reth_revm::{cancelled::CancelOnDrop, database::StateProviderDatabase, db::State}; use reth_scroll_engine_primitives::{ScrollBuiltPayload, ScrollPayloadBuilderAttributes}; -use reth_scroll_primitives::{transaction::signed::IsL1Message, ScrollPrimitives}; +use reth_scroll_primitives::{ScrollPrimitives, ScrollTransactionSigned}; use reth_storage_api::{StateProvider, StateProviderFactory}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::context::{Block, BlockEnv}; @@ -85,12 +85,11 @@ impl ScrollPayloadBuilder { } } -impl ScrollPayloadBuilder +impl ScrollPayloadBuilder where - Pool: TransactionPool>, + Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, - N: ScrollPayloadPrimitives, - Evm: ConfigureEvm, + Evm: ConfigureEvm, { /// Constructs an Scroll payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -102,11 +101,11 @@ where /// a result indicating success with the payload or an error in case of failure. fn build_payload<'a, Txs>( &self, - args: BuildArguments, ScrollBuiltPayload>, + args: BuildArguments, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - ) -> Result>, PayloadBuilderError> + ) -> Result, PayloadBuilderError> where - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, { let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; @@ -134,17 +133,16 @@ where } /// Implementation of the [`PayloadBuilder`] trait for [`ScrollPayloadBuilder`]. -impl PayloadBuilder for ScrollPayloadBuilder +impl PayloadBuilder for ScrollPayloadBuilder where Client: StateProviderFactory + ChainSpecProvider + Clone, - Pool: TransactionPool>, - Evm: ConfigureEvm, + Pool: TransactionPool>, + Evm: ConfigureEvm, Txs: ScrollPayloadTransactions, - N: ScrollPayloadPrimitives, { - type Attributes = ScrollPayloadBuilderAttributes; - type BuiltPayload = ScrollBuiltPayload; + type Attributes = ScrollPayloadBuilderAttributes; + type BuiltPayload = ScrollBuiltPayload; fn try_build( &self, @@ -202,17 +200,17 @@ impl<'a, Txs> std::fmt::Debug for ScrollBuilder<'a, Txs> { impl ScrollBuilder<'_, Txs> { /// Builds the payload on top of the state. - pub fn build( + pub fn build( self, db: impl Database, state_provider: impl StateProvider, ctx: ScrollPayloadBuilderCtx, - ) -> Result>, PayloadBuilderError> + ) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvm, + EvmConfig: + ConfigureEvm, ChainSpec: EthChainSpec + ScrollHardforks, - N: ScrollPayloadPrimitives, - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, { let Self { best } = self; tracing::debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); @@ -258,14 +256,15 @@ impl ScrollBuilder<'_, Txs> { ); // create the executed block data - let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { - block: ExecutedBlock { - recovered_block: Arc::new(block), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - }, - trie: Arc::new(trie_updates), - }; + let executed: ExecutedBlockWithTrieUpdates = + ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { + recovered_block: Arc::new(block), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + trie: Arc::new(trie_updates), + }; let no_tx_pool = ctx.attributes().no_tx_pool; @@ -295,11 +294,11 @@ 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. - pub best_payload: Option>, + pub best_payload: Option, } impl ScrollPayloadBuilderCtx @@ -315,7 +314,7 @@ where } /// Returns the builder attributes. - pub const fn attributes(&self) -> &ScrollPayloadBuilderAttributes> { + pub const fn attributes(&self) -> &ScrollPayloadBuilderAttributes { &self.config.attributes } @@ -383,8 +382,7 @@ where impl ScrollPayloadBuilderCtx where - Evm: - ConfigureEvm, + Evm: ConfigureEvm, ChainSpec: EthChainSpec + ScrollHardforks, { /// Executes all sequencer transactions that are included in the payload attributes. From 06ac47f214ac82ac6f6f458b7decdb29c1f1f1d7 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 2 Apr 2025 13:38:52 +0700 Subject: [PATCH 5/7] lint and clean up --- Cargo.lock | 1 - .../alloy/consensus/src/transaction/pooled.rs | 2 +- crates/scroll/alloy/rpc-types/src/receipt.rs | 2 +- crates/scroll/node/Cargo.toml | 12 ++--- crates/scroll/node/src/builder/payload.rs | 46 +------------------ crates/scroll/node/src/builder/pool.rs | 2 +- crates/scroll/node/src/test_utils.rs | 2 +- crates/scroll/node/tests/assets/genesis.json | 6 +-- crates/scroll/payload/Cargo.toml | 2 +- crates/scroll/payload/src/builder.rs | 33 ++----------- crates/scroll/payload/src/lib.rs | 2 - crates/scroll/payload/src/traits.rs | 29 ------------ crates/scroll/txpool/Cargo.toml | 11 +++-- crates/scroll/txpool/src/lib.rs | 2 +- crates/scroll/txpool/src/validator.rs | 4 +- 15 files changed, 24 insertions(+), 132 deletions(-) delete mode 100644 crates/scroll/payload/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index c1d158cc50e2..ace6f958747c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10001,7 +10001,6 @@ dependencies = [ "derive_more 2.0.1", "futures-util", "metrics", - "op-alloy-flz", "parking_lot", "reth-chain-state", "reth-chainspec", diff --git a/crates/scroll/alloy/consensus/src/transaction/pooled.rs b/crates/scroll/alloy/consensus/src/transaction/pooled.rs index a4a48fc2a461..9d018b6fdc98 100644 --- a/crates/scroll/alloy/consensus/src/transaction/pooled.rs +++ b/crates/scroll/alloy/consensus/src/transaction/pooled.rs @@ -106,7 +106,7 @@ impl ScrollPooledTransaction { } } - /// Converts the transaction into the optimism [`ScrollTxEnvelope`]. + /// Converts the transaction into the scroll [`ScrollTxEnvelope`]. pub fn into_op_envelope(self) -> ScrollTxEnvelope { match self { Self::Legacy(tx) => tx.into(), diff --git a/crates/scroll/alloy/rpc-types/src/receipt.rs b/crates/scroll/alloy/rpc-types/src/receipt.rs index b91705d2d09d..6a0a04c66025 100644 --- a/crates/scroll/alloy/rpc-types/src/receipt.rs +++ b/crates/scroll/alloy/rpc-types/src/receipt.rs @@ -167,7 +167,7 @@ mod tests { } #[test] - fn serialize_empty_optimism_transaction_receipt_fields_struct() { + fn serialize_empty_scroll_transaction_receipt_fields_struct() { let scroll_fields = ScrollTransactionReceiptFields::default(); let json = serde_json::to_value(scroll_fields).unwrap(); diff --git a/crates/scroll/node/Cargo.toml b/crates/scroll/node/Cargo.toml index 4a32b7d9b168..b94b463aa01e 100644 --- a/crates/scroll/node/Cargo.toml +++ b/crates/scroll/node/Cargo.toml @@ -17,7 +17,6 @@ reth-basic-payload-builder.workspace = true reth-chainspec.workspace = true reth-db = { workspace = true, features = ["scroll-alloy-traits"] } reth-engine-local = { workspace = true, features = ["scroll-alloy-traits"] } -reth-rpc-eth-types.workspace = true reth-eth-wire-types.workspace = true reth-evm = { workspace = true, features = ["scroll-alloy-traits"] } reth-e2e-test-utils = { workspace = true, optional = true } @@ -30,6 +29,7 @@ reth-payload-builder.workspace = true reth-primitives = { workspace = true, features = ["c-kzg"] } reth-primitives-traits.workspace = true reth-provider.workspace = true +reth-rpc-eth-types.workspace = true reth-rpc-server-types = { workspace = true, optional = true } reth-tasks = { workspace = true, optional = true } reth-tracing.workspace = true @@ -42,9 +42,11 @@ revm = { workspace = true, features = ["c-kzg"] } # alloy alloy-consensus.workspace = true alloy-eips.workspace = true +alloy-genesis = { workspace = true, optional = true } +alloy-primitives.workspace = true alloy-rpc-types-engine.workspace = true -# scroll +# scroll-reth reth-scroll-chainspec.workspace = true reth-scroll-consensus.workspace = true reth-scroll-engine-primitives.workspace = true @@ -53,15 +55,13 @@ reth-scroll-payload.workspace = true reth-scroll-primitives = { workspace = true, features = ["serde", "serde-bincode-compat", "reth-codec"] } reth-scroll-rpc.workspace = true reth-scroll-txpool.workspace = true + +# scroll-alloy scroll-alloy-consensus.workspace = true scroll-alloy-evm.workspace = true scroll-alloy-hardforks.workspace = true scroll-alloy-rpc-types-engine.workspace = true -# alloy -alloy-primitives.workspace = true -alloy-genesis = { workspace = true, optional = true } - # misc eyre.workspace = true serde_json = { workspace = true, optional = true } diff --git a/crates/scroll/node/src/builder/payload.rs b/crates/scroll/node/src/builder/payload.rs index 0344115f09ce..dd025cb6d654 100644 --- a/crates/scroll/node/src/builder/payload.rs +++ b/crates/scroll/node/src/builder/payload.rs @@ -16,7 +16,7 @@ pub struct ScrollPayloadBuilder { } impl ScrollPayloadBuilder { - /// A helper method to initialize [`reth_optimism_payload_builder::OpPayloadBuilder`] with the + /// A helper method to initialize [`reth_scroll_payload::ScrollPayloadBuilder`] with the /// given EVM config. pub fn build( self, @@ -49,49 +49,6 @@ impl ScrollPayloadBuilder { } } -// impl PayloadServiceBuilder for ScrollPayloadBuilder -// where -// Node: FullNodeTypes, -// Node::Types: NodeTypesWithEngine< -// Primitives = ScrollPrimitives, -// Engine = ScrollEngineTypes, -// ChainSpec = ScrollChainSpec, -// >, -// Pool: TransactionPool>> -// + Unpin -// + 'static, -// Txs: ScrollPayloadTransactions, -// { -// async fn spawn_payload_builder_service( -// self, -// ctx: &BuilderContext, -// _pool: Pool, -// ) -> eyre::Result::Engine>> { -// let payload_builder = reth_scroll_payload::ScrollPayloadBuilder::default(); - -// let conf = ctx.config().builder.clone(); - -// let payload_job_config = BasicPayloadJobGeneratorConfig::default() -// .interval(conf.interval) -// .deadline(conf.deadline) -// .max_payload_tasks(conf.max_payload_tasks); - -// let payload_generator = BasicPayloadJobGenerator::with_builder( -// ctx.provider().clone(), -// ctx.task_executor().clone(), -// payload_job_config, -// payload_builder, -// ); -// let (payload_service, payload_service_handle) = -// PayloadBuilderService::new(payload_generator, -// ctx.provider().canonical_state_stream()); - -// ctx.task_executor().spawn_critical("payload builder service", Box::pin(payload_service)); - -// Ok(payload_service_handle) -// } -// } - impl PayloadBuilderBuilder for ScrollPayloadBuilder where Node: FullNodeTypes< @@ -105,7 +62,6 @@ where + Unpin + 'static, Txs: ScrollPayloadTransactions, - ::Transaction: PoolTransaction, { type PayloadBuilder = reth_scroll_payload::ScrollPayloadBuilder; diff --git a/crates/scroll/node/src/builder/pool.rs b/crates/scroll/node/src/builder/pool.rs index 0691fc669579..81e2f9215035 100644 --- a/crates/scroll/node/src/builder/pool.rs +++ b/crates/scroll/node/src/builder/pool.rs @@ -12,7 +12,7 @@ use reth_transaction_pool::{ }; use scroll_alloy_hardforks::ScrollHardforks; -/// A basic optimism transaction pool. +/// A basic scroll transaction pool. /// /// This contains various settings that can be configured and take precedence over the node's /// config. diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs index 184b7cf7eae6..9bd8a2c9d4a7 100644 --- a/crates/scroll/node/src/test_utils.rs +++ b/crates/scroll/node/src/test_utils.rs @@ -14,7 +14,7 @@ use reth_tasks::TaskManager; use std::sync::Arc; use tokio::sync::Mutex; -/// Optimism Node Helper type +/// Scroll Node Helper type pub(crate) type ScrollNode = NodeHelperType< OtherScrollNode, BlockchainProvider>, diff --git a/crates/scroll/node/tests/assets/genesis.json b/crates/scroll/node/tests/assets/genesis.json index 5c652d084a52..6f1737c9a931 100644 --- a/crates/scroll/node/tests/assets/genesis.json +++ b/crates/scroll/node/tests/assets/genesis.json @@ -18,11 +18,7 @@ "bedrockBlock": 0, "regolithTime": 0, "terminalTotalDifficulty": 0, - "terminalTotalDifficultyPassed": true, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50 - } + "terminalTotalDifficultyPassed": true }, "nonce": "0x0", "timestamp": "0x0", diff --git a/crates/scroll/payload/Cargo.toml b/crates/scroll/payload/Cargo.toml index bc6594b71dbd..1f9a8afc7a4a 100644 --- a/crates/scroll/payload/Cargo.toml +++ b/crates/scroll/payload/Cargo.toml @@ -43,9 +43,9 @@ reth-payload-util.workspace = true # scroll reth-scroll-primitives.workspace = true reth-scroll-engine-primitives.workspace = true -futures-util = { workspace = true, optional = true } # misc +futures-util = { workspace = true, optional = true } thiserror.workspace = true tracing.workspace = true diff --git a/crates/scroll/payload/src/builder.rs b/crates/scroll/payload/src/builder.rs index 1e7b4b82f18c..1600961d7101 100644 --- a/crates/scroll/payload/src/builder.rs +++ b/crates/scroll/payload/src/builder.rs @@ -1,6 +1,6 @@ //! Scroll's payload builder implementation. -use super::{traits::ScrollPayloadPrimitives, ScrollPayloadBuilderError}; +use super::ScrollPayloadBuilderError; use alloy_consensus::{Transaction, Typed2718}; use alloy_primitives::{B256, U256}; use alloy_rlp::Encodable; @@ -67,7 +67,7 @@ pub struct ScrollPayloadBuilder { } impl ScrollPayloadBuilder { - /// Creates a new `ScrollPayloadBuilder`. + /// Creates a new [`ScrollPayloadBuilder`]. pub fn new(pool: Pool, evm_config: Evm, client: Client) -> Self { Self { evm_config, pool, client, best_transactions: () } } @@ -303,8 +303,7 @@ pub struct ScrollPayloadBuilderCtx { impl ScrollPayloadBuilderCtx where - Evm: - ConfigureEvm, + Evm: ConfigureEvm, ChainSpec: EthChainSpec + ScrollHardforks, { /// Returns the parent block the payload will be build on. @@ -318,23 +317,6 @@ where &self.config.attributes } - // Returns the extra data for the block. - // - // After holocene this extracts the extra data from the payload - // pub fn extra_data(&self) -> Result { - // if self.is_holocene_active() { - // self.attributes() - // .get_holocene_extra_data( - // self.chain_spec.base_fee_params_at_timestamp( - // self.attributes().payload_attributes.timestamp, - // ), - // ) - // .map_err(PayloadBuilderError::other) - // } else { - // Ok(Default::default()) - // } - // } - /// Returns the current fee settings for transactions from the mempool pub fn best_transaction_attributes(&self, block_env: &BlockEnv) -> BestTransactionsAttributes { BestTransactionsAttributes::new( @@ -348,11 +330,6 @@ where self.attributes().payload_id() } - // Returns true if holocene is active for the payload. - // pub fn is_holocene_active(&self) -> bool { - // self.chain_spec.is_holocene_active_at_timestamp(self.attributes().timestamp()) - // } - /// Returns true if the fees are higher than the previous payload. pub fn is_better_payload(&self, total_fees: U256) -> bool { is_better_payload(self.best_payload.as_ref(), total_fees) @@ -536,10 +513,6 @@ impl ExecutionInfo { /// Returns true if the transaction would exceed the block limits: /// - block gas limit: ensures the transaction still fits into the block. - /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit - /// per tx. - /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the - /// maximum allowed DA limit per block. pub fn is_tx_over_limits( &self, tx: &(impl Encodable + Transaction), diff --git a/crates/scroll/payload/src/lib.rs b/crates/scroll/payload/src/lib.rs index 8189d6877b1f..c396b52f63e9 100644 --- a/crates/scroll/payload/src/lib.rs +++ b/crates/scroll/payload/src/lib.rs @@ -11,8 +11,6 @@ pub use builder::{ScrollPayloadBuilder, ScrollPayloadTransactions}; mod error; pub use error::ScrollPayloadBuilderError; -mod traits; - #[cfg(feature = "test-utils")] mod test_utils; #[cfg(feature = "test-utils")] diff --git a/crates/scroll/payload/src/traits.rs b/crates/scroll/payload/src/traits.rs deleted file mode 100644 index 64b271b43625..000000000000 --- a/crates/scroll/payload/src/traits.rs +++ /dev/null @@ -1,29 +0,0 @@ -use alloy_consensus::{BlockBody, Header}; -use reth_primitives_traits::{NodePrimitives, Receipt, SignedTransaction}; -use reth_scroll_primitives::transaction::signed::IsL1Message; - -/// Helper trait to encapsulate common bounds on [`NodePrimitives`] for Scroll payload builder. -pub trait ScrollPayloadPrimitives: - NodePrimitives< - Receipt: Receipt, - SignedTx = Self::_TX, - BlockHeader = Header, - BlockBody = BlockBody, -> -{ - /// Helper AT to bound [`NodePrimitives::Block`] type without causing bound cycle. - type _TX: SignedTransaction + IsL1Message; -} - -impl ScrollPayloadPrimitives for T -where - Tx: SignedTransaction + IsL1Message, - T: NodePrimitives< - SignedTx = Tx, - Receipt: Receipt, - BlockHeader = Header, - BlockBody = BlockBody, - >, -{ - type _TX = Tx; -} diff --git a/crates/scroll/txpool/Cargo.toml b/crates/scroll/txpool/Cargo.toml index f891b1d8aa53..844dffe7a5fe 100644 --- a/crates/scroll/txpool/Cargo.toml +++ b/crates/scroll/txpool/Cargo.toml @@ -19,23 +19,24 @@ alloy-primitives.workspace = true alloy-rpc-types-eth.workspace = true # reth +reth-chain-state.workspace = true reth-chainspec.workspace = true reth-primitives-traits.workspace = true -reth-chain-state.workspace = true reth-revm.workspace = true reth-storage-api.workspace = true reth-transaction-pool.workspace = true -# revm +# revm-scroll revm-scroll.workspace = true -# optimism -scroll-alloy-consensus.workspace = true -op-alloy-flz.workspace = true +# reth-scroll reth-scroll-evm.workspace = true reth-scroll-forks.workspace = true reth-scroll-primitives.workspace = true +# scroll-alloy +scroll-alloy-consensus.workspace = true + # metrics reth-metrics.workspace = true metrics.workspace = true diff --git a/crates/scroll/txpool/src/lib.rs b/crates/scroll/txpool/src/lib.rs index b339807eeed5..5ee3ad58ee5f 100644 --- a/crates/scroll/txpool/src/lib.rs +++ b/crates/scroll/txpool/src/lib.rs @@ -8,7 +8,7 @@ pub use validator::ScrollTransactionValidator; use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; -/// Type alias for default optimism transaction pool +/// Type alias for default scroll transaction pool pub type ScrollTransactionPool = Pool< TransactionValidationTaskExecutor>, CoinbaseTipOrdering, diff --git a/crates/scroll/txpool/src/validator.rs b/crates/scroll/txpool/src/validator.rs index d0ef2804f58c..0322b3dc943b 100644 --- a/crates/scroll/txpool/src/validator.rs +++ b/crates/scroll/txpool/src/validator.rs @@ -30,7 +30,7 @@ pub struct ScrollL1BlockInfo { number: AtomicU64, } -/// Validator for Optimism transactions. +/// Validator for Scroll transactions. #[derive(Debug, Clone)] pub struct ScrollTransactionValidator { /// The type that performs the actual validation. @@ -90,8 +90,6 @@ where if let Ok(Some(block)) = this.inner.client().block_by_number_or_tag(alloy_eips::BlockNumberOrTag::Latest) { - // genesis block has no txs, so we can't extract L1 info, we set the block info to empty - // so that we will accept txs into the pool before the first block this.block_info.timestamp.store(block.header().timestamp(), Ordering::Relaxed); this.block_info.number.store(block.header().number(), Ordering::Relaxed); this.update_l1_block_info(block.header()); From fdd9249870367ffadd642413eaabd73f960697c0 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 2 Apr 2025 16:22:43 +0700 Subject: [PATCH 6/7] scroll merge --- crates/scroll/node/src/builder/payload.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/scroll/node/src/builder/payload.rs b/crates/scroll/node/src/builder/payload.rs index dd025cb6d654..a916a5b907fb 100644 --- a/crates/scroll/node/src/builder/payload.rs +++ b/crates/scroll/node/src/builder/payload.rs @@ -1,6 +1,6 @@ use reth_node_api::{ConfigureEvm, PrimitivesTy}; use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext, FullNodeTypes}; -use reth_node_types::{NodeTypesWithEngine, TxTy}; +use reth_node_types::{NodeTypes, TxTy}; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_engine_primitives::ScrollEngineTypes; use reth_scroll_evm::ScrollEvmConfig; @@ -26,8 +26,8 @@ impl ScrollPayloadBuilder { ) -> eyre::Result> where Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = ScrollEngineTypes, + Types: NodeTypes< + Payload = ScrollEngineTypes, ChainSpec = ScrollChainSpec, Primitives = ScrollPrimitives, >, @@ -52,8 +52,8 @@ impl ScrollPayloadBuilder { impl PayloadBuilderBuilder for ScrollPayloadBuilder where Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = ScrollEngineTypes, + Types: NodeTypes< + Payload = ScrollEngineTypes, ChainSpec = ScrollChainSpec, Primitives = ScrollPrimitives, >, From 57cebd97b2862974495f8a83c4b0b3c4e3deed7e Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 2 Apr 2025 16:46:56 +0700 Subject: [PATCH 7/7] lint and feedback --- .github/assets/check_wasm.sh | 1 + crates/e2e-test-utils/src/transaction.rs | 2 +- crates/scroll/alloy/consensus/src/transaction/pooled.rs | 4 ++-- crates/scroll/node/src/test_utils.rs | 2 +- crates/scroll/node/tests/e2e/main.rs | 2 -- crates/scroll/txpool/src/lib.rs | 2 +- crates/scroll/txpool/src/validator.rs | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/assets/check_wasm.sh b/.github/assets/check_wasm.sh index 4b659df1de6f..34ca57ac2671 100755 --- a/.github/assets/check_wasm.sh +++ b/.github/assets/check_wasm.sh @@ -83,6 +83,7 @@ exclude_crates=( reth-scroll-engine-primitives # proptest reth-scroll-payload # c-kzg reth-scroll-primitives # c-kzg + reth-scroll-txpool ) # Array to hold the results diff --git a/crates/e2e-test-utils/src/transaction.rs b/crates/e2e-test-utils/src/transaction.rs index 8e0dca9743a0..38493f8da79a 100644 --- a/crates/e2e-test-utils/src/transaction.rs +++ b/crates/e2e-test-utils/src/transaction.rs @@ -26,7 +26,7 @@ impl TransactionTestContext { signed.encoded_2718().into() } - /// Crates a transfer with a nonce and signs it, returning bytes. + /// Creates a transfer with a nonce and signs it, returning bytes. pub async fn transfer_tx_nonce_bytes( chain_id: u64, wallet: PrivateKeySigner, diff --git a/crates/scroll/alloy/consensus/src/transaction/pooled.rs b/crates/scroll/alloy/consensus/src/transaction/pooled.rs index 9d018b6fdc98..686fa7e9725d 100644 --- a/crates/scroll/alloy/consensus/src/transaction/pooled.rs +++ b/crates/scroll/alloy/consensus/src/transaction/pooled.rs @@ -107,7 +107,7 @@ impl ScrollPooledTransaction { } /// Converts the transaction into the scroll [`ScrollTxEnvelope`]. - pub fn into_op_envelope(self) -> ScrollTxEnvelope { + pub fn into_scroll_envelope(self) -> ScrollTxEnvelope { match self { Self::Legacy(tx) => tx.into(), Self::Eip2930(tx) => tx.into(), @@ -394,7 +394,7 @@ impl From for TxEnvelope { impl From for ScrollTxEnvelope { fn from(tx: ScrollPooledTransaction) -> Self { - tx.into_op_envelope() + tx.into_scroll_envelope() } } diff --git a/crates/scroll/node/src/test_utils.rs b/crates/scroll/node/src/test_utils.rs index 9bd8a2c9d4a7..99c5a9c5ecc4 100644 --- a/crates/scroll/node/src/test_utils.rs +++ b/crates/scroll/node/src/test_utils.rs @@ -63,7 +63,7 @@ pub async fn advance_chain( .await } -/// Helper function to create a new eth payload attributes +/// Helper function to create a new scroll payload attributes pub fn scroll_payload_attributes(timestamp: u64) -> ScrollPayloadBuilderAttributes { let attributes = PayloadAttributes { timestamp, diff --git a/crates/scroll/node/tests/e2e/main.rs b/crates/scroll/node/tests/e2e/main.rs index 7d12cfcf14f0..1402b98661a6 100644 --- a/crates/scroll/node/tests/e2e/main.rs +++ b/crates/scroll/node/tests/e2e/main.rs @@ -1,5 +1,3 @@ #![allow(missing_docs)] mod payload; - -const fn main() {} diff --git a/crates/scroll/txpool/src/lib.rs b/crates/scroll/txpool/src/lib.rs index 5ee3ad58ee5f..a1885e05acb3 100644 --- a/crates/scroll/txpool/src/lib.rs +++ b/crates/scroll/txpool/src/lib.rs @@ -4,7 +4,7 @@ mod transaction; pub use transaction::ScrollPooledTransaction; mod validator; -pub use validator::ScrollTransactionValidator; +pub use validator::{ScrollL1BlockInfo, ScrollTransactionValidator}; use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; diff --git a/crates/scroll/txpool/src/validator.rs b/crates/scroll/txpool/src/validator.rs index 0322b3dc943b..1a0f79a4215d 100644 --- a/crates/scroll/txpool/src/validator.rs +++ b/crates/scroll/txpool/src/validator.rs @@ -84,7 +84,7 @@ where Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, Tx: EthPoolTransaction, { - /// Create a new [`OpTransactionValidator`]. + /// Create a new [`ScrollTransactionValidator`]. pub fn new(inner: EthTransactionValidator) -> Self { let this = Self::with_block_info(inner, ScrollL1BlockInfo::default()); if let Ok(Some(block)) =