diff --git a/Cargo.lock b/Cargo.lock index 7cfba378..7625aa17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,7 +1501,6 @@ dependencies = [ "brotli", "bytes", "derive_more", - "reth-optimism-primitives", "rstest", "serde", "serde_json", @@ -1524,6 +1523,7 @@ version = "0.2.1" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-op-evm", "alloy-primitives", "alloy-provider", "alloy-rpc-client", diff --git a/Cargo.toml b/Cargo.toml index 34f19597..7c74d179 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ alloy-rpc-types-eth = "1.0.41" alloy-rpc-types-engine = "1.0.41" # op-alloy +alloy-op-evm = { version = "0.23.3", default-features = false } op-alloy-network = "0.22.0" op-alloy-rpc-types = "0.22.0" op-alloy-consensus = "0.22.0" diff --git a/crates/flashblocks/Cargo.toml b/crates/flashblocks/Cargo.toml index 91f16634..0f604b5b 100644 --- a/crates/flashblocks/Cargo.toml +++ b/crates/flashblocks/Cargo.toml @@ -32,6 +32,7 @@ alloy-provider.workspace = true alloy-rpc-types.workspace = true alloy-consensus.workspace = true alloy-primitives.workspace = true +alloy-op-evm.workspace = true alloy-rpc-types-eth.workspace = true alloy-rpc-types-engine.workspace = true diff --git a/crates/flashblocks/benches/pending_state.rs b/crates/flashblocks/benches/pending_state.rs index 4b4eb484..7b201576 100644 --- a/crates/flashblocks/benches/pending_state.rs +++ b/crates/flashblocks/benches/pending_state.rs @@ -5,9 +5,8 @@ use std::{ time::{Duration, Instant}, }; -use alloy_consensus::Receipt; use alloy_eips::{BlockHashOrNumber, Encodable2718}; -use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, b256, bytes, hex::FromHex}; +use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, bytes, hex::FromHex}; use alloy_rpc_types_engine::PayloadId; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, @@ -15,13 +14,12 @@ use base_flashtypes::{ use base_reth_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_reth_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; -use op_alloy_consensus::OpDepositReceipt; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, providers::BlockReader, transaction_pool::test_utils::TransactionBuilder, }; -use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned}; +use reth_optimism_primitives::{OpBlock, OpTransactionSigned}; use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::{runtime::Runtime, time::sleep}; use tracing_subscriber::{EnvFilter, filter::LevelFilter}; @@ -33,8 +31,6 @@ const CHUNK_SIZE: usize = 10; const BLOCK_INFO_TXN: Bytes = bytes!( "0x7ef90104a06c0c775b6b492bab9d7e81abdf27f77cafb698551226455a82f559e0f93fea3794deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b0098999be000008dd00101c1200000000000000020000000068869d6300000000015f277f000000000000000000000000000000000000000000000000000000000d42ac290000000000000000000000000000000000000000000000000000000000000001abf52777e63959936b1bf633a2a643f0da38d63deffe49452fed1bf8a44975d50000000000000000000000005050f69a9786f081509234f1a7f4684b5e5b76c9000000000000000000000000" ); -const BLOCK_INFO_TXN_HASH: B256 = - b256!("0xba56c8b0deb460ff070f8fca8e2ee01e51a3db27841cc862fdd94cc1a47662b6"); struct BenchSetup { provider: LocalNodeProvider, @@ -203,20 +199,6 @@ fn base_flashblock( canonical_block: &RecoveredBlock, block_number: BlockNumber, ) -> Flashblock { - let mut receipts = alloy_primitives::map::HashMap::default(); - receipts.insert( - BLOCK_INFO_TXN_HASH, - OpReceipt::Deposit(OpDepositReceipt { - inner: Receipt { - status: true.into(), - cumulative_gas_used: DEPOSIT_GAS_USED, - logs: vec![], - }, - deposit_nonce: Some(0), - deposit_receipt_version: None, - }), - ); - Flashblock { payload_id: PayloadId::default(), index: 0, @@ -242,7 +224,7 @@ fn base_flashblock( transactions: vec![BLOCK_INFO_TXN.clone()], blob_gas_used: Default::default(), }, - metadata: Metadata { block_number, receipts, new_account_balances: Default::default() }, + metadata: Metadata { block_number }, } } @@ -252,20 +234,11 @@ fn transaction_flashblock( transactions: &[OpTransactionSigned], gas_used: &mut u64, ) -> Flashblock { - let mut tx_receipts = alloy_primitives::map::HashMap::default(); let mut tx_bytes = Vec::with_capacity(transactions.len()); for tx in transactions { *gas_used += TX_GAS_USED; tx_bytes.push(tx.encoded_2718().into()); - tx_receipts.insert( - tx.hash().clone(), - OpReceipt::Eip1559(Receipt { - status: true.into(), - cumulative_gas_used: *gas_used, - logs: vec![], - }), - ); } Flashblock { @@ -283,11 +256,7 @@ fn transaction_flashblock( transactions: tx_bytes, blob_gas_used: Default::default(), }, - metadata: Metadata { - block_number, - receipts: tx_receipts, - new_account_balances: Default::default(), - }, + metadata: Metadata { block_number }, } } diff --git a/crates/flashblocks/src/processor.rs b/crates/flashblocks/src/processor.rs index b9906bb9..97b52adb 100644 --- a/crates/flashblocks/src/processor.rs +++ b/crates/flashblocks/src/processor.rs @@ -7,18 +7,19 @@ use std::{ }; use alloy_consensus::{ - Header, TxReceipt, + Eip658Value, Header, TxReceipt, transaction::{Recovered, SignerRecoverable, TransactionMeta}, }; use alloy_eips::BlockNumberOrTag; -use alloy_primitives::{Address, B256, BlockNumber, Bytes, Sealable, map::foldhash::HashMap}; +use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; +use alloy_primitives::{Address, B256, BlockNumber, Bytes, Sealable}; use alloy_rpc_types::{TransactionTrait, Withdrawal}; use alloy_rpc_types_engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3}; use alloy_rpc_types_eth::state::StateOverride; use arc_swap::ArcSwapOption; use base_flashtypes::Flashblock; use eyre::eyre; -use op_alloy_consensus::OpTxEnvelope; +use op_alloy_consensus::{OpDepositReceipt, OpTxEnvelope}; use op_alloy_network::TransactionResponse; use op_alloy_rpc_types::Transaction; use rayon::prelude::*; @@ -30,11 +31,11 @@ use reth::{ db::CacheDB, }, }; -use reth_evm::{ConfigureEvm, Evm}; +use reth_evm::{ConfigureEvm, Evm, eth::receipt_builder::ReceiptBuilderCtx}; use reth_optimism_chainspec::OpHardforks; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; -use reth_optimism_primitives::{DepositReceipt, OpBlock, OpPrimitives}; -use reth_optimism_rpc::OpReceiptBuilder; +use reth_optimism_primitives::{OpBlock, OpPrimitives}; +use reth_optimism_rpc::OpReceiptBuilder as OpRpcReceiptBuilder; use reth_primitives::RecoveredBlock; use reth_rpc_convert::transaction::ConvertReceiptInput; use tokio::sync::{Mutex, broadcast::Sender, mpsc::UnboundedReceiver}; @@ -330,22 +331,6 @@ where .flat_map(|flashblock| flashblock.diff.withdrawals.clone()) .collect(); - let receipt_by_hash = flashblocks - .iter() - .map(|flashblock| flashblock.metadata.receipts.clone()) - .fold(HashMap::default(), |mut acc, receipts| { - acc.extend(receipts); - acc - }); - - let updated_balances = flashblocks - .iter() - .map(|flashblock| flashblock.metadata.new_account_balances.clone()) - .fold(HashMap::default(), |mut acc, balances| { - acc.extend(balances); - acc - }); - pending_blocks_builder.with_flashblocks( flashblocks.iter().map(|&x| x.clone()).collect::>(), ); @@ -391,7 +376,7 @@ 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 cumulative_gas_used: u64 = 0; let mut next_log_index = 0; // Parallel sender recovery - batch all ECDSA operations upfront @@ -420,24 +405,7 @@ where pending_blocks_builder.with_transaction_sender(tx_hash, sender); pending_blocks_builder.increment_nonce(sender); - let receipt = receipt_by_hash - .get(&tx_hash) - .cloned() - .ok_or(eyre!("missing receipt for {:?}", tx_hash))?; - let recovered_transaction = Recovered::new_unchecked(transaction.clone(), sender); - let envelope = recovered_transaction.clone().convert::(); - - // Build Transaction - let (deposit_receipt_version, deposit_nonce) = if transaction.is_deposit() { - let deposit_receipt = receipt - .as_deposit_receipt() - .ok_or(eyre!("deposit transaction, non deposit receipt"))?; - - (deposit_receipt.deposit_receipt_version, deposit_receipt.deposit_nonce) - } else { - (None, None) - }; let effective_gas_price = if transaction.is_deposit() { 0 @@ -451,68 +419,70 @@ where .unwrap_or_else(|| transaction.max_fee_per_gas()) }; - let rpc_txn = Transaction { - inner: alloy_rpc_types_eth::Transaction { - inner: envelope, - block_hash: Some(header.hash()), - block_number: Some(base.block_number), - transaction_index: Some(idx as u64), - effective_gas_price: Some(effective_gas_price), - }, - deposit_nonce, - deposit_receipt_version, - }; + // Check if we have all the data we need (receipt + state) + let cached_data = prev_pending_blocks.as_ref().and_then(|p| { + let receipt = p.get_receipt(tx_hash)?; + let state = p.get_transaction_state(&tx_hash)?; + Some((receipt, state)) + }); - pending_blocks_builder.with_transaction(rpc_txn); - - // Receipt Generation - let op_receipt = prev_pending_blocks - .as_ref() - .and_then(|pending_blocks| pending_blocks.get_receipt(tx_hash)) - .unwrap_or_else(|| { - 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: receipt.clone(), - tx: Recovered::new_unchecked(transaction, sender), - gas_used: receipt.cumulative_gas_used() - gas_used, - next_log_index, - meta, - }; - - OpReceiptBuilder::new( - self.client.chain_spec().as_ref(), - input, - &mut l1_block_info, - ) - .unwrap() - .build() - }); - - pending_blocks_builder.with_receipt(tx_hash, op_receipt); - gas_used = receipt.cumulative_gas_used(); - next_log_index += receipt.logs().len(); - - let mut should_execute_transaction = true; - if let Some(state) = - prev_pending_blocks.as_ref().and_then(|p| p.get_transaction_state(&tx_hash)) - { + // If cached, we can fill out pending block data using previous execution results + // If not cached, we need to execute the transaction and build pending block data from scratch + // The `pending_blocks_builder.with*` calls should fill out the same data in both cases + // We also need to update the cumulative gas used and next log index in both cases + if let Some((receipt, state)) = cached_data { + let (deposit_receipt_version, deposit_nonce) = if transaction.is_deposit() { + let deposit_receipt = receipt + .inner + .inner + .as_deposit_receipt() + .ok_or(eyre!("deposit transaction, non deposit receipt"))?; + + (deposit_receipt.deposit_receipt_version, deposit_receipt.deposit_nonce) + } else { + (None, None) + }; + + let envelope = recovered_transaction.clone().convert::(); + let rpc_txn = Transaction { + inner: alloy_rpc_types_eth::Transaction { + inner: envelope, + block_hash: Some(header.hash()), + block_number: Some(base.block_number), + transaction_index: Some(idx as u64), + effective_gas_price: Some(effective_gas_price), + }, + deposit_nonce, + deposit_receipt_version, + }; + + pending_blocks_builder.with_transaction(rpc_txn); + pending_blocks_builder.with_receipt(tx_hash, receipt.clone()); + + for (address, account) in state.iter() { + if account.is_touched() { + pending_blocks_builder + .with_account_balance(*address, account.info.balance); + } + } pending_blocks_builder.with_transaction_state(tx_hash, state); - should_execute_transaction = false; - } - if should_execute_transaction { - match evm.transact(recovered_transaction) { - Ok(ResultAndState { state, .. }) => { + cumulative_gas_used = cumulative_gas_used + .checked_add(receipt.inner.gas_used) + .ok_or(eyre!("cumulative gas used overflow"))?; + next_log_index += receipt.inner.logs().len(); + } else { + let envelope = recovered_transaction.clone().convert::(); + + match evm.transact(recovered_transaction.clone()) { + Ok(ResultAndState { state, result }) => { + let gas_used = result.gas_used(); for (addr, acc) in &state { + if acc.is_touched() { + pending_blocks_builder + .with_account_balance(*addr, acc.info.balance); + } + let existing_override = state_overrides.entry(*addr).or_default(); existing_override.balance = Some(acc.info.balance); existing_override.nonce = Some(acc.info.nonce); @@ -527,6 +497,117 @@ where existing.extend(changed_slots); } + + cumulative_gas_used = cumulative_gas_used + .checked_add(gas_used) + .ok_or(eyre!("cumulative gas used overflow"))?; + + let receipt_builder = + evm_config.block_executor_factory().receipt_builder(); + + let is_canyon_active = self + .client + .chain_spec() + .is_canyon_active_at_timestamp(block.timestamp); + + let is_regolith_active = self + .client + .chain_spec() + .is_regolith_active_at_timestamp(block.timestamp); + + let receipt = match receipt_builder.build_receipt(ReceiptBuilderCtx { + tx: &recovered_transaction, + evm: &evm, + result, + state: &state, + cumulative_gas_used, + }) { + Ok(receipt) => receipt, + Err(ctx) => { + // This is a deposit transaction, so build the receipt from the context + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + let deposit_nonce = (is_regolith_active + && transaction.is_deposit()) + .then(|| { + evm.db_mut() + .load_account(recovered_transaction.signer()) + .map(|acc| acc.info.nonce) + }) + .transpose() + .map_err(|_| { + eyre!("failed to load cache account for depositor") + })?; + + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + deposit_receipt_version: is_canyon_active.then_some(1), + }) + } + }; + + 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: receipt.clone(), + tx: Recovered::new_unchecked(transaction, sender), + gas_used, + next_log_index, + meta, + }; + + let op_receipt = OpRpcReceiptBuilder::new( + self.client.chain_spec().as_ref(), + input, + &mut l1_block_info, + ) + .unwrap() + .build(); + next_log_index += receipt.logs().len(); + + let (deposit_receipt_version, deposit_nonce) = + if transaction.is_deposit() { + let deposit_receipt = + op_receipt.inner.inner.as_deposit_receipt().ok_or( + eyre!("deposit transaction, non deposit receipt"), + )?; + + ( + deposit_receipt.deposit_receipt_version, + deposit_receipt.deposit_nonce, + ) + } else { + (None, None) + }; + + let rpc_txn = Transaction { + inner: alloy_rpc_types_eth::Transaction { + inner: envelope, + block_hash: Some(header.hash()), + block_number: Some(base.block_number), + transaction_index: Some(idx as u64), + effective_gas_price: Some(effective_gas_price), + }, + deposit_nonce, + deposit_receipt_version, + }; + + pending_blocks_builder.with_transaction(rpc_txn); + pending_blocks_builder.with_receipt(tx_hash, op_receipt); pending_blocks_builder.with_transaction_state(tx_hash, state.clone()); evm.db_mut().commit(state); } @@ -542,10 +623,6 @@ where } } - for (address, balance) in updated_balances { - pending_blocks_builder.with_account_balance(address, balance); - } - db = evm.into_db(); last_block_header = block.header.clone(); } diff --git a/crates/flashblocks/tests/state.rs b/crates/flashblocks/tests/state.rs index 75ef5d82..8f998125 100644 --- a/crates/flashblocks/tests/state.rs +++ b/crates/flashblocks/tests/state.rs @@ -215,7 +215,7 @@ impl TestHarness { struct FlashblockBuilder<'a> { transactions: Vec, - receipts: HashMap, + receipts: Option>, harness: &'a TestHarness, canonical_block_number: Option, index: u64, @@ -226,7 +226,7 @@ impl<'a> FlashblockBuilder<'a> { Self { canonical_block_number: None, transactions: vec![L1_BLOCK_INFO_DEPOSIT_TX.clone()], - receipts: { + receipts: Some({ let mut receipts = alloy_primitives::map::HashMap::default(); receipts.insert( L1_BLOCK_INFO_DEPOSIT_TX_HASH, @@ -241,7 +241,7 @@ impl<'a> FlashblockBuilder<'a> { }), ); receipts - }, + }), index: 0, harness, } @@ -250,13 +250,13 @@ impl<'a> FlashblockBuilder<'a> { Self { canonical_block_number: None, transactions: Vec::new(), - receipts: HashMap::default(), + receipts: Some(HashMap::default()), harness, index, } } - fn with_receipts(&mut self, receipts: HashMap) -> &mut Self { + fn with_receipts(&mut self, receipts: Option>) -> &mut Self { self.receipts = receipts; self } @@ -269,14 +269,16 @@ impl<'a> FlashblockBuilder<'a> { for txn in transactions.iter() { cumulative_gas_used = cumulative_gas_used + txn.gas_limit(); self.transactions.push(txn.encoded_2718().into()); - self.receipts.insert( - txn.hash().clone(), - OpReceipt::Eip1559(Receipt { - status: true.into(), - cumulative_gas_used, - logs: vec![], - }), - ); + if let Some(ref mut receipts) = self.receipts { + receipts.insert( + txn.hash().clone(), + OpReceipt::Eip1559(Receipt { + status: true.into(), + cumulative_gas_used, + logs: vec![], + }), + ); + } } self } @@ -322,11 +324,7 @@ impl<'a> FlashblockBuilder<'a> { transactions: self.transactions.clone(), blob_gas_used: Default::default(), }, - metadata: Metadata { - block_number: canonical_block_num, - receipts: self.receipts.clone(), - new_account_balances: HashMap::default(), - }, + metadata: Metadata { block_number: canonical_block_num }, } } } @@ -715,29 +713,26 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { } #[tokio::test] -async fn test_missing_receipts_will_not_process() { +async fn test_metadata_receipts_are_optional() { + // Test to ensure that receipts are optional in the metadata + // and deposit receipts return None for nonce until the canonical block is processed let test = TestHarness::new().await; - test.send_flashblock(FlashblockBuilder::new_base(&test).build()).await; - - let current_block = test.flashblocks.get_pending_blocks().get_block(true); - - test.send_flashblock( - FlashblockBuilder::new(&test, 1) - .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, - 100, - )]) - .with_receipts(HashMap::default()) // Clear the receipts - .build(), - ) - .await; + // Send a flashblock with no receipts (only deposit transaction) + test.send_flashblock(FlashblockBuilder::new_base(&test).with_receipts(None).build()).await; - let pending_block = test.flashblocks.get_pending_blocks().get_block(true); + // Verify the block was created with the deposit transaction + let pending_block = + test.flashblocks.get_pending_blocks().get_block(true).expect("block should be created"); + assert_eq!(pending_block.transactions.len(), 1); - // When the flashblock is invalid, the chain doesn't progress - assert_eq!(pending_block.unwrap().hash(), current_block.unwrap().hash()); + // Check that the deposit transaction has the correct nonce + let deposit_tx = &pending_block.transactions.as_transactions().unwrap()[0]; + assert_eq!( + deposit_tx.deposit_nonce, + Some(0), + "deposit_nonce should be available even when no receipts" + ); } #[tokio::test] diff --git a/crates/flashtypes/Cargo.toml b/crates/flashtypes/Cargo.toml index 77ab3a64..d58513b7 100644 --- a/crates/flashtypes/Cargo.toml +++ b/crates/flashtypes/Cargo.toml @@ -18,9 +18,6 @@ alloy-primitives.workspace = true alloy-rpc-types-eth.workspace = true alloy-rpc-types-engine.workspace = true -# reth -reth-optimism-primitives.workspace = true - # misc bytes.workspace = true serde.workspace = true diff --git a/crates/flashtypes/src/block.rs b/crates/flashtypes/src/block.rs index df1ddaf0..c6aec69a 100644 --- a/crates/flashtypes/src/block.rs +++ b/crates/flashtypes/src/block.rs @@ -79,8 +79,6 @@ mod tests { #[case] encoder: fn(&FlashblocksPayloadV1) -> Bytes, ) { let payload = sample_payload(json!({ - "receipts": {}, - "new_account_balances": {}, "block_number": 1234u64 })); @@ -92,15 +90,11 @@ mod tests { assert_eq!(decoded.base, payload.base); assert_eq!(decoded.diff, payload.diff); assert_eq!(decoded.metadata.block_number, 1234); - assert!(decoded.metadata.receipts.is_empty()); - assert!(decoded.metadata.new_account_balances.is_empty()); } #[rstest] #[case::invalid_brotli(Bytes::from_static(b"not brotli data"))] #[case::missing_metadata(encode_plain(&sample_payload(json!({ - "receipts": {}, - "new_account_balances": {} }))))] // missing block_number in metadata fn try_decode_message_rejects_invalid_data(#[case] bytes: Bytes) { assert!(Flashblock::try_decode_message(bytes).is_err()); diff --git a/crates/flashtypes/src/metadata.rs b/crates/flashtypes/src/metadata.rs index daa4190c..41bc6955 100644 --- a/crates/flashtypes/src/metadata.rs +++ b/crates/flashtypes/src/metadata.rs @@ -1,16 +1,10 @@ //! Contains the [`Metadata`] type used in Flashblocks. -use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap}; -use reth_optimism_primitives::OpReceipt; use serde::{Deserialize, Serialize}; /// Metadata associated with a flashblock. #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)] pub struct Metadata { - /// Transaction receipts indexed by hash. - pub receipts: HashMap, - /// Updated account balances. - pub new_account_balances: HashMap, /// Block number this flashblock belongs to. pub block_number: u64, } diff --git a/crates/rpc/tests/eth_call_erc20.rs b/crates/rpc/tests/eth_call_erc20.rs index 86ba31bb..de846fc4 100644 --- a/crates/rpc/tests/eth_call_erc20.rs +++ b/crates/rpc/tests/eth_call_erc20.rs @@ -11,9 +11,8 @@ //! - MockERC20: Solmate's MockERC20 (lib/solmate) //! - TransparentUpgradeableProxy: OpenZeppelin's proxy (lib/openzeppelin-contracts) -use alloy_consensus::Receipt; use alloy_eips::BlockNumberOrTag; -use alloy_primitives::{Address, B256, Bytes, LogData, TxHash, U256, map::HashMap}; +use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_rpc_types_engine::PayloadId; use alloy_sol_types::{SolConstructor, SolValue}; @@ -21,25 +20,15 @@ use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_reth_test_utils::{ - FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, MockERC20, - TransparentUpgradeableProxy, + FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, }; use eyre::Result; -use op_alloy_consensus::OpDepositReceipt; -use reth_optimism_primitives::OpReceipt; - -/// ERC-20 Transfer event topic (keccak256("Transfer(address,address,uint256)")) -const TRANSFER_EVENT_TOPIC: B256 = - alloy_primitives::b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); - struct Erc20TestSetup { harness: FlashblocksHarness, token_address: Address, token_deploy_tx: Bytes, - token_deploy_hash: TxHash, proxy_address: Option
, proxy_deploy_tx: Option, - proxy_deploy_hash: Option, } impl Erc20TestSetup { @@ -55,10 +44,10 @@ impl Erc20TestSetup { }; let token_deploy_data = [MockERC20::BYTECODE.to_vec(), token_constructor.abi_encode()].concat(); - let (token_deploy_tx, token_address, token_deploy_hash) = + let (token_deploy_tx, token_address, _) = deployer.create_deployment_tx(Bytes::from(token_deploy_data), 0)?; - let (proxy_address, proxy_deploy_tx, proxy_deploy_hash) = if with_proxy { + let (proxy_address, proxy_deploy_tx, _) = if with_proxy { // Deploy TransparentUpgradeableProxy from OpenZeppelin // Constructor: (implementation, initialOwner, data) let proxy_constructor = TransparentUpgradeableProxy::constructorCall { @@ -77,25 +66,11 @@ impl Erc20TestSetup { (None, None, None) }; - Ok(Self { - harness, - token_address, - token_deploy_tx, - token_deploy_hash, - proxy_address, - proxy_deploy_tx, - proxy_deploy_hash, - }) - } - - fn interaction_address(&self) -> Address { - self.proxy_address.unwrap_or(self.token_address) + Ok(Self { harness, token_address, token_deploy_tx, proxy_address, proxy_deploy_tx }) } /// Create the base flashblock payload (block info only) fn create_base_payload(&self) -> Flashblock { - let deployer_address = self.harness.accounts().deployer.address; - Flashblock { payload_id: PayloadId::new([0; 8]), index: 0, @@ -115,61 +90,12 @@ impl Erc20TestSetup { transactions: vec![L1_BLOCK_INFO_DEPOSIT_TX], ..Default::default() }, - metadata: Metadata { - block_number: 1, - receipts: { - let mut receipts = HashMap::default(); - receipts.insert( - L1_BLOCK_INFO_DEPOSIT_TX_HASH, - OpReceipt::Deposit(OpDepositReceipt { - inner: Receipt { - status: true.into(), - cumulative_gas_used: 10000, - logs: vec![], - }, - deposit_nonce: Some(4012991u64), - deposit_receipt_version: None, - }), - ); - receipts - }, - new_account_balances: { - // Give deployer enough ETH for contract deployments - let mut balances = HashMap::default(); - balances - .insert(deployer_address, U256::from(10_000_000_000_000_000_000_000u128)); // 10000 ETH - balances - }, - }, + metadata: Metadata { block_number: 1 }, } } /// Create flashblock payload with token deployment fn create_deploy_payload(&self) -> Flashblock { - let mut receipts = HashMap::default(); - - // Token deployment receipt - receipts.insert( - self.token_deploy_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 500_000, // Approximate gas for ERC-20 deployment - logs: vec![], - }), - ); - - // Add proxy deployment if present - if let (Some(proxy_hash), Some(_)) = (self.proxy_deploy_hash, &self.proxy_deploy_tx) { - receipts.insert( - proxy_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 700_000, // Token + proxy deployment - logs: vec![], - }), - ); - } - let mut transactions = vec![self.token_deploy_tx.clone()]; if let Some(proxy_tx) = &self.proxy_deploy_tx { transactions.push(proxy_tx.clone()); @@ -190,49 +116,12 @@ impl Erc20TestSetup { logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { - block_number: 1, - receipts, - new_account_balances: HashMap::default(), - }, + metadata: Metadata { block_number: 1 }, } } /// Create flashblock payload with mint transaction - fn create_mint_payload( - &self, - mint_tx: Bytes, - mint_hash: TxHash, - to: Address, - amount: U256, - ) -> Flashblock { - let mut receipts = HashMap::default(); - - // Create Transfer event log (from address(0) for mint) - let transfer_log = alloy_primitives::Log { - address: self.interaction_address(), - data: LogData::new( - vec![ - TRANSFER_EVENT_TOPIC, - B256::left_padding_from(&Address::ZERO.0.0), // from = address(0) - B256::left_padding_from(&to.0.0), // to - ], - amount.to_be_bytes_vec().into(), - ) - .unwrap(), - }; - - // cumulative_gas_used must be greater than previous transactions - // Base payload: 10_000, Deploy payload: 700_000, so mint must be > 700_000 - receipts.insert( - mint_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 750_000, // 700_000 (deploy) + 50_000 (mint) - logs: vec![transfer_log], - }), - ); - + fn create_mint_payload(&self, mint_tx: Bytes) -> Flashblock { Flashblock { payload_id: PayloadId::new([0; 8]), index: 2, @@ -248,11 +137,7 @@ impl Erc20TestSetup { logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { - block_number: 1, - receipts, - new_account_balances: HashMap::default(), - }, + metadata: Metadata { block_number: 1 }, } } @@ -344,11 +229,10 @@ async fn test_erc20_mint() -> Result<()> { let mint_amount = U256::from(1000u64); let mint_tx_request = token.mint(accounts.alice.address, mint_amount).into_transaction_request(); - let (mint_tx, mint_hash) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(1))?; + let (mint_tx, _) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(1))?; // Send mint flashblock - let mint_payload = - setup.create_mint_payload(mint_tx, mint_hash, accounts.alice.address, mint_amount); + let mint_payload = setup.create_mint_payload(mint_tx); setup.harness.send_flashblock(mint_payload).await?; // Verify balance after mint @@ -382,11 +266,10 @@ async fn test_proxy_erc20_mint() -> Result<()> { let mint_amount = U256::from(5000u64); let mint_tx_request = token_via_proxy.mint(accounts.alice.address, mint_amount).into_transaction_request(); - let (mint_tx, mint_hash) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(2))?; + let (mint_tx, _) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(2))?; // Send mint flashblock (note: interaction_address returns proxy) - let mint_payload = - setup.create_mint_payload(mint_tx, mint_hash, accounts.alice.address, mint_amount); + let mint_payload = setup.create_mint_payload(mint_tx); setup.harness.send_flashblock(mint_payload).await?; // Verify balance after mint through proxy diff --git a/crates/rpc/tests/flashblocks_rpc.rs b/crates/rpc/tests/flashblocks_rpc.rs index fe9f103c..ca7eeb54 100644 --- a/crates/rpc/tests/flashblocks_rpc.rs +++ b/crates/rpc/tests/flashblocks_rpc.rs @@ -3,11 +3,9 @@ use std::str::FromStr; use DoubleCounter::DoubleCounterInstance; -use alloy_consensus::{Receipt, Transaction}; +use alloy_consensus::Transaction; use alloy_eips::BlockNumberOrTag; -use alloy_primitives::{ - Address, B256, Bytes, LogData, TxHash, U256, address, b256, bytes, map::HashMap, -}; +use alloy_primitives::{Address, B256, Bytes, TxHash, U256, address, b256, bytes}; use alloy_provider::Provider; use alloy_rpc_client::RpcClient; use alloy_rpc_types::simulate::{SimBlock, SimulatePayload}; @@ -16,20 +14,130 @@ use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{ - DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, -}; +use base_reth_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; -use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; use op_alloy_rpc_types::OpTransactionRequest; use reth::revm::context::TransactionType; -use reth_optimism_primitives::OpReceipt; use reth_rpc_eth_api::RpcReceipt; use serde_json::json; use tokio_tungstenite::{connect_async, tungstenite::Message}; +// LogEmitterB: Emits LOG1 with TEST_LOG_TOPIC_0 when called +// Runtime bytecode: +// PUSH32 0x01 ; data to log (32 bytes of value 1) +// PUSH1 0x00 ; memory offset to store data +// MSTORE ; store data at memory[0:32] +// PUSH32 topic0 ; TEST_LOG_TOPIC_0 +// PUSH1 0x20 ; log data size (32 bytes) +// PUSH1 0x00 ; log data offset +// LOG1 ; emit log with 1 topic +// STOP ; end execution +const LOG_EMITTER_B_RUNTIME: &str = concat!( + "7f", + "0000000000000000000000000000000000000000000000000000000000000001", // PUSH32 data + "6000", // PUSH1 0 + "52", // MSTORE + "7f", + "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // PUSH32 topic0 + "6020", // PUSH1 32 + "6000", // PUSH1 0 + "a1", // LOG1 + "00", // STOP +); + +// LogEmitterA: Emits LOG2 with topic0 and topic1, then CALLs LogEmitterB +// Runtime bytecode (LOG_EMITTER_B_ADDR will be patched in): +// PUSH32 data ; 1 ETH in wei as log data +// PUSH1 0x00 ; memory offset +// MSTORE ; store at memory[0:32] +// PUSH32 topic1 ; TEST_LOG_TOPIC_1 +// PUSH32 topic0 ; TEST_LOG_TOPIC_0 +// PUSH1 0x20 ; size +// PUSH1 0x00 ; offset +// LOG2 ; emit log with 2 topics +// ; Now CALL LogEmitterB +// PUSH1 0x00 ; retSize +// PUSH1 0x00 ; retOffset +// PUSH1 0x00 ; argsSize +// PUSH1 0x00 ; argsOffset +// PUSH1 0x00 ; value +// PUSH20 addr ; LogEmitterB address (patched) +// PUSH2 0xffff ; gas +// CALL ; call LogEmitterB +// STOP +fn log_emitter_a_runtime(log_emitter_b_addr: Address) -> String { + // Convert address to hex without 0x prefix + let addr_hex = format!("{:040x}", log_emitter_b_addr); + format!( + concat!( + "7f", + "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // PUSH32 data (1 ETH) + "6000", // PUSH1 0 + "52", // MSTORE + "7f", + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", // PUSH32 topic1 + "7f", + "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // PUSH32 topic0 + "6020", // PUSH1 32 + "6000", // PUSH1 0 + "a2", // LOG2 + "6000", // PUSH1 0 (retSize) + "6000", // PUSH1 0 (retOffset) + "6000", // PUSH1 0 (argsSize) + "6000", // PUSH1 0 (argsOffset) + "6000", // PUSH1 0 (value) + "73", + "{addr}", // PUSH20 LogEmitterB address + "61ffff", // PUSH2 0xffff (gas) + "f1", // CALL + "00", // STOP + ), + addr = addr_hex + ) +} + +// Wrap runtime code in init code that returns it +// Init code: CODECOPY runtime to memory[0:size], RETURN it +fn wrap_in_init_code(runtime_hex: &str) -> Bytes { + // Parse hex string (handle both with and without 0x prefix) + let hex_str = runtime_hex.strip_prefix("0x").unwrap_or(runtime_hex); + let mut runtime_bytes = Vec::new(); + for i in (0..hex_str.len()).step_by(2) { + let byte = u8::from_str_radix(&hex_str[i..i + 2], 16).expect("valid hex"); + runtime_bytes.push(byte); + } + let runtime_size = runtime_bytes.len(); + + // Init code: + // PUSH1 runtime_size + // PUSH1 init_size (12 bytes) + // PUSH1 0 + // CODECOPY + // PUSH1 runtime_size + // PUSH1 0 + // RETURN + // Total init: 12 bytes + let init_size = 12u8; + let mut init_code = vec![ + 0x60, + runtime_size as u8, // PUSH1 runtime_size + 0x60, + init_size, // PUSH1 init_size + 0x60, + 0x00, // PUSH1 0 + 0x39, // CODECOPY + 0x60, + runtime_size as u8, // PUSH1 runtime_size + 0x60, + 0x00, // PUSH1 0 + 0xf3, // RETURN + ]; + init_code.extend(runtime_bytes); + Bytes::from(init_code) +} + struct TestSetup { harness: FlashblocksHarness, txn_details: TransactionDetails, @@ -37,17 +145,27 @@ struct TestSetup { struct TransactionDetails { counter_deployment_tx: Bytes, - counter_deployment_hash: TxHash, counter_address: Address, counter_increment_tx: Bytes, - counter_increment_hash: TxHash, counter_increment2_tx: Bytes, - counter_increment2_hash: TxHash, alice_eth_transfer_tx: Bytes, alice_eth_transfer_hash: TxHash, + + // Log-emitting contracts for log tests + log_emitter_b_deployment_tx: Bytes, + log_emitter_b_address: Address, + + log_emitter_a_deployment_tx: Bytes, + log_emitter_a_address: Address, + + log_trigger_tx: Bytes, + log_trigger_hash: TxHash, + + // Balance transfer for balance test + balance_transfer_tx: Bytes, } impl TestSetup { @@ -59,16 +177,19 @@ impl TestSetup { let alice = &harness.accounts().alice; let bob = &harness.accounts().bob; - let (counter_deployment_tx, counter_address, counter_deployment_hash) = deployer + // DoubleCounter deployment at nonce 0 + let (counter_deployment_tx, counter_address, _) = deployer .create_deployment_tx(DoubleCounter::BYTECODE.clone(), 0) .expect("should be able to sign DoubleCounter deployment txn"); let counter = DoubleCounterInstance::new(counter_address.clone(), provider); - let (increment1_tx, increment1_tx_hash) = deployer + let (increment1_tx, _) = deployer .sign_txn_request(counter.increment().into_transaction_request().nonce(1)) .expect("should be able to sign increment() txn"); - let (increment2_tx, increment2_tx_hash) = deployer + let (increment2_tx, _) = deployer .sign_txn_request(counter.increment2().into_transaction_request().nonce(2)) .expect("should be able to sign increment2() txn"); + + // Alice's ETH transfer at nonce 0 let (eth_transfer_tx, eth_transfer_hash) = alice .sign_txn_request( OpTransactionRequest::default() @@ -82,16 +203,63 @@ impl TestSetup { ) .expect("should be able to sign eth transfer txn"); + // Log-emitting contracts: + // Deploy LogEmitterB at deployer nonce 3 + let log_emitter_b_address = deployer.address.create(3); + let log_emitter_b_bytecode = wrap_in_init_code(LOG_EMITTER_B_RUNTIME); + let (log_emitter_b_deployment_tx, _, _) = deployer + .create_deployment_tx(log_emitter_b_bytecode, 3) + .expect("should be able to sign LogEmitterB deployment txn"); + + // Deploy LogEmitterA at deployer nonce 4 (knows LogEmitterB's address) + let log_emitter_a_address = deployer.address.create(4); + let log_emitter_a_runtime = log_emitter_a_runtime(log_emitter_b_address); + let log_emitter_a_bytecode = wrap_in_init_code(&log_emitter_a_runtime); + let (log_emitter_a_deployment_tx, _, _) = deployer + .create_deployment_tx(log_emitter_a_bytecode, 4) + .expect("should be able to sign LogEmitterA deployment txn"); + + // Call LogEmitterA at deployer nonce 5 to trigger logs + let (log_trigger_tx, log_trigger_hash) = deployer + .sign_txn_request( + OpTransactionRequest::default() + .from(deployer.address) + .transaction_type(TransactionType::Eip1559.into()) + .gas_limit(100_000) + .nonce(5) + .to(log_emitter_a_address) + .into(), + ) + .expect("should be able to sign log trigger txn"); + + // Balance transfer: alice sends PENDING_BALANCE wei to TEST_ADDRESS at nonce 1 + let (balance_transfer_tx, _) = alice + .sign_txn_request( + OpTransactionRequest::default() + .from(alice.address) + .transaction_type(TransactionType::Eip1559.into()) + .gas_limit(21_000) + .nonce(1) + .to(TEST_ADDRESS) + .value(U256::from(PENDING_BALANCE)) + .into(), + ) + .expect("should be able to sign balance transfer txn"); + let txn_details = TransactionDetails { counter_deployment_tx, - counter_deployment_hash, counter_address, counter_increment_tx: increment1_tx, - counter_increment_hash: increment1_tx_hash, counter_increment2_tx: increment2_tx, - counter_increment2_hash: increment2_tx_hash, alice_eth_transfer_tx: eth_transfer_tx, alice_eth_transfer_hash: eth_transfer_hash, + log_emitter_b_deployment_tx, + log_emitter_b_address, + log_emitter_a_deployment_tx, + log_emitter_a_address, + log_trigger_tx, + log_trigger_hash, + balance_transfer_tx, }; Ok(Self { harness, txn_details }) @@ -117,26 +285,7 @@ impl TestSetup { transactions: vec![L1_BLOCK_INFO_DEPOSIT_TX], ..Default::default() }, - metadata: Metadata { - block_number: 1, - receipts: { - let mut receipts = HashMap::default(); - receipts.insert( - L1_BLOCK_INFO_DEPOSIT_TX_HASH, - OpReceipt::Deposit(OpDepositReceipt { - inner: Receipt { - status: true.into(), - cumulative_gas_used: 10000, - logs: vec![], - }, - deposit_nonce: Some(4012991u64), - deposit_receipt_version: None, - }), - ); - receipts - }, - new_account_balances: HashMap::default(), - }, + metadata: Metadata { block_number: 1 }, } } @@ -157,84 +306,18 @@ impl TestSetup { self.txn_details.counter_deployment_tx.clone(), self.txn_details.counter_increment_tx.clone(), self.txn_details.counter_increment2_tx.clone(), + // Log-emitting contracts and trigger + self.txn_details.log_emitter_b_deployment_tx.clone(), + self.txn_details.log_emitter_a_deployment_tx.clone(), + self.txn_details.log_trigger_tx.clone(), + // Balance transfer to TEST_ADDRESS + self.txn_details.balance_transfer_tx.clone(), ], withdrawals: Vec::new(), logs_bloom: Default::default(), withdrawals_root: Default::default(), }, - metadata: Metadata { - block_number: 1, - receipts: { - let mut receipts = HashMap::default(); - receipts.insert( - DEPOSIT_TX_HASH, - OpReceipt::Deposit(OpDepositReceipt { - inner: Receipt { - status: true.into(), - cumulative_gas_used: 31000, - logs: vec![], - }, - deposit_nonce: Some(4012992u64), - deposit_receipt_version: None, - }), - ); - receipts.insert( - self.txn_details.alice_eth_transfer_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 55000, - logs: vec![], - }), - ); - receipts.insert( - self.txn_details.counter_deployment_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 272279, - logs: vec![], - }), - ); - receipts.insert( - self.txn_details.counter_increment_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 272279 + 44000, - logs: vec![ - alloy_primitives::Log { - address: self.txn_details.counter_address, - data: LogData::new( - vec![TEST_LOG_TOPIC_0, TEST_LOG_TOPIC_1, TEST_LOG_TOPIC_2], - bytes!("0x0000000000000000000000000000000000000000000000000de0b6b3a7640000").into(), // 1 ETH in wei - ) - .unwrap(), - }, - alloy_primitives::Log { - address: TEST_ADDRESS, - data: LogData::new( - vec![TEST_LOG_TOPIC_0], - bytes!("0x0000000000000000000000000000000000000000000000000000000000000001").into(), // Value: 1 - ) - .unwrap(), - }, - ] - }), - ); - receipts.insert( - self.txn_details.counter_increment2_hash, - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 272279 + 44000 + 44000, - logs: vec![], - }), - ); - receipts - }, - new_account_balances: { - let mut map = HashMap::default(); - map.insert(TEST_ADDRESS, U256::from(PENDING_BALANCE)); - map - }, - }, + metadata: Metadata { block_number: 1 }, } } @@ -288,6 +371,7 @@ const DEPOSIT_SENDER: Address = address!("0xdeaddeaddeaddeaddeaddeaddeaddeaddead const DEPOSIT_TX: Bytes = bytes!( "0x7ef8f8a042a8ae5ec231af3d0f90f68543ec8bca1da4f7edd712d5b51b490688355a6db794deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e200000044d000a118b00000000000000040000000067cb7cb0000000000077dbd4000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000014edd27304108914dd6503b19b9eeb9956982ef197febbeeed8a9eac3dbaaabdf000000000000000000000000fc56e7272eebbba5bc6c544e159483c4a38f8ba3" ); +const DEPOSIT_GAS_USED: u64 = 24770; const DEPOSIT_TX_HASH: TxHash = b256!("0x2be2e6f8b01b03b87ae9f0ebca8bbd420f174bef0fbcc18c7802c5378b78f548"); @@ -296,8 +380,6 @@ const TEST_LOG_TOPIC_0: B256 = b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); // Transfer event const TEST_LOG_TOPIC_1: B256 = b256!("0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266"); // From address -const TEST_LOG_TOPIC_2: B256 = - b256!("0x0000000000000000000000001234567890123456789012345678901234567890"); // To address #[tokio::test] async fn test_get_pending_block() -> Result<()> { @@ -334,14 +416,18 @@ async fn test_get_pending_block() -> Result<()> { let second_payload = setup.create_second_payload(); setup.send_flashblock(second_payload).await?; - // Query pending block after sending the second payload with two transactions + // Query pending block after sending the second payload with transactions let block = provider .get_block_by_number(BlockNumberOrTag::Pending) .await? .expect("pending block expected"); assert_eq!(block.number(), 1); - assert_eq!(block.transactions.hashes().len(), 6); + // First flashblock: 1 L1Info transaction + // Second flashblock: 1 DEPOSIT_TX + 1 alice ETH transfer + 1 counter deploy + 1 counter increment + 1 counter increment2 + // + 1 LogEmitterB deploy + 1 LogEmitterA deploy + 1 log trigger + 1 balance transfer + // Total: 1 + 9 = 10 transactions + assert_eq!(block.transactions.hashes().len(), 10); Ok(()) } @@ -406,13 +492,13 @@ async fn test_get_transaction_receipt_pending() -> Result<()> { let receipt = provider.get_transaction_receipt(DEPOSIT_TX_HASH).await?.expect("receipt expected"); - assert_eq!(receipt.gas_used(), 21000); + assert_eq!(receipt.gas_used(), DEPOSIT_GAS_USED); let receipt = provider .get_transaction_receipt(setup.txn_details.alice_eth_transfer_hash) .await? .expect("receipt expected"); - assert_eq!(receipt.gas_used(), 24000); // 45000 - 21000 + assert_eq!(receipt.gas_used(), 21000); Ok(()) } @@ -432,8 +518,11 @@ async fn test_get_transaction_count() -> Result<()> { setup.send_test_payloads().await?; assert_eq!(provider.get_transaction_count(DEPOSIT_SENDER).pending().await?, 2); - assert_eq!(provider.get_transaction_count(deployer_addr).pending().await?, 3); - assert_eq!(provider.get_transaction_count(alice_addr).pending().await?, 1); + // Deployer has: counter deploy (0), counter increment (1), counter increment2 (2), + // LogEmitterB deploy (3), LogEmitterA deploy (4), log trigger (5) = nonce 6 + assert_eq!(provider.get_transaction_count(deployer_addr).pending().await?, 6); + // Alice has: big ETH transfer (0), balance transfer to TEST_ADDRESS (1) = nonce 2 + assert_eq!(provider.get_transaction_count(alice_addr).pending().await?, 2); Ok(()) } @@ -629,18 +718,18 @@ async fn test_get_logs_pending() -> Result<()> { ) .await?; - // We should now have 2 logs from the INCREMENT_TX transaction + // We should now have 2 logs from the log_trigger_tx transaction assert_eq!(logs.len(), 2); - // Verify the first log is from COUNTER_ADDRESS - assert_eq!(logs[0].address(), setup.txn_details.counter_address); + // Verify the first log is from LogEmitterA + assert_eq!(logs[0].address(), setup.txn_details.log_emitter_a_address); assert_eq!(logs[0].topics()[0], TEST_LOG_TOPIC_0); - assert_eq!(logs[0].transaction_hash, Some(setup.txn_details.counter_increment_hash)); + assert_eq!(logs[0].transaction_hash, Some(setup.txn_details.log_trigger_hash)); - // Verify the second log is from TEST_ADDRESS - assert_eq!(logs[1].address(), TEST_ADDRESS); + // Verify the second log is from LogEmitterB + assert_eq!(logs[1].address(), setup.txn_details.log_emitter_b_address); assert_eq!(logs[1].topics()[0], TEST_LOG_TOPIC_0); - assert_eq!(logs[1].transaction_hash, Some(setup.txn_details.counter_increment_hash)); + assert_eq!(logs[1].transaction_hash, Some(setup.txn_details.log_trigger_hash)); Ok(()) } @@ -652,35 +741,35 @@ async fn test_get_logs_filter_by_address() -> Result<()> { setup.send_test_payloads().await?; - // Test filtering by a specific address (COUNTER_ADDRESS) + // Test filtering by LogEmitterA address let logs = provider .get_logs( &alloy_rpc_types_eth::Filter::default() - .address(setup.txn_details.counter_address) + .address(setup.txn_details.log_emitter_a_address) .from_block(alloy_eips::BlockNumberOrTag::Pending) .to_block(alloy_eips::BlockNumberOrTag::Pending), ) .await?; - // Should get only 1 log from COUNTER_ADDRESS + // Should get only 1 log from LogEmitterA assert_eq!(logs.len(), 1); - assert_eq!(logs[0].address(), setup.txn_details.counter_address); - assert_eq!(logs[0].transaction_hash, Some(setup.txn_details.counter_increment_hash)); + assert_eq!(logs[0].address(), setup.txn_details.log_emitter_a_address); + assert_eq!(logs[0].transaction_hash, Some(setup.txn_details.log_trigger_hash)); - // Test filtering by TEST_ADDRESS + // Test filtering by LogEmitterB address let logs = provider .get_logs( &alloy_rpc_types_eth::Filter::default() - .address(TEST_ADDRESS) + .address(setup.txn_details.log_emitter_b_address) .from_block(alloy_eips::BlockNumberOrTag::Pending) .to_block(alloy_eips::BlockNumberOrTag::Pending), ) .await?; - // Should get only 1 log from TEST_ADDRESS + // Should get only 1 log from LogEmitterB assert_eq!(logs.len(), 1); - assert_eq!(logs[0].address(), TEST_ADDRESS); - assert_eq!(logs[0].transaction_hash, Some(setup.txn_details.counter_increment_hash)); + assert_eq!(logs[0].address(), setup.txn_details.log_emitter_b_address); + assert_eq!(logs[0].transaction_hash, Some(setup.txn_details.log_trigger_hash)); Ok(()) } @@ -705,7 +794,7 @@ async fn test_get_logs_topic_filtering() -> Result<()> { assert_eq!(logs.len(), 2); assert!(logs.iter().all(|log| log.topics()[0] == TEST_LOG_TOPIC_0)); - // Test filtering by specific topic combination - should match only the first log + // Test filtering by specific topic combination - should match only LogEmitterA (has 2 topics) let filter = alloy_rpc_types_eth::Filter::default() .topic1(TEST_LOG_TOPIC_1) .from_block(alloy_eips::BlockNumberOrTag::Pending) @@ -714,7 +803,7 @@ async fn test_get_logs_topic_filtering() -> Result<()> { let logs = provider.get_logs(&filter).await?; assert_eq!(logs.len(), 1); - assert_eq!(logs[0].address(), setup.txn_details.counter_address); + assert_eq!(logs[0].address(), setup.txn_details.log_emitter_a_address); assert_eq!(logs[0].topics()[1], TEST_LOG_TOPIC_1); Ok(()) @@ -739,8 +828,7 @@ async fn test_get_logs_mixed_block_ranges() -> Result<()> { // Should now include pending logs (2 logs from our test setup) assert_eq!(logs.len(), 2); assert!( - logs.iter() - .all(|log| log.transaction_hash == Some(setup.txn_details.counter_increment_hash)) + logs.iter().all(|log| log.transaction_hash == Some(setup.txn_details.log_trigger_hash)) ); // Test fromBlock: latest, toBlock: pending @@ -755,8 +843,7 @@ async fn test_get_logs_mixed_block_ranges() -> Result<()> { // Should include pending logs (historical part is empty in our test setup) assert_eq!(logs.len(), 2); assert!( - logs.iter() - .all(|log| log.transaction_hash == Some(setup.txn_details.counter_increment_hash)) + logs.iter().all(|log| log.transaction_hash == Some(setup.txn_details.log_trigger_hash)) ); // Test fromBlock: earliest, toBlock: pending @@ -771,8 +858,7 @@ async fn test_get_logs_mixed_block_ranges() -> Result<()> { // Should include pending logs (historical part is empty in our test setup) assert_eq!(logs.len(), 2); assert!( - logs.iter() - .all(|log| log.transaction_hash == Some(setup.txn_details.counter_increment_hash)) + logs.iter().all(|log| log.transaction_hash == Some(setup.txn_details.log_trigger_hash)) ); Ok(()) @@ -864,7 +950,7 @@ async fn test_eth_subscribe_multiple_flashblocks() -> eyre::Result<()> { let block2 = ¬if2["params"]["result"]; assert_eq!(block1["number"], block2["number"]); // Same block, incremental updates - assert_eq!(block2["transactions"].as_array().unwrap().len(), 6); + assert_eq!(block2["transactions"].as_array().unwrap().len(), 10); // 1 from first + 9 from second Ok(()) } @@ -1033,7 +1119,7 @@ async fn test_eth_subscribe_new_flashblock_transactions_hashes() -> eyre::Result let notification2 = ws_stream.next().await.unwrap()?; let notif2: serde_json::Value = serde_json::from_str(notification2.to_text()?)?; let txs2 = notif2["params"]["result"].as_array().expect("expected array of tx hashes"); - assert_eq!(txs2.len(), 6); + assert_eq!(txs2.len(), 10); // 1 from first flashblock + 9 from second = 10 total assert!(txs2.iter().all(|tx| tx.is_string())); Ok(()) @@ -1087,7 +1173,7 @@ async fn test_eth_subscribe_new_flashblock_transactions_full() -> eyre::Result<( let notification2 = ws_stream.next().await.unwrap()?; let notif2: serde_json::Value = serde_json::from_str(notification2.to_text()?)?; let txs2 = notif2["params"]["result"].as_array().expect("expected array of transactions"); - assert_eq!(txs2.len(), 6); + assert_eq!(txs2.len(), 10); // 1 from first flashblock + 9 from second = 10 total assert!(txs2.iter().all(|tx| tx["hash"].is_string() && tx["blockNumber"].is_string())); Ok(())