From 530c37013c1c9251e5da4538489d463b2fa69693 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Thu, 18 Dec 2025 20:39:49 +0700 Subject: [PATCH 01/18] feat: transact frags transaction --- based/Cargo.lock | 48 +++++ based/crates/reth/Cargo.toml | 50 +++++ based/crates/reth/src/error.rs | 20 ++ based/crates/reth/src/exec.rs | 232 +++++++++++++++++++++++- based/crates/reth/src/unsealed_block.rs | 27 +++ 5 files changed, 367 insertions(+), 10 deletions(-) diff --git a/based/Cargo.lock b/based/Cargo.lock index 5c3c6ddf7..a2b94c57a 100644 --- a/based/Cargo.lock +++ b/based/Cargo.lock @@ -1011,6 +1011,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -6774,8 +6780,50 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types", "anyhow", + "arc-swap", "bop-common", + "log", + "op-alloy-consensus", "op-alloy-rpc-types", + "reth-chain-state", + "reth-chainspec", + "reth-cli", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-engine-tree", + "reth-evm", + "reth-execution-errors", + "reth-node-ethereum", + "reth-node-types", + "reth-optimism-chainspec", + "reth-optimism-cli", + "reth-optimism-consensus", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-node", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-optimism-txpool", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc-builder", + "reth-rpc-layer", + "reth-stages-types", + "reth-storage-api", + "reth-storage-errors", + "reth-trie", + "reth-trie-common", + "reth-trie-db", + "reth-trie-parallel", + "revm", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-primitives", "thiserror 2.0.12", "tokio", "tracing", diff --git a/based/crates/reth/Cargo.toml b/based/crates/reth/Cargo.toml index 6942fd9b6..2f673f5fb 100644 --- a/based/crates/reth/Cargo.toml +++ b/based/crates/reth/Cargo.toml @@ -18,3 +18,53 @@ thiserror.workspace = true tokio.workspace = true anyhow = "1.0.98" + +reth-chain-state = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-chain-state" } +reth-chainspec = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-chainspec" } +reth-cli = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-cli" } +reth-consensus = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-consensus" } +reth-db = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-db" } +reth-db-api = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-db-api" } +reth-db-common = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-db-common" } +reth-evm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-evm" } +reth-execution-errors = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-execution-errors" } +reth-engine-tree = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-engine-tree" } +reth-node-ethereum = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-node-ethereum", features = ["test-utils"] } +reth-node-types = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-node-types" } +reth-optimism-chainspec = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-chainspec" } +reth-optimism-cli = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-cli" } +reth-optimism-consensus = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-consensus" } +reth-optimism-evm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-evm" } +reth-optimism-forks = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-forks" } +reth-optimism-node = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-node" } +reth-optimism-payload-builder = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-payload-builder" } +reth-optimism-txpool = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-txpool" } +reth-optimism-primitives = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-primitives" } +reth-primitives = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-primitives" } +reth-primitives-traits = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-primitives-traits" } +reth-provider = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-provider", features = ["test-utils"] } +reth-revm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-revm" } +reth-rpc-builder = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-rpc-builder" } +reth-rpc-layer = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-rpc-layer" } +reth-stages-types = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-stages-types" } +reth-storage-api = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-storage-api" } +reth-storage-errors = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-storage-errors" } +reth-trie = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie" } +reth-trie-common = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie-common", features = ["test-utils"] } +reth-trie-db = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie-db" } +reth-trie-parallel = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie-parallel" } + +revm = { version = "31.0.1", features = [ + "optional_balance_check", + "secp256k1", + "std", +], default-features = false } +revm-handler = "12.0.0" +revm-inspector = "12.0.1" +revm-interpreter = "29.0.1" +revm-primitives = { version = "21.0.2", features = [ + "std", +], default-features = false } +arc-swap = "1.7.1" +log = "0.4.27" +op-alloy-consensus = "0.22.1" diff --git a/based/crates/reth/src/error.rs b/based/crates/reth/src/error.rs index 96778fa2a..5a61c896d 100644 --- a/based/crates/reth/src/error.rs +++ b/based/crates/reth/src/error.rs @@ -1,5 +1,9 @@ +use alloy_consensus::crypto::RecoveryError; use alloy_eips::eip2718::Eip2718Error; use alloy_primitives::B256; +use reth_optimism_evm::OpBlockExecutionError; +use reth_storage_errors::ProviderError; +use op_alloy_consensus::EIP1559ParamError; use thiserror::Error; #[derive(Debug, Error)] @@ -93,4 +97,20 @@ pub enum ExecError { #[error("seal failed: {0}")] SealFailed(String), + + + #[error(transparent)] + StorageProvider(#[from] ProviderError), + + + #[error(transparent)] + OpBlockExecution(#[from] OpBlockExecutionError), + + + #[error(transparent)] + Recovery(#[from] RecoveryError), + + + #[error(transparent)] + Eip1559Param(#[from] EIP1559ParamError), } diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index e116d699f..fd8601271 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,9 +1,27 @@ use std::future::Future; - -use alloy_primitives::{B256, BlockNumber}; -use alloy_rpc_types::{Block, Log, TransactionReceipt}; +use std::sync::Arc; +use alloy_consensus::{BlockBody, Header, Transaction, TxEnvelope}; +use alloy_consensus::transaction::{Recovered, SignerRecoverable, TransactionMeta}; +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; +use alloy_primitives::utils::ParseUnits::U256; +use alloy_rpc_types::{Block, Log, TransactionReceipt, engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth_optimism_chainspec::OpHardforks; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_revm::database::StateProviderDatabase; +use reth_revm::{DatabaseCommit, State}; +use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; +use revm::database::CacheDB; +use arc_swap::ArcSwapOption; +use reth_optimism_primitives::{OpBlock, OpTransactionSigned}; +use reth_optimism_primitives::serde_bincode_compat::transaction::OpTxEnvelope; +use alloy_eips::eip2718::Decodable2718; +use alloy_rpc_types::state::StateOverride; use bop_common::p2p::{EnvV0, FragV0}; - +use log::error; +use reth_evm::{ConfigureEvm, Evm}; +use reth_revm::context::result::ResultAndState; use crate::{error::ExecError, unsealed_block::UnsealedBlock}; #[derive(Debug, Clone)] @@ -45,11 +63,18 @@ pub fn apply_exec_output(ub: &mut UnsealedBlock, out: ExecOutput) { ub.cumulative_gas_used = ub.cumulative_gas_used.saturating_add(out.gas_used_delta); } -/// A very small “dummy” executor so you can compile & test state machine early. -/// Replace with Reth executor. -pub struct NoopExecutor; +pub struct StateExecutor { + client: Client, + current_unsealed_block: Arc>, +} + +impl UnsealedExecutor for StateExecutor where + Client: StateProviderFactory + + ChainSpecProvider + OpHardforks> + + BlockReaderIdExt
+ + Clone + + 'static, { -impl UnsealedExecutor for NoopExecutor { fn ensure_env(&mut self, _env: &EnvV0) -> impl Future> + Send + '_ { async move { Ok(()) } } @@ -57,9 +82,111 @@ impl UnsealedExecutor for NoopExecutor { fn execute_frag( &mut self, _ub: &UnsealedBlock, - _frag: &FragV0, + frag: &FragV0, ) -> impl Future> + Send + '_ { - async move { Ok(ExecOutput { receipts: vec![], logs: vec![], gas_used_delta: 0 }) } + let client = self.client.clone(); + let ub_opt = self.current_unsealed_block.load_full(); + let frag = frag.clone(); + + async move { + let ub = ub_opt.ok_or(ExecError::NotInitialized)?; + let ub_cache = ub.get_db_cache(); + let canonical_block = ub.env.number.saturating_sub(1); + let last_block_header = client + .header_by_number(canonical_block) + .map_err(|e| ExecError::Failed(format!("header_by_number({canonical_block}) failed: {e}")))? + .ok_or_else(|| ExecError::Failed(format!("missing parent header at {canonical_block}")))?; + + let evm_config = OpEvmConfig::optimism(client.chain_spec()); + + let state_provider = client + .state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)) + .map_err(|e| ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")))?; + + let state_provider_db = StateProviderDatabase::new(state_provider); + let state = State::builder() + .with_database(state_provider_db) + .with_bundle_update() + .build(); + + let mut db = CacheDB { cache: ub_cache, db: state }; + + let mut state_overrides = match ub.get_state_overrides() { + Some(v) => v, + None => StateOverride::default(), + }; + + let block: OpBlock = build_op_block_from_ub_and_frag(ub.as_ref(), frag)?; + let mut l1_block_info = reth_optimism_evm::extract_l1_info(&block.body)?; + let header = block.header.clone().seal_slow(); + + let block_env_attributes = OpNextBlockEnvAttributes { + timestamp: ub.env.timestamp, + suggested_fee_recipient: ub.env.beneficiary, + prev_randao: ub.env.prevrandao, + gas_limit: ub.env.gas_limit, + parent_beacon_block_root: Some(ub.env.parent_beacon_block_root), + extra_data: block.extra_data.clone(), + }; + + let evm_env = evm_config.next_evm_env(&last_block_header, &block_env_attributes)?; + let mut evm = evm_config.evm_with_env(db, evm_env); + + let mut gas_used = 0; + let mut next_log_index = 0; + + for (idx, transaction) in block.body.transactions.iter().enumerate() { + let tx_hash = transaction.tx_hash(); + let sender = transaction.recover_signer()?; + + let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); + let envelope = recovered_transaction.clone().convert::(); + + let effective_gas_price = if transaction.is_deposit() { + 0 + } else { + block + .base_fee_per_gas + .map(|base_fee| { + transaction.effective_tip_per_gas(base_fee).unwrap_or_default() + + base_fee as u128 + }) + .unwrap_or_else(|| transaction.max_fee_per_gas()) + }; + + match evm.transact(recovered_transaction) { + Ok(ResultAndState { state, result }) => { + for (addr, acc) in &state { + let existing_override = state_overrides.entry(*addr).or_default(); + existing_override.balance = Some(acc.info.balance); + existing_override.nonce = Some(acc.info.nonce); + existing_override.code = + acc.info.code.clone().map(|code| code.bytes()); + + let existing = + existing_override.state_diff.get_or_insert(Default::default()); + let changed_slots = acc.storage.iter().map(|(&key, slot)| { + (B256::from(key), B256::from(slot.present_value)) + }); + + existing.extend(changed_slots); + } + result. + evm.db_mut().commit(state); + } + Err(e) => { + return Err(ExecError::Failed(format!( + "failed to execute transaction: {:?} tx_hash: {:?} sender: {:?}", + e, tx_hash, sender + ))); + } + } + } + + db = evm.into_db(); + + Ok(ExecOutput { receipts: vec![], logs: vec![], gas_used_delta: 0 }) + } } fn seal(&mut self, _ub: &UnsealedBlock) -> impl Future> + Send + '_ { @@ -80,3 +207,88 @@ impl UnsealedExecutor for NoopExecutor { fn reset(&mut self) {} } + + +fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: FragV0) -> Result { + // Decode EIP-2718 tx bytes -> OpTxEnvelope + let tx_list: Vec = frag + .txs + .iter() + .enumerate() + .map(|(i, tx_bytes)| { + let mut slice = tx_bytes.as_ref(); // &[u8] + + let eth_env = TxEnvelope::decode_2718_exact(&mut slice) + .map_err(|e| ExecError::Failed(format!("decode tx failed: {e}")))?; + + let op_env = OpTransactionSigned::try_from_eth_envelope(eth_env).map_err(|unsupported| { + ExecError::Failed(format!( + "tx variant not supported on OP (likely EIP-4844): {unsupported:?}" + )) + })?; + + Ok(op_env) + }) + .collect::, ExecError>>()?; + + // Convert EnvV0.extra_data (VariableList) -> Bytes for the header. + let extra_data: Bytes = Bytes::copy_from_slice(ub.env.extra_data.as_ref()); + + // Like your Go code, these roots/bloom are left "empty"/default because this is + // a synthetic block used for execution context + bookkeeping. + let header = Header { + parent_hash: ub.env.parent_hash, + + // Go: UncleHash = EmptyUncleHash (we'll keep defaults unless you have constants) + ommers_hash: Default::default(), + + beneficiary: ub.env.beneficiary, + + // Go: Root/TxHash/ReceiptHash/Bloom are empty in InsertNewFrag + state_root: B256::ZERO, + transactions_root: B256::ZERO, + receipts_root: B256::ZERO, + logs_bloom: Default::default(), + + difficulty: ub.env.difficulty, + number: frag.block_number, + gas_limit: ub.env.gas_limit, + + // Go: GasUsed = currentUnsealedBlock.CumulativeGasUsed + gas_used: ub.cumulative_gas_used, + + timestamp: ub.env.timestamp, + extra_data, + + // Go: MixDigest = Prevrandao + mix_hash: ub.env.prevrandao, + + nonce: Default::default(), + + // Go: BaseFee = currentUnsealedBlock.Env.Basefee + base_fee_per_gas: Some(ub.env.basefee), + + // If you want to mirror Go’s “empty withdrawals list”, set `withdrawals: Some(vec![])` + // and set withdrawals_root accordingly (if you have the empty-withdrawals root constant). + withdrawals_root: None, + + // Go: BlobGasUsed = ¤tUnsealedBlock.CumulativeBlobGasUsed + blob_gas_used: Some(ub.cumulative_blob_gas_used), + + // Go: ExcessBlobGas = new(uint64) (i.e. 0) + excess_blob_gas: Some(0), + + parent_beacon_block_root: Some(ub.env.parent_beacon_block_root), + + // post-requests fields + requests_hash: None, + }; + + let body = BlockBody { + transactions: tx_list, + ommers: vec![], + withdrawals: None, // or Some(vec![]) if you want an explicit empty list + }; + + Ok(OpBlock::new(header, body)) +} \ No newline at end of file diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 8f8145ed5..3ce8751b8 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -2,6 +2,8 @@ use alloy_consensus::{Header, TxEnvelope}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{B256, Bytes}; use alloy_rpc_types::{Log, TransactionReceipt}; +use alloy_rpc_types::state::StateOverride; +use reth_revm::db::Cache; use bop_common::p2p::{EnvV0, FragV0, Transaction as TxBytes}; use crate::error::UnsealedBlockError; @@ -28,6 +30,9 @@ pub struct UnsealedBlock { pub cumulative_gas_used: u64, /// Cumulative blob gas used across all blob-carrying transactions in the block. pub cumulative_blob_gas_used: u64, + + db_cache: Cache, + state_overrides: Option, } impl UnsealedBlock { @@ -41,6 +46,8 @@ impl UnsealedBlock { logs: Vec::new(), cumulative_gas_used: 0, cumulative_blob_gas_used: 0, + db_cache: Cache::default(), + state_overrides: None, } } @@ -156,4 +163,24 @@ impl UnsealedBlock { pub fn reset_to_env(&mut self, env: EnvV0) { *self = Self::new(env); } + + pub(crate) fn with_db_cache(&mut self, cache: Cache) -> &Self { + self.db_cache = cache; + self + } + + /// Returns the database cache. + pub fn get_db_cache(&self) -> Cache { + self.db_cache.clone() + } + + pub(crate) fn with_state_overrides(&mut self, state_overrides: StateOverride) -> &Self { + self.state_overrides = Some(state_overrides); + self + } + + /// Returns the state overrides for the pending state. + pub fn get_state_overrides(&self) -> Option { + self.state_overrides.clone() + } } From d02f031d7ff56c773e4668b8a9ce9a136ab2fda4 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Thu, 18 Dec 2025 22:28:48 +0700 Subject: [PATCH 02/18] feat: execute frag done --- based/Cargo.lock | 2 + based/crates/reth/Cargo.toml | 2 + based/crates/reth/src/driver.rs | 9 +- based/crates/reth/src/error.rs | 10 +- based/crates/reth/src/exec.rs | 214 ++++++++++++++---------- based/crates/reth/src/unsealed_block.rs | 33 ++-- 6 files changed, 160 insertions(+), 110 deletions(-) diff --git a/based/Cargo.lock b/based/Cargo.lock index a2b94c57a..e8f08b4b3 100644 --- a/based/Cargo.lock +++ b/based/Cargo.lock @@ -6805,12 +6805,14 @@ dependencies = [ "reth-optimism-node", "reth-optimism-payload-builder", "reth-optimism-primitives", + "reth-optimism-rpc", "reth-optimism-txpool", "reth-primitives", "reth-primitives-traits", "reth-provider", "reth-revm", "reth-rpc-builder", + "reth-rpc-convert", "reth-rpc-layer", "reth-stages-types", "reth-storage-api", diff --git a/based/crates/reth/Cargo.toml b/based/crates/reth/Cargo.toml index 2f673f5fb..8cac3c6f0 100644 --- a/based/crates/reth/Cargo.toml +++ b/based/crates/reth/Cargo.toml @@ -35,6 +35,8 @@ reth-optimism-chainspec = { git = "https://github.com/gattaca-com/based-op-reth" reth-optimism-cli = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-cli" } reth-optimism-consensus = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-consensus" } reth-optimism-evm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-evm" } +reth-optimism-rpc = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-rpc" } +reth-rpc-convert = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-rpc-convert" } reth-optimism-forks = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-forks" } reth-optimism-node = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-node" } reth-optimism-payload-builder = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-payload-builder" } diff --git a/based/crates/reth/src/driver.rs b/based/crates/reth/src/driver.rs index 378738bea..a826f9d3f 100644 --- a/based/crates/reth/src/driver.rs +++ b/based/crates/reth/src/driver.rs @@ -8,7 +8,7 @@ use tracing::{error, info}; use crate::{ error::{DriverError, ValidateSealError}, - exec::{UnsealedExecutor, apply_exec_output}, + exec::UnsealedExecutor, unsealed_block::UnsealedBlock, }; @@ -222,8 +222,8 @@ impl DriverInner { return Err(DriverError::from(e)); } - let out = match self.exec.execute_frag(ub, &frag).await { - Ok(out) => out, + match self.exec.execute_frag(&frag).await { + Ok(()) => (), Err(e) => { error!(error = %e, "execution failed, discarding unsealed block"); self.reset_current_unsealed_block(); @@ -231,9 +231,6 @@ impl DriverInner { } }; - apply_exec_output(ub, out); - ub.accept_frag(frag); - info!(elapsed_ms = start.elapsed().as_millis(), "frag inserted + executed"); if ub.last_frag().is_some_and(|f| f.is_last) { diff --git a/based/crates/reth/src/error.rs b/based/crates/reth/src/error.rs index 5a61c896d..2ab30b0c0 100644 --- a/based/crates/reth/src/error.rs +++ b/based/crates/reth/src/error.rs @@ -1,9 +1,10 @@ use alloy_consensus::crypto::RecoveryError; use alloy_eips::eip2718::Eip2718Error; use alloy_primitives::B256; +use op_alloy_consensus::EIP1559ParamError; use reth_optimism_evm::OpBlockExecutionError; +use reth_optimism_rpc::OpEthApiError; use reth_storage_errors::ProviderError; -use op_alloy_consensus::EIP1559ParamError; use thiserror::Error; #[derive(Debug, Error)] @@ -98,19 +99,18 @@ pub enum ExecError { #[error("seal failed: {0}")] SealFailed(String), - #[error(transparent)] StorageProvider(#[from] ProviderError), - #[error(transparent)] OpBlockExecution(#[from] OpBlockExecutionError), - #[error(transparent)] Recovery(#[from] RecoveryError), - #[error(transparent)] Eip1559Param(#[from] EIP1559ParamError), + + #[error(transparent)] + OpEthApi(#[from] OpEthApiError), } diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index fd8601271..529e14a31 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,35 +1,37 @@ -use std::future::Future; -use std::sync::Arc; -use alloy_consensus::{BlockBody, Header, Transaction, TxEnvelope}; -use alloy_consensus::transaction::{Recovered, SignerRecoverable, TransactionMeta}; -use alloy_eips::BlockNumberOrTag; -use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; -use alloy_primitives::utils::ParseUnits::U256; -use alloy_rpc_types::{Block, Log, TransactionReceipt, engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}}; +use std::{future::Future, sync::Arc}; + +use alloy_consensus::{ + BlockBody, Header, Receipt, Transaction, TxEnvelope, TxReceipt, + transaction::{Recovered, SignerRecoverable, TransactionMeta}, +}; +use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; +use alloy_primitives::{B256, BlockNumber, Bytes, Sealable, utils::ParseUnits::U256}; +use alloy_rpc_types::{ + Block, Log, TransactionReceipt, + engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}, + state::StateOverride, +}; +use arc_swap::ArcSwapOption; +use bop_common::p2p::{EnvV0, FragV0}; +use op_alloy_consensus::OpReceiptEnvelope; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; -use reth_revm::database::StateProviderDatabase; -use reth_revm::{DatabaseCommit, State}; +use reth_optimism_primitives::{ + OpBlock, OpPrimitives, OpReceipt, OpTransactionSigned, serde_bincode_compat::transaction::OpTxEnvelope, +}; +use reth_optimism_rpc::OpReceiptBuilder; +use reth_revm::{ + DatabaseCommit, State, + context::result::{ExecutionResult, ResultAndState}, + database::StateProviderDatabase, +}; +use reth_rpc_convert::transaction::ConvertReceiptInput; use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; use revm::database::CacheDB; -use arc_swap::ArcSwapOption; -use reth_optimism_primitives::{OpBlock, OpTransactionSigned}; -use reth_optimism_primitives::serde_bincode_compat::transaction::OpTxEnvelope; -use alloy_eips::eip2718::Decodable2718; -use alloy_rpc_types::state::StateOverride; -use bop_common::p2p::{EnvV0, FragV0}; -use log::error; -use reth_evm::{ConfigureEvm, Evm}; -use reth_revm::context::result::ResultAndState; -use crate::{error::ExecError, unsealed_block::UnsealedBlock}; -#[derive(Debug, Clone)] -pub struct ExecOutput { - pub receipts: Vec, - pub logs: Vec, - pub gas_used_delta: u64, -} +use crate::{error::ExecError, unsealed_block::UnsealedBlock}; /// This trait is the ONLY place that needs to know about Reth internals. /// Everything else is just state-machine + bookkeeping. @@ -40,11 +42,7 @@ pub trait UnsealedExecutor: Send { /// Execute all txs in `frag` on top of current overlay state. /// /// MUST be cumulative: txs execute after all previous frags's txs. - fn execute_frag( - &mut self, - ub: &UnsealedBlock, - frag: &FragV0, - ) -> impl Future> + Send + '_; + fn execute_frag(&mut self, frag: &FragV0) -> impl Future> + Send + '_; fn seal(&mut self, ub: &UnsealedBlock) -> impl Future> + Send + '_; @@ -56,34 +54,24 @@ pub trait UnsealedExecutor: Send { fn reset(&mut self); } -/// Apply the executor output to the UnsealedBlock (common logic). -pub fn apply_exec_output(ub: &mut UnsealedBlock, out: ExecOutput) { - ub.receipts.extend(out.receipts); - ub.logs.extend(out.logs); - ub.cumulative_gas_used = ub.cumulative_gas_used.saturating_add(out.gas_used_delta); -} - -pub struct StateExecutor { +pub struct StateExecutor { client: Client, current_unsealed_block: Arc>, } -impl UnsealedExecutor for StateExecutor where +impl UnsealedExecutor for StateExecutor +where Client: StateProviderFactory - + ChainSpecProvider + OpHardforks> - + BlockReaderIdExt
- + Clone - + 'static, { - + + ChainSpecProvider + OpHardforks> + + BlockReaderIdExt
+ + Clone + + 'static, +{ fn ensure_env(&mut self, _env: &EnvV0) -> impl Future> + Send + '_ { async move { Ok(()) } } - fn execute_frag( - &mut self, - _ub: &UnsealedBlock, - frag: &FragV0, - ) -> impl Future> + Send + '_ { + fn execute_frag(&mut self, frag: &FragV0) -> impl Future> + Send + '_ { let client = self.client.clone(); let ub_opt = self.current_unsealed_block.load_full(); let frag = frag.clone(); @@ -99,15 +87,13 @@ impl UnsealedExecutor for StateExecutor where let evm_config = OpEvmConfig::optimism(client.chain_spec()); - let state_provider = client - .state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)) - .map_err(|e| ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")))?; + let state_provider = + client.state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)).map_err(|e| { + ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")) + })?; let state_provider_db = StateProviderDatabase::new(state_provider); - let state = State::builder() - .with_database(state_provider_db) - .with_bundle_update() - .build(); + let state = State::builder().with_database(state_provider_db).with_bundle_update().build(); let mut db = CacheDB { cache: ub_cache, db: state }; @@ -116,7 +102,7 @@ impl UnsealedExecutor for StateExecutor where None => StateOverride::default(), }; - let block: OpBlock = build_op_block_from_ub_and_frag(ub.as_ref(), frag)?; + let block: OpBlock = build_op_block_from_ub_and_frag(ub.as_ref(), &frag)?; let mut l1_block_info = reth_optimism_evm::extract_l1_info(&block.body)?; let header = block.header.clone().seal_slow(); @@ -132,27 +118,16 @@ impl UnsealedExecutor for StateExecutor where let evm_env = evm_config.next_evm_env(&last_block_header, &block_env_attributes)?; let mut evm = evm_config.evm_with_env(db, evm_env); - let mut gas_used = 0; + let mut gas_used: u64 = ub.cumulative_blob_gas_used; + let mut logs: Vec = Vec::new(); let mut next_log_index = 0; + let mut receipts: Vec>> = Vec::new(); for (idx, transaction) in block.body.transactions.iter().enumerate() { let tx_hash = transaction.tx_hash(); let sender = transaction.recover_signer()?; let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); - let envelope = recovered_transaction.clone().convert::(); - - let effective_gas_price = if transaction.is_deposit() { - 0 - } else { - block - .base_fee_per_gas - .map(|base_fee| { - transaction.effective_tip_per_gas(base_fee).unwrap_or_default() - + base_fee as u128 - }) - .unwrap_or_else(|| transaction.max_fee_per_gas()) - }; match evm.transact(recovered_transaction) { Ok(ResultAndState { state, result }) => { @@ -160,19 +135,56 @@ impl UnsealedExecutor for StateExecutor where let existing_override = state_overrides.entry(*addr).or_default(); existing_override.balance = Some(acc.info.balance); existing_override.nonce = Some(acc.info.nonce); - existing_override.code = - acc.info.code.clone().map(|code| code.bytes()); + existing_override.code = acc.info.code.clone().map(|code| code.bytes()); - let existing = - existing_override.state_diff.get_or_insert(Default::default()); - let changed_slots = acc.storage.iter().map(|(&key, slot)| { - (B256::from(key), B256::from(slot.present_value)) - }); + let existing = existing_override.state_diff.get_or_insert(Default::default()); + let changed_slots = acc + .storage + .iter() + .map(|(&key, slot)| (B256::from(key), B256::from(slot.present_value))); existing.extend(changed_slots); } - result. + evm.db_mut().commit(state); + + let (success, tx_gas_used, tx_logs) = split_execution_result(&result); + gas_used = gas_used.saturating_add(tx_gas_used); + logs.extend(tx_logs.iter().map(|inner| Log { inner: inner.clone(), ..Default::default() })); + + let base_receipt = + Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; + + let ty = transaction.ty(); + + let op_receipt = wrap_op_receipt(ty, base_receipt, None, None)?; + + let meta = TransactionMeta { + tx_hash, + index: idx as u64, + block_hash: header.hash(), + block_number: block.number, + base_fee: block.base_fee_per_gas, + excess_blob_gas: block.excess_blob_gas, + timestamp: block.timestamp, + }; + + let op_cgu = op_receipt.cumulative_gas_used(); + let input: ConvertReceiptInput<'_, OpPrimitives> = ConvertReceiptInput { + receipt: op_receipt, + tx: Recovered::new_unchecked(transaction, sender), + gas_used: op_cgu, + next_log_index, + meta, + }; + + let receipt = + OpReceiptBuilder::new(self.client.chain_spec().as_ref(), input, &mut l1_block_info)? + .core_receipt; + + next_log_index += receipt.logs().len(); + + receipts.push(receipt) } Err(e) => { return Err(ExecError::Failed(format!( @@ -184,8 +196,11 @@ impl UnsealedExecutor for StateExecutor where } db = evm.into_db(); + let mut new_unsealed = + UnsealedBlock::new(ub.env.clone()).with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); + new_unsealed.accept_frag_execution(frag, logs, receipts, gas_used); - Ok(ExecOutput { receipts: vec![], logs: vec![], gas_used_delta: 0 }) + Ok(()) } } @@ -208,8 +223,7 @@ impl UnsealedExecutor for StateExecutor where fn reset(&mut self) {} } - -fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: FragV0) -> Result { +fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result { // Decode EIP-2718 tx bytes -> OpTxEnvelope let tx_list: Vec = frag .txs @@ -222,9 +236,7 @@ fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: FragV0) -> Result Result) -> (bool, u64, Vec) { + match result { + ExecutionResult::Success { gas_used, logs, .. } => (true, *gas_used, logs.clone()), + ExecutionResult::Revert { gas_used, .. } => (false, *gas_used, vec![]), + ExecutionResult::Halt { gas_used, .. } => (false, *gas_used, vec![]), + } +} + +fn wrap_op_receipt( + tx_type: u8, + receipt: alloy_consensus::Receipt, + deposit_nonce: Option, + deposit_receipt_version: Option, +) -> Result { + Ok(match tx_type { + 0x00 => OpReceipt::Legacy(receipt), + 0x01 => OpReceipt::Eip2930(receipt), + 0x02 => OpReceipt::Eip1559(receipt), + 0x04 => OpReceipt::Eip7702(receipt), + t if t == op_alloy_consensus::DEPOSIT_TX_TYPE_ID => OpReceipt::Deposit(op_alloy_consensus::OpDepositReceipt { + inner: receipt, + deposit_nonce, + deposit_receipt_version, + }), + other => return Err(ExecError::Failed(format!("unsupported tx type for receipt: 0x{other:02x}"))), + }) +} diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 3ce8751b8..4af1f5299 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -1,10 +1,10 @@ use alloy_consensus::{Header, TxEnvelope}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{B256, Bytes}; -use alloy_rpc_types::{Log, TransactionReceipt}; -use alloy_rpc_types::state::StateOverride; -use reth_revm::db::Cache; +use alloy_rpc_types::{Log, TransactionReceipt, state::StateOverride}; use bop_common::p2p::{EnvV0, FragV0, Transaction as TxBytes}; +use op_alloy_consensus::OpReceiptEnvelope; +use reth_revm::db::Cache; use crate::error::UnsealedBlockError; @@ -23,7 +23,7 @@ pub struct UnsealedBlock { pub hash: B256, /// Transaction receipts for executed transactions. - pub receipts: Vec, + pub receipts: Vec>>, /// Flattened logs emitted during execution. pub logs: Vec, /// Cumulative execution gas used across all transactions in the block. @@ -101,10 +101,19 @@ impl UnsealedBlock { /// Apply the accepted frag into in-memory bookkeeping (NOT executing txs). /// /// Execution results (receipts/logs/gas) should be appended separately. - pub fn accept_frag(&mut self, f: FragV0) { + pub fn accept_frag_execution( + &mut self, + f: FragV0, + logs: Vec, + receipts: Vec>>, + cummulative_gas_used: u64, + ) { self.last_sequence_number = Some(f.seq); self.cumulative_blob_gas_used = self.cumulative_blob_gas_used.saturating_add(f.blob_gas_used); - self.frags.push(f); + self.frags.push(f.clone()); + self.logs.extend_from_slice(logs.as_slice()); + self.receipts.extend_from_slice(receipts.as_slice()); + self.cumulative_gas_used = cummulative_gas_used; } /// Validate frag against current state (equivalent to your ValidateNewFragV0 + sequencing gate). @@ -164,21 +173,21 @@ impl UnsealedBlock { *self = Self::new(env); } - pub(crate) fn with_db_cache(&mut self, cache: Cache) -> &Self { + pub fn with_db_cache(mut self, cache: Cache) -> Self { self.db_cache = cache; self } + pub fn with_state_overrides(mut self, state_overrides: Option) -> Self { + self.state_overrides = state_overrides; + self + } + /// Returns the database cache. pub fn get_db_cache(&self) -> Cache { self.db_cache.clone() } - pub(crate) fn with_state_overrides(&mut self, state_overrides: StateOverride) -> &Self { - self.state_overrides = Some(state_overrides); - self - } - /// Returns the state overrides for the pending state. pub fn get_state_overrides(&self) -> Option { self.state_overrides.clone() From 95d252abd404c2a4d5966a7956b5639a76f0690f Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Thu, 18 Dec 2025 22:30:02 +0700 Subject: [PATCH 03/18] fix cargo --- based/Cargo.lock | 31 ------------------------------- based/crates/reth/Cargo.toml | 35 ----------------------------------- 2 files changed, 66 deletions(-) diff --git a/based/Cargo.lock b/based/Cargo.lock index e8f08b4b3..d1a6ab552 100644 --- a/based/Cargo.lock +++ b/based/Cargo.lock @@ -6782,50 +6782,19 @@ dependencies = [ "anyhow", "arc-swap", "bop-common", - "log", "op-alloy-consensus", "op-alloy-rpc-types", - "reth-chain-state", "reth-chainspec", - "reth-cli", - "reth-consensus", - "reth-db", - "reth-db-api", - "reth-db-common", - "reth-engine-tree", "reth-evm", - "reth-execution-errors", - "reth-node-ethereum", - "reth-node-types", "reth-optimism-chainspec", - "reth-optimism-cli", - "reth-optimism-consensus", "reth-optimism-evm", - "reth-optimism-forks", - "reth-optimism-node", - "reth-optimism-payload-builder", "reth-optimism-primitives", "reth-optimism-rpc", - "reth-optimism-txpool", - "reth-primitives", - "reth-primitives-traits", - "reth-provider", "reth-revm", - "reth-rpc-builder", "reth-rpc-convert", - "reth-rpc-layer", - "reth-stages-types", "reth-storage-api", "reth-storage-errors", - "reth-trie", - "reth-trie-common", - "reth-trie-db", - "reth-trie-parallel", "revm", - "revm-handler", - "revm-inspector", - "revm-interpreter", - "revm-primitives", "thiserror 2.0.12", "tokio", "tracing", diff --git a/based/crates/reth/Cargo.toml b/based/crates/reth/Cargo.toml index 8cac3c6f0..6246fd8bf 100644 --- a/based/crates/reth/Cargo.toml +++ b/based/crates/reth/Cargo.toml @@ -17,56 +17,21 @@ alloy-eips.workspace = true thiserror.workspace = true tokio.workspace = true anyhow = "1.0.98" - - -reth-chain-state = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-chain-state" } reth-chainspec = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-chainspec" } -reth-cli = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-cli" } -reth-consensus = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-consensus" } -reth-db = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-db" } -reth-db-api = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-db-api" } -reth-db-common = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-db-common" } reth-evm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-evm" } -reth-execution-errors = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-execution-errors" } -reth-engine-tree = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-engine-tree" } -reth-node-ethereum = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-node-ethereum", features = ["test-utils"] } -reth-node-types = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-node-types" } reth-optimism-chainspec = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-chainspec" } -reth-optimism-cli = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-cli" } -reth-optimism-consensus = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-consensus" } reth-optimism-evm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-evm" } reth-optimism-rpc = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-rpc" } reth-rpc-convert = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-rpc-convert" } -reth-optimism-forks = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-forks" } -reth-optimism-node = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-node" } -reth-optimism-payload-builder = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-payload-builder" } -reth-optimism-txpool = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-txpool" } reth-optimism-primitives = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-optimism-primitives" } -reth-primitives = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-primitives" } -reth-primitives-traits = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-primitives-traits" } -reth-provider = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-provider", features = ["test-utils"] } reth-revm = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-revm" } -reth-rpc-builder = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-rpc-builder" } -reth-rpc-layer = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-rpc-layer" } -reth-stages-types = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-stages-types" } reth-storage-api = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-storage-api" } reth-storage-errors = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-storage-errors" } -reth-trie = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie" } -reth-trie-common = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie-common", features = ["test-utils"] } -reth-trie-db = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie-db" } -reth-trie-parallel = { git = "https://github.com/gattaca-com/based-op-reth", rev = "b053849462eb48e61b24d965cfee59cead7f8a3b", package = "reth-trie-parallel" } revm = { version = "31.0.1", features = [ "optional_balance_check", "secp256k1", "std", ], default-features = false } -revm-handler = "12.0.0" -revm-inspector = "12.0.1" -revm-interpreter = "29.0.1" -revm-primitives = { version = "21.0.2", features = [ - "std", -], default-features = false } arc-swap = "1.7.1" -log = "0.4.27" op-alloy-consensus = "0.22.1" From 780c97510ffcf453cf7018fbf704b3450cabaac1 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Thu, 18 Dec 2025 22:34:27 +0700 Subject: [PATCH 04/18] update ub --- based/crates/reth/src/exec.rs | 25 ++++++++++--------------- based/crates/reth/src/unsealed_block.rs | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 529e14a31..55f5cc65c 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,16 +1,12 @@ use std::{future::Future, sync::Arc}; use alloy_consensus::{ - BlockBody, Header, Receipt, Transaction, TxEnvelope, TxReceipt, + BlockBody, Header, Receipt, TxEnvelope, TxReceipt, transaction::{Recovered, SignerRecoverable, TransactionMeta}, }; use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; -use alloy_primitives::{B256, BlockNumber, Bytes, Sealable, utils::ParseUnits::U256}; -use alloy_rpc_types::{ - Block, Log, TransactionReceipt, - engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}, - state::StateOverride, -}; +use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; +use alloy_rpc_types::{Block, Log, TransactionReceipt, state::StateOverride}; use arc_swap::ArcSwapOption; use bop_common::p2p::{EnvV0, FragV0}; use op_alloy_consensus::OpReceiptEnvelope; @@ -18,9 +14,7 @@ use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; -use reth_optimism_primitives::{ - OpBlock, OpPrimitives, OpReceipt, OpTransactionSigned, serde_bincode_compat::transaction::OpTxEnvelope, -}; +use reth_optimism_primitives::{OpBlock, OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_optimism_rpc::OpReceiptBuilder; use reth_revm::{ DatabaseCommit, State, @@ -196,9 +190,10 @@ where } db = evm.into_db(); - let mut new_unsealed = - UnsealedBlock::new(ub.env.clone()).with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); - new_unsealed.accept_frag_execution(frag, logs, receipts, gas_used); + let mut next = ub.clone_for_update().with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); + next.accept_frag_execution(frag, logs, receipts, gas_used); + + self.current_unsealed_block.store(Some(Arc::new(next))); Ok(()) } @@ -229,7 +224,7 @@ fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result< .txs .iter() .enumerate() - .map(|(i, tx_bytes)| { + .map(|(_, tx_bytes)| { let mut slice = tx_bytes.as_ref(); // &[u8] let eth_env = TxEnvelope::decode_2718_exact(&mut slice) @@ -315,7 +310,7 @@ fn split_execution_result(result: &ExecutionResult) -> (bool, u64, fn wrap_op_receipt( tx_type: u8, - receipt: alloy_consensus::Receipt, + receipt: Receipt, deposit_nonce: Option, deposit_receipt_version: Option, ) -> Result { diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 4af1f5299..a38895c7f 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -192,4 +192,19 @@ impl UnsealedBlock { pub fn get_state_overrides(&self) -> Option { self.state_overrides.clone() } + + pub fn clone_for_update(&self) -> Self { + Self { + env: self.env.clone(), + frags: self.frags.clone(), + last_sequence_number: self.last_sequence_number, + hash: self.hash, + receipts: self.receipts.clone(), + logs: self.logs.clone(), + cumulative_gas_used: self.cumulative_gas_used, + cumulative_blob_gas_used: self.cumulative_blob_gas_used, + db_cache: self.db_cache.clone(), + state_overrides: self.state_overrides.clone(), + } + } } From 8f3d3f32148203759d658a2d679874a44bc5bf65 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Thu, 18 Dec 2025 22:37:37 +0700 Subject: [PATCH 05/18] improve docs --- based/crates/reth/src/exec.rs | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 55f5cc65c..831a4840b 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -219,13 +219,13 @@ where } fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result { - // Decode EIP-2718 tx bytes -> OpTxEnvelope + // Decode EIP-2718 tx bytes -> OpTransactionSigned let tx_list: Vec = frag .txs .iter() .enumerate() .map(|(_, tx_bytes)| { - let mut slice = tx_bytes.as_ref(); // &[u8] + let mut slice = tx_bytes.as_ref(); let eth_env = TxEnvelope::decode_2718_exact(&mut slice) .map_err(|e| ExecError::Failed(format!("decode tx failed: {e}")))?; @@ -238,63 +238,35 @@ fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result< }) .collect::, ExecError>>()?; - // Convert EnvV0.extra_data (VariableList) -> Bytes for the header. let extra_data: Bytes = Bytes::copy_from_slice(ub.env.extra_data.as_ref()); - - // Like your Go code, these roots/bloom are left "empty"/default because this is - // a synthetic block used for execution context + bookkeeping. let header = Header { parent_hash: ub.env.parent_hash, - - // Go: UncleHash = EmptyUncleHash (we'll keep defaults unless you have constants) ommers_hash: Default::default(), - beneficiary: ub.env.beneficiary, - - // Go: Root/TxHash/ReceiptHash/Bloom are empty in InsertNewFrag state_root: B256::ZERO, transactions_root: B256::ZERO, receipts_root: B256::ZERO, logs_bloom: Default::default(), - difficulty: ub.env.difficulty, number: frag.block_number, gas_limit: ub.env.gas_limit, - - // Go: GasUsed = currentUnsealedBlock.CumulativeGasUsed gas_used: ub.cumulative_gas_used, - timestamp: ub.env.timestamp, extra_data, - - // Go: MixDigest = Prevrandao mix_hash: ub.env.prevrandao, - nonce: Default::default(), - - // Go: BaseFee = currentUnsealedBlock.Env.Basefee base_fee_per_gas: Some(ub.env.basefee), - - // If you want to mirror Go’s “empty withdrawals list”, set `withdrawals: Some(vec![])` - // and set withdrawals_root accordingly (if you have the empty-withdrawals root constant). withdrawals_root: None, - - // Go: BlobGasUsed = ¤tUnsealedBlock.CumulativeBlobGasUsed blob_gas_used: Some(ub.cumulative_blob_gas_used), - - // Go: ExcessBlobGas = new(uint64) (i.e. 0) excess_blob_gas: Some(0), - parent_beacon_block_root: Some(ub.env.parent_beacon_block_root), - - // post-requests fields requests_hash: None, }; let body = BlockBody { transactions: tx_list, ommers: vec![], - withdrawals: None, // or Some(vec![]) if you want an explicit empty list + withdrawals: None, }; Ok(OpBlock::new(header, body)) From ac0030a66f5c4ccdc367dfa56d4d2c42cb4f7890 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 18 Dec 2025 19:35:07 +0100 Subject: [PATCH 06/18] chore(reth): smol --- based/crates/reth/src/exec.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 831a4840b..45940a98d 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,7 +1,7 @@ use std::{future::Future, sync::Arc}; use alloy_consensus::{ - BlockBody, Header, Receipt, TxEnvelope, TxReceipt, + BlockBody, Header, Receipt, TxReceipt, transaction::{Recovered, SignerRecoverable, TransactionMeta}, }; use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; @@ -9,7 +9,7 @@ use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; use alloy_rpc_types::{Block, Log, TransactionReceipt, state::StateOverride}; use arc_swap::ArcSwapOption; use bop_common::p2p::{EnvV0, FragV0}; -use op_alloy_consensus::OpReceiptEnvelope; +use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; @@ -225,16 +225,8 @@ fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result< .iter() .enumerate() .map(|(_, tx_bytes)| { - let mut slice = tx_bytes.as_ref(); - - let eth_env = TxEnvelope::decode_2718_exact(&mut slice) - .map_err(|e| ExecError::Failed(format!("decode tx failed: {e}")))?; - - let op_env = OpTransactionSigned::try_from_eth_envelope(eth_env).map_err(|unsupported| { - ExecError::Failed(format!("tx variant not supported on OP (likely EIP-4844): {unsupported:?}")) - })?; - - Ok(op_env) + Ok(OpTxEnvelope::decode_2718(&mut tx_bytes.as_ref()) + .map_err(|e| ExecError::Failed(format!("decode tx failed: {e}")))?) }) .collect::, ExecError>>()?; @@ -263,11 +255,7 @@ fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result< requests_hash: None, }; - let body = BlockBody { - transactions: tx_list, - ommers: vec![], - withdrawals: None, - }; + let body = BlockBody { transactions: tx_list, ommers: vec![], withdrawals: None }; Ok(OpBlock::new(header, body)) } From dd80f738635b80eaeb7b9430068c30ab620a9b1f Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 18 Dec 2025 19:36:38 +0100 Subject: [PATCH 07/18] fix(reth): receipt gas used --- based/crates/reth/src/exec.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 45940a98d..012395b00 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -163,11 +163,10 @@ where timestamp: block.timestamp, }; - let op_cgu = op_receipt.cumulative_gas_used(); let input: ConvertReceiptInput<'_, OpPrimitives> = ConvertReceiptInput { receipt: op_receipt, tx: Recovered::new_unchecked(transaction, sender), - gas_used: op_cgu, + gas_used: tx_gas_used, next_log_index, meta, }; From ab59ac9a431b1ef83e2832734ee32ebf82b1a1e5 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Fri, 19 Dec 2025 02:07:10 +0700 Subject: [PATCH 08/18] finish execute frag --- based/crates/reth/src/exec.rs | 68 ++++++++++++++++++------- based/crates/reth/src/unsealed_block.rs | 52 +++++++++++++++---- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index e2bf3c384..60b642c3f 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,7 +1,7 @@ use std::{future::Future, sync::Arc}; use alloy_consensus::{ - BlockBody, Header, Receipt, TxReceipt, + BlockBody, Header, Receipt, Transaction, TxReceipt, transaction::{Recovered, SignerRecoverable, TransactionMeta}, }; use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; @@ -10,6 +10,7 @@ use alloy_rpc_types::{Block, Log, TransactionReceipt, state::StateOverride}; use arc_swap::ArcSwapOption; use bop_common::p2p::{EnvV0, FragV0}; use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; +use op_alloy_rpc_types::Transaction as RPCTransaction; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; @@ -77,13 +78,21 @@ where fn execute_frag(&mut self, frag: &FragV0) -> impl Future> + Send + '_ { let client = self.client.clone(); - let ub_opt = self.current_unsealed_block.load_full(); + let chain_spec = client.chain_spec().clone(); // capture, don't use `self` inside async move + let current_unsealed_block = self.current_unsealed_block.clone(); + + let ub_arc_opt = current_unsealed_block.load_full(); let frag = frag.clone(); async move { - let ub = ub_opt.ok_or(ExecError::NotInitialized)?; + let ub_arc = ub_arc_opt.ok_or(ExecError::NotInitialized)?; + + // Make an owned, mutable working copy from the start + let mut ub = ub_arc.as_ref().clone_for_update(); + let ub_cache = ub.get_db_cache(); let canonical_block = ub.env.number.saturating_sub(1); + let last_block_header = client .header_by_number(canonical_block) .map_err(|e| ExecError::Failed(format!("header_by_number({canonical_block}) failed: {e}")))? @@ -101,12 +110,9 @@ where let mut db = CacheDB { cache: ub_cache, db: state }; - let mut state_overrides = match ub.get_state_overrides() { - Some(v) => v, - None => StateOverride::default(), - }; + let mut state_overrides = ub.get_state_overrides().unwrap_or_default(); - let block: OpBlock = build_op_block_from_ub_and_frag(ub.as_ref(), &frag)?; + let block: OpBlock = build_op_block_from_ub_and_frag(&ub, &frag)?; let mut l1_block_info = reth_optimism_evm::extract_l1_info(&block.body)?; let header = block.header.clone().seal_slow(); @@ -124,14 +130,41 @@ where let mut gas_used: u64 = ub.cumulative_blob_gas_used; let mut logs: Vec = Vec::new(); - let mut next_log_index = 0; + let mut next_log_index = 0usize; let mut receipts: Vec>> = Vec::new(); for (idx, transaction) in block.body.transactions.iter().enumerate() { let tx_hash = transaction.tx_hash(); let sender = transaction.recover_signer()?; + ub.increment_nonce(sender); let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); + let envelope = recovered_transaction.clone().convert::(); + + let effective_gas_price = if transaction.is_deposit() { + 0 + } else { + block + .base_fee_per_gas + .map(|base_fee| { + transaction.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 + }) + .unwrap_or_else(|| transaction.max_fee_per_gas()) + }; + + let rpc_txn = RPCTransaction { + inner: alloy_rpc_types_eth::Transaction { + inner: envelope, + block_hash: Some(header.hash()), + block_number: Some(block.number), + transaction_index: Some(idx as u64), + effective_gas_price: Some(effective_gas_price), + }, + deposit_nonce: None, + deposit_receipt_version: None, + }; + + ub.with_transaction(rpc_txn); match evm.transact(recovered_transaction) { Ok(ResultAndState { state, result }) => { @@ -154,13 +187,13 @@ where let (success, tx_gas_used, tx_logs) = split_execution_result(&result); gas_used = gas_used.saturating_add(tx_gas_used); + logs.extend(tx_logs.iter().map(|inner| Log { inner: inner.clone(), ..Default::default() })); let base_receipt = Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; let ty = transaction.ty(); - let op_receipt = wrap_op_receipt(ty, base_receipt, None, None)?; let meta = TransactionMeta { @@ -172,7 +205,6 @@ where excess_blob_gas: block.excess_blob_gas, timestamp: block.timestamp, }; - let input: ConvertReceiptInput<'_, OpPrimitives> = ConvertReceiptInput { receipt: op_receipt, tx: Recovered::new_unchecked(transaction, sender), @@ -182,12 +214,11 @@ where }; let receipt = - OpReceiptBuilder::new(self.client.chain_spec().as_ref(), input, &mut l1_block_info)? - .core_receipt; + OpReceiptBuilder::new(chain_spec.as_ref(), input, &mut l1_block_info)?.core_receipt; next_log_index += receipt.logs().len(); - - receipts.push(receipt) + ub.with_transaction_receipt(tx_hash, receipt.clone()); + receipts.push(receipt); } Err(e) => { return Err(ExecError::Failed(format!( @@ -199,10 +230,11 @@ where } db = evm.into_db(); - let mut next = ub.clone_for_update().with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); - next.accept_frag_execution(frag, logs, receipts, gas_used); + ub = ub.with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); + + ub.accept_frag_execution(frag, logs, receipts, gas_used); - self.current_unsealed_block.store(Some(Arc::new(next))); + self.current_unsealed_block.store(Some(Arc::new(ub))); Ok(()) } diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 146e971d3..0e930e5e1 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -36,7 +36,7 @@ pub struct UnsealedBlock { pub cumulative_blob_gas_used: u64, transaction_count: HashMap, - transaction: Vec, + transactions: Vec, transaction_receipts: HashMap>>, state_overrides: Option, @@ -44,6 +44,7 @@ pub struct UnsealedBlock { } impl UnsealedBlock { + /// Create a fresh unsealed block state for `env` with empty frags/results/caches. pub fn new(env: EnvV0) -> Self { Self { env, @@ -55,7 +56,7 @@ impl UnsealedBlock { cumulative_gas_used: 0, cumulative_blob_gas_used: 0, transaction_count: Default::default(), - transaction: vec![], + transactions: vec![], transaction_receipts: Default::default(), state_overrides: None, db_cache: Default::default(), @@ -80,12 +81,12 @@ impl UnsealedBlock { } } - /// Raw tx bytes iterator (flattening frags) + /// Raw tx bytes iterator (flattening frags). pub fn transactions_iter_bytes(&self) -> impl Iterator + '_ { self.frags.iter().flat_map(|frag| frag.txs.iter()) } - /// Decoded txs iterator (lazy decode) + /// Decoded txs iterator (lazy decode). pub fn transactions_iter_decoded(&self) -> impl Iterator> + '_ { self.transactions_iter_bytes().enumerate().map(|(index, tx)| { // allocate a Vec to decode from @@ -94,17 +95,17 @@ impl UnsealedBlock { }) } - /// Decoded txs (allocates Vec), like Go `Transactions()` but decoded + /// Decoded txs (allocates Vec), like Go `Transactions()` but decoded. pub fn transactions(&self) -> Result, UnsealedBlockError> { self.transactions_iter_decoded().collect() } - /// Raw tx bytes (allocates Vec>), like Go `ByteTransactions()` + /// Raw tx bytes (allocates Vec>), like Go `ByteTransactions()`. pub fn byte_transactions(&self) -> Vec> { self.transactions_iter_bytes().map(|tx| tx.iter().copied().collect::>()).collect() } - // Return the last frag on the list. + /// Return the last fragment in the list (if any). pub fn last_frag(&self) -> Option<&FragV0> { self.frags.last() } @@ -184,11 +185,13 @@ impl UnsealedBlock { *self = Self::new(env); } + /// Attach/replace the DB cache to carry execution overlay state forward. pub fn with_db_cache(mut self, cache: Cache) -> Self { self.db_cache = cache; self } + /// Attach/replace the state overrides that represent the current overlay diff. pub fn with_state_overrides(mut self, state_overrides: Option) -> Self { self.state_overrides = state_overrides; self @@ -199,6 +202,7 @@ impl UnsealedBlock { self.db_cache.clone() } + /// Clone this unsealed block into a mutable working copy for in-place updates. pub fn clone_for_update(&self) -> Self { Self { env: self.env.clone(), @@ -210,7 +214,7 @@ impl UnsealedBlock { cumulative_gas_used: self.cumulative_gas_used, cumulative_blob_gas_used: self.cumulative_blob_gas_used, transaction_count: Default::default(), - transaction: vec![], + transactions: vec![], db_cache: self.db_cache.clone(), state_overrides: self.state_overrides.clone(), transaction_receipts: Default::default(), @@ -251,9 +255,10 @@ impl UnsealedBlock { Some(account.info.balance) } + /// Return a decoded header snapshot derived from the current env + local counters. pub fn get_header(&self) -> Header { let last_frag_number = match self.frags.last() { - Some(frag) => (frag.block_number), + Some(frag) => frag.block_number, None => 0, }; Header { @@ -281,11 +286,36 @@ impl UnsealedBlock { } } - /// Convert current unsealed block into RpcBlock + /// Append a fully materialized transaction to the RPC `transactions` list. + pub(crate) fn with_transaction(&mut self, transaction: Transaction) -> &Self { + self.transactions.push(transaction); + self + } + + /// Insert/replace the receipt for `tx_hash` in the per-tx receipt map. + pub(crate) fn with_transaction_receipt( + &mut self, + tx_hash: B256, + receipt: TransactionReceipt>, + ) -> &Self { + self.transaction_receipts.insert(tx_hash, receipt); + self + } + + /// Increment the locally tracked nonce for `sender` after accepting a tx. + pub(crate) fn increment_nonce(&mut self, sender: Address) -> &Self { + let zero = U256::from(0); + let current_count = self.transaction_count.get(&sender).unwrap_or(&zero); + + _ = self.transaction_count.insert(sender, *current_count + U256::from(1)); + self + } + + /// Convert current unsealed block into RpcBlock. pub fn to_block(&self, full: bool) -> RpcBlock { let header = self.get_header(); let header = header.clone().seal_slow(); - let block_transactions = self.transaction.clone(); + let block_transactions = self.transactions.clone(); let transactions = if full { BlockTransactions::Full(block_transactions) From 945090ef78602d3e088831a9178e0f15ce009b0d Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 18 Dec 2025 20:21:17 +0100 Subject: [PATCH 09/18] feat(reth): start wiring things together --- based/Cargo.lock | 91 ++++++++++++------------ based/crates/reth/Cargo.toml | 39 +++++------ based/crates/reth/src/cli.rs | 92 +++++++++++++------------ based/crates/reth/src/driver.rs | 17 ++--- based/crates/reth/src/exec.rs | 2 +- based/crates/reth/src/unsealed_block.rs | 2 +- 6 files changed, 120 insertions(+), 123 deletions(-) diff --git a/based/Cargo.lock b/based/Cargo.lock index f3db7abbf..f57da8cea 100644 --- a/based/Cargo.lock +++ b/based/Cargo.lock @@ -1583,6 +1583,52 @@ version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +[[package]] +name = "based-op-reth" +version = "0.1.0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types", + "alloy-rpc-types-eth", + "anyhow", + "arc-swap", + "bop-common", + "clap", + "eyre", + "futures", + "jsonrpsee", + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-rpc-types", + "reth", + "reth-chainspec 1.9.3", + "reth-db 1.9.3", + "reth-engine-tree 1.9.3", + "reth-evm 1.9.3", + "reth-exex 1.9.3", + "reth-node-builder 1.9.3", + "reth-optimism-chainspec 1.9.3", + "reth-optimism-cli 1.9.3", + "reth-optimism-evm 1.9.3", + "reth-optimism-node 1.9.3", + "reth-optimism-primitives 1.9.3", + "reth-optimism-rpc 1.9.3", + "reth-revm 1.9.3", + "reth-rpc 1.9.3", + "reth-rpc-convert 1.9.3", + "reth-rpc-eth-api 1.9.3", + "reth-storage-api 1.9.3", + "reth-storage-errors 1.9.3", + "revm", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "based-portal" version = "0.1.0" @@ -7100,51 +7146,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" -[[package]] -name = "reth" -version = "0.1.0" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network", - "alloy-primitives", - "alloy-rpc-types", - "alloy-rpc-types-eth", - "anyhow", - "arc-swap", - "bop-common", - "clap", - "eyre", - "futures", - "jsonrpsee", - "op-alloy-consensus", - "op-alloy-network", - "op-alloy-rpc-types", - "reth 1.9.3", - "reth-chainspec 1.9.3", - "reth-db 1.9.3", - "reth-evm 1.9.3", - "reth-exex 1.9.3", - "reth-node-builder 1.9.3", - "reth-optimism-chainspec 1.9.3", - "reth-optimism-cli 1.9.3", - "reth-optimism-evm 1.9.3", - "reth-optimism-node 1.9.3", - "reth-optimism-primitives 1.9.3", - "reth-optimism-rpc 1.9.3", - "reth-revm 1.9.3", - "reth-rpc 1.9.3", - "reth-rpc-convert 1.9.3", - "reth-rpc-eth-api 1.9.3", - "reth-storage-api 1.9.3", - "reth-storage-errors 1.9.3", - "revm", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tracing", -] - [[package]] name = "reth" version = "1.9.3" diff --git a/based/crates/reth/Cargo.toml b/based/crates/reth/Cargo.toml index 06bf13449..553c1cebe 100644 --- a/based/crates/reth/Cargo.toml +++ b/based/crates/reth/Cargo.toml @@ -1,6 +1,6 @@ [package] edition.workspace = true -name = "reth" +name = "based-op-reth" repository.workspace = true rust-version.workspace = true version.workspace = true @@ -11,41 +11,38 @@ alloy-eips.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true alloy-rpc-types.workspace = true +alloy-rpc-types-eth = "1.1.3" anyhow = "1.0.98" +arc-swap = "1.7.1" bop-common.workspace = true clap.workspace = true eyre.workspace = true futures.workspace = true jsonrpsee.workspace = true +op-alloy-consensus = "0.22.1" op-alloy-network.workspace = true op-alloy-rpc-types.workspace = true -thiserror.workspace = true -tokio.workspace = true -tokio-stream.workspace = true -tracing.workspace = true -revm = { version = "31.0.1", features = [ - "optional_balance_check", - "secp256k1", - "std", -], default-features = false } -arc-swap = "1.7.1" -op-alloy-consensus = "0.22.1" -alloy-rpc-types-eth = "1.1.3" reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-chainspec" } reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-engine-tree" } +reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-evm" } reth-exex = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-optimism-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-optimism-node = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-chainspec" } -reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-evm" } reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-optimism-chainspec" } +reth-optimism-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-optimism-evm" } -reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-optimism-rpc" } -reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-rpc-convert" } +reth-optimism-node = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-optimism-primitives" } +reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-optimism-rpc" } reth-revm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-revm" } +reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-rpc-convert" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-storage-api" } reth-storage-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-storage-errors" } +revm = { version = "31.0.1", features = ["optional_balance_check", "secp256k1", "std"], default-features = false } +thiserror.workspace = true +tokio.workspace = true +tokio-stream.workspace = true +tracing.workspace = true diff --git a/based/crates/reth/src/cli.rs b/based/crates/reth/src/cli.rs index c1f73fef8..bb62f6f1f 100644 --- a/based/crates/reth/src/cli.rs +++ b/based/crates/reth/src/cli.rs @@ -1,6 +1,8 @@ +use std::sync::{Arc, OnceLock}; + use clap::Parser; use futures::TryStreamExt as _; -use reth::providers::CanonStateSubscriptions as _; +use reth::providers::{CanonStateSubscriptions as _, providers::BlockchainProvider}; use reth_exex::ExExEvent; use reth_node_builder::Node; use reth_optimism_cli::{Cli, chainspec::OpChainSpecParser}; @@ -54,20 +56,21 @@ where /// Internal helper that runs the node with a parsed CLI instance. fn run_with_cli(cli: Cli) -> eyre::Result<()> { cli.run(|builder, args| async move { - let driver = Driver::new(args.based_op.unsealed_as_latest); + let driver = Arc::new(OnceLock::::new()); let op_node = OpNode::new(args.rollup.clone()); let node_handle = builder - .with_types::() + .with_types_and_provider::>() .with_components(op_node.components()) .with_add_ons(op_node.add_ons()) // Install the execution extension to handle canonical chain updates .install_exex("based-op", { // Get a clone of the driver handle. - let driver = driver.clone(); + let driver = Arc::clone(&driver); move |mut ctx| async move { Ok(async move { + let driver = driver.get_or_init(|| Driver::new(ctx.provider().clone())); while let Some(note) = ctx.notifications.try_next().await? { // TODO: Handle reorged and reverted chains? if let Some(committed) = note.committed_chain() { @@ -84,45 +87,48 @@ fn run_with_cli(cli: Cli) -> eyre::Result<() }) } }) - .extend_rpc_modules(move |ctx| { - let provider = ctx.provider().clone(); - - let mut canon_stream = provider.subscribe_to_canonical_state(); - - // NOTE: Not entirely sure why this is needed - // Ref: - tokio::spawn(async move { - while let Ok(notif) = canon_stream.recv().await { - provider.canonical_in_memory_state().notify_canon_state(notif); - } - }); - - // Add based engine API modules to the existing auth module. - ctx.auth_module.merge_auth_methods(BasedEngineApi::new(driver).into_rpc())?; - - // Print supported engine_ methods - let methods = ctx - .auth_module - .module_mut() - .method_names() - .filter(|m| m.starts_with("engine_")) - .collect::>(); - - tracing::info!(supported_methods = ?methods, "Configured based engine API"); - - // Configure extended Eth API - let eth = EthApi { - canonical: ctx.registry.eth_api().clone(), - eth_filter: ctx.registry.eth_handlers().filter.clone(), - unsealed_state: (), - unsealed_as_latest: args.based_op.unsealed_as_latest, - }; - - ctx.modules.replace_configured(eth.into_rpc())?; - - // TODO: - // - Replace eth API - Ok(()) + .extend_rpc_modules({ + let driver = driver.clone(); + move |ctx| { + let provider = ctx.provider().clone(); + + let mut canon_stream = provider.subscribe_to_canonical_state(); + + // NOTE: Not entirely sure why this is needed + // Ref: + tokio::spawn(async move { + while let Ok(notif) = canon_stream.recv().await { + provider.canonical_in_memory_state().notify_canon_state(notif); + } + }); + + let driver = driver.get_or_init(|| Driver::new(ctx.provider().clone())); + + // Add based engine API modules to the existing auth module. + ctx.auth_module.merge_auth_methods(BasedEngineApi::new(driver.clone()).into_rpc())?; + + // Print supported engine_ methods + let methods = ctx + .auth_module + .module_mut() + .method_names() + .filter(|m| m.starts_with("engine_")) + .collect::>(); + + tracing::info!(supported_methods = ?methods, "Configured based engine API"); + + // Configure extended Eth API + let eth = EthApi { + canonical: ctx.registry.eth_api().clone(), + eth_filter: ctx.registry.eth_handlers().filter.clone(), + unsealed_state: (), + unsealed_as_latest: args.based_op.unsealed_as_latest, + }; + + ctx.modules.replace_configured(eth.into_rpc())?; + + Ok(()) + } }) .launch() .await?; diff --git a/based/crates/reth/src/driver.rs b/based/crates/reth/src/driver.rs index 98b2f7bd6..de328e646 100644 --- a/based/crates/reth/src/driver.rs +++ b/based/crates/reth/src/driver.rs @@ -28,7 +28,7 @@ pub enum FragStatus { } /// Actor handle for sending unsealed-block commands to the driver task. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Driver { tx: mpsc::Sender, } @@ -69,6 +69,7 @@ fn respond(resp: Resp, res: Result) { }); } +#[derive(Debug)] enum Cmd { EnvV0 { env: EnvV0, resp: Resp<()> }, NewFragV0 { frag: FragV0, resp: Resp }, @@ -87,14 +88,13 @@ pub struct HeaderView { /// Essentially should be implemented using based-op-reth #[derive(Debug)] pub struct DriverInner { - pub enabled_unsealed_as_latest: bool, pub current_unsealed_block: Arc>, pub exec: E, pub fcu_count_since_unseal_reset: usize, } impl Driver { - pub fn new(unsealed_as_latest: bool, client: Client) -> Self + pub fn new(client: Client) -> Self where Client: StateProviderFactory + ChainSpecProvider + OpHardforks> @@ -105,16 +105,12 @@ impl Driver { let executor = StateExecutor::new(client); let current_unsealed_block = executor.shared_unsealed_block(); - Self::spawn(DriverInner { - enabled_unsealed_as_latest: unsealed_as_latest, - current_unsealed_block, - exec: executor, - fcu_count_since_unseal_reset: 0, - }) + Self::spawn(DriverInner { current_unsealed_block, exec: executor, fcu_count_since_unseal_reset: 0 }) } /// Spawns the driver actor task and returns a handle used to send commands to it. pub fn spawn(inner: DriverInner) -> Self { + info!(target: "based-op", "Spawning frag driver"); let (tx, mut rx) = mpsc::channel::(256); tokio::spawn(async move { @@ -360,9 +356,6 @@ impl DriverInner { } fn get_header_view(&self) -> HeaderView { - if !self.enabled_unsealed_as_latest { - return HeaderView { enabled: false, header: None }; - } let header = match self.current_unsealed_block.load_full() { Some(ub) => Some(ub.get_header()), None => None, diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index e2bf3c384..1d46a7dbe 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,7 +1,7 @@ use std::{future::Future, sync::Arc}; use alloy_consensus::{ - BlockBody, Header, Receipt, TxReceipt, + BlockBody, Header, Receipt, transaction::{Recovered, SignerRecoverable, TransactionMeta}, }; use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 146e971d3..1a847b066 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -253,7 +253,7 @@ impl UnsealedBlock { pub fn get_header(&self) -> Header { let last_frag_number = match self.frags.last() { - Some(frag) => (frag.block_number), + Some(frag) => frag.block_number, None => 0, }; Header { From 50d47791ca8109418581721661e58bd1033b1c8e Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Fri, 19 Dec 2025 02:51:10 +0700 Subject: [PATCH 10/18] fix header number --- based/crates/reth/src/unsealed_block.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 0e930e5e1..da2552e05 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -257,10 +257,6 @@ impl UnsealedBlock { /// Return a decoded header snapshot derived from the current env + local counters. pub fn get_header(&self) -> Header { - let last_frag_number = match self.frags.last() { - Some(frag) => frag.block_number, - None => 0, - }; Header { parent_hash: self.env.parent_hash, ommers_hash: Default::default(), @@ -270,7 +266,7 @@ impl UnsealedBlock { receipts_root: B256::ZERO, logs_bloom: Default::default(), difficulty: self.env.difficulty, - number: last_frag_number, + number: self.env.number, gas_limit: self.env.gas_limit, gas_used: self.cumulative_gas_used, timestamp: self.env.timestamp, From 6adc04f4051ad9f1f78d4e18d2808f95aa5132ab Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 18 Dec 2025 20:58:03 +0100 Subject: [PATCH 11/18] feat(reth): almost complete eth_ API --- based/crates/reth/src/api/eth.rs | 148 ++++++++++++------------ based/crates/reth/src/cli.rs | 2 +- based/crates/reth/src/driver.rs | 9 +- based/crates/reth/src/unsealed_block.rs | 41 +++++-- 4 files changed, 118 insertions(+), 82 deletions(-) diff --git a/based/crates/reth/src/api/eth.rs b/based/crates/reth/src/api/eth.rs index b29896700..4c319ee5f 100644 --- a/based/crates/reth/src/api/eth.rs +++ b/based/crates/reth/src/api/eth.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, time::Duration}; +use std::{collections::HashSet, sync::Arc, time::Duration}; use alloy_eips::{BlockId, BlockNumberOrTag}; use alloy_primitives::{Address, TxHash, U256}; @@ -7,6 +7,7 @@ use alloy_rpc_types::{ simulate::{SimBlock, SimulatePayload, SimulatedBlock}, state::{EvmOverrides, StateOverride, StateOverridesBuilder}, }; +use arc_swap::ArcSwapOption; use jsonrpsee::{ core::{RpcResult, async_trait}, proc_macros::rpc, @@ -16,12 +17,16 @@ use op_alloy_network::Optimism; use op_alloy_rpc_types::OpTransactionRequest; use reth::{providers::CanonStateSubscriptions as _, rpc::server_types::eth::EthApiError}; use reth_rpc::EthFilter; +use reth_rpc_convert::RpcReceipt; use reth_rpc_eth_api::{ - EthApiTypes, EthFilterApiServer, RpcBlock, RpcReceipt, RpcTransaction, + EthApiTypes, EthFilterApiServer, RpcBlock, RpcTransaction, helpers::{EthBlocks, EthCall, EthState, EthTransactions, FullEthApi}, }; +use tokio::sync::broadcast::error::RecvError; use tokio_stream::{StreamExt, wrappers::BroadcastStream}; +use crate::unsealed_block::UnsealedBlock; + /// Max configured timeout for `eth_sendRawTransactionSync`. const SEND_RAW_TX_SYNC_TIMEOUT: Duration = Duration::from_millis(6_000); @@ -93,7 +98,7 @@ pub trait EthApi { pub struct EthApi { pub canonical: Eth, pub eth_filter: EthFilter, - pub unsealed_state: (), + pub unsealed_block: Arc>, pub unsealed_as_latest: bool, } @@ -136,6 +141,11 @@ where } // TODO: Implement pending transaction receipts + if let Some(unsealed_block) = self.unsealed_block.load_full() { + if let Some(receipt) = unsealed_block.get_transaction_receipt(&tx_hash) { + todo!("Type conversion") + } + } Ok(None) } @@ -147,7 +157,11 @@ where ); let block_id = block_number.unwrap_or_default(); if self.use_unsealed_state(&block_id) { - // TODO: Pending balance + if let Some(unsealed_block) = self.unsealed_block.load_full() { + if let Some(balance) = unsealed_block.get_balance(address) { + return Ok(balance); + } + } } EthState::balance(&self.canonical, address, block_number).await.map_err(Into::into) @@ -160,22 +174,18 @@ where ); let block_id = block_number.unwrap_or_default(); - if self.use_unsealed_state(&block_id) { - todo!(); - // let pending_blocks = self.flashblocks_state.get_pending_blocks(); - // let canon_block = pending_blocks.get_canonical_block_number(); - // let fb_count = pending_blocks.get_transaction_count(address); - - // let fb_count = 0; - // let canon_count = EthState::transaction_count(&self.canonical, address, Some(canon_block.into())) - // .await - // .map_err(Into::into)?; + let mut count = + EthState::transaction_count(&self.canonical, address, block_number).await.map_err(Into::into)?; - // return Ok(canon_count + fb_count); + if self.use_unsealed_state(&block_id) { + if let Some(unsealed_block) = self.unsealed_block.load_full() { + let unsealed_count = unsealed_block.get_transaction_count(address); + count += unsealed_count; + } } - EthState::transaction_count(&self.canonical, address, block_number).await.map_err(Into::into) + Ok(count) } async fn transaction_by_hash(&self, tx_hash: TxHash) -> RpcResult>> { @@ -194,13 +204,11 @@ where return Ok(Some(canonical_tx)); } - // TODO: - // Fall back to flashblocks for pending transactions - // let pending_blocks = self.flashblocks_state.get_pending_blocks(); - // if let Some(fb_transaction) = pending_blocks.get_transaction_by_hash(tx_hash) { - // self.metrics.get_transaction_by_hash.increment(1); - // return Ok(Some(fb_transaction)); - // } + if let Some(unsealed_block) = self.unsealed_block.load_full() { + if let Some(tx) = unsealed_block.get_transaction(&tx_hash) { + return Ok(Some(tx)); + } + } Ok(None) } @@ -280,14 +288,13 @@ where block_overrides = ?block_overrides, ); - let mut block_id = block_number.unwrap_or_default(); + let block_id = block_number.unwrap_or_default(); let mut pending_overrides = EvmOverrides::default(); // If the call is to pending block use cached override (if they exist) - if self.use_unsealed_state(&block_id) { - // TODO: - // let pending_blocks = self.flashblocks_state.get_pending_blocks(); - // block_id = pending_blocks.get_canonical_block_number().into(); - // pending_overrides.state = pending_blocks.get_state_overrides(); + if self.use_unsealed_state(&block_id) && + let Some(unsealed_block) = self.unsealed_block.load_full() + { + pending_overrides.state = unsealed_block.get_state_overrides(); } // Apply user's overrides on top @@ -319,14 +326,13 @@ where overrides = ?overrides, ); - let mut block_id = block_number.unwrap_or_default(); + let block_id = block_number.unwrap_or_default(); let mut pending_overrides = EvmOverrides::default(); // If the call is to pending block use cached override (if they exist) - if self.use_unsealed_state(&block_id) { - // TODO: - // let pending_blocks = self.flashblocks_state.get_pending_blocks(); - // block_id = pending_blocks.get_canonical_block_number().into(); - // pending_overrides.state = pending_blocks.get_state_overrides(); + if self.use_unsealed_state(&block_id) && + let Some(unsealed_block) = self.unsealed_block.load_full() + { + pending_overrides.state = unsealed_block.get_state_overrides(); } let mut state_overrides_builder = StateOverridesBuilder::new(pending_overrides.state.unwrap_or_default()); @@ -348,15 +354,14 @@ where block_number = ?block_number, ); - let mut block_id = block_number.unwrap_or_default(); + let block_id = block_number.unwrap_or_default(); let mut pending_overrides = EvmOverrides::default(); // If the call is to pending block use cached override (if they exist) - if self.use_unsealed_state(&block_id) { - // TODO: - // let pending_blocks = self.flashblocks_state.get_pending_blocks(); - // block_id = pending_blocks.get_canonical_block_number().into(); - // pending_overrides.state = pending_blocks.get_state_overrides(); + if self.use_unsealed_state(&block_id) && + let Some(unsealed_block) = self.unsealed_block.load_full() + { + pending_overrides.state = unsealed_block.get_state_overrides(); } // Prepend flashblocks pending overrides to the block state calls @@ -399,11 +404,13 @@ where // Mixed query: toBlock is pending, so we need to combine historical + pending logs let mut all_logs = Vec::new(); - // TODO: - // let pending_blocks = self.flashblocks_state.get_pending_blocks(); - // let pending_logs = pending_blocks.get_pending_logs(&filter); + if self.use_unsealed_state(&to_block.unwrap_or_default()) && + let Some(unsealed_block) = self.unsealed_block.load_full() + { + let pending_logs = unsealed_block.get_unsealed_logs(&filter); + all_logs.extend(pending_logs); + } - let mut fetched_logs = HashSet::new(); // Get historical logs if fromBlock is not pending if !matches!(from_block, Some(BlockNumberOrTag::Pending)) { // Create a filter for historical data (fromBlock to latest) @@ -412,9 +419,6 @@ where FilterBlockOption::Range { from_block, to_block: Some(BlockNumberOrTag::Latest) }; let historical_logs: Vec = self.eth_filter.logs(historical_filter).await?; - for log in &historical_logs { - fetched_logs.insert((log.block_number, log.log_index)); - } all_logs.extend(historical_logs); } @@ -422,12 +426,7 @@ where // TODO: // Dedup any logs from the pending state that may already have been covered in the historical logs - // let deduped_pending_logs: Vec = pending_logs - // .iter() - // .filter(|log| !fetched_logs.contains(&(log.block_number, log.log_index))) - // .cloned() - // .collect(); - // all_logs.extend(deduped_pending_logs); + all_logs.dedup(); Ok(all_logs) } @@ -441,24 +440,29 @@ where // TODO: Subscribe to frags // let mut receiver = self.flashblocks_state.subscribe_to_flashblocks(); - // loop { - // match receiver.recv().await { - // Ok(pending_state) if pending_state.get_receipt(tx_hash).is_some() => { - // debug!(message = "found receipt in flashblock", tx_hash = %tx_hash); - // return pending_state.get_receipt(tx_hash); - // } - // Ok(_) => { - // trace!(message = "flashblock does not contain receipt", tx_hash = %tx_hash); - // } - // Err(RecvError::Closed) => { - // debug!(message = "flashblocks receipt queue closed"); - // return None; - // } - // Err(RecvError::Lagged(_)) => { - // warn!("Flashblocks receipt queue lagged, maybe missing receipts"); - // } - // } - // } + if let Some(unsealed_block) = self.unsealed_block.load_full() { + let mut receiver = unsealed_block.subscribe_new_blocks(); + + loop { + match receiver.recv().await { + Ok(_) => { + if let Some(receipt) = unsealed_block.get_transaction_receipt(&tx_hash) { + tracing::debug!(%tx_hash, "Receipt found"); + todo!("Type conversion") + } + + continue; + } + Err(RecvError::Closed) => { + tracing::debug!("Unsealed block receipt queue closed"); + return None; + } + Err(RecvError::Lagged(_)) => { + tracing::warn!("Unsealed block receipt queue lagged, maybe missing receipts"); + } + } + } + } None } diff --git a/based/crates/reth/src/cli.rs b/based/crates/reth/src/cli.rs index bb62f6f1f..9fe556889 100644 --- a/based/crates/reth/src/cli.rs +++ b/based/crates/reth/src/cli.rs @@ -121,7 +121,7 @@ fn run_with_cli(cli: Cli) -> eyre::Result<() let eth = EthApi { canonical: ctx.registry.eth_api().clone(), eth_filter: ctx.registry.eth_handlers().filter.clone(), - unsealed_state: (), + unsealed_block: driver.unsealed_block(), unsealed_as_latest: args.based_op.unsealed_as_latest, }; diff --git a/based/crates/reth/src/driver.rs b/based/crates/reth/src/driver.rs index de328e646..dda0dcf36 100644 --- a/based/crates/reth/src/driver.rs +++ b/based/crates/reth/src/driver.rs @@ -31,6 +31,7 @@ pub enum FragStatus { #[derive(Clone, Debug)] pub struct Driver { tx: mpsc::Sender, + unsealed_block: Arc>, } impl From> for DriverError { @@ -112,6 +113,7 @@ impl Driver { pub fn spawn(inner: DriverInner) -> Self { info!(target: "based-op", "Spawning frag driver"); let (tx, mut rx) = mpsc::channel::(256); + let unsealed_block = inner.current_unsealed_block.clone(); tokio::spawn(async move { let mut inner = inner; @@ -137,7 +139,12 @@ impl Driver { } }); - Self { tx } + Self { tx, unsealed_block } + } + + /// Returns a clone of the current unsealed block. + pub fn unsealed_block(&self) -> Arc> { + Arc::clone(&self.unsealed_block) } /// Starts a new unsealed block execution context for the given environment. diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 0e930e5e1..eee9d6141 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -1,7 +1,7 @@ use alloy_consensus::{Header, TxEnvelope}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{Address, B256, Bytes, Sealable, TxHash, U256, map::foldhash::HashMap}; -use alloy_rpc_types::{BlockTransactions, Log, TransactionReceipt, state::StateOverride}; +use alloy_rpc_types::{BlockTransactions, Filter, Log, TransactionReceipt, state::StateOverride}; use alloy_rpc_types_eth::Header as RPCHeader; use bop_common::p2p::{EnvV0, FragV0, Transaction as TxBytes}; use op_alloy_consensus::OpReceiptEnvelope; @@ -9,6 +9,7 @@ use op_alloy_network::{Optimism, TransactionResponse}; use op_alloy_rpc_types::Transaction; use reth::revm::db::Cache; use reth_rpc_eth_api::RpcBlock; +use tokio::sync::broadcast; use crate::error::UnsealedBlockError; @@ -40,12 +41,16 @@ pub struct UnsealedBlock { transaction_receipts: HashMap>>, state_overrides: Option, + new_block_sender: broadcast::Sender>, + db_cache: Cache, } impl UnsealedBlock { /// Create a fresh unsealed block state for `env` with empty frags/results/caches. pub fn new(env: EnvV0) -> Self { + let (new_block_sender, _) = broadcast::channel(16); + Self { env, frags: Vec::new(), @@ -59,10 +64,21 @@ impl UnsealedBlock { transactions: vec![], transaction_receipts: Default::default(), state_overrides: None, + new_block_sender, db_cache: Default::default(), } } + /// Returns the canonical block number. + pub fn canonical_block_number(&self) -> u64 { + // TODO: Is this correct? + self.env.number.saturating_sub(1) + } + + pub fn subscribe_new_blocks(&self) -> broadcast::Receiver> { + self.new_block_sender.subscribe() + } + /// Returns `true` if no fragments have been added yet. pub fn is_empty(&self) -> bool { self.frags.is_empty() @@ -126,6 +142,9 @@ impl UnsealedBlock { self.logs.extend_from_slice(logs.as_slice()); self.receipts.extend_from_slice(receipts.as_slice()); self.cumulative_gas_used = cummulative_gas_used; + + // TODO: Is this correct? Is everything applied here? + let _ = self.new_block_sender.send(self.to_block(false)); } /// Validate frag against current state (equivalent to your ValidateNewFragV0 + sequencing gate). @@ -217,23 +236,29 @@ impl UnsealedBlock { transactions: vec![], db_cache: self.db_cache.clone(), state_overrides: self.state_overrides.clone(), + new_block_sender: self.new_block_sender.clone(), transaction_receipts: Default::default(), } } /// Returns a cloned list of unsealed logs collected so far. - pub fn get_unsealed_logs(self) -> Vec { - self.logs.clone() + pub fn get_unsealed_logs(&self, filter: &Filter) -> Vec { + self.logs.clone().into_iter().filter(|log| filter.matches(&alloy_primitives::Log::from(log.clone()))).collect() } /// Returns a cloned list of fragments accepted into this unsealed block. - pub fn get_unsealed_frags(self) -> Vec { + pub fn get_unsealed_frags(&self) -> Vec { self.frags.clone() } /// Looks up and returns a cloned transaction receipt by transaction hash, if present. - pub fn get_transaction_receipt(self, tx_hash: B256) -> Option>> { - self.transaction_receipts.get(&tx_hash).cloned() + pub fn get_transaction_receipt(&self, tx_hash: &TxHash) -> Option>> { + self.transaction_receipts.get(tx_hash).cloned() + } + + /// Looks up and returns a cloned transaction by transaction hash, if present. + pub fn get_transaction(&self, tx_hash: &TxHash) -> Option { + self.transactions.iter().find(|tx| tx.tx_hash() == *tx_hash).cloned() } /// Returns a cloned copy of the current state overrides, if any are set. @@ -242,12 +267,12 @@ impl UnsealedBlock { } /// Returns the locally tracked transaction count (nonce) for `address`, or zero if unknown. - pub fn get_transaction_count(self, address: Address) -> U256 { + pub fn get_transaction_count(&self, address: Address) -> U256 { self.transaction_count.get(&address).cloned().unwrap_or(U256::from(0)) } /// Returns the cached balance for `address` from the DB cache, if the account is present. - pub fn get_balance(self, address: Address) -> Option { + pub fn get_balance(&self, address: Address) -> Option { let Some(account) = self.db_cache.accounts.get(&address) else { return None; }; From 0422fd454c6fc18aa13bd971ab925feaccc132d9 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Thu, 18 Dec 2025 21:00:57 +0100 Subject: [PATCH 12/18] feat(reth): smol update --- based/crates/reth/src/api/eth.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/based/crates/reth/src/api/eth.rs b/based/crates/reth/src/api/eth.rs index 4c319ee5f..7e6678a34 100644 --- a/based/crates/reth/src/api/eth.rs +++ b/based/crates/reth/src/api/eth.rs @@ -437,17 +437,14 @@ where Eth: FullEthApi + Send + Sync + 'static, { async fn wait_for_frag_receipt(&self, tx_hash: TxHash) -> Option> { - // TODO: Subscribe to frags - // let mut receiver = self.flashblocks_state.subscribe_to_flashblocks(); - if let Some(unsealed_block) = self.unsealed_block.load_full() { let mut receiver = unsealed_block.subscribe_new_blocks(); loop { match receiver.recv().await { - Ok(_) => { + Ok(block) => { if let Some(receipt) = unsealed_block.get_transaction_receipt(&tx_hash) { - tracing::debug!(%tx_hash, "Receipt found"); + tracing::debug!(%tx_hash, block_number = block.number(), block_hash = %block.hash(), "Receipt found"); todo!("Type conversion") } From 0dfd9fd7d38932006a97da011dfded52dad30d69 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Fri, 19 Dec 2025 14:07:21 +0700 Subject: [PATCH 13/18] remove async from execute frag --- based/crates/reth/src/driver.rs | 2 +- based/crates/reth/src/exec.rs | 278 ++++++++++++++++---------------- 2 files changed, 138 insertions(+), 142 deletions(-) diff --git a/based/crates/reth/src/driver.rs b/based/crates/reth/src/driver.rs index de328e646..5734d75e1 100644 --- a/based/crates/reth/src/driver.rs +++ b/based/crates/reth/src/driver.rs @@ -246,7 +246,7 @@ impl DriverInner { return Err(DriverError::from(e)); } - match self.exec.execute_frag(&frag).await { + match self.exec.execute_frag(&frag) { Ok(()) => (), Err(e) => { error!(error = %e, "execution failed, discarding unsealed block"); diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index a5c8968d6..62a6b07d2 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -37,7 +37,7 @@ pub trait UnsealedExecutor: Send { /// Execute all txs in `frag` on top of current overlay state. /// /// MUST be cumulative: txs execute after all previous frags's txs. - fn execute_frag(&mut self, frag: &FragV0) -> impl Future> + Send + '_; + fn execute_frag(&mut self, frag: &FragV0) -> Result<(), ExecError>; fn seal(&mut self) -> impl Future> + Send + '_; @@ -76,168 +76,164 @@ where async move { Ok(()) } } - fn execute_frag(&mut self, frag: &FragV0) -> impl Future> + Send + '_ { - let client = self.client.clone(); - let chain_spec = client.chain_spec().clone(); // capture, don't use `self` inside async move - let current_unsealed_block = self.current_unsealed_block.clone(); + fn execute_frag(&mut self, frag: &FragV0) -> Result<(), ExecError> { + let chain_spec = self.client.chain_spec().clone(); - let ub_arc_opt = current_unsealed_block.load_full(); + let ub_arc_opt = self.current_unsealed_block.load_full(); let frag = frag.clone(); - async move { - let ub_arc = ub_arc_opt.ok_or(ExecError::NotInitialized)?; + let ub_arc = ub_arc_opt.ok_or(ExecError::NotInitialized)?; - // Make an owned, mutable working copy from the start - let mut ub = ub_arc.as_ref().clone_for_update(); + // Make an owned, mutable working copy from the start + let mut ub = ub_arc.as_ref().clone_for_update(); - let ub_cache = ub.get_db_cache(); - let canonical_block = ub.env.number.saturating_sub(1); + let ub_cache = ub.get_db_cache(); + let canonical_block = ub.env.number.saturating_sub(1); - let last_block_header = client - .header_by_number(canonical_block) - .map_err(|e| ExecError::Failed(format!("header_by_number({canonical_block}) failed: {e}")))? - .ok_or_else(|| ExecError::Failed(format!("missing parent header at {canonical_block}")))?; + let last_block_header = self.client + .header_by_number(canonical_block) + .map_err(|e| ExecError::Failed(format!("header_by_number({canonical_block}) failed: {e}")))? + .ok_or_else(|| ExecError::Failed(format!("missing parent header at {canonical_block}")))?; - let evm_config = OpEvmConfig::optimism(client.chain_spec()); + let evm_config = OpEvmConfig::optimism(self.client.chain_spec()); - let state_provider = - client.state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)).map_err(|e| { - ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")) - })?; + let state_provider = + self.client.state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)).map_err(|e| { + ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")) + })?; - let state_provider_db = StateProviderDatabase::new(state_provider); - let state = State::builder().with_database(state_provider_db).with_bundle_update().build(); + let state_provider_db = StateProviderDatabase::new(state_provider); + let state = State::builder().with_database(state_provider_db).with_bundle_update().build(); - let mut db = CacheDB { cache: ub_cache, db: state }; + let mut db = CacheDB { cache: ub_cache, db: state }; - let mut state_overrides = ub.get_state_overrides().unwrap_or_default(); + let mut state_overrides = ub.get_state_overrides().unwrap_or_default(); - let block: OpBlock = build_op_block_from_ub_and_frag(&ub, &frag)?; - let mut l1_block_info = reth_optimism_evm::extract_l1_info(&block.body)?; - let header = block.header.clone().seal_slow(); + let block: OpBlock = build_op_block_from_ub_and_frag(&ub, &frag)?; + let mut l1_block_info = reth_optimism_evm::extract_l1_info(&block.body)?; + let header = block.header.clone().seal_slow(); - let block_env_attributes = OpNextBlockEnvAttributes { - timestamp: ub.env.timestamp, - suggested_fee_recipient: ub.env.beneficiary, - prev_randao: ub.env.prevrandao, - gas_limit: ub.env.gas_limit, - parent_beacon_block_root: Some(ub.env.parent_beacon_block_root), - extra_data: block.extra_data.clone(), + let block_env_attributes = OpNextBlockEnvAttributes { + timestamp: ub.env.timestamp, + suggested_fee_recipient: ub.env.beneficiary, + prev_randao: ub.env.prevrandao, + gas_limit: ub.env.gas_limit, + parent_beacon_block_root: Some(ub.env.parent_beacon_block_root), + extra_data: block.extra_data.clone(), + }; + + let evm_env = evm_config.next_evm_env(&last_block_header, &block_env_attributes)?; + let mut evm = evm_config.evm_with_env(db, evm_env); + + let mut gas_used: u64 = ub.cumulative_blob_gas_used; + let mut logs: Vec = Vec::new(); + let mut next_log_index = 0usize; + let mut receipts: Vec>> = Vec::new(); + + for (idx, transaction) in block.body.transactions.iter().enumerate() { + let tx_hash = transaction.tx_hash(); + let sender = transaction.recover_signer()?; + ub.increment_nonce(sender); + + let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); + let envelope = recovered_transaction.clone().convert::(); + + let effective_gas_price = if transaction.is_deposit() { + 0 + } else { + block + .base_fee_per_gas + .map(|base_fee| { + transaction.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 + }) + .unwrap_or_else(|| transaction.max_fee_per_gas()) }; - let evm_env = evm_config.next_evm_env(&last_block_header, &block_env_attributes)?; - let mut evm = evm_config.evm_with_env(db, evm_env); - - let mut gas_used: u64 = ub.cumulative_blob_gas_used; - let mut logs: Vec = Vec::new(); - let mut next_log_index = 0usize; - let mut receipts: Vec>> = Vec::new(); - - for (idx, transaction) in block.body.transactions.iter().enumerate() { - let tx_hash = transaction.tx_hash(); - let sender = transaction.recover_signer()?; - ub.increment_nonce(sender); - - let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); - let envelope = recovered_transaction.clone().convert::(); - - let effective_gas_price = if transaction.is_deposit() { - 0 - } else { - block - .base_fee_per_gas - .map(|base_fee| { - transaction.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 - }) - .unwrap_or_else(|| transaction.max_fee_per_gas()) - }; - - let rpc_txn = RPCTransaction { - inner: alloy_rpc_types_eth::Transaction { - inner: envelope, - block_hash: Some(header.hash()), - block_number: Some(block.number), - transaction_index: Some(idx as u64), - effective_gas_price: Some(effective_gas_price), - }, - deposit_nonce: None, - deposit_receipt_version: None, - }; - - ub.with_transaction(rpc_txn); - - match evm.transact(recovered_transaction) { - Ok(ResultAndState { state, result }) => { - for (addr, acc) in &state { - let existing_override = state_overrides.entry(*addr).or_default(); - existing_override.balance = Some(acc.info.balance); - existing_override.nonce = Some(acc.info.nonce); - existing_override.code = acc.info.code.clone().map(|code| code.bytes()); - - let existing = existing_override.state_diff.get_or_insert(Default::default()); - let changed_slots = acc - .storage - .iter() - .map(|(&key, slot)| (B256::from(key), B256::from(slot.present_value))); - - existing.extend(changed_slots); - } - - evm.db_mut().commit(state); - - let (success, tx_gas_used, tx_logs) = split_execution_result(&result); - gas_used = gas_used.saturating_add(tx_gas_used); - - logs.extend(tx_logs.iter().map(|inner| Log { inner: inner.clone(), ..Default::default() })); - - let base_receipt = - Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; - - let ty = transaction.ty(); - let op_receipt = wrap_op_receipt(ty, base_receipt, None, None)?; - - let meta = TransactionMeta { - tx_hash, - index: idx as u64, - block_hash: header.hash(), - block_number: block.number, - base_fee: block.base_fee_per_gas, - excess_blob_gas: block.excess_blob_gas, - timestamp: block.timestamp, - }; - let input: ConvertReceiptInput<'_, OpPrimitives> = ConvertReceiptInput { - receipt: op_receipt, - tx: Recovered::new_unchecked(transaction, sender), - gas_used: tx_gas_used, - next_log_index, - meta, - }; - - let receipt = - OpReceiptBuilder::new(chain_spec.as_ref(), input, &mut l1_block_info)?.core_receipt; - - next_log_index += receipt.logs().len(); - ub.with_transaction_receipt(tx_hash, receipt.clone()); - receipts.push(receipt); - } - Err(e) => { - return Err(ExecError::Failed(format!( - "failed to execute transaction: {:?} tx_hash: {:?} sender: {:?}", - e, tx_hash, sender - ))); + let rpc_txn = RPCTransaction { + inner: alloy_rpc_types_eth::Transaction { + inner: envelope, + block_hash: Some(header.hash()), + block_number: Some(block.number), + transaction_index: Some(idx as u64), + effective_gas_price: Some(effective_gas_price), + }, + deposit_nonce: None, + deposit_receipt_version: None, + }; + + ub.with_transaction(rpc_txn); + + match evm.transact(recovered_transaction) { + Ok(ResultAndState { state, result }) => { + for (addr, acc) in &state { + let existing_override = state_overrides.entry(*addr).or_default(); + existing_override.balance = Some(acc.info.balance); + existing_override.nonce = Some(acc.info.nonce); + existing_override.code = acc.info.code.clone().map(|code| code.bytes()); + + let existing = existing_override.state_diff.get_or_insert(Default::default()); + let changed_slots = acc + .storage + .iter() + .map(|(&key, slot)| (B256::from(key), B256::from(slot.present_value))); + + existing.extend(changed_slots); } + + evm.db_mut().commit(state); + + let (success, tx_gas_used, tx_logs) = split_execution_result(&result); + gas_used = gas_used.saturating_add(tx_gas_used); + + logs.extend(tx_logs.iter().map(|inner| Log { inner: inner.clone(), ..Default::default() })); + + let base_receipt = + Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; + + let ty = transaction.ty(); + let op_receipt = wrap_op_receipt(ty, base_receipt, None, None)?; + + let meta = TransactionMeta { + tx_hash, + index: idx as u64, + block_hash: header.hash(), + block_number: block.number, + base_fee: block.base_fee_per_gas, + excess_blob_gas: block.excess_blob_gas, + timestamp: block.timestamp, + }; + let input: ConvertReceiptInput<'_, OpPrimitives> = ConvertReceiptInput { + receipt: op_receipt, + tx: Recovered::new_unchecked(transaction, sender), + gas_used: tx_gas_used, + next_log_index, + meta, + }; + + let receipt = + OpReceiptBuilder::new(chain_spec.as_ref(), input, &mut l1_block_info)?.core_receipt; + + next_log_index += receipt.logs().len(); + ub.with_transaction_receipt(tx_hash, receipt.clone()); + receipts.push(receipt); + } + Err(e) => { + return Err(ExecError::Failed(format!( + "failed to execute transaction: {:?} tx_hash: {:?} sender: {:?}", + e, tx_hash, sender + ))); } } + } - db = evm.into_db(); - ub = ub.with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); + db = evm.into_db(); + ub = ub.with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); - ub.accept_frag_execution(frag, logs, receipts, gas_used); + ub.accept_frag_execution(frag, logs, receipts, gas_used); - self.current_unsealed_block.store(Some(Arc::new(ub))); + self.current_unsealed_block.store(Some(Arc::new(ub))); - Ok(()) - } + Ok(()) } fn seal(&mut self) -> impl Future> + Send + '_ { From 98289f9932854a7fe7cb508d28c227421ac9fb91 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 19 Dec 2025 09:14:20 +0100 Subject: [PATCH 14/18] feat(reth): fix transaction receipt conversions --- based/crates/reth/src/api/eth.rs | 20 +++++-------- based/crates/reth/src/api/mod.rs | 24 +++++++++++++++ based/crates/reth/src/exec.rs | 39 ++++++++++++------------- based/crates/reth/src/unsealed_block.rs | 16 ++++------ 4 files changed, 56 insertions(+), 43 deletions(-) diff --git a/based/crates/reth/src/api/eth.rs b/based/crates/reth/src/api/eth.rs index 7e6678a34..2f43acf13 100644 --- a/based/crates/reth/src/api/eth.rs +++ b/based/crates/reth/src/api/eth.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use alloy_eips::{BlockId, BlockNumberOrTag}; use alloy_primitives::{Address, TxHash, U256}; @@ -25,7 +25,7 @@ use reth_rpc_eth_api::{ use tokio::sync::broadcast::error::RecvError; use tokio_stream::{StreamExt, wrappers::BroadcastStream}; -use crate::unsealed_block::UnsealedBlock; +use crate::{api::ToRpc as _, unsealed_block::UnsealedBlock}; /// Max configured timeout for `eth_sendRawTransactionSync`. const SEND_RAW_TX_SYNC_TIMEOUT: Duration = Duration::from_millis(6_000); @@ -120,10 +120,10 @@ where block_number = ?number ); - if self.use_unsealed_state(&number) { - // TODO: Implement pending blocks - - EthBlocks::rpc_block(&self.canonical, BlockNumberOrTag::Latest.into(), full).await.map_err(Into::into) + if self.use_unsealed_state(&number) && + let Some(unsealed_block) = self.unsealed_block.load_full() + { + Ok(Some(unsealed_block.to_block(full))) } else { EthBlocks::rpc_block(&self.canonical, number.into(), full).await.map_err(Into::into) } @@ -140,10 +140,9 @@ where return Ok(Some(canonical_receipt)); } - // TODO: Implement pending transaction receipts if let Some(unsealed_block) = self.unsealed_block.load_full() { if let Some(receipt) = unsealed_block.get_transaction_receipt(&tx_hash) { - todo!("Type conversion") + return Ok(Some(receipt.into_rpc())); } } @@ -422,9 +421,6 @@ where all_logs.extend(historical_logs); } - // Always get pending logs when toBlock is pending - - // TODO: // Dedup any logs from the pending state that may already have been covered in the historical logs all_logs.dedup(); @@ -445,7 +441,7 @@ where Ok(block) => { if let Some(receipt) = unsealed_block.get_transaction_receipt(&tx_hash) { tracing::debug!(%tx_hash, block_number = block.number(), block_hash = %block.hash(), "Receipt found"); - todo!("Type conversion") + return Some(receipt.into_rpc()); } continue; diff --git a/based/crates/reth/src/api/mod.rs b/based/crates/reth/src/api/mod.rs index c4997653c..80b0be592 100644 --- a/based/crates/reth/src/api/mod.rs +++ b/based/crates/reth/src/api/mod.rs @@ -1,2 +1,26 @@ +use op_alloy_network::Optimism; +use op_alloy_rpc_types::OpTransactionReceipt; +use reth_rpc_eth_api::RpcReceipt; + pub mod engine; pub mod eth; + +pub(crate) trait ToRpc { + type RpcVariant; + + fn as_rpc(&self) -> Self::RpcVariant; + + fn into_rpc(self) -> Self::RpcVariant; +} + +impl ToRpc for OpTransactionReceipt { + type RpcVariant = RpcReceipt; + + fn as_rpc(&self) -> Self::RpcVariant { + RpcReceipt::::from(self.clone()) + } + + fn into_rpc(self) -> Self::RpcVariant { + RpcReceipt::::from(self) + } +} diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 62a6b07d2..a609a0ba0 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -6,11 +6,11 @@ use alloy_consensus::{ }; use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; -use alloy_rpc_types::{Block, Log, TransactionReceipt}; +use alloy_rpc_types::{Block, Log}; use arc_swap::ArcSwapOption; use bop_common::p2p::{EnvV0, FragV0}; -use op_alloy_consensus::{OpReceiptEnvelope, OpTxEnvelope}; -use op_alloy_rpc_types::Transaction as RPCTransaction; +use op_alloy_consensus::OpTxEnvelope; +use op_alloy_rpc_types::{OpTransactionReceipt, Transaction as RPCTransaction}; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; @@ -90,17 +90,18 @@ where let ub_cache = ub.get_db_cache(); let canonical_block = ub.env.number.saturating_sub(1); - let last_block_header = self.client + let last_block_header = self + .client .header_by_number(canonical_block) .map_err(|e| ExecError::Failed(format!("header_by_number({canonical_block}) failed: {e}")))? .ok_or_else(|| ExecError::Failed(format!("missing parent header at {canonical_block}")))?; let evm_config = OpEvmConfig::optimism(self.client.chain_spec()); - let state_provider = - self.client.state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)).map_err(|e| { - ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")) - })?; + let state_provider = self + .client + .state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)) + .map_err(|e| ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")))?; let state_provider_db = StateProviderDatabase::new(state_provider); let state = State::builder().with_database(state_provider_db).with_bundle_update().build(); @@ -128,7 +129,7 @@ where let mut gas_used: u64 = ub.cumulative_blob_gas_used; let mut logs: Vec = Vec::new(); let mut next_log_index = 0usize; - let mut receipts: Vec>> = Vec::new(); + let mut receipts: Vec = Vec::new(); for (idx, transaction) in block.body.transactions.iter().enumerate() { let tx_hash = transaction.tx_hash(); @@ -143,9 +144,7 @@ where } else { block .base_fee_per_gas - .map(|base_fee| { - transaction.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 - }) + .map(|base_fee| transaction.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128) .unwrap_or_else(|| transaction.max_fee_per_gas()) }; @@ -172,10 +171,8 @@ where existing_override.code = acc.info.code.clone().map(|code| code.bytes()); let existing = existing_override.state_diff.get_or_insert(Default::default()); - let changed_slots = acc - .storage - .iter() - .map(|(&key, slot)| (B256::from(key), B256::from(slot.present_value))); + let changed_slots = + acc.storage.iter().map(|(&key, slot)| (B256::from(key), B256::from(slot.present_value))); existing.extend(changed_slots); } @@ -187,8 +184,7 @@ where logs.extend(tx_logs.iter().map(|inner| Log { inner: inner.clone(), ..Default::default() })); - let base_receipt = - Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; + let base_receipt = Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; let ty = transaction.ty(); let op_receipt = wrap_op_receipt(ty, base_receipt, None, None)?; @@ -202,6 +198,7 @@ where excess_blob_gas: block.excess_blob_gas, timestamp: block.timestamp, }; + let input: ConvertReceiptInput<'_, OpPrimitives> = ConvertReceiptInput { receipt: op_receipt, tx: Recovered::new_unchecked(transaction, sender), @@ -210,10 +207,10 @@ where meta, }; - let receipt = - OpReceiptBuilder::new(chain_spec.as_ref(), input, &mut l1_block_info)?.core_receipt; + let receipt = OpReceiptBuilder::new(chain_spec.as_ref(), input, &mut l1_block_info)?.build(); - next_log_index += receipt.logs().len(); + // TODO: Is this correct?q + next_log_index += receipt.inner.logs().len(); ub.with_transaction_receipt(tx_hash, receipt.clone()); receipts.push(receipt); } diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index ee5f6ad11..b79d22848 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -6,7 +6,7 @@ use alloy_rpc_types_eth::Header as RPCHeader; use bop_common::p2p::{EnvV0, FragV0, Transaction as TxBytes}; use op_alloy_consensus::OpReceiptEnvelope; use op_alloy_network::{Optimism, TransactionResponse}; -use op_alloy_rpc_types::Transaction; +use op_alloy_rpc_types::{OpTransactionReceipt, Transaction}; use reth::revm::db::Cache; use reth_rpc_eth_api::RpcBlock; use tokio::sync::broadcast; @@ -28,7 +28,7 @@ pub struct UnsealedBlock { pub hash: B256, /// Transaction receipts for executed transactions. - pub receipts: Vec>>, + pub receipts: Vec, /// Flattened logs emitted during execution. pub logs: Vec, /// Cumulative execution gas used across all transactions in the block. @@ -38,7 +38,7 @@ pub struct UnsealedBlock { transaction_count: HashMap, transactions: Vec, - transaction_receipts: HashMap>>, + transaction_receipts: HashMap, state_overrides: Option, new_block_sender: broadcast::Sender>, @@ -133,7 +133,7 @@ impl UnsealedBlock { &mut self, f: FragV0, logs: Vec, - receipts: Vec>>, + receipts: Vec, cummulative_gas_used: u64, ) { self.last_sequence_number = Some(f.seq); @@ -252,7 +252,7 @@ impl UnsealedBlock { } /// Looks up and returns a cloned transaction receipt by transaction hash, if present. - pub fn get_transaction_receipt(&self, tx_hash: &TxHash) -> Option>> { + pub fn get_transaction_receipt(&self, tx_hash: &TxHash) -> Option { self.transaction_receipts.get(tx_hash).cloned() } @@ -314,11 +314,7 @@ impl UnsealedBlock { } /// Insert/replace the receipt for `tx_hash` in the per-tx receipt map. - pub(crate) fn with_transaction_receipt( - &mut self, - tx_hash: B256, - receipt: TransactionReceipt>, - ) -> &Self { + pub(crate) fn with_transaction_receipt(&mut self, tx_hash: B256, receipt: OpTransactionReceipt) -> &Self { self.transaction_receipts.insert(tx_hash, receipt); self } From bbe10f547425ec12a0feb095e52c3fed481a4719 Mon Sep 17 00:00:00 2001 From: Jonas Bostoen Date: Fri, 19 Dec 2025 09:15:32 +0100 Subject: [PATCH 15/18] feat(reth): fix transaction receipt conversions --- based/crates/reth/src/api/mod.rs | 7 +------ based/crates/reth/src/unsealed_block.rs | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/based/crates/reth/src/api/mod.rs b/based/crates/reth/src/api/mod.rs index 80b0be592..696463efb 100644 --- a/based/crates/reth/src/api/mod.rs +++ b/based/crates/reth/src/api/mod.rs @@ -8,18 +8,13 @@ pub mod eth; pub(crate) trait ToRpc { type RpcVariant; - fn as_rpc(&self) -> Self::RpcVariant; - + /// Convert the type into its RPC variant. fn into_rpc(self) -> Self::RpcVariant; } impl ToRpc for OpTransactionReceipt { type RpcVariant = RpcReceipt; - fn as_rpc(&self) -> Self::RpcVariant { - RpcReceipt::::from(self.clone()) - } - fn into_rpc(self) -> Self::RpcVariant { RpcReceipt::::from(self) } diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index b79d22848..c30d417c8 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -1,10 +1,9 @@ use alloy_consensus::{Header, TxEnvelope}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{Address, B256, Bytes, Sealable, TxHash, U256, map::foldhash::HashMap}; -use alloy_rpc_types::{BlockTransactions, Filter, Log, TransactionReceipt, state::StateOverride}; +use alloy_rpc_types::{BlockTransactions, Filter, Log, state::StateOverride}; use alloy_rpc_types_eth::Header as RPCHeader; use bop_common::p2p::{EnvV0, FragV0, Transaction as TxBytes}; -use op_alloy_consensus::OpReceiptEnvelope; use op_alloy_network::{Optimism, TransactionResponse}; use op_alloy_rpc_types::{OpTransactionReceipt, Transaction}; use reth::revm::db::Cache; From 3f0bb95683c277b0dc0aafa7728cada8864042f8 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Fri, 19 Dec 2025 17:06:31 +0700 Subject: [PATCH 16/18] feath(based-op-reth): handleEnvV0 and reset shared state (#273) Co-authored-by: Ki Ageng Satria Pamungkas Co-authored-by: Jonas Bostoen --- based/crates/reth/src/driver.rs | 35 +++++++----------- based/crates/reth/src/error.rs | 16 ++++----- based/crates/reth/src/exec.rs | 47 ++++++++++++++++++++----- based/crates/reth/src/unsealed_block.rs | 6 +--- 4 files changed, 61 insertions(+), 43 deletions(-) diff --git a/based/crates/reth/src/driver.rs b/based/crates/reth/src/driver.rs index 41c916b25..7f864712f 100644 --- a/based/crates/reth/src/driver.rs +++ b/based/crates/reth/src/driver.rs @@ -75,7 +75,7 @@ enum Cmd { EnvV0 { env: EnvV0, resp: Resp<()> }, NewFragV0 { frag: FragV0, resp: Resp }, SealFragV0 { seal: SealV0, resp: Resp<()> }, - ForkchoiceUpdated { block: OpBlock, resp: Resp<()> }, + ForkchoiceUpdated { block: Box, resp: Resp<()> }, GetHeaderView { resp: Resp }, } @@ -130,7 +130,7 @@ impl Driver { respond(resp, inner.handle_seal_frag_v0(seal).await); } Cmd::ForkchoiceUpdated { block, resp } => { - respond(resp, inner.handle_forkchoice_updated(block).await); + respond(resp, inner.handle_forkchoice_updated(*block).await); } Cmd::GetHeaderView { resp } => { let _ = resp.send(Reply::Ok(inner.get_header_view())); @@ -171,7 +171,7 @@ impl Driver { /// Notifies the driver about a forkchoice update and resets state on mismatch. pub async fn forkchoice_updated(&self, block: OpBlock) -> Result<(), DriverError> { let (resp_tx, resp_rx) = oneshot::channel(); - self.tx.send(Cmd::ForkchoiceUpdated { block, resp: resp_tx }).await?; + self.tx.send(Cmd::ForkchoiceUpdated { block: Box::new(block), resp: resp_tx }).await?; resp_rx.await?.into_result() } @@ -210,7 +210,7 @@ impl DriverInner { self.reset_current_unsealed_block(); } - self.exec.ensure_env(&env).await?; // ths should update current_unsealed_block too + self.exec.ensure_env(&env)?; // this should update current_unsealed_block too because shared arc self.fcu_count_since_unseal_reset = 0; Ok(()) } @@ -315,58 +315,49 @@ impl DriverInner { ) -> Result<(), ValidateSealError> { let expected_block_hash: B256 = presealed_block.header.hash.into(); if expected_block_hash != seal.block_hash { - return Err(ValidateSealError::BlockHashMismatch { expected: expected_block_hash, got: seal.block_hash }); + return Err(ValidateSealError::BlockHash { expected: expected_block_hash, got: seal.block_hash }); } let expected_parent_hash = presealed_block.header.parent_hash; if expected_parent_hash != seal.parent_hash { - return Err(ValidateSealError::ParentHashMismatch { expected: expected_parent_hash, got: seal.parent_hash }); + return Err(ValidateSealError::ParentHash { expected: expected_parent_hash, got: seal.parent_hash }); } let expected_state_root = presealed_block.header.state_root; if expected_state_root != seal.state_root { - return Err(ValidateSealError::StateRootMismatch { expected: expected_state_root, got: seal.state_root }); + return Err(ValidateSealError::StateRoot { expected: expected_state_root, got: seal.state_root }); } let expected_tx_root = presealed_block.header.transactions_root; if expected_tx_root != seal.transactions_root { - return Err(ValidateSealError::TransactionsRootMismatch { - expected: expected_tx_root, - got: seal.transactions_root, - }); + return Err(ValidateSealError::TransactionsRoot { expected: expected_tx_root, got: seal.transactions_root }); } let expected_receipts_root = presealed_block.header.receipts_root; if expected_receipts_root != seal.receipts_root { - return Err(ValidateSealError::ReceiptsRootMismatch { - expected: expected_receipts_root, - got: seal.receipts_root, - }); + return Err(ValidateSealError::ReceiptsRoot { expected: expected_receipts_root, got: seal.receipts_root }); } let expected_gas_used = presealed_block.header.gas_used; if expected_gas_used != seal.gas_used { - return Err(ValidateSealError::GasUsedMismatch { expected: expected_gas_used, got: seal.gas_used }); + return Err(ValidateSealError::GasUsed { expected: expected_gas_used, got: seal.gas_used }); } let expected_gas_limit = presealed_block.header.gas_limit; if expected_gas_limit != seal.gas_limit { - return Err(ValidateSealError::GasLimitMismatch { expected: expected_gas_limit, got: seal.gas_limit }); + return Err(ValidateSealError::GasLimit { expected: expected_gas_limit, got: seal.gas_limit }); } let expected_total_frags = ub.frags.len() as u64; if expected_total_frags != seal.total_frags { - return Err(ValidateSealError::TotalFragsMismatch { expected: expected_total_frags, got: seal.total_frags }); + return Err(ValidateSealError::TotalFrags { expected: expected_total_frags, got: seal.total_frags }); } Ok(()) } fn get_header_view(&self) -> HeaderView { - let header = match self.current_unsealed_block.load_full() { - Some(ub) => Some(ub.get_header()), - None => None, - }; + let header = self.current_unsealed_block.load_full().map(|ub| ub.get_header()); HeaderView { enabled: true, header } } } diff --git a/based/crates/reth/src/error.rs b/based/crates/reth/src/error.rs index 2ab30b0c0..eb77400d8 100644 --- a/based/crates/reth/src/error.rs +++ b/based/crates/reth/src/error.rs @@ -42,28 +42,28 @@ pub enum DriverError { #[derive(Debug, Error)] pub enum ValidateSealError { #[error("block hash mismatch, expected {expected:?}, got {got:?}")] - BlockHashMismatch { expected: B256, got: B256 }, + BlockHash { expected: B256, got: B256 }, #[error("parent hash mismatch, expected {expected:?}, got {got:?}")] - ParentHashMismatch { expected: B256, got: B256 }, + ParentHash { expected: B256, got: B256 }, #[error("state root mismatch, expected {expected:?}, got {got:?}")] - StateRootMismatch { expected: B256, got: B256 }, + StateRoot { expected: B256, got: B256 }, #[error("transactions root mismatch, expected {expected:?}, got {got:?}")] - TransactionsRootMismatch { expected: B256, got: B256 }, + TransactionsRoot { expected: B256, got: B256 }, #[error("receipts root mismatch, expected {expected:?}, got {got:?}")] - ReceiptsRootMismatch { expected: B256, got: B256 }, + ReceiptsRoot { expected: B256, got: B256 }, #[error("gas used mismatch, expected {expected}, got {got}")] - GasUsedMismatch { expected: u64, got: u64 }, + GasUsed { expected: u64, got: u64 }, #[error("gas limit mismatch, expected {expected}, got {got}")] - GasLimitMismatch { expected: u64, got: u64 }, + GasLimit { expected: u64, got: u64 }, #[error("total frags mismatch, expected {expected}, got {got}")] - TotalFragsMismatch { expected: u64, got: u64 }, + TotalFrags { expected: u64, got: u64 }, } #[derive(Debug, Error)] diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index a609a0ba0..779aa848d 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -11,6 +11,7 @@ use arc_swap::ArcSwapOption; use bop_common::p2p::{EnvV0, FragV0}; use op_alloy_consensus::OpTxEnvelope; use op_alloy_rpc_types::{OpTransactionReceipt, Transaction as RPCTransaction}; +use reth::api::Block as RethBlock; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; @@ -32,7 +33,7 @@ use crate::{error::ExecError, unsealed_block::UnsealedBlock}; /// Everything else is just state-machine + bookkeeping. pub trait UnsealedExecutor: Send { /// Ensure the executor context is ready for this env (initialize overlay state, block env, etc.) - fn ensure_env(&mut self, env: &EnvV0) -> impl Future> + Send + '_; + fn ensure_env(&mut self, env: &EnvV0) -> Result<(), ExecError>; /// Execute all txs in `frag` on top of current overlay state. /// @@ -72,8 +73,37 @@ where + Clone + 'static, { - fn ensure_env(&mut self, _env: &EnvV0) -> impl Future> + Send + '_ { - async move { Ok(()) } + fn ensure_env(&mut self, env: &EnvV0) -> Result<(), ExecError> { + let Some(parent) = self.client.block_by_hash(env.parent_hash)? else { + return Err(ExecError::Failed(format!("parent block {} not found", env.parent_hash))) + }; + + let parent_header = parent.header(); + let None = self.current_unsealed_block.load_full() else { return Err(ExecError::NotInitialized) }; + + let expected_block_number = parent_header.number.saturating_sub(1); + if env.number != expected_block_number { + return Err(ExecError::Failed(format!( + "env block number doesn't match expected block number, expected {}, received {}", + expected_block_number, env.number + ))) + } + + if env.timestamp < parent_header.timestamp { + return Err(ExecError::Failed(format!( + "env timestamp is lower than parent block timestamp, parent timestamp {}, env timestamp {}", + parent_header.timestamp, env.timestamp + ))) + } + + let state_provider = + self.client.state_by_block_number_or_tag(BlockNumberOrTag::Number(parent_header.number))?; + let state_provider_db = StateProviderDatabase::new(state_provider); + let state = State::builder().with_database(state_provider_db).with_bundle_update().build(); + let ub = UnsealedBlock::new(env.clone()).with_db_cache(CacheDB::new(state).cache); + self.current_unsealed_block.store(Some(Arc::new(ub))); + + Ok(()) } fn execute_frag(&mut self, frag: &FragV0) -> Result<(), ExecError> { @@ -249,7 +279,9 @@ where async move { Ok(Block::default()) } } - fn reset(&mut self) {} + fn reset(&mut self) { + self.current_unsealed_block.store(None); + } } fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result { @@ -257,10 +289,9 @@ fn build_op_block_from_ub_and_frag(ub: &UnsealedBlock, frag: &FragV0) -> Result< let tx_list: Vec = frag .txs .iter() - .enumerate() - .map(|(_, tx_bytes)| { - Ok(OpTxEnvelope::decode_2718(&mut tx_bytes.as_ref()) - .map_err(|e| ExecError::Failed(format!("decode tx failed: {e}")))?) + .map(|tx_bytes| { + OpTxEnvelope::decode_2718(&mut tx_bytes.as_ref()) + .map_err(|e| ExecError::Failed(format!("decode tx failed: {e}"))) }) .collect::, ExecError>>()?; diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index c30d417c8..77b922b68 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -272,11 +272,7 @@ impl UnsealedBlock { /// Returns the cached balance for `address` from the DB cache, if the account is present. pub fn get_balance(&self, address: Address) -> Option { - let Some(account) = self.db_cache.accounts.get(&address) else { - return None; - }; - - Some(account.info.balance) + self.db_cache.accounts.get(&address).map(|account| account.info.balance) } /// Return a decoded header snapshot derived from the current env + local counters. From 510075850959b17d400c00f7b854f44c41e333f0 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Fri, 19 Dec 2025 20:13:52 +0700 Subject: [PATCH 17/18] construct deposit nonce and deposit receipt version --- based/crates/reth/src/exec.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 779aa848d..3312c60a4 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -8,7 +8,10 @@ use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; use alloy_rpc_types::{Block, Log}; use arc_swap::ArcSwapOption; -use bop_common::p2p::{EnvV0, FragV0}; +use bop_common::{ + p2p::{EnvV0, FragV0}, + typedefs::Database, +}; use op_alloy_consensus::OpTxEnvelope; use op_alloy_rpc_types::{OpTransactionReceipt, Transaction as RPCTransaction}; use reth::api::Block as RethBlock; @@ -168,8 +171,9 @@ where let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); let envelope = recovered_transaction.clone().convert::(); + let is_deposit = transaction.is_deposit(); - let effective_gas_price = if transaction.is_deposit() { + let effective_gas_price = if is_deposit { 0 } else { block @@ -178,6 +182,21 @@ where .unwrap_or_else(|| transaction.max_fee_per_gas()) }; + let deposit_nonce = if is_deposit && chain_spec.is_regolith_active_at_timestamp(ub.env.timestamp) { + // depositor nonce (use signer account) + let acc = evm + .db_mut() + .basic(sender) + .map_err(|e| ExecError::Failed(format!("get acc nonce basic() failed: {e}")))? + .unwrap_or_default(); + Some(acc.nonce) // pre-tx nonce + } else { + None + }; + + let deposit_receipt_version = + if is_deposit && chain_spec.is_canyon_active_at_timestamp(ub.env.timestamp) { Some(1) } else { None }; + let rpc_txn = RPCTransaction { inner: alloy_rpc_types_eth::Transaction { inner: envelope, @@ -186,8 +205,8 @@ where transaction_index: Some(idx as u64), effective_gas_price: Some(effective_gas_price), }, - deposit_nonce: None, - deposit_receipt_version: None, + deposit_nonce, + deposit_receipt_version, }; ub.with_transaction(rpc_txn); @@ -217,7 +236,7 @@ where let base_receipt = Receipt { status: success.into(), cumulative_gas_used: gas_used, logs: tx_logs }; let ty = transaction.ty(); - let op_receipt = wrap_op_receipt(ty, base_receipt, None, None)?; + let op_receipt = wrap_op_receipt(ty, base_receipt, deposit_nonce, deposit_receipt_version)?; let meta = TransactionMeta { tx_hash, From 488802a2e1bfa0e745724d7617e97fd032fe9bb6 Mon Sep 17 00:00:00 2001 From: Ki Ageng Satria Pamungkas Date: Fri, 19 Dec 2025 21:32:08 +0700 Subject: [PATCH 18/18] feat(based-op-reth): Seal Block on last frags and Set Canonical Block on SealFragsV0 (#274) Co-authored-by: Ki Ageng Satria Pamungkas Co-authored-by: Jonas Bostoen --- based/Cargo.lock | 1 + based/crates/reth/Cargo.toml | 1 + based/crates/reth/src/api/eth.rs | 2 +- based/crates/reth/src/driver.rs | 25 +++--- based/crates/reth/src/error.rs | 6 ++ based/crates/reth/src/exec.rs | 111 +++++++++++++++++++----- based/crates/reth/src/unsealed_block.rs | 93 +++++++++++++++++--- 7 files changed, 190 insertions(+), 49 deletions(-) diff --git a/based/Cargo.lock b/based/Cargo.lock index f57da8cea..e263ab6e1 100644 --- a/based/Cargo.lock +++ b/based/Cargo.lock @@ -1612,6 +1612,7 @@ dependencies = [ "reth-node-builder 1.9.3", "reth-optimism-chainspec 1.9.3", "reth-optimism-cli 1.9.3", + "reth-optimism-consensus 1.9.3", "reth-optimism-evm 1.9.3", "reth-optimism-node 1.9.3", "reth-optimism-primitives 1.9.3", diff --git a/based/crates/reth/Cargo.toml b/based/crates/reth/Cargo.toml index 553c1cebe..e594d714b 100644 --- a/based/crates/reth/Cargo.toml +++ b/based/crates/reth/Cargo.toml @@ -46,3 +46,4 @@ thiserror.workspace = true tokio.workspace = true tokio-stream.workspace = true tracing.workspace = true +reth_optimism_consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", package = "reth-optimism-consensus" } diff --git a/based/crates/reth/src/api/eth.rs b/based/crates/reth/src/api/eth.rs index 2f43acf13..6e8835183 100644 --- a/based/crates/reth/src/api/eth.rs +++ b/based/crates/reth/src/api/eth.rs @@ -123,7 +123,7 @@ where if self.use_unsealed_state(&number) && let Some(unsealed_block) = self.unsealed_block.load_full() { - Ok(Some(unsealed_block.to_block(full))) + Ok(Some(unsealed_block.to_rpc_block(full))) } else { EthBlocks::rpc_block(&self.canonical, number.into(), full).await.map_err(Into::into) } diff --git a/based/crates/reth/src/driver.rs b/based/crates/reth/src/driver.rs index 7f864712f..fcf4768a4 100644 --- a/based/crates/reth/src/driver.rs +++ b/based/crates/reth/src/driver.rs @@ -2,7 +2,6 @@ use std::{sync::Arc, time::Instant}; use alloy_consensus::Header; use alloy_primitives::B256; -use alloy_rpc_types::Block; use arc_swap::ArcSwapOption; use bop_common::{ p2p::{EnvV0, FragV0, SealV0}, @@ -10,7 +9,9 @@ use bop_common::{ }; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_optimism_chainspec::OpHardforks; -use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{ + BlockReaderIdExt, BlockWriter, CanonChainTracker, DatabaseProviderFactory, StateProviderFactory, +}; use tokio::sync::{mpsc, oneshot}; use tracing::{error, info}; @@ -23,6 +24,7 @@ use crate::{ /// Result of submitting a frag to the driver. #[derive(Debug, Clone, Copy)] pub enum FragStatus { + Ignored, Valid, Invalid, } @@ -99,9 +101,12 @@ impl Driver { where Client: StateProviderFactory + ChainSpecProvider + OpHardforks> - + BlockReaderIdExt
+ + BlockReaderIdExt
+ + CanonChainTracker
+ + DatabaseProviderFactory + Clone + 'static, + ::ProviderRW: BlockWriter, { let executor = StateExecutor::new(client); let current_unsealed_block = executor.shared_unsealed_block(); @@ -244,7 +249,7 @@ impl DriverInner { if frag.block_number < ub.env.number { info!(frag_block = frag.block_number, env_number = ub.env.number, "stale frag (older block), ignoring"); - return Ok(FragStatus::Valid); + return Ok(FragStatus::Ignored); } if let Err(e) = ub.validate_new_frag(&frag) { @@ -266,7 +271,7 @@ impl DriverInner { if ub.last_frag().is_some_and(|f| f.is_last) { info!("last frag received, pre-sealing block"); - if let Err(e) = self.exec.seal().await { + if let Err(e) = self.exec.seal() { error!(error = %e, "seal failed, discarding unsealed block"); self.reset_current_unsealed_block(); return Err(DriverError::from(e)); @@ -287,9 +292,7 @@ impl DriverInner { return Ok(()); } - let presealed_block = self.exec.get_block(seal.block_hash, seal.block_number).await; - - let presealed_block = match presealed_block { + let presealed_block = match self.exec.get_block(seal.block_hash) { Ok(b) => b, Err(e) => { self.reset_current_unsealed_block(); @@ -299,7 +302,7 @@ impl DriverInner { self.validate_seal_frag_v0(&presealed_block, ub.as_ref(), &seal)?; - self.exec.set_canonical(&presealed_block).await?; + self.exec.set_canonical(&presealed_block)?; self.reset_current_unsealed_block(); @@ -309,11 +312,11 @@ impl DriverInner { fn validate_seal_frag_v0( &self, - presealed_block: &Block, + presealed_block: &OpBlock, ub: &UnsealedBlock, seal: &SealV0, ) -> Result<(), ValidateSealError> { - let expected_block_hash: B256 = presealed_block.header.hash.into(); + let expected_block_hash: B256 = presealed_block.header.hash_slow(); if expected_block_hash != seal.block_hash { return Err(ValidateSealError::BlockHash { expected: expected_block_hash, got: seal.block_hash }); } diff --git a/based/crates/reth/src/error.rs b/based/crates/reth/src/error.rs index eb77400d8..f96808ff9 100644 --- a/based/crates/reth/src/error.rs +++ b/based/crates/reth/src/error.rs @@ -86,6 +86,9 @@ pub enum UnsealedBlockError { #[error("received frag after last frag already accepted")] AlreadyEnded, + + #[error("operation failed: {0}")] + Failed(String), } #[derive(Debug, Error)] @@ -113,4 +116,7 @@ pub enum ExecError { #[error(transparent)] OpEthApi(#[from] OpEthApiError), + + #[error(transparent)] + UnsealedBlock(#[from] UnsealedBlockError), } diff --git a/based/crates/reth/src/exec.rs b/based/crates/reth/src/exec.rs index 3312c60a4..ff71cfc2c 100644 --- a/based/crates/reth/src/exec.rs +++ b/based/crates/reth/src/exec.rs @@ -1,12 +1,12 @@ -use std::{future::Future, sync::Arc}; +use std::sync::{Arc, Mutex}; use alloy_consensus::{ BlockBody, Header, Receipt, Transaction, transaction::{Recovered, SignerRecoverable, TransactionMeta}, }; use alloy_eips::{BlockNumberOrTag, Typed2718, eip2718::Decodable2718}; -use alloy_primitives::{B256, BlockNumber, Bytes, Sealable}; -use alloy_rpc_types::{Block, Log}; +use alloy_primitives::{B256, Bytes, Sealable}; +use alloy_rpc_types::Log; use arc_swap::ArcSwapOption; use bop_common::{ p2p::{EnvV0, FragV0}, @@ -14,10 +14,15 @@ use bop_common::{ }; use op_alloy_consensus::OpTxEnvelope; use op_alloy_rpc_types::{OpTransactionReceipt, Transaction as RPCTransaction}; -use reth::api::Block as RethBlock; -use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth::{ + api::Block as RethBlock, + network::cache::LruMap, + primitives::{SealedBlock, SealedHeader}, +}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks as _}; use reth_evm::{ConfigureEvm, Evm, op_revm::OpHaltReason}; use reth_optimism_chainspec::OpHardforks; +use reth_optimism_consensus::isthmus; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_primitives::{OpBlock, OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_optimism_rpc::OpReceiptBuilder; @@ -27,11 +32,15 @@ use reth_revm::{ database::StateProviderDatabase, }; use reth_rpc_convert::transaction::ConvertReceiptInput; -use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{ + BlockReaderIdExt, BlockWriter, CanonChainTracker, DBProvider, DatabaseProviderFactory, StateProviderFactory, +}; use revm::database::CacheDB; use crate::{error::ExecError, unsealed_block::UnsealedBlock}; +const BLOCK_CACHE_LIMIT: u32 = 256; + /// This trait is the ONLY place that needs to know about Reth internals. /// Everything else is just state-machine + bookkeeping. pub trait UnsealedExecutor: Send { @@ -43,11 +52,11 @@ pub trait UnsealedExecutor: Send { /// MUST be cumulative: txs execute after all previous frags's txs. fn execute_frag(&mut self, frag: &FragV0) -> Result<(), ExecError>; - fn seal(&mut self) -> impl Future> + Send + '_; + fn seal(&mut self) -> Result<(), ExecError>; - fn set_canonical(&mut self, b: &Block) -> impl Future> + Send + '_; + fn set_canonical(&mut self, b: &OpBlock) -> Result<(), ExecError>; - fn get_block(&self, hash: B256, number: BlockNumber) -> impl Future> + Send + '_; + fn get_block(&self, hash: B256) -> Result; /// Reset overlay state completely. fn reset(&mut self); @@ -56,11 +65,16 @@ pub trait UnsealedExecutor: Send { pub struct StateExecutor { client: Client, current_unsealed_block: Arc>, + block_cache: Mutex>, } impl StateExecutor { pub fn new(client: Client) -> Self { - Self { client, current_unsealed_block: Arc::new(ArcSwapOption::new(None)) } + Self { + client, + current_unsealed_block: Arc::new(ArcSwapOption::new(None)), + block_cache: Mutex::new(LruMap::new(BLOCK_CACHE_LIMIT)), + } } pub fn shared_unsealed_block(&self) -> Arc> { @@ -72,9 +86,12 @@ impl UnsealedExecutor for StateExecutor where Client: StateProviderFactory + ChainSpecProvider + OpHardforks> - + BlockReaderIdExt
+ + BlockReaderIdExt
+ + CanonChainTracker
+ + DatabaseProviderFactory + Clone + 'static, + ::ProviderRW: BlockWriter, { fn ensure_env(&mut self, env: &EnvV0) -> Result<(), ExecError> { let Some(parent) = self.client.block_by_hash(env.parent_hash)? else { @@ -103,7 +120,11 @@ where self.client.state_by_block_number_or_tag(BlockNumberOrTag::Number(parent_header.number))?; let state_provider_db = StateProviderDatabase::new(state_provider); let state = State::builder().with_database(state_provider_db).with_bundle_update().build(); - let ub = UnsealedBlock::new(env.clone()).with_db_cache(CacheDB::new(state).cache); + + // Check if the current block is a prague block + let is_prague = self.client.chain_spec().is_prague_active_at_timestamp(env.timestamp); + + let ub = UnsealedBlock::new(env.clone(), is_prague).with_db_cache(CacheDB::new(state).cache); self.current_unsealed_block.store(Some(Arc::new(ub))); Ok(()) @@ -273,7 +294,10 @@ where } db = evm.into_db(); - ub = ub.with_db_cache(db.cache).with_state_overrides(Some(state_overrides)); + ub = ub + .with_db_cache(db.cache) + .with_state_overrides(Some(state_overrides)) + .with_bundle_state(db.db.bundle_state); ub.accept_frag_execution(frag, logs, receipts, gas_used); @@ -282,20 +306,61 @@ where Ok(()) } - fn seal(&mut self) -> impl Future> + Send + '_ { - async move { Ok(()) } + fn seal(&mut self) -> Result<(), ExecError> { + let ub = self.current_unsealed_block.load_full().ok_or(ExecError::NotInitialized)?; + let withdrawals_hash = if ub.is_prague { + let canonical_block = ub.env.number.saturating_sub(1); + + let state_provider = + self.client.state_by_block_number_or_tag(BlockNumberOrTag::Number(canonical_block)).map_err(|e| { + ExecError::Failed(format!("state_by_block_number_or_tag({canonical_block}) failed: {e}")) + })?; + let bundle_state = ub.get_bundle_state(); + Some(isthmus::withdrawals_root(bundle_state, state_provider)?) + } else { + None + }; + + let block = ub.to_op_block(withdrawals_hash)?; + let sealed = SealedBlock::seal_slow(block); + let recovered = sealed.try_recover().map_err(|e| ExecError::Failed(format!("recover senders: {e}")))?; + + let provider_rw = self.client.database_provider_rw()?; + provider_rw.insert_block(recovered)?; + provider_rw.commit()?; + Ok(()) } - fn set_canonical(&mut self, _b: &Block) -> impl Future> + Send + '_ { - async move { Ok(()) } + fn set_canonical(&mut self, b: &OpBlock) -> Result<(), ExecError> { + let sealed = SealedHeader::seal_slow(b.header.clone()); + self.client.set_canonical_head(sealed); + Ok(()) } - fn get_block( - &self, - _hash: B256, - _number: BlockNumber, - ) -> impl Future> + Send + '_ { - async move { Ok(Block::default()) } + fn get_block(&self, hash: B256) -> Result { + if let Some(block) = self + .block_cache + .lock() + .map_err(|_| ExecError::Failed("block_cache mutex poisoned".into()))? + .get(&hash) + .cloned() + { + return Ok(block); + } + + // fetch + let block = self + .client + .block_by_hash(hash) + .map_err(|e| ExecError::Failed(format!("block_by_hash failed: {e}")))? + .ok_or_else(|| ExecError::Failed("pre-sealed block not found".into()))?; + + self.block_cache + .lock() + .map_err(|_| ExecError::Failed("block_cache mutex poisoned".into()))? + .insert(hash, block.clone()); + + Ok(block) } fn reset(&mut self) { diff --git a/based/crates/reth/src/unsealed_block.rs b/based/crates/reth/src/unsealed_block.rs index 77b922b68..8e71f2287 100644 --- a/based/crates/reth/src/unsealed_block.rs +++ b/based/crates/reth/src/unsealed_block.rs @@ -1,12 +1,14 @@ -use alloy_consensus::{Header, TxEnvelope}; -use alloy_eips::eip2718::Decodable2718; +use alloy_consensus::{BlockBody, Header, TxEnvelope}; +use alloy_eips::{eip2718::Decodable2718, eip7685::EMPTY_REQUESTS_HASH}; use alloy_primitives::{Address, B256, Bytes, Sealable, TxHash, U256, map::foldhash::HashMap}; use alloy_rpc_types::{BlockTransactions, Filter, Log, state::StateOverride}; use alloy_rpc_types_eth::Header as RPCHeader; use bop_common::p2p::{EnvV0, FragV0, Transaction as TxBytes}; +use op_alloy_consensus::{OpBlock, OpTxEnvelope}; use op_alloy_network::{Optimism, TransactionResponse}; use op_alloy_rpc_types::{OpTransactionReceipt, Transaction}; -use reth::revm::db::Cache; +use reth::revm::db::{BundleState, Cache}; +use reth_optimism_primitives::OpTransactionSigned; use reth_rpc_eth_api::RpcBlock; use tokio::sync::broadcast; @@ -34,6 +36,7 @@ pub struct UnsealedBlock { pub cumulative_gas_used: u64, /// Cumulative blob gas used across all blob-carrying transactions in the block. pub cumulative_blob_gas_used: u64, + pub is_prague: bool, transaction_count: HashMap, transactions: Vec, @@ -43,11 +46,12 @@ pub struct UnsealedBlock { new_block_sender: broadcast::Sender>, db_cache: Cache, + bundle_state: BundleState, } impl UnsealedBlock { /// Create a fresh unsealed block state for `env` with empty frags/results/caches. - pub fn new(env: EnvV0) -> Self { + pub fn new(env: EnvV0, is_prague: bool) -> Self { let (new_block_sender, _) = broadcast::channel(16); Self { @@ -59,12 +63,14 @@ impl UnsealedBlock { logs: Vec::new(), cumulative_gas_used: 0, cumulative_blob_gas_used: 0, + is_prague, transaction_count: Default::default(), transactions: vec![], transaction_receipts: Default::default(), state_overrides: None, new_block_sender, db_cache: Default::default(), + bundle_state: Default::default(), } } @@ -110,9 +116,9 @@ impl UnsealedBlock { }) } - /// Decoded txs (allocates Vec), like Go `Transactions()` but decoded. - pub fn transactions(&self) -> Result, UnsealedBlockError> { - self.transactions_iter_decoded().collect() + /// Return list of transaction + pub fn transactions(&self) -> Vec { + self.transactions.clone() } /// Raw tx bytes (allocates Vec>), like Go `ByteTransactions()`. @@ -142,8 +148,7 @@ impl UnsealedBlock { self.receipts.extend_from_slice(receipts.as_slice()); self.cumulative_gas_used = cummulative_gas_used; - // TODO: Is this correct? Is everything applied here? - let _ = self.new_block_sender.send(self.to_block(false)); + let _ = self.new_block_sender.send(self.to_rpc_block(false)); } /// Validate frag against current state (equivalent to your ValidateNewFragV0 + sequencing gate). @@ -200,7 +205,7 @@ impl UnsealedBlock { /// Reset to a fresh env (drop frags/results/counters). pub fn reset_to_env(&mut self, env: EnvV0) { - *self = Self::new(env); + *self = Self::new(env, self.is_prague); } /// Attach/replace the DB cache to carry execution overlay state forward. @@ -209,6 +214,12 @@ impl UnsealedBlock { self } + /// Attach/replace the bundle state to carry execution overlay state forward. + pub fn with_bundle_state(mut self, bundle_state: BundleState) -> Self { + self.bundle_state = bundle_state; + self + } + /// Attach/replace the state overrides that represent the current overlay diff. pub fn with_state_overrides(mut self, state_overrides: Option) -> Self { self.state_overrides = state_overrides; @@ -220,6 +231,11 @@ impl UnsealedBlock { self.db_cache.clone() } + /// Returns the bundle state. + pub fn get_bundle_state(&self) -> &BundleState { + &self.bundle_state + } + /// Clone this unsealed block into a mutable working copy for in-place updates. pub fn clone_for_update(&self) -> Self { Self { @@ -231,12 +247,14 @@ impl UnsealedBlock { logs: self.logs.clone(), cumulative_gas_used: self.cumulative_gas_used, cumulative_blob_gas_used: self.cumulative_blob_gas_used, - transaction_count: Default::default(), - transactions: vec![], + is_prague: self.is_prague, + transaction_count: self.transaction_count.clone(), + transactions: self.transactions.clone(), db_cache: self.db_cache.clone(), state_overrides: self.state_overrides.clone(), new_block_sender: self.new_block_sender.clone(), - transaction_receipts: Default::default(), + transaction_receipts: self.transaction_receipts.clone(), + bundle_state: self.bundle_state.clone(), } } @@ -324,7 +342,7 @@ impl UnsealedBlock { } /// Convert current unsealed block into RpcBlock. - pub fn to_block(&self, full: bool) -> RpcBlock { + pub fn to_rpc_block(&self, full: bool) -> RpcBlock { let header = self.get_header(); let header = header.clone().seal_slow(); let block_transactions = self.transactions.clone(); @@ -343,4 +361,51 @@ impl UnsealedBlock { withdrawals: None, } } + + pub fn to_op_block(&self, withdrawals_hash: Option) -> Result { + // Decode EIP-2718 tx bytes -> OpTransactionSigned + let tx_list: Vec = self + .frags + .iter() + .enumerate() + .flat_map(|(frag_idx, frag)| { + frag.txs.iter().enumerate().map(move |(tx_idx, tx_bytes)| { + OpTxEnvelope::decode_2718(&mut tx_bytes.as_ref()).map_err(|e| { + UnsealedBlockError::Failed(format!("decode tx failed (frag={frag_idx} tx={tx_idx}): {e}")) + }) + }) + }) + .collect::, UnsealedBlockError>>()?; + + let requests_hash = self.is_prague.then_some(EMPTY_REQUESTS_HASH); + + let extra_data: Bytes = Bytes::copy_from_slice(self.env.extra_data.as_ref()); + let header = Header { + parent_hash: self.env.parent_hash, + ommers_hash: Default::default(), + beneficiary: self.env.beneficiary, + state_root: B256::ZERO, + transactions_root: B256::ZERO, + receipts_root: B256::ZERO, + logs_bloom: Default::default(), + difficulty: self.env.difficulty, + number: self.env.number, + gas_limit: self.env.gas_limit, + gas_used: self.cumulative_gas_used, + timestamp: self.env.timestamp, + extra_data, + mix_hash: self.env.prevrandao, + nonce: Default::default(), + base_fee_per_gas: Some(self.env.basefee), + withdrawals_root: withdrawals_hash, + blob_gas_used: Some(self.cumulative_blob_gas_used), + excess_blob_gas: Some(0), + parent_beacon_block_root: Some(self.env.parent_beacon_block_root), + requests_hash, + }; + + let body = BlockBody { transactions: tx_list, ommers: vec![], withdrawals: None }; + + Ok(reth_optimism_primitives::OpBlock::new(header, body)) + } }