diff --git a/Cargo.lock b/Cargo.lock index 47da0f5b..0677eb60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,6 +95,29 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae62e633fa48b4190af5e841eb05179841bb8b713945103291e2c0867037c0d1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-trie", +] + [[package]] name = "alloy-chains" version = "0.2.9" @@ -150,6 +173,41 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-contract" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630288cf4f3a34a8c6bc75c03dce1dbd47833138f65f37d53a1661eafc96b83f" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "serde_json", + "thiserror 2.0.16", +] + +[[package]] +name = "alloy-core" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe6c56d58fbfa9f0f6299376e8ce33091fc6494239466814c3f54b55743cb09" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + [[package]] name = "alloy-dyn-abi" version = "1.3.1" @@ -503,6 +561,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "alloy-rpc-types-mev", "alloy-serde", "serde", ] @@ -723,6 +782,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74e91b0b553c115d14bd0ed41898309356dc85d0e3d4b9014c4e7715e48c8ad" dependencies = [ + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", @@ -741,12 +801,14 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84194d31220803f5f62d0a00f583fd3a062b36382e2bea446f1af96727754565" dependencies = [ + "alloy-json-abi", "const-hex", "dunce", "heck", "macro-string", "proc-macro2", "quote", + "serde_json", "syn 2.0.106", "syn-solidity", ] @@ -1447,9 +1509,7 @@ dependencies = [ "alloy-genesis", "alloy-network", "alloy-primitives", - "alloy-provider", "alloy-rlp", - "alloy-rpc-client", "alloy-rpc-types", "alloy-rpc-types-eth", "alloy-rpc-types-trace", @@ -1466,6 +1526,7 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", "modular-bitfield", + "rblib", "reth", "reth-basic-payload-builder", "reth-chainspec", @@ -1495,7 +1556,6 @@ dependencies = [ "reth-payload-validator", "reth-primitives-traits", "reth-rpc", - "reth-rpc-builder", "reth-rpc-convert", "reth-rpc-engine-api", "reth-rpc-eth-api", @@ -2272,16 +2332,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -3196,21 +3246,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -3784,22 +3819,6 @@ dependencies = [ "webpki-roots 1.0.2", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.16" @@ -5120,23 +5139,6 @@ dependencies = [ "unsigned-varint", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "7.1.3" @@ -5405,50 +5407,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags 2.9.4", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -5658,6 +5622,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pipelines-macros" +version = "0.3.1" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -6124,6 +6098,42 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rblib" +version = "0.3.1" +dependencies = [ + "alloy", + "alloy-evm", + "alloy-serde", + "dashmap 6.1.0", + "derive_more", + "eyre", + "futures", + "itertools 0.14.0", + "jsonrpsee", + "metrics", + "parking_lot", + "pipelines-macros", + "reth", + "reth-basic-payload-builder", + "reth-cli", + "reth-errors", + "reth-ethereum", + "reth-ethereum-payload-builder", + "reth-evm", + "reth-node-builder", + "reth-payload-builder", + "reth-rpc-api", + "reth-transaction-pool", + "serde", + "smallvec", + "thiserror 2.0.16", + "tokio", + "tokio-stream", + "tracing", + "uuid", +] + [[package]] name = "recvmsg" version = "1.0.0" @@ -6236,11 +6246,9 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", - "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -6252,7 +6260,6 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -7168,6 +7175,44 @@ dependencies = [ "thiserror 2.0.16", ] +[[package]] +name = "reth-ethereum" +version = "1.8.2" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.8.2#9c30bf7af5e0d45deaf5917375c9922c16654b28" +dependencies = [ + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "reth-chainspec", + "reth-codecs", + "reth-consensus", + "reth-consensus-common", + "reth-db", + "reth-engine-local", + "reth-eth-wire", + "reth-ethereum-consensus", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-network", + "reth-network-api", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie", + "reth-trie-db", +] + [[package]] name = "reth-ethereum-cli" version = "1.8.2" @@ -9393,7 +9438,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.4.0", + "security-framework", ] [[package]] @@ -9412,7 +9457,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni", "log", @@ -9421,7 +9466,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.4.0", + "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", "windows-sys 0.59.0", @@ -9595,19 +9640,6 @@ dependencies = [ "cc", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.4.0" @@ -9615,7 +9647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" dependencies = [ "bitflags 2.9.4", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -10442,16 +10474,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.2" diff --git a/Cargo.toml b/Cargo.toml index 40582cb4..0fb0e3fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,14 @@ version = "1.3.0" edition = "2024" license = "MIT OR Apache-2.0" +[[bin]] +name = "bera-reth" +path = "src/main.rs" + +[[bin]] +name = "bera-sequencer" +path = "src/bin/bera-sequencer.rs" + [dependencies] alloy-consensus = "1.0.37" alloy-eips = "1.0.37" @@ -23,6 +31,9 @@ derive_more = "2.0.1" eyre = "0.6.12" sha2 = "0.10" +# Requires minor modifications to rblib such that `FixedTransactions` is public +rblib = { path = "../rblib", default-features = false } + # rpc jsonrpsee = "0.26.0" jsonrpsee-core = { version = "0.26.0", features = ["server"] } @@ -69,12 +80,9 @@ tokio = "1.46.0" tracing = "0.1.41" [dev-dependencies] -alloy-provider = "1.0.37" -alloy-rpc-client = "1.0.37" alloy-rpc-types-trace = "1.0.37" eyre = "0.6.12" reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.8.2" } -reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.8.2" } revm-inspectors = "0.30.0" serde_json = "1.0" test-fuzz = "7" diff --git a/src/bin/bera-sequencer.rs b/src/bin/bera-sequencer.rs new file mode 100644 index 00000000..25e69de8 --- /dev/null +++ b/src/bin/bera-sequencer.rs @@ -0,0 +1,104 @@ +//! Berachain Sequencer Binary +//! +//! This binary runs bera-reth with a custom payload builder for sequencing. +//! +//! Usage: `cargo run --bin bera-sequencer -- node` + +#[global_allocator] +static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); + +use bera_reth::{ + chainspec::{BerachainChainSpec, BerachainChainSpecParser}, + consensus::BerachainBeaconConsensus, + engine::{rpc::BerachainEngineApiBuilder, validator::BerachainEngineValidatorBuilder}, + evm::BerachainEvmFactory, + node::{BerachainNode, evm::config::BerachainEvmConfig}, + platform::BerachainPlatform, + rpc::{BerachainAddOns, BerachainEthApiBuilder}, + version::init_bera_version, +}; +use clap::Parser; +use rblib::{ + pool::{AppendOrders, HostNodeInstaller, OrderPool}, + prelude::{Loop, Pipeline}, + steps::{OrderByPriorityFee, RemoveRevertedTransactions}, +}; +use reth::CliRunner; +use reth_cli_commands::node::NoArgs; +use reth_ethereum_cli::Cli; +use reth_node_builder::{Node, NodeHandle}; +use std::sync::Arc; +use tracing::info; + +/// Example Berachain builder pipeline with Revert Protection +fn berachain_builder_pipeline(pool: &OrderPool) -> Pipeline { + let pipeline = Pipeline::::named("classic").with_pipeline( + Loop, + ( + AppendOrders::from_pool(pool), + OrderByPriorityFee::default(), + RemoveRevertedTransactions::default(), + ), + ); + + pool.attach_pipeline(&pipeline); + pipeline +} + +fn main() { + // Install signal handler for better crash reporting + reth_cli_util::sigsegv_handler::install(); + + // Initialize Bera-Reth version metadata + init_bera_version().expect("Failed to initialize Bera-Reth version metadata"); + + // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. + if std::env::var_os("RUST_BACKTRACE").is_none() { + unsafe { std::env::set_var("RUST_BACKTRACE", "1") }; + } + + let cli_components_builder = |spec: Arc| { + ( + BerachainEvmConfig::new_with_evm_factory(spec.clone(), BerachainEvmFactory::default()), + Arc::new(BerachainBeaconConsensus::new(spec)), + ) + }; + + if let Err(err) = Cli::::parse() + .with_runner_and_components::( + CliRunner::try_default_runtime().expect("Failed to create default runtime"), + cli_components_builder, + async move |builder, _| { + info!(target: "reth::cli", "Launching Berachain Sequencer node"); + let pool = OrderPool::::default(); + let pipeline = berachain_builder_pipeline(&pool); + let berachain_node = BerachainNode::default(); + + // Helps compiler + let add_ons: BerachainAddOns< + _, + BerachainEthApiBuilder, + BerachainEngineValidatorBuilder, + BerachainEngineApiBuilder, + > = BerachainAddOns::default(); + + let NodeHandle { node: _node, node_exit_future } = builder + .with_types::() + .with_components( + berachain_node + .components_builder() + .attach_pool(&pool) + .payload(pipeline.into_service()), + ) + .with_add_ons(add_ons) + .launch_with_debug_capabilities() + .await?; + + node_exit_future.await + }, + ) + { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/src/lib.rs b/src/lib.rs index 30b4118d..e7f2bf71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,11 +9,11 @@ pub mod evm; pub mod genesis; pub mod hardforks; pub mod node; +pub mod platform; pub mod pool; pub mod primitives; pub mod rpc; -pub mod transaction; -pub mod version; - #[cfg(test)] pub mod test_utils; +pub mod transaction; +pub mod version; diff --git a/src/node/evm/mod.rs b/src/node/evm/mod.rs index 465d9263..dd848598 100644 --- a/src/node/evm/mod.rs +++ b/src/node/evm/mod.rs @@ -1,17 +1,17 @@ //! Berachain EVM executor using standard Ethereum execution with Berachain chain spec mod assembler; -mod block_context; +pub mod block_context; pub mod config; pub mod error; pub mod executor; pub mod receipt; -use crate::{ - evm::BerachainEvmFactory, - node::{BerachainNode, evm::config::BerachainEvmConfig}, -}; +pub use config::BerachainEvmConfig; + +use crate::evm; use alloy_primitives::Bytes; +use reth_node_api::NodeTypes; use reth_node_builder::{BuilderContext, FullNodeTypes, components::ExecutorBuilder}; /// Default extra data for Berachain blocks @@ -20,7 +20,7 @@ fn default_extra_data() -> String { } /// Default extra data in bytes for Berachain blocks -fn default_extra_data_bytes() -> Bytes { +pub fn default_extra_data_bytes() -> Bytes { Bytes::from(default_extra_data().as_bytes().to_vec()) } @@ -30,7 +30,12 @@ pub struct BerachainExecutorBuilder; impl ExecutorBuilder for BerachainExecutorBuilder where - Node: FullNodeTypes, + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec = crate::chainspec::BerachainChainSpec, + Primitives = crate::primitives::BerachainPrimitives, + >, + >, { /// The EVM configuration type that will be built type EVM = BerachainEvmConfig; @@ -39,7 +44,7 @@ where async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { let evm_config = BerachainEvmConfig::new_with_evm_factory( ctx.chain_spec(), - BerachainEvmFactory::default(), + evm::BerachainEvmFactory::default(), ) .with_extra_data(default_extra_data_bytes()); Ok(evm_config) diff --git a/src/node/mod.rs b/src/node/mod.rs index 1b87f649..72f675bc 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -2,17 +2,19 @@ pub mod evm; +pub use crate::{ + consensus::BerachainConsensusBuilder, pool::BerachainPoolBuilder, rpc::BerachainAddOns, +}; +pub use evm::BerachainExecutorBuilder; + use crate::{ chainspec::BerachainChainSpec, - consensus::BerachainConsensusBuilder, engine::{ BerachainEngineTypes, builder::BerachainPayloadServiceBuilder, validator::BerachainEngineValidatorBuilder, }, - node::evm::BerachainExecutorBuilder, - pool::BerachainPoolBuilder, primitives::{BerachainHeader, BerachainPrimitives}, - rpc::{BerachainAddOns, BerachainEthApiBuilder}, + rpc::BerachainEthApiBuilder, transaction::BerachainTxEnvelope, }; use alloy_consensus::{SignableTransaction, error::ValueError}; diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 00000000..9c8b1fe3 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,404 @@ +use crate::{ + chainspec::BerachainChainSpec, + engine::{ + BerachainEngineTypes, + payload::{BerachainBuiltPayload, BerachainPayloadBuilderAttributes}, + }, + hardforks::BerachainHardforks, + node::{ + BerachainNode, + evm::config::{BerachainEvmConfig, BerachainNextBlockEnvAttributes}, + }, + pool::transaction::BerachainPooledTransaction, + primitives::BerachainHeader, + transaction::BerachainTxEnvelope, +}; +use alloy_consensus::Transaction; +use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M; +use alloy_primitives::U256; +use alloy_rlp::Encodable; +use rblib::{ + alloy::evm::{ + Evm, + revm::{context::Block, database::State}, + }, + prelude::*, + reth::{ + errors::{BlockExecutionError, BlockValidationError, ConsensusError}, + ethereum::{ + chainspec::EthereumHardforks, evm::revm::database::StateProviderDatabase, + provider::StateProvider, + }, + }, +}; +use reth::api::PayloadTypes; +use reth_basic_payload_builder::{BuildArguments, BuildOutcome, PayloadConfig, is_better_payload}; +use reth_chainspec::EthChainSpec; +use reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE; +use reth_ethereum_engine_primitives::BlobSidecars; +use reth_ethereum_payload_builder::EthereumBuilderConfig; +use reth_evm::{ + ConfigureEvm, + execute::{BlockBuilder, BlockBuilderOutcome}, +}; +use reth_payload_primitives::PayloadBuilderAttributes; +use reth_primitives_traits::transaction::error::InvalidTransactionError; +use reth_transaction_pool::{ + BestTransactions, BestTransactionsAttributes, BestTransactionsFor, PoolTransaction, + TransactionPool, ValidPoolTransaction, + error::{Eip4844PoolTransactionError, InvalidPoolTransactionError}, + noop::NoopTransactionPool, +}; +use std::sync::Arc; +use tracing::{debug, trace, warn}; + +use crate::evm::BerachainEvmFactory; +use rblib::prelude::pool::FixedTransactions; +use std::time::Duration; + +/// Default limits for Berachain payload building +#[derive(Debug, Clone, Default)] +pub struct BerachainLimits; + +impl PlatformLimits for BerachainLimits { + fn create(&self, _block: &BlockContext) -> Limits { + Limits { + gas_limit: 30_000_000, // TODO: calculate correctly + blob_params: None, // TODO: Get blob configuration from chainspec + max_transactions: Some(1_000), + deadline: Some(Duration::from_secs(2)), // bera block time + } + } +} + +/// Platform implementation for Berachain +/// +/// This type implements rblib's Platform trait for Berachain, +/// allowing the use of rblib's advanced block building capabilities. +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct BerachainPlatform; + +impl Platform for BerachainPlatform { + type NodeTypes = BerachainNode; + type EvmConfig = BerachainEvmConfig; + type PooledTransaction = BerachainPooledTransaction; + type Bundle = FlashbotsBundle; + type DefaultLimits = BerachainLimits; + + fn evm_config

(chainspec: Arc) -> BerachainEvmConfig + where + P: traits::PlatformExecBounds, + { + BerachainEvmConfig::new_with_evm_factory(chainspec, BerachainEvmFactory::default()) + } + + fn next_block_environment_context

( + _chainspec: &BerachainChainSpec, + _parent: &BerachainHeader, + attributes: &BerachainPayloadBuilderAttributes, + ) -> ::NextBlockEnvCtx + where + P: traits::PlatformExecBounds, + { + use crate::node::evm::config::BerachainNextBlockEnvAttributes; + + BerachainNextBlockEnvAttributes { + timestamp: attributes.timestamp, + suggested_fee_recipient: attributes.suggested_fee_recipient, + prev_randao: attributes.prev_randao, + gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M, // TODO: Get from config + parent_beacon_block_root: attributes.parent_beacon_block_root, + withdrawals: Some(attributes.withdrawals.clone()), + prev_proposer_pubkey: attributes.prev_proposer_pubkey, + } + } + + fn build_payload

( + payload: Checkpoint

, + provider: &dyn StateProvider, + ) -> Result<::BuiltPayload, PayloadBuilderError> + where + P: traits::PlatformExecBounds, + { + let evm_config = payload.block().evm_config().clone(); + let chain_spec = payload.block().chainspec(); + + let payload_config = PayloadConfig::new( + Arc::new(payload.block().parent().clone()), + payload.block().attributes().clone(), + ); + + let build_args = + BuildArguments::new(Default::default(), payload_config, Default::default(), None); + + let builder_config = EthereumBuilderConfig::new(); + + // This will reorder transactions. Something we want to avoid for flashblocks. + let transactions = payload.history().transactions().cloned().collect(); + let transactions = Box::new(FixedTransactions::::new(transactions)); + + default_berachain_payload_for_platform( + evm_config, + chain_spec, + provider, + NoopTransactionPool::new(), + &builder_config, + build_args, + |_| { + transactions + as Box< + dyn BestTransactions< + Item = Arc>, + >, + > + }, + )? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) + } +} + +#[inline] +pub fn default_berachain_payload_for_platform( + evm_config: BerachainEvmConfig, + chain_spec: &Arc>, + state_provider: &dyn StateProvider, + pool: Pool, + builder_config: &EthereumBuilderConfig, + args: BuildArguments, + best_txs: F, +) -> Result, PayloadBuilderError> +where + Pool: TransactionPool>, + F: FnOnce(BestTransactionsAttributes) -> BestTransactionsFor, +{ + let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; + let PayloadConfig { parent_header, attributes } = config; + + let state = StateProviderDatabase::new(&state_provider); + let mut db = + State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build(); + + let mut builder = evm_config + .builder_for_next_block( + &mut db, + &parent_header, + BerachainNextBlockEnvAttributes { + timestamp: attributes.timestamp(), + suggested_fee_recipient: attributes.suggested_fee_recipient(), + prev_randao: attributes.prev_randao(), + gas_limit: builder_config.gas_limit(parent_header.gas_limit), + parent_beacon_block_root: attributes.parent_beacon_block_root(), + withdrawals: Some(attributes.withdrawals().clone()), + prev_proposer_pubkey: attributes.prev_proposer_pubkey, + }, + ) + .map_err(PayloadBuilderError::other)?; + + debug!(target: "payload_builder", id=%attributes.id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload"); + let mut cumulative_gas_used = 0; + let block_gas_limit: u64 = builder.evm_mut().block().gas_limit; + let base_fee = builder.evm_mut().block().basefee; + + let mut best_txs = best_txs(BestTransactionsAttributes::new( + base_fee, + builder.evm_mut().block().blob_gasprice().map(|gasprice| gasprice as u64), + )); + let mut total_fees = U256::ZERO; + + builder.apply_pre_execution_changes().map_err(|err| { + warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); + PayloadBuilderError::Internal(err.into()) + })?; + + // initialize empty blob sidecars at first. If cancun is active then this will be populated by + // blob sidecars if any. + let mut blob_sidecars = BlobSidecars::Empty; + + let mut block_blob_count = 0; + let mut block_transactions_rlp_length = 0; + + let blob_params = chain_spec.blob_params_at_timestamp(attributes.timestamp); + let max_blob_count = + blob_params.as_ref().map(|params| params.max_blob_count).unwrap_or_default(); + + let is_osaka = chain_spec.is_osaka_active_at_timestamp(attributes.timestamp); + + // Check if Prague3 is active and skip all transactions if so + if chain_spec.is_prague3_active_at_timestamp(attributes.timestamp()) { + warn!(target: "payload_builder", "Prague3 is active, building payload without transactions is not supported"); + return Err(PayloadBuilderError::Other(Box::from( + "Prague 3 block building is not supported", + ))) + } + // Skip all transactions and proceed to finalize the empty block + while let Some(pool_tx) = best_txs.next() { + // ensure we still have capacity for this transaction + if cumulative_gas_used + pool_tx.gas_limit() > 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( + &pool_tx, + InvalidPoolTransactionError::ExceedsGasLimit(pool_tx.gas_limit(), block_gas_limit), + ); + continue + } + + // check if the job was cancelled, if so we can exit early + if cancel.is_cancelled() { + return Ok(BuildOutcome::Cancelled) + } + + // convert tx to a signed transaction + let tx = pool_tx.to_consensus(); + + let estimated_block_size_with_tx = block_transactions_rlp_length + + tx.inner().length() + + attributes.withdrawals().length() + + 1024; // 1Kb of overhead for the block header + + if is_osaka && estimated_block_size_with_tx > MAX_RLP_BLOCK_SIZE { + best_txs.mark_invalid( + &pool_tx, + InvalidPoolTransactionError::OversizedData( + estimated_block_size_with_tx, + MAX_RLP_BLOCK_SIZE, + ), + ); + continue; + } + + // There's only limited amount of blob space available per block, so we need to check if + // the EIP-4844 can still fit in the block + let mut blob_tx_sidecar = None; + if let Some(blob_tx) = tx.as_eip4844() { + let tx_blob_count = blob_tx.tx().blob_versioned_hashes.len() as u64; + + if block_blob_count + tx_blob_count > max_blob_count { + // we can't fit this _blob_ transaction into the block, so we mark it as + // invalid, which removes its dependent transactions from + // the iterator. This is similar to the gas limit condition + // for regular transactions above. + trace!(target: "payload_builder", tx=?tx.hash(), ?block_blob_count, "skipping blob transaction because it would exceed the max blob count per block"); + best_txs.mark_invalid( + &pool_tx, + InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::TooManyEip4844Blobs { + have: block_blob_count + tx_blob_count, + permitted: max_blob_count, + }, + ), + ); + continue + } + + let blob_sidecar_result = 'sidecar: { + let Some(sidecar) = + pool.get_blob(*tx.hash()).map_err(PayloadBuilderError::other)? + else { + break 'sidecar Err(Eip4844PoolTransactionError::MissingEip4844BlobSidecar) + }; + + if is_osaka { + if sidecar.is_eip7594() { + Ok(sidecar) + } else { + Err(Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka) + } + } else if sidecar.is_eip4844() { + Ok(sidecar) + } else { + Err(Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka) + } + }; + + blob_tx_sidecar = match blob_sidecar_result { + Ok(sidecar) => Some(sidecar), + Err(error) => { + best_txs.mark_invalid(&pool_tx, InvalidPoolTransactionError::Eip4844(error)); + continue + } + }; + } + + // Execute the transaction + 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 + 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 + trace!(target: "payload_builder", %error, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid( + &pool_tx, + InvalidPoolTransactionError::Consensus( + InvalidTransactionError::TxTypeNotSupported, + ), + ); + } + continue + } + // this is an error that we should treat as fatal for this attempt + Err(err) => return Err(PayloadBuilderError::evm(err)), + }; + + // add to the total blob gas used if the transaction successfully executed + if let Some(blob_tx) = tx.as_eip4844() { + block_blob_count += blob_tx.tx().blob_versioned_hashes.len() as u64; + + // if we've reached the max blob count, we can skip blob txs entirely + if block_blob_count == max_blob_count { + best_txs.skip_blobs(); + } + } + + block_transactions_rlp_length += tx.inner().length(); + + // update and add to total fees + let miner_fee = + tx.effective_tip_per_gas(base_fee).expect("fee is always valid; execution succeeded"); + total_fees += U256::from(miner_fee) * U256::from(gas_used); + cumulative_gas_used += gas_used; + + // Add blob tx sidecar to the payload. + if let Some(sidecar) = blob_tx_sidecar { + blob_sidecars.push_sidecar_variant(sidecar.as_ref().clone()); + } + } + + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), total_fees) { + // Release db + drop(builder); + // can skip building the block + return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads }) + } + + let BlockBuilderOutcome { execution_result, block, .. } = builder.finish(state_provider)?; + + let requests = chain_spec + .is_prague_active_at_timestamp(attributes.timestamp) + .then_some(execution_result.requests); + + let sealed_block = Arc::new(block.sealed_block().clone()); + debug!(target: "payload_builder", id=%attributes.id, sealed_block_header = ?sealed_block.sealed_header(), "sealed built block"); + + if is_osaka && sealed_block.rlp_length() > MAX_RLP_BLOCK_SIZE { + return Err(PayloadBuilderError::other(ConsensusError::BlockTooLarge { + rlp_length: sealed_block.rlp_length(), + max_rlp_length: MAX_RLP_BLOCK_SIZE, + })); + } + + let payload = BerachainBuiltPayload::new(attributes.id, sealed_block, total_fees, requests) + // add blob sidecars from the executed txs + .with_sidecars(blob_sidecars); + + Ok(BuildOutcome::Better { payload, cached_reads }) +} diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 58f61be1..801cb1f5 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -19,7 +19,7 @@ use reth_transaction_pool::TransactionValidationTaskExecutor; use std::{fmt::Debug, time::SystemTime}; use tracing::{debug, info}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, Copy)] pub struct BerachainPoolBuilder; impl PoolBuilder for BerachainPoolBuilder diff --git a/src/rpc/api.rs b/src/rpc/api.rs index 76eca4c5..8893164b 100644 --- a/src/rpc/api.rs +++ b/src/rpc/api.rs @@ -25,7 +25,7 @@ use reth_evm::{SpecFor, TxEnvFor}; use reth_rpc::eth::DevSigner; use reth_rpc_convert::SignableTxRequest; use reth_rpc_eth_api::{ - EthApiTypes, RpcNodeCore, RpcNodeCoreExt, + EthApiTypes, RpcNodeCore, RpcNodeCoreExt, RpcReceipt, helpers::{ AddDevSigners, Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, @@ -475,6 +475,28 @@ where Ok(hash) } + + /// Returns the transaction receipt for the given hash. + /// + /// Returns None if the transaction does not exist or is pending + /// Note: The tx receipt is not available for pending transactions. + /// TOOD: Override this to use Berachain Flashblocks + async fn transaction_receipt( + &self, + hash: B256, + ) -> Result>, Self::Error> + where + Self: LoadReceipt + 'static, + { + match self.load_transaction_and_receipt(hash).await? { + Some((tx, meta, receipt)) => { + self.build_transaction_receipt(tx, meta, receipt).await.map(Some) + } + // TODO: In the none case, we should attempt to fetch from flashblock state + // The flashblock state needs to be ingested from WS stream + None => Ok(None), + } + } } impl LoadTransaction for BerachainApi