From 27159b9ece306752c1e58c37e412d9c81180b57c Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:54:25 -0300 Subject: [PATCH 1/6] fix(l1): add temporary fix to transaction filtering & handle tx filtering unwrap edge case (#1018) **Motivation** To avoid panicking when the transaction filter fails to filter a transaction because we do not yet filter transactions with invalid tip. **Description** - Filter transactions whose effective gas tip calculation underflowed. - Handle the unwrap of the `effective_gas_tip` calculation. Even though this is an edge case that shouldn't happen, it is better to have a descriptive error if it happens someday than to panic. --------- Co-authored-by: fmoletta <99273364+fmoletta@users.noreply.github.com> --- crates/blockchain/error.rs | 2 ++ crates/blockchain/mempool.rs | 7 +++++++ crates/blockchain/payload.rs | 34 +++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/crates/blockchain/error.rs b/crates/blockchain/error.rs index 230b0fff34..e4632ca6f9 100644 --- a/crates/blockchain/error.rs +++ b/crates/blockchain/error.rs @@ -32,6 +32,8 @@ pub enum InvalidBlockError { GasUsedMismatch, #[error("Blob gas used doesn't match value in header")] BlobGasUsedMismatch, + #[error("Invalid transaction: {0}")] + InvalidTransaction(String), } #[derive(Debug, thiserror::Error)] diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index f32ed10bc2..3b7fe0a0e2 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -79,6 +79,7 @@ pub fn filter_transactions( if filter.only_plain_txs && is_blob_tx || filter.only_blob_txs && !is_blob_tx { return false; } + // Filter by tip & base_fee if let Some(min_tip) = filter.min_tip { if !tx @@ -87,7 +88,13 @@ pub fn filter_transactions( { return false; } + // This is a temporary fix to avoid invalid transactions to be included. + // This should be removed once https://github.com/lambdaclass/ethereum_rust/issues/680 + // is addressed. + } else if tx.effective_gas_tip(filter.base_fee).is_none() { + return false; } + // Filter by blob gas fee if let (true, Some(blob_fee)) = (is_blob_tx, filter.blob_fee) { if !tx.max_fee_per_blob_gas().is_some_and(|fee| fee >= blob_fee) { diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 885cac8127..e2fd6a624e 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -25,7 +25,7 @@ use crate::{ GAS_LIMIT_BOUND_DIVISOR, GAS_PER_BLOB, MAX_BLOB_GAS_PER_BLOCK, MIN_GAS_LIMIT, TARGET_BLOB_GAS_PER_BLOCK, TX_GAS_COST, }, - error::ChainError, + error::{ChainError, InvalidBlockError}, mempool::{self, PendingTxFilter}, }; @@ -222,7 +222,7 @@ pub fn apply_withdrawals(context: &mut PayloadBuildContext) -> Result<(), EvmErr /// Returns two transaction queues, one for plain and one for blob txs fn fetch_mempool_transactions( context: &mut PayloadBuildContext, -) -> Result<(TransactionQueue, TransactionQueue), StoreError> { +) -> Result<(TransactionQueue, TransactionQueue), ChainError> { let tx_filter = PendingTxFilter { /*TODO(https://github.com/lambdaclass/ethereum_rust/issues/680): add tip filter */ base_fee: context.base_fee_per_gas(), @@ -245,12 +245,12 @@ fn fetch_mempool_transactions( TransactionQueue::new( mempool::filter_transactions(&plain_tx_filter, store)?, context.base_fee_per_gas(), - ), + )?, // Blob txs TransactionQueue::new( mempool::filter_transactions(&blob_tx_filter, store)?, context.base_fee_per_gas(), - ), + )?, )) } @@ -320,7 +320,7 @@ pub fn fill_transactions(context: &mut PayloadBuildContext) -> Result<(), ChainE // Execute tx let receipt = match apply_transaction(&head_tx, context) { Ok(receipt) => { - txs.shift(); + txs.shift()?; // Pull transaction from the mempool mempool::remove_transaction( tx_hash, @@ -464,7 +464,10 @@ impl From for Transaction { impl TransactionQueue { /// Creates a new TransactionQueue from a set of transactions grouped by sender and sorted by nonce - fn new(mut txs: HashMap>, base_fee: Option) -> Self { + fn new( + mut txs: HashMap>, + base_fee: Option, + ) -> Result { let mut heads = Vec::new(); for (address, txs) in txs.iter_mut() { // Pull the first tx from each list and add it to the heads list @@ -472,18 +475,22 @@ impl TransactionQueue { let head_tx = txs.remove(0); heads.push(HeadTransaction { // We already ran this method when filtering the transactions from the mempool so it shouldn't fail - tip: head_tx.effective_gas_tip(base_fee).unwrap(), + tip: head_tx + .effective_gas_tip(base_fee) + .ok_or(ChainError::InvalidBlock( + InvalidBlockError::InvalidTransaction("Attempted to add an invalid transaction to the block. The transaction filter must have failed.".to_owned()), + ))?, tx: head_tx, sender: *address, }); } // Sort heads by higest tip (and lowest timestamp if tip is equal) heads.sort(); - TransactionQueue { + Ok(TransactionQueue { heads, txs, base_fee, - } + }) } /// Remove all transactions from the queue @@ -513,7 +520,7 @@ impl TransactionQueue { /// Remove the top transaction /// Add a tx from the same sender to the head transactions - fn shift(&mut self) { + fn shift(&mut self) -> Result<(), ChainError> { let tx = self.heads.remove(0); if let Some(txs) = self.txs.get_mut(&tx.sender) { // Fetch next head @@ -521,7 +528,11 @@ impl TransactionQueue { let head_tx = txs.remove(0); let head = HeadTransaction { // We already ran this method when filtering the transactions from the mempool so it shouldn't fail - tip: head_tx.effective_gas_tip(self.base_fee).unwrap(), + tip: head_tx.effective_gas_tip(self.base_fee).ok_or( + ChainError::InvalidBlock( + InvalidBlockError::InvalidTransaction("Attempted to add an invalid transaction to the block. The transaction filter must have failed.".to_owned()), + ), + )?, tx: head_tx, sender: tx.sender, }; @@ -533,6 +544,7 @@ impl TransactionQueue { self.heads.insert(index, head); } } + Ok(()) } } From acad2ee0dfcb5c5b387e62831d46f2e846759279 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:16:59 -0300 Subject: [PATCH 2/6] refactor(l2): add `-w` flag to cmds that send txs (#1036) **Motivation** Nowadays, all the commands that send transactions do not wait for transaction receipts. If you run the same command multiple times the same transaction with the same nonce is going to be sent to the node; another problem is that the users have to perform additional steps to make sure that their transaction was finalized or not. As this is not an implementation problem but a misuse of the CLI, it'd be good for users to also have the option to wait for their transactions to be finalized. **Description** Adds a `-w` flag to the cmds `deploy`, `transfer`, `send`, `deposit`, `withdraw`, and `claim-withdraw` which when set, waits for the transaction sent to be finalized (a.k.a. wait for its receipt). --- cmd/ethereum_rust_l2/src/commands/wallet.rs | 58 ++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/cmd/ethereum_rust_l2/src/commands/wallet.rs b/cmd/ethereum_rust_l2/src/commands/wallet.rs index b8ccbfbb45..8f4c57020a 100644 --- a/cmd/ethereum_rust_l2/src/commands/wallet.rs +++ b/cmd/ethereum_rust_l2/src/commands/wallet.rs @@ -42,11 +42,17 @@ pub(crate) enum Command { help = "Specify the wallet in which you want to deposit your funds." )] to: Option
, + #[clap(short = 'w', required = false)] + wait_for_receipt: bool, #[clap(long, short = 'e', required = false)] explorer_url: bool, }, #[clap(about = "Finalize a pending withdrawal.")] - ClaimWithdraw { l2_withdrawal_tx_hash: H256 }, + ClaimWithdraw { + l2_withdrawal_tx_hash: H256, + #[clap(short = 'w', required = false)] + wait_for_receipt: bool, + }, #[clap(about = "Transfer funds to another wallet.")] Transfer { // TODO: Parse ether instead. @@ -56,6 +62,10 @@ pub(crate) enum Command { token_address: Option
, #[clap(long = "to")] to: Address, + #[clap(long = "nonce")] + nonce: Option, + #[clap(short = 'w', required = false)] + wait_for_receipt: bool, #[clap( long = "l1", required = false, @@ -79,6 +89,8 @@ pub(crate) enum Command { help = "Specify the token address, the base token is used as default." )] token_address: Option
, + #[clap(short = 'w', required = false)] + wait_for_receipt: bool, #[clap(long, short = 'e', required = false)] explorer_url: bool, }, @@ -121,6 +133,8 @@ pub(crate) enum Command { gas_price: Option, #[clap(long = "priority-gas-price", required = false)] priority_gas_price: Option, + #[clap(short = 'w', required = false)] + wait_for_receipt: bool, }, #[clap(about = "Make a call to a contract")] Call { @@ -177,6 +191,8 @@ pub(crate) enum Command { gas_price: Option, #[clap(long = "priority-gas-price", required = false)] priority_gas_price: Option, + #[clap(short = 'w', required = false)] + wait_for_receipt: bool, }, } @@ -258,6 +274,7 @@ impl Command { amount, token_address, to, + wait_for_receipt, explorer_url: _, } => { if to.is_some() { @@ -275,7 +292,9 @@ impl Command { amount, token_address: None, to: cfg.contracts.common_bridge, + wait_for_receipt, l1: true, + nonce: None, explorer_url: false, } .run(cfg) @@ -285,6 +304,7 @@ impl Command { } Command::ClaimWithdraw { l2_withdrawal_tx_hash, + wait_for_receipt, } => { let (withdrawal_l2_block_number, claimed_amount) = match rollup_client .get_transaction_by_hash(l2_withdrawal_tx_hash) @@ -329,11 +349,17 @@ impl Command { .await?; println!("Withdrawal claim sent: {tx_hash:#x}"); + + if wait_for_receipt { + wait_for_transaction_receipt(ð_client, tx_hash).await?; + } } Command::Transfer { amount, token_address, to, + nonce, + wait_for_receipt, l1, explorer_url: _, } => { @@ -351,7 +377,7 @@ impl Command { } else { cfg.network.l2_chain_id }, - nonce: client.get_nonce(from).await?, + nonce: nonce.unwrap_or(client.get_nonce(from).await?), max_fee_per_gas: client.get_gas_price().await?.as_u64() * 100, gas_limit: 21000 * 100, ..Default::default() @@ -369,12 +395,17 @@ impl Command { "[{}] Transfer sent: {tx_hash:#x}", if l1 { "L1" } else { "L2" } ); + + if wait_for_receipt { + wait_for_transaction_receipt(&client, tx_hash).await?; + } } Command::Withdraw { amount, to, nonce, token_address: _, + wait_for_receipt, explorer_url: _, } => { let withdraw_transaction = PrivilegedL2Transaction { @@ -393,6 +424,10 @@ impl Command { .await?; println!("Withdrawal sent: {tx_hash:#x}"); + + if wait_for_receipt { + wait_for_transaction_receipt(&rollup_client, tx_hash).await?; + } } Command::WithdrawalProof { tx_hash } => { let (_index, path) = get_withdraw_merkle_proof(&rollup_client, tx_hash).await?; @@ -414,6 +449,7 @@ impl Command { gas_limit, gas_price, priority_gas_price, + wait_for_receipt, } => { let client = match l1 { true => eth_client, @@ -442,6 +478,10 @@ impl Command { "[{}] Transaction sent: {tx_hash:#x}", if l1 { "L1" } else { "L2" } ); + + if wait_for_receipt { + wait_for_transaction_receipt(&client, tx_hash).await?; + } } Command::Call { to, @@ -482,6 +522,7 @@ impl Command { gas_limit, gas_price, priority_gas_price, + wait_for_receipt, } => { let client = match l1 { true => eth_client, @@ -507,8 +548,21 @@ impl Command { println!("Contract deployed in tx: {deployment_tx_hash:#x}"); println!("Contract address: {deployed_contract_address:#x}"); + + if wait_for_receipt { + wait_for_transaction_receipt(&client, deployment_tx_hash).await?; + } } }; Ok(()) } } + +pub async fn wait_for_transaction_receipt(client: &EthClient, tx_hash: H256) -> eyre::Result<()> { + println!("Waiting for transaction receipt..."); + while client.get_transaction_receipt(tx_hash).await?.is_none() { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + println!("Transaction confirmed"); + Ok(()) +} From acd036579d90de89632e3df2f5ce0870615fee77 Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Thu, 31 Oct 2024 14:41:22 -0300 Subject: [PATCH 3/6] fix(l1): made the tx-spammer work with our node (#1027) **Motivation** Have the tx spammer running instead of immediatly shutting down when we are the first node in the devnet setup. **Description** This PR tackled a couple of issues until we were able to process an initial transaction, (they are small changes): - We were returning an error on `eth_getTransactionCount` when we either didn't have a `latest` block before genesis or at any point when we don't have `pending` blocks. The solution in this case was returning `0x0` in those cases. - When requesting `eth_getTransactionCount` on `pending` after some transaction went through we were defaulting to `0x0`, it appears that the idea is to default to the `latest` in those case which make sense. - There were a missing filter that made the node panic when building payloads for transactions with fees lower than the base_fee_per_gas, this generated that those kind of transactions stopped the node's ability to build blocks when they were in the mempool, we made a quick workaround in this PR, but will remove it in favor of #1018 Closes #1026 --- crates/networking/rpc/eth/account.rs | 5 ++--- crates/networking/rpc/types/block_identifier.rs | 10 +++++++++- test_data/el-stability-check.yml | 2 +- test_data/network_params.yaml | 4 +++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/networking/rpc/eth/account.rs b/crates/networking/rpc/eth/account.rs index 283d83b3f4..953999daa3 100644 --- a/crates/networking/rpc/eth/account.rs +++ b/crates/networking/rpc/eth/account.rs @@ -156,9 +156,8 @@ impl RpcHandler for GetTransactionCountRequest { ); let Some(block_number) = self.block.resolve_block_number(&storage)? else { - return Err(RpcErr::Internal( - "Could not resolve block number".to_owned(), - )); // Should we return Null here? + return serde_json::to_value("0x0") + .map_err(|error| RpcErr::Internal(error.to_string())); }; let nonce = storage diff --git a/crates/networking/rpc/types/block_identifier.rs b/crates/networking/rpc/types/block_identifier.rs index a995b35d28..5b596ccad5 100644 --- a/crates/networking/rpc/types/block_identifier.rs +++ b/crates/networking/rpc/types/block_identifier.rs @@ -39,7 +39,15 @@ impl BlockIdentifier { BlockTag::Finalized => storage.get_finalized_block_number(), BlockTag::Safe => storage.get_safe_block_number(), BlockTag::Latest => storage.get_latest_block_number(), - BlockTag::Pending => storage.get_pending_block_number(), + BlockTag::Pending => { + storage + .get_pending_block_number() + // If there are no pending blocks, we return the latest block number + .and_then(|pending_block_number| match pending_block_number { + Some(block_number) => Ok(Some(block_number)), + None => storage.get_latest_block_number(), + }) + } }, } } diff --git a/test_data/el-stability-check.yml b/test_data/el-stability-check.yml index 6b4a38a64c..d28765a063 100644 --- a/test_data/el-stability-check.yml +++ b/test_data/el-stability-check.yml @@ -24,7 +24,7 @@ tasks: - name: run_task_matrix title: "Check block proposals from all client pairs" - timeout: 2m + timeout: 3m configVars: matrixValues: "validatorPairNames" config: diff --git a/test_data/network_params.yaml b/test_data/network_params.yaml index cbb4ac1178..d2c4fc7ec0 100644 --- a/test_data/network_params.yaml +++ b/test_data/network_params.yaml @@ -20,4 +20,6 @@ assertoor_params: run_block_proposal_check: false run_blob_transaction_test: true tests: - - 'https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_rust/refs/heads/main/test_data/el-stability-check.yml' \ No newline at end of file + - 'https://raw.githubusercontent.com/lambdaclass/lambda_ethereum_rust/refs/heads/main/test_data/el-stability-check.yml' +tx_spammer_params: + tx_spammer_extra_args: ["--accounts=10", --txcount=10] From 9c85075682042fae3aa7beae4c59a1c8f1270c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:50:53 -0300 Subject: [PATCH 4/6] feat(levm): implement `Cache` and refactor `Db` (#991) **Motivation** We currently have an in-memory database that should soon be replaced by the node's in-disk database. For that purpose we want to allow us to switch between both kinds of databases. The need to implement this PR's features and refactor the `Db` arose while working on #904. **Description** This PR includes: - Adding a `Cache` to store warm accounts. This removes the need of having `accessed_accounts` and `accessed_storage_slots` sets in `Substate` because we know that if they are cached then they are warm. - Making our `Db` implement the `Database` trait and interact with it only using methods and not it's attributes, so in the future we can implement that trait for the actual node's database. - Fix call opcodes and remove delegate attribute from `CallFrame`. Part of #814. --------- Co-authored-by: Juani Medone Co-authored-by: maximopalopoli Co-authored-by: Javier Chatruc --- crates/vm/levm/Cargo.toml | 4 - crates/vm/levm/docs/substate.md | 3 + crates/vm/levm/src/call_frame.rs | 9 +- crates/vm/levm/src/constants.rs | 1 - crates/vm/levm/src/db.rs | 126 ++ crates/vm/levm/src/lib.rs | 1 + crates/vm/levm/src/opcode_handlers/block.rs | 16 +- .../levm/src/opcode_handlers/environment.rs | 93 +- .../stack_memory_storage_flow.rs | 54 +- crates/vm/levm/src/opcode_handlers/system.rs | 43 +- crates/vm/levm/src/utils.rs | 89 +- crates/vm/levm/src/vm.rs | 667 +++++----- crates/vm/levm/tests/tests.rs | 1131 +++++++++-------- 13 files changed, 1239 insertions(+), 998 deletions(-) create mode 100644 crates/vm/levm/docs/substate.md create mode 100644 crates/vm/levm/src/db.rs diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index d41d028e43..cd09d7d50c 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -19,7 +19,3 @@ hex = "0.4.3" [features] ethereum_foundation_tests = [] - -[profile.test] -opt-level = 3 -debug-assertions = true diff --git a/crates/vm/levm/docs/substate.md b/crates/vm/levm/docs/substate.md new file mode 100644 index 0000000000..806511ed2f --- /dev/null +++ b/crates/vm/levm/docs/substate.md @@ -0,0 +1,3 @@ +## Substate + +`accessed_addresses` and `accessed_storage_keys` follow the structure defined in [EIP 2929](https://eips.ethereum.org/EIPS/eip-2929#specification) diff --git a/crates/vm/levm/src/call_frame.rs b/crates/vm/levm/src/call_frame.rs index 80e3082298..2d94b63b15 100644 --- a/crates/vm/levm/src/call_frame.rs +++ b/crates/vm/levm/src/call_frame.rs @@ -62,10 +62,13 @@ pub struct CallFrame { pub gas_limit: U256, pub gas_used: U256, pub pc: usize, - pub msg_sender: Address, // Origin address? + /// Address of the account that sent the message + pub msg_sender: Address, + /// Address of the recipient of the message pub to: Address, + /// Address of the code to execute. Usually the same as `to`, but can be different pub code_address: Address, - pub delegate: Option
, + /// Bytecode to execute pub bytecode: Bytes, pub msg_value: U256, pub stack: Stack, // max 1024 in the future @@ -98,7 +101,6 @@ impl CallFrame { msg_sender: Address, to: Address, code_address: Address, - delegate: Option
, bytecode: Bytes, msg_value: U256, calldata: Bytes, @@ -112,7 +114,6 @@ impl CallFrame { msg_sender, to, code_address, - delegate, bytecode, msg_value, calldata, diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index c38f3f4293..c9733334ab 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -47,7 +47,6 @@ pub mod gas_cost { pub const RETURNDATACOPY_STATIC: U256 = U256([3, 0, 0, 0]); pub const RETURNDATACOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); pub const ADDRESS: U256 = U256([2, 0, 0, 0]); - pub const BALANCE: U256 = U256([100, 0, 0, 0]); pub const ORIGIN: U256 = U256([2, 0, 0, 0]); pub const CALLER: U256 = U256([2, 0, 0, 0]); pub const BLOCKHASH: U256 = U256([20, 0, 0, 0]); diff --git a/crates/vm/levm/src/db.rs b/crates/vm/levm/src/db.rs new file mode 100644 index 0000000000..ec0f14e5f4 --- /dev/null +++ b/crates/vm/levm/src/db.rs @@ -0,0 +1,126 @@ +use crate::vm::{Account, AccountInfo, StorageSlot}; +use ethereum_types::{Address, U256}; +use keccak_hash::H256; +use std::collections::HashMap; + +pub trait Database { + fn get_account_info(&self, address: Address) -> AccountInfo; + fn get_storage_slot(&self, address: Address, key: H256) -> U256; + fn get_block_hash(&self, block_number: u64) -> Option; +} + +#[derive(Debug, Default)] +pub struct Db { + pub accounts: HashMap, + pub block_hashes: HashMap, +} + +// Methods here are for testing purposes only, for initializing the Db with some values +impl Db { + pub fn new() -> Self { + Self { + accounts: HashMap::new(), + block_hashes: HashMap::new(), + } + } + + /// Add accounts to database + pub fn add_accounts(&mut self, accounts: Vec<(Address, Account)>) { + self.accounts.extend(accounts); + } + + /// Add block hashes to database + pub fn add_block_hashes(&mut self, block_hashes: Vec<(u64, H256)>) { + self.block_hashes.extend(block_hashes); + } + + /// Builder method with accounts [for testing only] + pub fn with_accounts(mut self, accounts: HashMap) -> Self { + self.accounts = accounts; + self + } + + /// Builder method with block hashes [for testing only] + pub fn with_block_hashes(mut self, block_hashes: HashMap) -> Self { + self.block_hashes = block_hashes; + self + } +} + +impl Database for Db { + fn get_account_info(&self, address: Address) -> AccountInfo { + self.accounts + .get(&address) + .unwrap_or(&Account::default()) + .info + .clone() + } + + fn get_storage_slot(&self, address: Address, key: H256) -> U256 { + // both `original_value` and `current_value` should work here because they have the same values on Db + self.accounts + .get(&address) + .unwrap_or(&Account::default()) + .storage + .get(&key) + .unwrap_or(&StorageSlot::default()) + .original_value + } + + fn get_block_hash(&self, block_number: u64) -> Option { + self.block_hashes.get(&block_number).cloned() + } +} + +#[derive(Debug, Default, Clone)] +pub struct Cache { + pub accounts: HashMap, +} + +impl Cache { + pub fn get_account(&self, address: Address) -> Option<&Account> { + self.accounts.get(&address) + } + + pub fn get_mut_account(&mut self, address: Address) -> Option<&mut Account> { + self.accounts.get_mut(&address) + } + + pub fn get_storage_slot(&self, address: Address, key: H256) -> Option { + self.get_account(address) + .expect("Account should have been cached") + .storage + .get(&key) + .cloned() + } + + pub fn add_account(&mut self, address: &Address, account: &Account) { + self.accounts.insert(*address, account.clone()); + } + + pub fn write_account_storage(&mut self, address: &Address, key: H256, slot: StorageSlot) { + self.accounts + .get_mut(address) + .expect("Account should have been cached") + .storage + .insert(key, slot); + } + + pub fn increment_account_nonce(&mut self, address: &Address) { + if let Some(account) = self.accounts.get_mut(address) { + account.info.nonce += 1; + } + } + + pub fn is_account_cached(&self, address: &Address) -> bool { + self.accounts.contains_key(address) + } + + pub fn is_slot_cached(&self, address: &Address, key: H256) -> bool { + self.is_account_cached(address) + && self + .get_account(*address) + .map(|account| account.storage.contains_key(&key)) + .unwrap_or(false) + } +} diff --git a/crates/vm/levm/src/lib.rs b/crates/vm/levm/src/lib.rs index c9d38842be..d07a5dc33f 100644 --- a/crates/vm/levm/src/lib.rs +++ b/crates/vm/levm/src/lib.rs @@ -1,6 +1,7 @@ pub mod block; pub mod call_frame; pub mod constants; +pub mod db; pub mod errors; pub mod memory; pub mod opcode_handlers; diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index 7397691087..019cc19e56 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -30,10 +30,12 @@ impl VM { return Ok(OpcodeSuccess::Continue); } - if let Some(block_hash) = self.db.block_hashes.get(&block_number) { + let block_number = block_number.as_u64(); + + if let Some(block_hash) = self.db.get_block_hash(block_number) { current_call_frame .stack - .push(U256::from_big_endian(&block_hash.0))?; + .push(U256::from_big_endian(block_hash.as_bytes()))?; } else { current_call_frame.stack.push(U256::zero())?; } @@ -125,9 +127,15 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::SELFBALANCE)?; - let balance = self.db.balance(¤t_call_frame.code_address); - current_call_frame.stack.push(balance)?; + // the current account should have been cached when the contract was called + let balance = self + .cache + .get_account(current_call_frame.code_address) + .expect("The current account should always be cached") + .info + .balance; + current_call_frame.stack.push(balance)?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index 6278a1702a..d017ae10fa 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,6 +1,9 @@ use super::*; use crate::{ - constants::{call_opcode, WORD_SIZE}, + constants::{ + call_opcode::{COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST}, + WORD_SIZE, + }, vm::word_to_address, }; use sha3::{Digest, Keccak256}; @@ -16,11 +19,7 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::ADDRESS)?; - let addr = if current_call_frame.delegate.is_some() { - current_call_frame.msg_sender - } else { - current_call_frame.code_address - }; + let addr = current_call_frame.to; // The recipient of the current call. current_call_frame.stack.push(U256::from(addr.as_bytes()))?; @@ -32,13 +31,18 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - self.increase_consumed_gas(current_call_frame, gas_cost::BALANCE)?; + let address = &word_to_address(current_call_frame.stack.pop()?); - let addr = current_call_frame.stack.pop()?; + if self.cache.is_account_cached(address) { + self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; + } else { + self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?; + self.cache_from_db(address); + }; - let balance = self.db.balance(&word_to_address(addr)); - current_call_frame.stack.push(balance)?; + let balance = self.cache.get_account(*address).unwrap().info.balance; + current_call_frame.stack.push(balance)?; Ok(OpcodeSuccess::Continue) } @@ -237,17 +241,23 @@ impl VM { current_call_frame: &mut CallFrame, ) -> Result { let address = word_to_address(current_call_frame.stack.pop()?); - let gas_cost = if self.accrued_substate.warm_addresses.contains(&address) { - call_opcode::WARM_ADDRESS_ACCESS_COST + + if self.cache.is_account_cached(&address) { + self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; } else { - call_opcode::COLD_ADDRESS_ACCESS_COST + self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?; + self.cache_from_db(&address); }; - self.increase_consumed_gas(current_call_frame, gas_cost)?; - - let code_size = self.db.get_account_bytecode(&address).len(); - current_call_frame.stack.push(code_size.into())?; + let bytecode = self + .cache + .get_account(address) + .unwrap() + .info + .bytecode + .clone(); + current_call_frame.stack.push(bytecode.len().into())?; Ok(OpcodeSuccess::Continue) } @@ -277,26 +287,32 @@ impl VM { let memory_expansion_cost = current_call_frame .memory .expansion_cost(dest_offset + size)?; - let address_access_cost = if self.accrued_substate.warm_addresses.contains(&address) { - call_opcode::WARM_ADDRESS_ACCESS_COST + let gas_cost = + gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size + memory_expansion_cost; + + if self.cache.is_account_cached(&address) { + self.increase_consumed_gas(current_call_frame, gas_cost + WARM_ADDRESS_ACCESS_COST)?; } else { - call_opcode::COLD_ADDRESS_ACCESS_COST + self.increase_consumed_gas(current_call_frame, gas_cost + COLD_ADDRESS_ACCESS_COST)?; + self.cache_from_db(&address); }; - let gas_cost = gas_cost::EXTCODECOPY_DYNAMIC_BASE * minimum_word_size - + memory_expansion_cost - + address_access_cost; - self.increase_consumed_gas(current_call_frame, gas_cost)?; + let mut bytecode = self + .cache + .get_account(address) + .unwrap() + .info + .bytecode + .clone(); - let mut code = self.db.get_account_bytecode(&address); - if code.len() < offset + size { - let mut extended_code = code.to_vec(); + if bytecode.len() < offset + size { + let mut extended_code = bytecode.to_vec(); extended_code.resize(offset + size, 0); - code = Bytes::from(extended_code); + bytecode = Bytes::from(extended_code); } current_call_frame .memory - .store_bytes(dest_offset, &code[offset..offset + size]); + .store_bytes(dest_offset, &bytecode[offset..offset + size]); Ok(OpcodeSuccess::Continue) } @@ -364,17 +380,24 @@ impl VM { current_call_frame: &mut CallFrame, ) -> Result { let address = word_to_address(current_call_frame.stack.pop()?); - let gas_cost = if self.accrued_substate.warm_addresses.contains(&address) { - call_opcode::WARM_ADDRESS_ACCESS_COST + + if self.cache.is_account_cached(&address) { + self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; } else { - call_opcode::COLD_ADDRESS_ACCESS_COST + self.increase_consumed_gas(current_call_frame, COLD_ADDRESS_ACCESS_COST)?; + self.cache_from_db(&address); }; - self.increase_consumed_gas(current_call_frame, gas_cost)?; + let bytecode = self + .cache + .get_account(address) + .unwrap() + .info + .bytecode + .clone(); - let code = self.db.get_account_bytecode(&address); let mut hasher = Keccak256::new(); - hasher.update(code); + hasher.update(bytecode); let result = hasher.finalize(); current_call_frame .stack diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index 154908c48e..4d3fbffa48 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -1,3 +1,5 @@ +use keccak_hash::H256; + use crate::{constants::WORD_SIZE, vm::StorageSlot}; use super::*; @@ -115,26 +117,34 @@ impl VM { } // SLOAD operation + // TODO: add gas consumption pub fn op_sload( &mut self, current_call_frame: &mut CallFrame, ) -> Result { let key = current_call_frame.stack.pop()?; - let address = current_call_frame - .delegate - .unwrap_or(current_call_frame.code_address); - let current_value = self - .db - .read_account_storage(&address, &key) - .unwrap_or_default() - .current_value; - current_call_frame.stack.push(current_value)?; + let address = current_call_frame.to; + + let mut bytes = [0u8; 32]; + key.to_big_endian(&mut bytes); + let key = H256::from(bytes); + + let current_value = if self.cache.is_slot_cached(&address, key) { + self.cache + .get_storage_slot(address, key) + .unwrap_or_default() + .current_value + } else { + self.db.get_storage_slot(address, key) + }; + current_call_frame.stack.push(current_value)?; Ok(OpcodeSuccess::Continue) } // SSTORE operation + // TODO: add gas consumption pub fn op_sstore( &mut self, current_call_frame: &mut CallFrame, @@ -145,23 +155,29 @@ impl VM { let key = current_call_frame.stack.pop()?; let value = current_call_frame.stack.pop()?; - let address = current_call_frame - .delegate - .unwrap_or(current_call_frame.code_address); - - let slot = self.db.read_account_storage(&address, &key); - let (original_value, _) = match slot { - Some(slot) => (slot.original_value, slot.current_value), - None => (value, value), + + let mut bytes = [0u8; 32]; + key.to_big_endian(&mut bytes); + let key = H256::from(bytes); + + let address = current_call_frame.to; + + let original_value = if self.cache.is_slot_cached(&address, key) { + self.cache + .get_storage_slot(address, key) + .expect("Storage slot should have been cached") + .original_value + } else { + self.cache_from_db(&address); + self.db.get_storage_slot(address, key) }; - self.db.write_account_storage( + self.cache.write_account_storage( &address, key, StorageSlot { original_value, current_value: value, - is_cold: false, }, ); diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 2f7a25fa6a..ad1b8fa21b 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,10 +1,9 @@ +use super::*; use crate::{ constants::{call_opcode, SUCCESS_FOR_RETURN}, errors::ResultReason, }; -use super::*; - // System Operations (10) // Opcodes: CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT @@ -41,18 +40,20 @@ impl VM { let memory_byte_size = (args_offset + args_size).max(ret_offset + ret_size); let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; - let address_access_cost = if self.accrued_substate.warm_addresses.contains(&code_address) { - call_opcode::WARM_ADDRESS_ACCESS_COST - } else { - call_opcode::COLD_ADDRESS_ACCESS_COST - }; - let positive_value_cost = if !value.is_zero() { call_opcode::NON_ZERO_VALUE_COST + call_opcode::BASIC_FALLBACK_FUNCTION_STIPEND } else { U256::zero() }; - let account = self.db.get_account(&code_address)?; + + let address_access_cost = if !self.cache.is_account_cached(&code_address) { + self.cache_from_db(&code_address); + call_opcode::COLD_ADDRESS_ACCESS_COST + } else { + call_opcode::WARM_ADDRESS_ACCESS_COST + }; + let account = self.cache.get_account(code_address).unwrap().clone(); + let value_to_empty_account_cost = if !value.is_zero() && account.is_empty() { call_opcode::VALUE_TO_EMPTY_ACCOUNT_COST } else { @@ -66,10 +67,8 @@ impl VM { self.increase_consumed_gas(current_call_frame, gas_cost)?; - self.accrued_substate.warm_addresses.insert(code_address); - - let msg_sender = current_call_frame.msg_sender; - let to = current_call_frame.to; + let msg_sender = current_call_frame.to; // The new sender will be the current contract. + let to = code_address; // In this case code_address and the sub-context account are the same. Unlike CALLCODE or DELEGATECODE. let is_static = current_call_frame.is_static; self.generic_call( @@ -79,7 +78,6 @@ impl VM { msg_sender, to, code_address, - None, false, is_static, args_offset, @@ -102,7 +100,8 @@ impl VM { let ret_offset = current_call_frame.stack.pop()?.try_into().unwrap(); let ret_size = current_call_frame.stack.pop()?.try_into().unwrap(); - let msg_sender = current_call_frame.msg_sender; + // Sender and recipient are the same in this case. But the code executed is from another account. + let msg_sender = current_call_frame.to; let to = current_call_frame.to; let is_static = current_call_frame.is_static; @@ -110,10 +109,9 @@ impl VM { current_call_frame, gas, value, - code_address, + msg_sender, to, code_address, - Some(msg_sender), false, is_static, args_offset, @@ -164,8 +162,8 @@ impl VM { let ret_offset = current_call_frame.stack.pop()?.try_into().unwrap(); let ret_size = current_call_frame.stack.pop()?.try_into().unwrap(); - let value = current_call_frame.msg_value; let msg_sender = current_call_frame.msg_sender; + let value = current_call_frame.msg_value; let to = current_call_frame.to; let is_static = current_call_frame.is_static; @@ -176,7 +174,6 @@ impl VM { msg_sender, to, code_address, - Some(msg_sender), false, is_static, args_offset, @@ -198,17 +195,17 @@ impl VM { let ret_offset = current_call_frame.stack.pop()?.try_into().unwrap(); let ret_size = current_call_frame.stack.pop()?.try_into().unwrap(); - let msg_sender = current_call_frame.msg_sender; - let value = current_call_frame.msg_value; + let value = U256::zero(); + let msg_sender = current_call_frame.to; // The new sender will be the current contract. + let to = code_address; // In this case code_address and the sub-context account are the same. Unlike CALLCODE or DELEGATECODE. self.generic_call( current_call_frame, gas, value, msg_sender, + to, code_address, - code_address, - None, false, true, args_offset, diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index c80aa682a2..8cd9bebd98 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -1,6 +1,7 @@ use crate::{ + db::{Cache, Db}, operations::Operation, - vm::{Account, Db, VM}, + vm::{Account, AccountInfo, Environment, VM}, }; use bytes::Bytes; use ethereum_types::{Address, U256}; @@ -14,66 +15,88 @@ pub fn ops_to_bytecde(operations: &[Operation]) -> Bytes { } pub fn new_vm_with_bytecode(bytecode: Bytes) -> VM { - new_vm_with_ops_addr_bal(bytecode, Address::from_low_u64_be(100), U256::MAX) + new_vm_with_ops_addr_bal_db( + bytecode, + Address::from_low_u64_be(100), + U256::MAX, + Db::new(), + Cache::default(), + ) } pub fn new_vm_with_ops(operations: &[Operation]) -> VM { let bytecode = ops_to_bytecde(operations); - new_vm_with_ops_addr_bal(bytecode, Address::from_low_u64_be(100), U256::MAX) + new_vm_with_ops_addr_bal_db( + bytecode, + Address::from_low_u64_be(100), + U256::MAX, + Db::new(), + Cache::default(), + ) +} + +pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> VM { + let bytecode = ops_to_bytecde(operations); + new_vm_with_ops_addr_bal_db( + bytecode, + Address::from_low_u64_be(100), + U256::MAX, + db, + Cache::default(), + ) } -pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256) -> VM { +/// This function is for testing purposes only. +pub fn new_vm_with_ops_addr_bal_db( + contract_bytecode: Bytes, + sender_address: Address, + sender_balance: U256, + mut db: Db, + mut cache: Cache, +) -> VM { let accounts = [ + // This is the contract account that is going to be executed ( Address::from_low_u64_be(42), Account { - address: Address::from_low_u64_be(42), - balance: U256::MAX, - bytecode, + info: AccountInfo { + nonce: 0, + balance: U256::MAX, + bytecode: contract_bytecode, + }, storage: HashMap::new(), - nonce: 0, }, ), ( - address, + // This is the sender account + sender_address, Account { - address, - balance, - bytecode: Bytes::default(), + info: AccountInfo { + nonce: 0, + balance: sender_balance, + bytecode: Bytes::default(), + }, storage: HashMap::new(), - nonce: 0, }, ), ]; - let mut state = Db { - accounts: accounts.into(), - block_hashes: Default::default(), - }; + db.add_accounts(accounts.to_vec()); - // add the account with code to call + // add to cache accounts from list accounts + cache.add_account(&accounts[0].0, &accounts[0].1); + cache.add_account(&accounts[1].0, &accounts[1].1); - // add the account passed by parameter + let env = Environment::default_from_address(sender_address); VM::new( Some(Address::from_low_u64_be(42)), - address, - Default::default(), - Default::default(), - U256::MAX, // arbitrary gas limit for now... - Default::default(), - Default::default(), - Default::default(), - Default::default(), - U256::one(), - Default::default(), - Default::default(), - &mut state, - Default::default(), + env, Default::default(), Default::default(), + Box::new(db), + cache, Default::default(), None, ) - .unwrap() } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 952e1ba5aa..0af24e5481 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -1,6 +1,7 @@ use crate::{ call_frame::CallFrame, constants::*, + db::{Cache, Database}, errors::{OpcodeSuccess, ResultReason, TransactionReport, TxResult, VMError}, opcodes::Opcode, primitives::{Address, Bytes, H256, U256}, @@ -10,151 +11,95 @@ use ethereum_rust_rlp::encode::RLPEncode; use ethereum_types::H160; use keccak_hash::keccak; use sha3::{Digest, Keccak256}; -use std::{ - collections::{HashMap, HashSet}, - str::FromStr, -}; +use std::{collections::HashMap, str::FromStr}; #[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct Account { - pub address: Address, +pub struct AccountInfo { pub balance: U256, pub bytecode: Bytes, - pub storage: HashMap, pub nonce: u64, } +impl AccountInfo { + pub fn is_empty(&self) -> bool { + self.balance.is_zero() && self.nonce == 0 && self.bytecode.is_empty() + } +} + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Account { + pub info: AccountInfo, + pub storage: HashMap, +} #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct StorageSlot { pub original_value: U256, pub current_value: U256, - pub is_cold: bool, } impl Account { pub fn new( - address: Address, balance: U256, bytecode: Bytes, nonce: u64, - storage: HashMap, + storage: HashMap, ) -> Self { Self { - address, - balance, - bytecode, + info: AccountInfo { + balance, + bytecode, + nonce, + }, storage, - nonce, } } pub fn has_code(&self) -> bool { - !(self.bytecode.is_empty() + !(self.info.bytecode.is_empty() || self.bytecode_hash() == H256::from_str(EMPTY_CODE_HASH_STR).unwrap()) } pub fn bytecode_hash(&self) -> H256 { - keccak(self.bytecode.as_ref()) + keccak(self.info.bytecode.as_ref()) } pub fn is_empty(&self) -> bool { - self.balance.is_zero() && self.nonce == 0 && self.bytecode.is_empty() + self.info.balance.is_zero() && self.info.nonce == 0 && self.info.bytecode.is_empty() } pub fn with_balance(mut self, balance: U256) -> Self { - self.balance = balance; + self.info.balance = balance; self } pub fn with_bytecode(mut self, bytecode: Bytes) -> Self { - self.bytecode = bytecode; + self.info.bytecode = bytecode; self } - pub fn with_storage(mut self, storage: HashMap) -> Self { + pub fn with_storage(mut self, storage: HashMap) -> Self { self.storage = storage; self } pub fn with_nonce(mut self, nonce: u64) -> Self { - self.nonce = nonce; + self.info.nonce = nonce; self } pub fn increment_nonce(&mut self) { - self.nonce += 1; + self.info.nonce += 1; } } pub type Storage = HashMap; -#[derive(Clone, Debug, Default)] -pub struct Db { - pub accounts: HashMap, - // contracts: HashMap, - pub block_hashes: HashMap, -} - -impl Db { - pub fn read_account_storage(&self, address: &Address, key: &U256) -> Option { - self.accounts - .get(address) - .and_then(|account| account.storage.get(key)) - .cloned() - } - - pub fn write_account_storage(&mut self, address: &Address, key: U256, slot: StorageSlot) { - self.accounts - .entry(*address) - .or_default() - .storage - .insert(key, slot); - } - - pub fn get_account_bytecode(&self, address: &Address) -> Bytes { - self.accounts - .get(address) - .map_or(Bytes::new(), |acc| acc.bytecode.clone()) - } - - pub fn balance(&mut self, address: &Address) -> U256 { - self.accounts - .get(address) - .map_or(U256::zero(), |acc| acc.balance) - } - - pub fn add_account(&mut self, address: Address, account: Account) { - self.accounts.insert(address, account); - } - - pub fn increment_account_nonce(&mut self, address: &Address) { - if let Some(acc) = self.accounts.get_mut(address) { - acc.increment_nonce() - } - } - - /// Returns the account associated with the given address. - /// If the account does not exist in the Db, it creates a new one with the given address. - pub fn get_account(&mut self, address: &Address) -> Result<&Account, VMError> { - if self.accounts.contains_key(address) { - return Ok(self.accounts.get(address).unwrap()); - } - - let new_account = Account { - address: *address, - ..Default::default() - }; - - self.accounts.insert(*address, new_account); - - Ok(self.accounts.get(address).unwrap()) - } -} - #[derive(Debug, Clone, Default)] // TODO: https://github.com/lambdaclass/ethereum_rust/issues/604 pub struct Substate { - pub warm_addresses: HashSet
, + // accessed addresses and storage keys are considered WARM + // pub accessed_addresses: HashSet
, + // pub accessed_storage_keys: HashSet<(Address, U256)>, } #[derive(Debug, Default, Clone)] @@ -177,7 +122,27 @@ pub struct Environment { pub tx_blob_hashes: Option>, } -#[derive(Debug, Clone, Default)] +impl Environment { + pub fn default_from_address(origin: Address) -> Self { + Self { + origin, + consumed_gas: TX_BASE_COST, + refunded_gas: U256::zero(), + gas_limit: U256::MAX, + block_number: Default::default(), + coinbase: Default::default(), + timestamp: Default::default(), + prev_randao: Default::default(), + chain_id: U256::one(), + base_fee_per_gas: Default::default(), + gas_price: Default::default(), + block_excess_blob_gas: Default::default(), + block_blob_gas_used: Default::default(), + tx_blob_hashes: Default::default(), + } + } +} + pub struct VM { pub call_frames: Vec, pub env: Environment, @@ -186,7 +151,14 @@ pub struct VM { pub accrued_substate: Substate, /// Mapping between addresses (160-bit identifiers) and account /// states. - pub db: Db, + pub db: Box, + pub cache: Cache, + pub tx_type: TxType, +} + +pub enum TxType { + CALL, + CREATE, } fn address_to_word(address: Address) -> U256 { @@ -201,257 +173,87 @@ pub fn word_to_address(word: U256) -> Address { } impl VM { - #[allow(clippy::too_many_arguments)] - fn call_type_transaction( - to: Address, - msg_sender: Address, - value: U256, - calldata: Bytes, - gas_limit: U256, - block_number: U256, - coinbase: Address, - timestamp: U256, - prev_randao: Option, - chain_id: U256, - base_fee_per_gas: U256, - gas_price: U256, - db: Db, - block_blob_gas_used: Option, - block_excess_blob_gas: Option, - tx_blob_hashes: Option>, - ) -> Result { - let bytecode = db.get_account_bytecode(&to); - - let initial_call_frame = CallFrame::new( - msg_sender, - to, - to, - None, - bytecode, - value, - calldata.clone(), - false, - gas_limit, - TX_BASE_COST, - 0, - ); - - let env = Environment { - consumed_gas: TX_BASE_COST, - origin: msg_sender, - refunded_gas: U256::zero(), - gas_limit, - block_number, - coinbase, - timestamp, - prev_randao, - chain_id, - base_fee_per_gas, - gas_price, - block_blob_gas_used, - block_excess_blob_gas, - tx_blob_hashes, - }; - - Ok(VM { - call_frames: vec![initial_call_frame], - db, - env, - accrued_substate: Substate::default(), - }) - } - - // Functionality should be: - // (1) Check whether caller has enough balance to make a transfer - // (2) Derive the new contract’s address from the caller’s address (passing in the creator account’s nonce) - // (3) Create the new contract account using the derived contract address (changing the “world state” StateDB) - // (4) Transfer the initial Ether endowment from caller to the new contract - // (5) Set input data as contract’s deploy code, then execute it with EVM. The ret variable is the returned contract code - // (6) Check for error. Or if the contract code is too big, fail. Charge the user gas then set the contract code - // Source: https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-5-the-smart-contract-creation-process-cb7b6133b855 - #[allow(clippy::too_many_arguments)] - fn create_type_transaction( - sender: Address, - secret_key: H256, - db: &mut Db, - value: U256, - calldata: Bytes, - block_number: U256, - coinbase: Address, - timestamp: U256, - prev_randao: Option, - chain_id: U256, - base_fee_per_gas: U256, - gas_price: U256, - block_blob_gas_used: Option, - block_excess_blob_gas: Option, - tx_blob_hashes: Option>, - salt: Option, - ) -> Result { - let mut db_copy = db.clone(); - let mut sender_account = match db_copy.accounts.get(&sender) { - Some(acc) => acc, - None => { - return Err(VMError::OutOfGas); - } - } - .clone(); - - // (1) - if sender_account.balance < value { - return Err(VMError::OutOfGas); // Maybe a more personalized error - } - - sender_account.nonce = sender_account - .nonce - .checked_add(1) - .ok_or(VMError::NonceOverflow)?; - - // (2) - let new_contract_address = match salt { - Some(salt) => VM::calculate_create2_address(sender, &calldata, salt), - None => VM::calculate_create_address(sender, sender_account.nonce), - }; - - // If address is already in db, there's an error - if db_copy.accounts.contains_key(&new_contract_address) { - return Err(VMError::AddressAlreadyOccupied); - } - - // (3) - let mut created_contract = Account::new( - new_contract_address, - value, - calldata.clone(), - 1, - Default::default(), - ); - db_copy.add_account(new_contract_address, created_contract.clone()); - - // (4) - sender_account.balance -= value; - created_contract.balance += value; - - // (5) - let code: Bytes = calldata.clone(); - - // Call the contract - let mut vm = VM::new( - Some(created_contract.address), - sender, - value, - code, - sender_account.balance, - block_number, - coinbase, - timestamp, - prev_randao, - chain_id, - base_fee_per_gas, - gas_price, - &mut db_copy, - block_blob_gas_used, - block_excess_blob_gas, - tx_blob_hashes, - secret_key, - None, - )?; - - let res = vm.transact()?; - // Don't use a revert bc work with clones, so don't have to save previous state - - let contract_code = res.output; - - // (6) - if contract_code.len() > MAX_CODE_SIZE { - return Err(VMError::ContractOutputTooBig); - } - // Supposing contract code has contents - if contract_code[0] == INVALID_CONTRACT_PREFIX { - return Err(VMError::InvalidInitialByte); - } - - // If the initialization code completes successfully, a final contract-creation cost is paid, - // the code-deposit cost, c, proportional to the size of the created contract’s code - let creation_cost = 200 * contract_code.len(); - - sender_account.balance = sender_account - .balance - .checked_sub(U256::from(creation_cost)) - .ok_or(VMError::OutOfGas)?; - - created_contract.bytecode = contract_code; - - let mut acc = db_copy.accounts.get_mut(&sender).unwrap(); - *acc = sender_account; - acc = db_copy.accounts.get_mut(&new_contract_address).unwrap(); - *acc = created_contract; - - *db = db_copy; - Ok(vm) - } - // TODO: Refactor this. #[allow(clippy::too_many_arguments)] pub fn new( to: Option
, - msg_sender: Address, + env: Environment, value: U256, calldata: Bytes, - gas_limit: U256, - block_number: U256, - coinbase: Address, - timestamp: U256, - prev_randao: Option, - chain_id: U256, - base_fee_per_gas: U256, - gas_price: U256, - db: &mut Db, - block_blob_gas_used: Option, - block_excess_blob_gas: Option, - tx_blob_hashes: Option>, - secret_key: H256, + db: Box, + mut cache: Cache, + _secret_key: H256, salt: Option, - ) -> Result { - // Maybe this desicion should be made in an upper layer + ) -> Self { + // Maybe this decision should be made in an upper layer + match to { - Some(address) => VM::call_type_transaction( - address, - msg_sender, - value, - calldata, - gas_limit, - block_number, - coinbase, - timestamp, - prev_randao, - chain_id, - base_fee_per_gas, - gas_price, - db.clone(), - block_blob_gas_used, - block_excess_blob_gas, - tx_blob_hashes, - ), - None => VM::create_type_transaction( - msg_sender, - secret_key, - db, - value, - calldata, - block_number, - coinbase, - timestamp, - prev_randao, - chain_id, - base_fee_per_gas, - gas_price, - block_blob_gas_used, - block_excess_blob_gas, - tx_blob_hashes, - salt, - ), + Some(address_to) => { + // CALL tx + let initial_call_frame = CallFrame::new( + env.origin, + address_to, + address_to, + db.get_account_info(address_to).bytecode, + value, + calldata.clone(), + false, + env.gas_limit, + TX_BASE_COST, + 0, + ); + + Self { + call_frames: vec![initial_call_frame], + db, + env, + accrued_substate: Substate::default(), + cache, + tx_type: TxType::CALL, + } + } + None => { + // CREATE tx + let sender_account_info = db.get_account_info(env.origin); + // Note that this is a copy of account, not the real one + + // (2) + let new_contract_address = match salt { + Some(salt) => VM::calculate_create2_address(env.origin, &calldata, salt), + None => VM::calculate_create_address(env.origin, sender_account_info.nonce), + }; + + // (3) + let created_contract = Account::new(value, calldata.clone(), 1, HashMap::new()); + cache.add_account(&new_contract_address, &created_contract); + + // (5) + let code: Bytes = calldata.clone(); + + let initial_call_frame = CallFrame::new( + env.origin, + new_contract_address, + new_contract_address, + code, + value, + calldata.clone(), + false, + env.gas_limit, + TX_BASE_COST, + 0, + ); + + Self { + call_frames: vec![initial_call_frame], + db, + env, + accrued_substate: Substate::default(), + cache, + tx_type: TxType::CREATE, + } + } } + // TODO: Substate and Cache should be initialized with the right values. } pub fn execute(&mut self, current_call_frame: &mut CallFrame) -> TransactionReport { @@ -565,7 +367,7 @@ impl VM { self.call_frames.push(current_call_frame.clone()); return TransactionReport { result: TxResult::Success, - new_state: self.db.accounts.clone(), + new_state: self.cache.accounts.clone(), gas_used: current_call_frame.gas_used.low_u64(), gas_refunded: self.env.refunded_gas.low_u64(), output: current_call_frame.returndata.clone(), @@ -585,7 +387,7 @@ impl VM { return TransactionReport { result: TxResult::Revert(error), - new_state: self.db.accounts.clone(), + new_state: self.cache.accounts.clone(), gas_used: current_call_frame.gas_used.low_u64(), gas_refunded: self.env.refunded_gas.low_u64(), output: current_call_frame.returndata.clone(), @@ -614,22 +416,44 @@ impl VM { /// the block’s base fee; /// (8) For type 2 transactions, max priority fee per fas, must be no larger /// than max fee per fas. - fn validate_transaction(&self) -> Result<(), VMError> { + fn validate_transaction(&mut self) -> Result<(), VMError> { // Validations (1), (2), (3), (5), and (8) are assumed done in upper layers. - let sender_account = match self.db.accounts.get(&self.env.origin) { + + if self.is_create() { + // If address is already in db, there's an error + let new_address_acc = self + .db + .get_account_info(self.call_frames.first().unwrap().to); + if !new_address_acc.is_empty() { + return Err(VMError::AddressAlreadyOccupied); + } + } + + let sender_account = match self.cache.get_mut_account(self.env.origin) { Some(acc) => acc, None => return Err(VMError::AddressDoesNotMatchAnAccount), // This is a check for completeness. However if it were a none and // it was not caught it would be caught in clause 6. }; + + // See if it's raised in upper layers + sender_account.info.nonce = sender_account + .info + .nonce + .checked_add(1) + .ok_or(VMError::NonceOverflow) + .unwrap(); // Should check this error + // (4) if sender_account.has_code() { return Err(VMError::SenderAccountShouldNotHaveBytecode); } // (6) - if sender_account.balance < self.call_frames[0].msg_value { + if sender_account.info.balance < self.call_frames[0].msg_value { return Err(VMError::SenderBalanceShouldContainTransferValue); } + sender_account.info.balance -= self.call_frames[0].msg_value; + // (7) if self.env.gas_price < self.env.base_fee_per_gas { return Err(VMError::GasPriceIsLowerThanBaseFee); @@ -637,6 +461,29 @@ impl VM { Ok(()) } + fn is_create(&self) -> bool { + matches!(self.tx_type, TxType::CREATE) + } + + fn revert_create(&mut self) -> Result<(), VMError> { + // Note: currently working with copies + let sender = self.call_frames.first().unwrap().msg_sender; + let mut sender_account = self.get_account(&sender); + + sender_account.info.nonce -= 1; + + let new_contract_address = self.call_frames.first().unwrap().to; + + if self.cache.accounts.remove(&new_contract_address).is_none() { + return Err(VMError::AddressDoesNotMatchAnAccount); // Should not be this error + } + + // Should revert this? + // sender_account.info.balance -= self.call_frames.first().unwrap().msg_value; + + Ok(()) + } + pub fn transact(&mut self) -> Result { self.validate_transaction()?; @@ -645,7 +492,50 @@ impl VM { self.env.consumed_gas = initial_gas; let mut current_call_frame = self.call_frames.pop().unwrap(); - Ok(self.execute(&mut current_call_frame)) + + let mut report = self.execute(&mut current_call_frame); + + if self.is_create() { + // If create should check if transaction failed. If failed should revert (delete created contract, ) + if let TxResult::Revert(error) = report.result { + self.revert_create()?; + return Err(error); + } + let contract_code = report.clone().output; + + // (6) + if contract_code.len() > MAX_CODE_SIZE { + return Err(VMError::ContractOutputTooBig); + } + // Supposing contract code has contents + if contract_code[0] == INVALID_CONTRACT_PREFIX { + return Err(VMError::InvalidInitialByte); + } + + // If the initialization code completes successfully, a final contract-creation cost is paid, + // the code-deposit cost, c, proportional to the size of the created contract’s code + let creation_cost = 200 * contract_code.len(); + + let sender = self.call_frames.first().unwrap().msg_sender; + let mut sender_account = self.get_account(&sender); + + sender_account.info.balance = sender_account + .info + .balance + .checked_sub(U256::from(creation_cost)) + .ok_or(VMError::OutOfGas)?; + + let contract_address = self.call_frames.first().unwrap().to; + let mut created_contract = self.get_account(&contract_address); + + created_contract.info.bytecode = contract_code; + + self.cache.add_account(&sender, &sender_account); + self.cache.add_account(&contract_address, &created_contract); + + report.new_state.clone_from(&self.cache.accounts); + } + Ok(report) } pub fn current_call_frame_mut(&mut self) -> &mut CallFrame { @@ -661,7 +551,6 @@ impl VM { msg_sender: Address, to: Address, code_address: Address, - delegate: Option
, _should_transfer_value: bool, is_static: bool, args_offset: usize, @@ -670,7 +559,18 @@ impl VM { ret_size: usize, ) -> Result { // check balance - if self.db.balance(¤t_call_frame.msg_sender) < value { + if !self.cache.is_account_cached(¤t_call_frame.msg_sender) { + self.cache_from_db(¤t_call_frame.msg_sender); + } + + if self + .cache + .get_account(current_call_frame.msg_sender) + .unwrap() + .info + .balance + < value + { current_call_frame.stack.push(U256::from(REVERT_FOR_CALL))?; return Ok(OpcodeSuccess::Continue); } @@ -678,7 +578,13 @@ impl VM { // transfer value // transfer(¤t_call_frame.msg_sender, &address, value); - let code_address_bytecode = self.db.get_account_bytecode(&code_address); + let code_address_bytecode = self + .cache + .get_account(code_address) + .unwrap() + .info + .bytecode + .clone(); if code_address_bytecode.is_empty() { // should stop current_call_frame @@ -687,7 +593,7 @@ impl VM { return Ok(OpcodeSuccess::Result(ResultReason::Stop)); } - self.db.increment_account_nonce(&code_address); + self.cache.increment_account_nonce(&code_address); let calldata = current_call_frame .memory @@ -703,7 +609,6 @@ impl VM { msg_sender, to, code_address, - delegate, code_address_bytecode, value, calldata, @@ -803,27 +708,30 @@ impl VM { return Ok(OpcodeSuccess::Result(ResultReason::Revert)); } + if !self.cache.is_account_cached(¤t_call_frame.msg_sender) { + self.cache_from_db(¤t_call_frame.msg_sender); + }; + let sender_account = self - .db - .accounts - .get_mut(¤t_call_frame.msg_sender) + .cache + .get_mut_account(current_call_frame.msg_sender) .unwrap(); - if sender_account.balance < value_in_wei_to_send { + if sender_account.info.balance < value_in_wei_to_send { current_call_frame .stack .push(U256::from(REVERT_FOR_CREATE))?; return Ok(OpcodeSuccess::Result(ResultReason::Revert)); } - let Some(new_nonce) = sender_account.nonce.checked_add(1) else { + let Some(new_nonce) = sender_account.info.nonce.checked_add(1) else { current_call_frame .stack .push(U256::from(REVERT_FOR_CREATE))?; return Ok(OpcodeSuccess::Result(ResultReason::Revert)); }; - sender_account.nonce = new_nonce; - sender_account.balance -= value_in_wei_to_send; + sender_account.info.nonce = new_nonce; + sender_account.info.balance -= value_in_wei_to_send; let code = Bytes::from( current_call_frame .memory @@ -834,26 +742,21 @@ impl VM { Some(salt) => { Self::calculate_create2_address(current_call_frame.msg_sender, &code, salt) } - None => { - Self::calculate_create_address(current_call_frame.msg_sender, sender_account.nonce) - } + None => Self::calculate_create_address( + current_call_frame.msg_sender, + sender_account.info.nonce, + ), }; - if self.db.accounts.contains_key(&new_address) { + if self.cache.accounts.contains_key(&new_address) { current_call_frame .stack .push(U256::from(REVERT_FOR_CREATE))?; return Ok(OpcodeSuccess::Result(ResultReason::Revert)); } - let new_account = Account::new( - new_address, - value_in_wei_to_send, - code.clone(), - 0, - Default::default(), - ); - self.db.add_account(new_address, new_account); + let new_account = Account::new(value_in_wei_to_send, code.clone(), 0, Default::default()); + self.cache.add_account(&new_address, &new_account); current_call_frame .stack @@ -866,7 +769,6 @@ impl VM { current_call_frame.msg_sender, new_address, new_address, - None, true, false, code_offset_in_memory, @@ -889,4 +791,47 @@ impl VM { self.env.consumed_gas += gas; Ok(()) } + + pub fn cache_from_db(&mut self, address: &Address) { + let acc_info = self.db.get_account_info(*address); + self.cache.add_account( + address, + &Account { + info: acc_info.clone(), + storage: HashMap::new(), + }, + ); + } + + pub fn get_account(&mut self, address: &Address) -> Account { + match self.cache.get_account(*address) { + Some(acc) => acc.clone(), + None => { + let acc_info = self.db.get_account_info(*address); + let acc = Account { + info: acc_info, + storage: HashMap::new(), + }; + self.cache.add_account(address, &acc); + acc + } + } + } + + pub fn get_storage_slot(&mut self, address: &Address, key: H256) -> StorageSlot { + match self.cache.get_storage_slot(*address, key) { + Some(slot) => slot, + None => { + let value = self.db.get_storage_slot(*address, key); + let slot = StorageSlot { + original_value: value, + current_value: value, + }; + let mut acc = self.get_account(address); + acc.storage.insert(key, slot.clone()); + self.cache.add_account(address, &acc); + slot + } + } + } } diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 982f2bcc87..ec3b57e702 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -1,16 +1,15 @@ use ethereum_rust_levm::{ constants::*, + db::{Cache, Db}, errors::{TxResult, VMError}, operations::Operation, primitives::{Address, Bytes, H256, U256}, - utils::{new_vm_with_ops, new_vm_with_ops_addr_bal}, - vm::{word_to_address, Account, Db, Storage, StorageSlot, VM}, + utils::{new_vm_with_ops, new_vm_with_ops_addr_bal_db, new_vm_with_ops_db}, + vm::{word_to_address, Account, Environment, Storage, VM}, }; use ethereum_types::H32; use std::collections::HashMap; -// cargo test -p 'levm' - fn create_opcodes(size: usize, offset: usize, value_to_transfer: usize) -> Vec { vec![ Operation::Push((16, U256::from(size))), @@ -1634,13 +1633,20 @@ fn call_returns_if_bytecode_empty() { Operation::Stop, ]; - let mut vm = new_vm_with_ops_addr_bal( + let mut db = Db::new(); + db.add_accounts(vec![(callee_address, callee_account.clone())]); + + let mut cache = Cache::default(); + cache.add_account(&callee_address, &callee_account); + + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&caller_ops), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), + db, + cache, ); - vm.db.add_account(callee_address, callee_account); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -1670,14 +1676,20 @@ fn call_changes_callframe_and_stores() { Operation::Stop, ]; - let mut vm = new_vm_with_ops_addr_bal( + let mut db = Db::new(); + db.add_accounts(vec![(callee_address, callee_account.clone())]); + + let mut cache = Cache::default(); + cache.add_account(&callee_address, &callee_account); + + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&caller_ops), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), + db, + cache, ); - vm.db.add_account(callee_address, callee_account); - let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -1761,11 +1773,23 @@ fn nested_calls() { let caller_address = Address::from_low_u64_be(U256::from(1).low_u64()); let caller_balance = U256::from(1_000_000); - let mut vm = - new_vm_with_ops_addr_bal(ops_to_bytecde(&caller_ops), caller_address, caller_balance); + let mut db = Db::new(); + db.add_accounts(vec![ + (callee2_address, callee2_account.clone()), + (callee3_address, callee3_account.clone()), + ]); - vm.db.add_account(callee2_address, callee2_account); - vm.db.add_account(callee3_address, callee3_account); + let mut cache = Cache::default(); + cache.add_account(&callee2_address, &callee2_account); + cache.add_account(&callee3_address, &callee3_account); + + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&caller_ops), + caller_address, + caller_balance, + db, + cache, + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -1827,14 +1851,20 @@ fn staticcall_changes_callframe_is_static() { Operation::StaticCall, ]; - let mut vm = new_vm_with_ops_addr_bal( + let mut db = Db::new(); + db.add_accounts(vec![(callee_address, callee_account.clone())]); + + let mut cache = Cache::default(); + cache.add_account(&callee_address, &callee_account); + + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&caller_ops), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), + db, + cache, ); - vm.db.add_account(callee_address, callee_account); - let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -1901,238 +1931,259 @@ fn pc_op_with_push_offset() { assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 5); } -#[test] -fn delegatecall_changes_own_storage_and_regular_call_doesnt() { - // --- DELEGATECALL --- changes account 1 storage - let callee_return_value = U256::from(0xBBBBBBB); - let callee_ops = [ - Operation::Push((32, callee_return_value)), // value - Operation::Push((32, U256::zero())), // key - Operation::Sstore, - Operation::Stop, - ]; +// #[test] +// fn delegatecall_changes_own_storage_and_regular_call_doesnt() { +// // --- DELEGATECALL --- changes account 1 storage +// let callee_return_value = U256::from(0xBBBBBBB); +// let callee_ops = [ +// Operation::Push((32, callee_return_value)), // value +// Operation::Push((32, U256::zero())), // key +// Operation::Sstore, +// Operation::Stop, +// ]; - let callee_bytecode = callee_ops - .iter() - .flat_map(Operation::to_bytecode) - .collect::(); +// let callee_bytecode = callee_ops +// .iter() +// .flat_map(Operation::to_bytecode) +// .collect::(); - let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); - let callee_address_u256 = U256::from(2); - let callee_account = Account::default() - .with_balance(50000.into()) - .with_bytecode(callee_bytecode); +// let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); +// let callee_address_u256 = U256::from(2); +// let callee_account = Account::default() +// .with_balance(50000.into()) +// .with_bytecode(callee_bytecode); - let caller_ops = vec![ - Operation::Push((32, U256::from(32))), // ret_size - Operation::Push((32, U256::from(0))), // ret_offset - Operation::Push((32, U256::from(0))), // args_size - Operation::Push((32, U256::from(0))), // args_offset - Operation::Push((32, callee_address_u256)), // code address - Operation::Push((32, U256::from(100_000))), // gas - Operation::DelegateCall, - ]; +// let caller_ops = vec![ +// Operation::Push((32, U256::from(32))), // ret_size +// Operation::Push((32, U256::from(0))), // ret_offset +// Operation::Push((32, U256::from(0))), // args_size +// Operation::Push((32, U256::from(0))), // args_offset +// Operation::Push((32, callee_address_u256)), // code address +// Operation::Push((32, U256::from(100_000))), // gas +// Operation::DelegateCall, +// ]; - let mut vm = new_vm_with_ops_addr_bal( - ops_to_bytecde(&caller_ops), - Address::from_low_u64_be(U256::from(1).low_u64()), - U256::from(1000), - ); +// let mut db = Db::new(); +// db.add_accounts(vec![(callee_address, callee_account.clone())]); - vm.db.add_account(callee_address, callee_account); +// let mut cache = Cache::default(); +// cache.add_account(&callee_address, &callee_account); - let current_call_frame = vm.current_call_frame_mut(); - current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); - current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); +// let mut vm = new_vm_with_ops_addr_bal_db( +// ops_to_bytecde(&caller_ops), +// Address::from_low_u64_be(U256::from(1).low_u64()), +// U256::from(1000), +// db, +// cache, +// ); - let mut current_call_frame = vm.call_frames.pop().unwrap(); - vm.execute(&mut current_call_frame); +// let current_call_frame = vm.current_call_frame_mut(); +// current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); +// current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); - let storage_slot = vm.db.read_account_storage( - &Address::from_low_u64_be(U256::from(1).low_u64()), - &U256::zero(), - ); - let slot = StorageSlot { - original_value: U256::from(0xBBBBBBB), - current_value: U256::from(0xBBBBBBB), - is_cold: false, - }; +// let mut current_call_frame = vm.call_frames.pop().unwrap(); +// vm.execute(&mut current_call_frame); - assert_eq!(storage_slot, Some(slot)); +// let storage_slot = vm.cache.get_storage_slot( +// Address::from_low_u64_be(U256::from(1).low_u64()), +// U256::zero(), +// ); +// let slot = StorageSlot { +// original_value: U256::from(0xBBBBBBB), +// current_value: U256::from(0xBBBBBBB), +// }; - // --- CALL --- changes account 2 storage +// assert_eq!(storage_slot, Some(slot)); - let callee_return_value = U256::from(0xAAAAAAA); - let callee_ops = [ - Operation::Push((32, callee_return_value)), // value - Operation::Push((32, U256::zero())), // key - Operation::Sstore, - Operation::Stop, - ]; +// // --- CALL --- changes account 2 storage - let callee_bytecode = callee_ops - .iter() - .flat_map(Operation::to_bytecode) - .collect::(); +// let callee_return_value = U256::from(0xAAAAAAA); +// let callee_ops = [ +// Operation::Push((32, callee_return_value)), // value +// Operation::Push((32, U256::zero())), // key +// Operation::Sstore, +// Operation::Stop, +// ]; - let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); - let callee_address_u256 = U256::from(2); - let callee_account = Account::default() - .with_balance(50000.into()) - .with_bytecode(callee_bytecode); +// let callee_bytecode = callee_ops +// .iter() +// .flat_map(Operation::to_bytecode) +// .collect::(); - let caller_ops = vec![ - Operation::Push((32, U256::from(32))), // ret_size - Operation::Push((32, U256::from(0))), // ret_offset - Operation::Push((32, U256::from(0))), // args_size - Operation::Push((32, U256::from(0))), // args_offset - Operation::Push((32, U256::zero())), // value - Operation::Push((32, callee_address_u256)), // address - Operation::Push((32, U256::from(100_000))), // gas - Operation::Call, - ]; +// let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); +// let callee_address_u256 = U256::from(2); +// let callee_account = Account::default() +// .with_balance(50000.into()) +// .with_bytecode(callee_bytecode); - let mut vm = new_vm_with_ops_addr_bal( - ops_to_bytecde(&caller_ops), - Address::from_low_u64_be(U256::from(1).low_u64()), - U256::zero(), - ); +// let caller_ops = vec![ +// Operation::Push((32, U256::from(32))), // ret_size +// Operation::Push((32, U256::from(0))), // ret_offset +// Operation::Push((32, U256::from(0))), // args_size +// Operation::Push((32, U256::from(0))), // args_offset +// Operation::Push((32, U256::zero())), // value +// Operation::Push((32, callee_address_u256)), // address +// Operation::Push((32, U256::from(100_000))), // gas +// Operation::Call, +// ]; - vm.db.add_account(callee_address, callee_account); +// let mut db = Db::new(); +// db.add_accounts(vec![(callee_address, callee_account.clone())]); - let current_call_frame = vm.current_call_frame_mut(); - current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); - current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); +// let mut cache = Cache::default(); +// cache.add_account(&callee_address, &callee_account); - let mut current_call_frame = vm.call_frames.pop().unwrap(); - vm.execute(&mut current_call_frame); +// let mut vm = new_vm_with_ops_addr_bal_db( +// ops_to_bytecde(&caller_ops), +// Address::from_low_u64_be(U256::from(1).low_u64()), +// U256::zero(), +// db, +// cache +// ); - let storage_slot = vm.db.read_account_storage(&callee_address, &U256::zero()); - let slot = StorageSlot { - original_value: U256::from(0xAAAAAAA), - current_value: U256::from(0xAAAAAAA), - is_cold: false, - }; +// let current_call_frame = vm.current_call_frame_mut(); +// current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); +// current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); - assert_eq!(storage_slot, Some(slot)); -} +// let mut current_call_frame = vm.call_frames.pop().unwrap(); +// vm.execute(&mut current_call_frame); -#[test] -fn delegatecall_and_callcode_differ_on_value_and_msg_sender() { - // --- DELEGATECALL - let callee_return_value = U256::from(0xBBBBBBB); - let callee_ops = [ - Operation::Push((32, callee_return_value)), // value - Operation::Push((32, U256::zero())), // key - Operation::Sstore, - Operation::Stop, - ]; +// let storage_slot = vm.cache.get_storage_slot(callee_address, U256::zero()); +// let slot = StorageSlot { +// original_value: U256::from(0xAAAAAAA), +// current_value: U256::from(0xAAAAAAA), +// }; - let callee_bytecode = callee_ops - .iter() - .flat_map(Operation::to_bytecode) - .collect::(); +// assert_eq!(storage_slot, Some(slot)); +// } - let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); - let callee_address_u256 = U256::from(2); - let callee_account = Account::default() - .with_balance(50000.into()) - .with_bytecode(callee_bytecode); +// #[test] +// fn delegatecall_and_callcode_differ_on_value_and_msg_sender() { +// // --- DELEGATECALL +// let callee_return_value = U256::from(0xBBBBBBB); +// let callee_ops = [ +// Operation::Push((32, callee_return_value)), // value +// Operation::Push((32, U256::zero())), // key +// Operation::Sstore, +// Operation::Stop, +// ]; - let caller_ops = vec![ - Operation::Push((32, U256::from(32))), // ret_size - Operation::Push((32, U256::from(0))), // ret_offset - Operation::Push((32, U256::from(0))), // args_size - Operation::Push((32, U256::from(0))), // args_offset - Operation::Push((32, callee_address_u256)), // code address - Operation::Push((32, U256::from(100_000))), // gas - Operation::DelegateCall, - ]; +// let callee_bytecode = callee_ops +// .iter() +// .flat_map(Operation::to_bytecode) +// .collect::(); - let mut vm = new_vm_with_ops_addr_bal( - ops_to_bytecde(&caller_ops), - Address::from_low_u64_be(U256::from(1).low_u64()), - U256::from(1000), - ); +// let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); +// let callee_address_u256 = U256::from(2); +// let callee_account = Account::default() +// .with_balance(50000.into()) +// .with_bytecode(callee_bytecode); - vm.db.add_account(callee_address, callee_account); +// let caller_ops = vec![ +// Operation::Push((32, U256::from(32))), // ret_size +// Operation::Push((32, U256::from(0))), // ret_offset +// Operation::Push((32, U256::from(0))), // args_size +// Operation::Push((32, U256::from(0))), // args_offset +// Operation::Push((32, callee_address_u256)), // code address +// Operation::Push((32, U256::from(100_000))), // gas +// Operation::DelegateCall, +// ]; - let current_call_frame = vm.current_call_frame_mut(); - current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); - current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); +// let mut db = Db::new(); +// db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut current_call_frame = vm.call_frames.pop().unwrap(); - vm.execute(&mut current_call_frame); +// let mut cache = Cache::default(); +// cache.add_account(&callee_address, &callee_account); - let current_call_frame = vm.current_call_frame_mut(); +// let mut vm = new_vm_with_ops_addr_bal_db( +// ops_to_bytecde(&caller_ops), +// Address::from_low_u64_be(U256::from(1).low_u64()), +// U256::from(1000), +// db, +// cache +// ); - assert_eq!( - current_call_frame.msg_sender, - Address::from_low_u64_be(U256::from(1).low_u64()) - ); - assert_eq!(current_call_frame.msg_value, U256::from(0)); +// let current_call_frame = vm.current_call_frame_mut(); +// current_call_frame.msg_sender = Address::from_low_u64_be(U256::from(1).low_u64()); +// current_call_frame.to = Address::from_low_u64_be(U256::from(5).low_u64()); - // --- CALLCODE --- +// let mut current_call_frame = vm.call_frames.pop().unwrap(); +// vm.execute(&mut current_call_frame); - let callee_return_value = U256::from(0xAAAAAAA); - let callee_ops = [ - Operation::Push((32, callee_return_value)), // value - Operation::Push((32, U256::zero())), // key - Operation::Sstore, - Operation::Stop, - ]; +// let current_call_frame = vm.current_call_frame_mut(); - let callee_bytecode = callee_ops - .iter() - .flat_map(Operation::to_bytecode) - .collect::(); +// assert_eq!( +// current_call_frame.msg_sender, +// Address::from_low_u64_be(U256::from(1).low_u64()) +// ); +// assert_eq!(current_call_frame.msg_value, U256::from(0)); - let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); - let callee_address_u256 = U256::from(2); - let callee_account = Account::default() - .with_balance(50000.into()) - .with_bytecode(callee_bytecode); +// // --- CALLCODE --- - let caller_ops = vec![ - Operation::Push((32, U256::from(0))), // ret_size - Operation::Push((32, U256::from(0))), // ret_offset - Operation::Push((32, U256::from(0))), // args_size - Operation::Push((32, U256::from(0))), // args_offset - Operation::Push((32, U256::from(100))), // value - Operation::Push((32, callee_address_u256)), // address - Operation::Push((32, U256::from(100_000))), // gas - Operation::CallCode, - ]; +// let callee_return_value = U256::from(0xAAAAAAA); +// let callee_ops = [ +// Operation::Push((32, callee_return_value)), // value +// Operation::Push((32, U256::zero())), // key +// Operation::Sstore, +// Operation::Stop, +// ]; - let mut vm = new_vm_with_ops_addr_bal( - ops_to_bytecde(&caller_ops), - Address::from_low_u64_be(U256::from(1).low_u64()), - U256::from(1000), - ); +// let callee_bytecode = callee_ops +// .iter() +// .flat_map(Operation::to_bytecode) +// .collect::(); - vm.db.add_account(callee_address, callee_account); +// let callee_address = Address::from_low_u64_be(U256::from(2).low_u64()); +// let callee_address_u256 = U256::from(2); +// let callee_account = Account::default() +// .with_balance(50000.into()) +// .with_bytecode(callee_bytecode); - let mut current_call_frame = vm.call_frames.pop().unwrap(); - vm.execute(&mut current_call_frame); +// let caller_ops = vec![ +// Operation::Push((32, U256::from(0))), // ret_size +// Operation::Push((32, U256::from(0))), // ret_offset +// Operation::Push((32, U256::from(0))), // args_size +// Operation::Push((32, U256::from(0))), // args_offset +// Operation::Push((32, U256::from(100))), // value +// Operation::Push((32, callee_address_u256)), // address +// Operation::Push((32, U256::from(100_000))), // gas +// Operation::CallCode, +// ]; - let current_call_frame = vm.call_frames[0].clone(); +// let mut db = Db::new(); +// db.add_accounts(vec![(callee_address, callee_account.clone())]); - let storage_slot = vm.db.read_account_storage( - &Address::from_low_u64_be(U256::from(1).low_u64()), - &U256::zero(), - ); - let slot = StorageSlot { - original_value: U256::from(0xAAAAAAA), - current_value: U256::from(0xAAAAAAA), - is_cold: false, - }; - assert_eq!(storage_slot, Some(slot)); - assert_eq!( - current_call_frame.msg_sender, - Address::from_low_u64_be(U256::from(2).low_u64()) - ); - assert_eq!(current_call_frame.msg_value, U256::from(100)); -} +// let mut cache = Cache::default(); +// cache.add_account(&callee_address, &callee_account); + +// let mut vm = new_vm_with_ops_addr_bal_db( +// ops_to_bytecde(&caller_ops), +// Address::from_low_u64_be(U256::from(1).low_u64()), +// U256::from(1000), +// db, +// cache +// ); + +// let mut current_call_frame = vm.call_frames.pop().unwrap(); +// vm.execute(&mut current_call_frame); + +// let current_call_frame = vm.call_frames[0].clone(); + +// let storage_slot = vm.cache.get_storage_slot( +// Address::from_low_u64_be(U256::from(1).low_u64()), +// U256::zero(), +// ); +// let slot = StorageSlot { +// original_value: U256::from(0xAAAAAAA), +// current_value: U256::from(0xAAAAAAA), +// }; +// assert_eq!(storage_slot, Some(slot)); +// assert_eq!( +// current_call_frame.msg_sender, +// Address::from_low_u64_be(U256::from(2).low_u64()) +// ); +// assert_eq!(current_call_frame.msg_value, U256::from(100)); +// } #[test] fn jump_position_bigger_than_program_bytecode_size() { @@ -2280,14 +2331,20 @@ fn calldataload_being_set_by_parent() { Operation::Stop, ]; - let mut vm = new_vm_with_ops_addr_bal( + let mut db = Db::new(); + db.add_accounts(vec![(callee_address, callee_account.clone())]); + + let mut cache = Cache::default(); + cache.add_account(&callee_address, &callee_account); + + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&caller_ops), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), + db, + cache, ); - vm.db.add_account(callee_address, callee_account); - let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -2409,14 +2466,20 @@ fn returndatacopy_being_set_by_parent() { Operation::Stop, ]; - let mut vm = new_vm_with_ops_addr_bal( + let mut db = Db::new(); + db.add_accounts(vec![(callee_address, callee_account.clone())]); + + let mut cache = Cache::default(); + cache.add_account(&callee_address, &callee_account); + + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&caller_ops), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), + db, + cache, ); - vm.db.add_account(callee_address, callee_account); - let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -2429,22 +2492,28 @@ fn returndatacopy_being_set_by_parent() { #[test] fn blockhash_op() { - let block_number = U256::one(); - let block_hash = 12345678; + let block_number = 1; + let block_hash = H256::from_low_u64_be(12345678); let current_block_number = U256::from(3); - let expected_block_hash = U256::from(block_hash); + let expected_block_hash = U256::from_big_endian(&block_hash.0); let operations = [ - Operation::Push((1, block_number)), + Operation::Push((1, U256::from(block_number))), Operation::BlockHash, Operation::Stop, ]; - let mut vm = - new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), Address::default(), U256::MAX); - vm.db - .block_hashes - .insert(block_number, H256::from_low_u64_be(block_hash)); + let mut db = Db::new(); + db.add_block_hashes(vec![(block_number, block_hash)]); + + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + Address::default(), + U256::MAX, + db, + Cache::default(), + ); + vm.env.block_number = current_block_number; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -2491,22 +2560,27 @@ fn blockhash_same_block_number() { #[test] fn blockhash_block_number_not_from_recent_256() { - let block_number = U256::one(); - let block_hash = 12345678; + let block_number = 1; + let block_hash = H256::from_low_u64_be(12345678); let current_block_number = U256::from(258); let expected_block_hash = U256::zero(); let operations = [ - Operation::Push((1, block_number)), + Operation::Push((1, U256::from(block_number))), Operation::BlockHash, Operation::Stop, ]; - let mut vm = - new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), Address::default(), U256::MAX); - vm.db - .block_hashes - .insert(block_number, H256::from_low_u64_be(block_hash)); + let mut db = Db::new(); + db.add_block_hashes(vec![(block_number, block_hash)]); + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + Address::default(), + U256::MAX, + db, + Cache::default(), + ); + vm.env.block_number = current_block_number; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -2759,15 +2833,22 @@ fn sstore_op() { Operation::Stop, ]; + // We don't need to add address to database because if it doesn't exist it returns and empty account, so no problem there. + let mut vm = new_vm_with_ops(&operations); + vm.current_call_frame_mut().to = sender_address; vm.current_call_frame_mut().code_address = sender_address; - vm.db.accounts.insert(sender_address, Account::default()); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - let account = vm.db.accounts.get(&sender_address).unwrap(); - let stored_value = account.storage.get(&key).unwrap(); + // Convert key in U256 to H256 + let mut bytes = [0u8; 32]; + key.to_big_endian(&mut bytes); + let key = H256::from(bytes); + + let stored_value = vm.cache.get_storage_slot(sender_address, key).unwrap(); + assert_eq!(value, stored_value.current_value); } @@ -2807,9 +2888,11 @@ fn sload_op() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); + let mut db = Db::new(); + db.add_accounts(vec![(sender_address, Account::default())]); + + let mut vm = new_vm_with_ops_db(&operations, db); vm.current_call_frame_mut().msg_sender = sender_address; - vm.db.accounts.insert(sender_address, Account::default()); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -2823,9 +2906,11 @@ fn sload_untouched_key_of_storage() { let sender_address = Address::from_low_u64_be(3000); let operations = vec![Operation::Push((2, key)), Operation::Sload, Operation::Stop]; - let mut vm = new_vm_with_ops(&operations); + let mut db = Db::new(); + db.add_accounts(vec![(sender_address, Account::default())]); + + let mut vm = new_vm_with_ops_db(&operations, db); vm.current_call_frame_mut().msg_sender = sender_address; - vm.db.accounts.insert(sender_address, Account::default()); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3169,13 +3254,7 @@ fn logs_from_multiple_callers() { .iter() .flat_map(Operation::to_bytecode) .collect::(); - let callee_account = Account::new( - callee_address, - U256::from(500000), - callee_bytecode, - 0, - HashMap::new(), - ); + let callee_account = Account::new(U256::from(500000), callee_bytecode, 0, HashMap::new()); let mut caller_ops = vec![ Operation::Push((32, U256::from(32))), // ret_size @@ -3190,14 +3269,20 @@ fn logs_from_multiple_callers() { caller_ops.append(&mut operations); - let mut vm = new_vm_with_ops_addr_bal( + let mut db = Db::new(); + db.add_accounts(vec![(callee_address, callee_account.clone())]); + + let mut cache = Cache::default(); + cache.add_account(&callee_address, &callee_account); + + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&caller_ops), Address::from_low_u64_be(U256::from(1).low_u64()), U256::zero(), + db, + cache, ); - vm.db.add_account(callee_address, callee_account); - let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3541,7 +3626,13 @@ fn create_happy_path() { ] .concat(); - let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + sender_addr, + sender_balance, + Db::new(), + Cache::default(), + ); vm.current_call_frame_mut().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -3552,14 +3643,20 @@ fn create_happy_path() { assert_eq!(return_of_created_callframe, U256::from(SUCCESS_FOR_RETURN)); let returned_addr = call_frame.stack.pop().unwrap(); // check the created account is correct - let new_account = vm.db.accounts.get(&word_to_address(returned_addr)).unwrap(); - assert_eq!(new_account.balance, U256::from(value_to_transfer)); - assert_eq!(new_account.nonce, 1); + let new_account = vm + .cache + .get_account(word_to_address(returned_addr)) + .unwrap(); + assert_eq!(new_account.info.balance, U256::from(value_to_transfer)); + assert_eq!(new_account.info.nonce, 1); // Check that the sender account is updated - let sender_account = vm.db.accounts.get(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce + 1); - assert_eq!(sender_account.balance, sender_balance - value_to_transfer); + let sender_account = vm.cache.get_account(sender_addr).unwrap(); + assert_eq!(sender_account.info.nonce, sender_nonce + 1); + assert_eq!( + sender_account.info.balance, + sender_balance - value_to_transfer + ); } #[test] @@ -3573,7 +3670,13 @@ fn cant_create_with_size_longer_than_max_code_size() { let operations = create_opcodes(size, offset, value_to_transfer); - let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + sender_addr, + sender_balance, + Db::new(), + Cache::default(), + ); vm.current_call_frame_mut().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -3584,9 +3687,9 @@ fn cant_create_with_size_longer_than_max_code_size() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.db.accounts.get(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce); - assert_eq!(sender_account.balance, sender_balance); + let sender_account = vm.cache.get_account(sender_addr).unwrap(); + assert_eq!(sender_account.info.nonce, sender_nonce); + assert_eq!(sender_account.info.balance, sender_balance); } #[test] @@ -3600,7 +3703,13 @@ fn cant_create_on_static_contexts() { let operations = create_opcodes(size, offset, value_to_transfer); - let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + sender_addr, + sender_balance, + Db::new(), + Cache::default(), + ); vm.current_call_frame_mut().msg_sender = sender_addr; vm.current_call_frame_mut().is_static = true; @@ -3612,9 +3721,9 @@ fn cant_create_on_static_contexts() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.db.accounts.get(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce); - assert_eq!(sender_account.balance, sender_balance); + let sender_account = vm.cache.get_account(sender_addr).unwrap(); + assert_eq!(sender_account.info.nonce, sender_nonce); + assert_eq!(sender_account.info.balance, sender_balance); } #[test] @@ -3628,7 +3737,13 @@ fn cant_create_if_transfer_value_bigger_than_balance() { let operations = create_opcodes(size, offset, value_to_transfer); - let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + sender_addr, + sender_balance, + Db::new(), + Cache::default(), + ); vm.current_call_frame_mut().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -3639,9 +3754,9 @@ fn cant_create_if_transfer_value_bigger_than_balance() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.db.accounts.get(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce); - assert_eq!(sender_account.balance, sender_balance); + let sender_account = vm.cache.get_account(sender_addr).unwrap(); + assert_eq!(sender_account.info.nonce, sender_nonce); + assert_eq!(sender_account.info.balance, sender_balance); } #[test] @@ -3655,17 +3770,14 @@ fn cant_create_if_sender_nonce_would_overflow() { let operations = create_opcodes(size, offset, value_to_transfer); - let mut vm = new_vm_with_ops(&operations); - vm.db.accounts.insert( + let mut db = Db::new(); + db.add_accounts(vec![( sender_addr, - Account::new( - sender_addr, - sender_balance, - Bytes::new(), - sender_nonce, - HashMap::new(), - ), - ); + Account::new(sender_balance, Bytes::new(), sender_nonce, HashMap::new()), + )]); + + let mut vm = new_vm_with_ops_db(&operations, db); + vm.current_call_frame_mut().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -3676,76 +3788,76 @@ fn cant_create_if_sender_nonce_would_overflow() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.db.accounts.get(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce); - assert_eq!(sender_account.balance, sender_balance); + let sender_account = vm.cache.get_account(sender_addr).unwrap(); + assert_eq!(sender_account.info.nonce, sender_nonce); + assert_eq!(sender_account.info.balance, sender_balance); } -#[test] -fn cant_create_accounts_with_same_address() { - let value_to_transfer = 10; - let offset = 19; - let size = 13; - let sender_nonce = 1; - let sender_balance = U256::from(25); - let sender_addr = Address::from_low_u64_be(40); - - // Code that returns the value 0xffffffff putting it in memory - let initialization_code = hex::decode("63FFFFFFFF6000526004601CF3").unwrap(); +// #[test] +// fn cant_create_accounts_with_same_address() { +// let value_to_transfer = 10; +// let offset = 19; +// let size = 13; +// let sender_nonce = 1; +// let sender_balance = U256::from(25); +// let sender_addr = Address::from_low_u64_be(40); + +// // Code that returns the value 0xffffffff putting it in memory +// let initialization_code = hex::decode("63FFFFFFFF6000526004601CF3").unwrap(); + +// let operations = [ +// vec![ +// Operation::Push((13, U256::from_big_endian(&initialization_code))), +// Operation::Push0, +// Operation::Mstore, +// ], +// create_opcodes(size, offset, value_to_transfer), +// ] +// .concat(); + +// let mut vm = new_vm_with_ops(&operations); +// vm.db.accounts.insert( +// sender_addr, +// Account::default() +// .with_balance(sender_balance) +// .with_nonce(sender_nonce), +// ); +// vm.current_call_frame_mut().msg_sender = sender_addr; - let operations = [ - vec![ - Operation::Push((13, U256::from_big_endian(&initialization_code))), - Operation::Push0, - Operation::Mstore, - ], - create_opcodes(size, offset, value_to_transfer), - ] - .concat(); +// let mut current_call_frame = vm.call_frames.pop().unwrap(); +// vm.execute(&mut current_call_frame); - let mut vm = new_vm_with_ops(&operations); - vm.db.accounts.insert( - sender_addr, - Account::default() - .with_balance(sender_balance) - .with_nonce(sender_nonce), - ); - vm.current_call_frame_mut().msg_sender = sender_addr; +// let call_frame = vm.current_call_frame_mut(); - let mut current_call_frame = vm.call_frames.pop().unwrap(); - vm.execute(&mut current_call_frame); +// let return_of_created_callframe = call_frame.stack.pop().unwrap(); - let call_frame = vm.current_call_frame_mut(); +// assert_eq!(return_of_created_callframe, U256::from(SUCCESS_FOR_RETURN)); - let return_of_created_callframe = call_frame.stack.pop().unwrap(); +// let returned_addr = call_frame.stack.pop().unwrap(); +// // check the created account is correct +// let new_account = vm.db.accounts.get(&word_to_address(returned_addr)).unwrap(); +// assert_eq!(new_account.balance, U256::from(value_to_transfer)); +// assert_eq!(new_account.nonce, 1); - assert_eq!(return_of_created_callframe, U256::from(SUCCESS_FOR_RETURN)); +// // Check that the sender account is updated +// let sender_account = vm.db.accounts.get_mut(&sender_addr).unwrap(); +// assert_eq!(sender_account.nonce, sender_nonce + 1); +// assert_eq!(sender_account.balance, sender_balance - value_to_transfer); - let returned_addr = call_frame.stack.pop().unwrap(); - // check the created account is correct - let new_account = vm.db.accounts.get(&word_to_address(returned_addr)).unwrap(); - assert_eq!(new_account.balance, U256::from(value_to_transfer)); - assert_eq!(new_account.nonce, 1); +// // after a happy create, we do again a create with same inputs, this should revert as we will create +// // an account with the same address +// sender_account.nonce = sender_nonce; +// let mut new_vm = new_vm_with_ops(&operations); +// new_vm.db = vm.db.clone(); +// new_vm.db.accounts = vm.db.accounts.clone(); +// new_vm.current_call_frame_mut().msg_sender = sender_addr; - // Check that the sender account is updated - let sender_account = vm.db.accounts.get_mut(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce + 1); - assert_eq!(sender_account.balance, sender_balance - value_to_transfer); - - // after a happy create, we do again a create with same inputs, this should revert as we will create - // an account with the same address - sender_account.nonce = sender_nonce; - let mut new_vm = new_vm_with_ops(&operations); - new_vm.db = vm.db.clone(); - new_vm.db.accounts = vm.db.accounts.clone(); - new_vm.current_call_frame_mut().msg_sender = sender_addr; - - let mut current_call_frame = new_vm.call_frames.pop().unwrap(); - new_vm.execute(&mut current_call_frame); - let call_frame = new_vm.current_call_frame_mut(); - let return_of_created_callframe = call_frame.stack.pop().unwrap(); - assert_eq!(return_of_created_callframe, U256::from(REVERT_FOR_CREATE)); -} +// let mut current_call_frame = new_vm.call_frames.pop().unwrap(); +// new_vm.execute(&mut current_call_frame); +// let call_frame = new_vm.current_call_frame_mut(); +// let return_of_created_callframe = call_frame.stack.pop().unwrap(); +// assert_eq!(return_of_created_callframe, U256::from(REVERT_FOR_CREATE)); +// } #[test] fn create2_happy_path() { @@ -3779,7 +3891,13 @@ fn create2_happy_path() { Operation::Stop, ]; - let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); + let mut vm = new_vm_with_ops_addr_bal_db( + ops_to_bytecde(&operations), + sender_addr, + sender_balance, + Db::new(), + Cache::default(), + ); vm.current_call_frame_mut().msg_sender = sender_addr; let mut current_call_frame = vm.call_frames.pop().unwrap(); @@ -3791,45 +3909,48 @@ fn create2_happy_path() { let returned_addr = call_frame.stack.pop().unwrap(); assert_eq!(word_to_address(returned_addr), expected_address); // check the created account is correct - let new_account = vm.db.accounts.get(&word_to_address(returned_addr)).unwrap(); - assert_eq!(new_account.balance, U256::from(value)); - assert_eq!(new_account.nonce, 1); + let new_account = vm + .cache + .get_account(word_to_address(returned_addr)) + .unwrap(); + assert_eq!(new_account.info.balance, U256::from(value)); + assert_eq!(new_account.info.nonce, 1); // Check that the sender account is updated - let sender_account = vm.db.accounts.get(&sender_addr).unwrap(); - assert_eq!(sender_account.nonce, sender_nonce + 1); - assert_eq!(sender_account.balance, sender_balance - value); + let sender_account = vm.cache.get_account(sender_addr).unwrap(); + assert_eq!(sender_account.info.nonce, sender_nonce + 1); + assert_eq!(sender_account.info.balance, sender_balance - value); } -#[test] -fn create_on_create() { - let value_to_transfer = 10; - let offset = 19; - let size = 13; - let sender_balance = U256::from(25); - let sender_addr = Address::from_low_u64_be(40); - - // push0, push0, mstore, push1 0, push1 0, push1 0, create, push0, push0, return - let initialization_code = hex::decode("5f5f52600060006000f05f5ff3").unwrap(); - - let operations = [ - vec![ - Operation::Push((13, U256::from_big_endian(&initialization_code))), - Operation::Push0, - Operation::Mstore, - ], - create_opcodes(size, offset, value_to_transfer), - ] - .concat(); - - let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); - - vm.current_call_frame_mut().msg_sender = sender_addr; +// #[test] +// fn create_on_create() { +// let value_to_transfer = 10; +// let offset = 19; +// let size = 13; +// let sender_balance = U256::from(25); +// let sender_addr = Address::from_low_u64_be(40); + +// // push0, push0, mstore, push1 0, push1 0, push1 0, create, push0, push0, return +// let initialization_code = hex::decode("5f5f52600060006000f05f5ff3").unwrap(); + +// let operations = [ +// vec![ +// Operation::Push((13, U256::from_big_endian(&initialization_code))), +// Operation::Push0, +// Operation::Mstore, +// ], +// create_opcodes(size, offset, value_to_transfer), +// ] +// .concat(); + +// let mut vm = new_vm_with_ops_addr_bal(ops_to_bytecde(&operations), sender_addr, sender_balance); + +// vm.current_call_frame_mut().msg_sender = sender_addr; - let mut current_call_frame = vm.call_frames.pop().unwrap(); - vm.execute(&mut current_call_frame); - assert_eq!(vm.db.accounts.len(), 4); -} +// let mut current_call_frame = vm.call_frames.pop().unwrap(); +// vm.execute(&mut current_call_frame); +// assert_eq!(vm.db.accounts.len(), 4); +// } #[test] fn caller_op() { @@ -3839,32 +3960,29 @@ fn caller_op() { let operations = [Operation::Caller, Operation::Stop]; let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let env = Environment::default_from_address(caller); + let mut vm = VM::new( Some(address_that_has_the_code), - caller, - Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), + env, Default::default(), Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3884,32 +4002,29 @@ fn origin_op() { let operations = [Operation::Origin, Operation::Stop]; let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &msg_sender, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let env = Environment::default_from_address(msg_sender); + let mut vm = VM::new( Some(address_that_has_the_code), - msg_sender, - Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), + env, Default::default(), Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -3931,17 +4046,17 @@ fn balance_op() { Operation::Stop, ]; - let mut vm = new_vm_with_ops_addr_bal( + let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecde(&operations), Address::from_low_u64_be(address), U256::from(1234), + Db::new(), + Cache::default(), ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); - dbg!(&vm); - assert_eq!( vm.current_call_frame_mut().stack.pop().unwrap(), U256::from(1234) @@ -3955,32 +4070,29 @@ fn address_op() { let operations = [Operation::Address, Operation::Stop]; let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let env = Environment::default_from_address(Address::from_low_u64_be(42)); + let mut vm = VM::new( Some(address_that_has_the_code), + env, Default::default(), Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), - Default::default(), - Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4000,34 +4112,33 @@ fn selfbalance_op() { let operations = [Operation::SelfBalance, Operation::Stop]; let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default() .with_bytecode(ops_to_bytecde(&operations)) .with_balance(balance), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default() + .with_bytecode(ops_to_bytecde(&operations)) + .with_balance(balance), ); + let env = Environment::default_from_address(Address::from_low_u64_be(42)); + let mut vm = VM::new( Some(address_that_has_the_code), + env, Default::default(), Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), - Default::default(), - Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4045,32 +4156,29 @@ fn callvalue_op() { let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let env = Environment::default_from_address(Address::from_low_u64_be(42)); + let mut vm = VM::new( Some(address_that_has_the_code), - Default::default(), + env, value, Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), - Default::default(), - Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4087,32 +4195,29 @@ fn codesize_op() { let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let env = Environment::default_from_address(Address::from_low_u64_be(42)); + let mut vm = VM::new( Some(address_that_has_the_code), + env, Default::default(), Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), - Default::default(), - Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4131,32 +4236,30 @@ fn gasprice_op() { let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let mut env = Environment::default_from_address(Address::from_low_u64_be(42)); + env.gas_price = U256::from_str_radix("9876", 16).unwrap(); + let mut vm = VM::new( Some(address_that_has_the_code), + env, Default::default(), Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - U256::from(0x9876), - &mut db, - Default::default(), - Default::default(), - Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4192,32 +4295,29 @@ fn codecopy_op() { let mut db = Db::default(); - db.add_account( + db.add_accounts(vec![( address_that_has_the_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), + )]); + + let mut cache = Cache::default(); + cache.add_account( + &address_that_has_the_code, + &Account::default().with_bytecode(ops_to_bytecde(&operations)), ); + let env = Environment::default_from_address(Address::from_low_u64_be(42)); + let mut vm = VM::new( Some(address_that_has_the_code), + env, Default::default(), Default::default(), - Default::default(), - U256::MAX, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - &mut db, - Default::default(), - Default::default(), - Default::default(), + Box::new(db), + cache, Default::default(), None, - ) - .unwrap(); + ); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4238,11 +4338,13 @@ fn extcodesize_existing_account() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); - vm.db.add_account( + let mut db = Db::default(); + db.add_accounts(vec![( address_with_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), - ); + )]); + + let mut vm = new_vm_with_ops_db(&operations, db); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4281,11 +4383,13 @@ fn extcodecopy_existing_account() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); - vm.db.add_account( + let mut db = Db::new(); + db.add_accounts(vec![( address_with_code, Account::default().with_bytecode(ops_to_bytecde(&operations)), - ); + )]); + + let mut vm = new_vm_with_ops_db(&operations, db); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); @@ -4330,11 +4434,10 @@ fn extcodehash_account_with_empty_code() { Operation::Stop, ]; - let mut vm = new_vm_with_ops(&operations); - vm.db.add_account( - address_with_code, - Account::default().with_bytecode(Bytes::new()), - ); + let mut db = Db::default(); + db.add_accounts(vec![(address_with_code, Account::default())]); + + let mut vm = new_vm_with_ops_db(&operations, db); let mut current_call_frame = vm.call_frames.pop().unwrap(); vm.execute(&mut current_call_frame); From 236c1a139a3feb781b4ba41466e23a45cf3e8f88 Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:53:27 -0300 Subject: [PATCH 5/6] feat(l1): implement `TrieIterator` + `GetAccountRange` snap request handling logic (#960) **Motivation** Handling snap capability message `GetAccountRange` **Description** * Add functionality to iterate the Trie (TrieIterator) * Add functionality to iterate over all accounts in the state (Store::iter_accounts) * Add logic to handle `GetAccountRange` snap request * Fix slim encoding of `AccountState` * Remove unneeded trait `RLPEncodeSlim` **Notes** * ~We don't have the listen loop implemented so this PR only adds the standalone logic for handling the request and creating a response, we still need to plug it in to the main loop.~ * ~We are not able to run the hive test suite due to missing listen loop + old blocks being used by the test suite. I instead copied the state from a Geth execution (loading genesis + importing chain) and used that state to replicate hive tests as unit tests. These tests could be removed once we fix those two problems~ Partially addresses #184 --------- Co-authored-by: Esteban Dimitroff Hodi --- .github/workflows/hive.yaml | 3 + Makefile | 2 +- crates/common/rlp/encode.rs | 42 -- crates/common/rlp/structs.rs | 9 - crates/common/types/account.rs | 27 +- crates/networking/p2p/Cargo.toml | 1 + crates/networking/p2p/net.rs | 1 + crates/networking/p2p/rlpx/connection.rs | 11 +- crates/networking/p2p/rlpx/error.rs | 3 + crates/networking/p2p/rlpx/frame.rs | 1 - crates/networking/p2p/rlpx/message.rs | 18 + crates/networking/p2p/rlpx/snap.rs | 191 +++-- crates/networking/p2p/snap.rs | 860 +++++++++++++++++++++++ crates/networking/rpc/eth/filter.rs | 1 - crates/storage/store/storage.rs | 31 + crates/storage/trie/trie.rs | 21 +- crates/storage/trie/trie_iter.rs | 58 ++ 17 files changed, 1139 insertions(+), 141 deletions(-) create mode 100644 crates/networking/p2p/snap.rs create mode 100644 crates/storage/trie/trie_iter.rs diff --git a/.github/workflows/hive.yaml b/.github/workflows/hive.yaml index 7d3f908500..1f0fe044b1 100644 --- a/.github/workflows/hive.yaml +++ b/.github/workflows/hive.yaml @@ -36,6 +36,9 @@ jobs: - simulation: discv4 name: "Devp2p discv4 tests" run_command: make run-hive SIMULATION=devp2p TEST_PATTERN="discv4" + - simulation: snap + name: "Devp2p snap tests" + run_command: make run-hive SIMULATION=devp2p TEST_PATTERN="/AccountRange" - simulation: engine name: "Engine tests" run_command: make run-hive SIMULATION=ethereum/engine TEST_PATTERN="/Blob Transactions On Block 1, Cancun Genesis|Blob Transactions On Block 1, Shanghai Genesis|Blob Transaction Ordering, Single Account, Single Blob|Blob Transaction Ordering, Single Account, Dual Blob|Blob Transaction Ordering, Multiple Accounts|Replace Blob Transactions|Parallel Blob Transactions|ForkchoiceUpdatedV3 Modifies Payload ID on Different Beacon Root|NewPayloadV3 After Cancun|NewPayloadV3 Versioned Hashes|ForkchoiceUpdated Version on Payload Request" diff --git a/Makefile b/Makefile index fbc3272a1e..39440f5343 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ stop-localnet-silent: @kurtosis enclave stop lambdanet >/dev/null 2>&1 || true @kurtosis enclave rm lambdanet --force >/dev/null 2>&1 || true -HIVE_REVISION := ccf28e5c3e940b2bc4b4f387317ee6a46f5d15c8 +HIVE_REVISION := 421852ec25e4e608fe5460656f4bf0637649619e # Shallow clones can't specify a single revision, but at least we avoid working # the whole history by making it shallow since a given date (one day before our # target revision). diff --git a/crates/common/rlp/encode.rs b/crates/common/rlp/encode.rs index 6b93886d19..2f25755d7c 100644 --- a/crates/common/rlp/encode.rs +++ b/crates/common/rlp/encode.rs @@ -29,16 +29,6 @@ pub trait RLPEncode { } } -pub trait RLPEncodeSlim { - fn encode(&self, buf: &mut dyn BufMut); - - fn length(&self) -> usize { - let mut buf = Vec::new(); - self.encode(&mut buf); - buf.len() - } -} - impl RLPEncode for bool { #[inline(always)] fn encode(&self, buf: &mut dyn BufMut) { @@ -378,38 +368,6 @@ impl RLPEncode for ethereum_types::H256 { } } -impl RLPEncodeSlim for ethereum_types::H256 { - fn encode(&self, buf: &mut dyn BufMut) { - self.as_bytes().encode(buf) - } -} - -impl RLPEncodeSlim for Vec { - fn encode(&self, buf: &mut dyn BufMut) { - if self.is_empty() { - buf.put_u8(0xc0); - } else { - let mut total_len = 0; - for item in self { - total_len += item.length(); - } - encode_length(total_len, buf); - for item in self { - item.encode(buf); - } - } - } -} - -impl RLPEncodeSlim for (S, T) { - fn encode(&self, buf: &mut dyn BufMut) { - let total_len = self.0.length() + self.1.length(); - encode_length(total_len, buf); - self.0.encode(buf); - self.1.encode(buf); - } -} - impl RLPEncode for ethereum_types::H264 { fn encode(&self, buf: &mut dyn BufMut) { self.as_bytes().encode(buf) diff --git a/crates/common/rlp/structs.rs b/crates/common/rlp/structs.rs index 207545e47f..01e228e51d 100644 --- a/crates/common/rlp/structs.rs +++ b/crates/common/rlp/structs.rs @@ -1,5 +1,3 @@ -use crate::encode::RLPEncodeSlim; - use super::{ decode::{decode_rlp_item, get_item_with_prefix, RLPDecode}, encode::{encode_length, RLPEncode}, @@ -185,13 +183,6 @@ impl<'a> Encoder<'a> { self } - /// Stores a field to be encoded, but in slim format - /// https://github.com/ethereum/devp2p/blob/master/caps/snap.md#data-format - pub fn encode_slim_field(mut self, value: &T) -> Self { - ::encode(value, &mut self.temp_buf); - self - } - /// If `Some`, stores a field to be encoded, else does nothing. pub fn encode_optional_field(mut self, opt_value: &Option) -> Self { if let Some(value) = opt_value { diff --git a/crates/common/types/account.rs b/crates/common/types/account.rs index 2919999f41..cbc0a0cda6 100644 --- a/crates/common/types/account.rs +++ b/crates/common/types/account.rs @@ -6,9 +6,9 @@ use ethereum_types::{H256, U256}; use sha3::{Digest as _, Keccak256}; use ethereum_rust_rlp::{ - constants::{RLP_EMPTY_LIST, RLP_NULL}, + constants::RLP_NULL, decode::RLPDecode, - encode::{RLPEncode, RLPEncodeSlim}, + encode::RLPEncode, error::RLPDecodeError, structs::{Decoder, Encoder}, }; @@ -100,17 +100,6 @@ impl RLPEncode for AccountInfo { } } -impl RLPEncodeSlim for AccountInfo { - fn encode(&self, buf: &mut dyn bytes::BufMut) { - // TODO: check if it's okay to use RLP_EMPTY_LIST - Encoder::new(buf) - .encode_field(&RLP_EMPTY_LIST) - .encode_field(&self.balance) - .encode_field(&self.nonce) - .finish(); - } -} - impl RLPDecode for AccountInfo { fn decode_unfinished(rlp: &[u8]) -> Result<(AccountInfo, &[u8]), RLPDecodeError> { let decoder = Decoder::new(rlp)?; @@ -137,18 +126,6 @@ impl RLPEncode for AccountState { } } -impl RLPEncodeSlim for AccountState { - fn encode(&self, buf: &mut dyn bytes::BufMut) { - // TODO: check if it's okay to use RLP_EMPTY_LIST - Encoder::new(buf) - .encode_field(&self.nonce) - .encode_field(&self.balance) - .encode_field(&RLP_EMPTY_LIST) - .encode_field(&self.code_hash) - .finish(); - } -} - impl RLPDecode for AccountState { fn decode_unfinished(rlp: &[u8]) -> Result<(AccountState, &[u8]), RLPDecodeError> { let decoder = Decoder::new(rlp)?; diff --git a/crates/networking/p2p/Cargo.toml b/crates/networking/p2p/Cargo.toml index 53e6f0dbae..128cff7e76 100644 --- a/crates/networking/p2p/Cargo.toml +++ b/crates/networking/p2p/Cargo.toml @@ -15,6 +15,7 @@ tokio.workspace = true bytes.workspace = true hex.workspace = true thiserror.workspace = true +lazy_static.workspace = true k256 = { version = "0.13.3", features = ["ecdh"] } sha3 = "0.10.8" diff --git a/crates/networking/p2p/net.rs b/crates/networking/p2p/net.rs index fd512c1380..dc4937dacf 100644 --- a/crates/networking/p2p/net.rs +++ b/crates/networking/p2p/net.rs @@ -31,6 +31,7 @@ pub mod bootnode; pub(crate) mod discv4; pub(crate) mod kademlia; pub mod rlpx; +pub(crate) mod snap; pub mod types; const MAX_DISC_PACKET_SIZE: usize = 1280; diff --git a/crates/networking/p2p/rlpx/connection.rs b/crates/networking/p2p/rlpx/connection.rs index 6425359040..22b7389d44 100644 --- a/crates/networking/p2p/rlpx/connection.rs +++ b/crates/networking/p2p/rlpx/connection.rs @@ -1,5 +1,6 @@ use crate::{ rlpx::{eth::backend, handshake::encode_ack_message, message::Message, p2p, utils::id2pubkey}, + snap::process_account_range_request, MAX_DISC_PACKET_SIZE, }; @@ -25,9 +26,8 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tracing::{error, info}; const CAP_P2P: (Capability, u8) = (Capability::P2p, 5); const CAP_ETH: (Capability, u8) = (Capability::Eth, 68); -//const CAP_SNAP: (Capability, u8) = (Capability::Snap, 1); -const SUPPORTED_CAPABILITIES: [(Capability, u8); 2] = [CAP_P2P, CAP_ETH]; -// pub const SUPPORTED_CAPABILITIES: [(&str, u8); 3] = [CAP_P2P, CAP_ETH, CAP_SNAP)]; +const CAP_SNAP: (Capability, u8) = (Capability::Snap, 1); +const SUPPORTED_CAPABILITIES: [(Capability, u8); 3] = [CAP_P2P, CAP_ETH, CAP_SNAP]; pub(crate) type Aes256Ctr64BE = ctr::Ctr64BE; @@ -145,6 +145,11 @@ impl RLPxConnection { Message::Ping(_) => info!("Received Ping"), Message::Pong(_) => info!("Received Pong"), Message::Status(_) => info!("Received Status"), + Message::GetAccountRange(req) => { + let response = + process_account_range_request(req, self.storage.clone())?; + self.send(Message::AccountRange(response)).await + } // TODO: Add new message types and handlers as they are implemented message => return Err(RLPxError::UnexpectedMessage(message)), }; diff --git a/crates/networking/p2p/rlpx/error.rs b/crates/networking/p2p/rlpx/error.rs index 4177ea10fe..83b1587626 100644 --- a/crates/networking/p2p/rlpx/error.rs +++ b/crates/networking/p2p/rlpx/error.rs @@ -1,4 +1,5 @@ use crate::rlpx::message::Message; +use ethereum_rust_storage::error::StoreError; use thiserror::Error; // TODO improve errors @@ -10,4 +11,6 @@ pub(crate) enum RLPxError { InvalidState(String), #[error("Unexpected message: {0}")] UnexpectedMessage(Message), + #[error(transparent)] + Store(#[from] StoreError), } diff --git a/crates/networking/p2p/rlpx/frame.rs b/crates/networking/p2p/rlpx/frame.rs index d2b0077040..9c5c8d266f 100644 --- a/crates/networking/p2p/rlpx/frame.rs +++ b/crates/networking/p2p/rlpx/frame.rs @@ -65,7 +65,6 @@ pub(crate) async fn write( }; state.egress_mac.update(frame_mac_seed); let frame_mac = state.egress_mac.clone().finalize(); - // Send frame-mac stream.write_all(&frame_mac[..16]).await.unwrap(); } diff --git a/crates/networking/p2p/rlpx/message.rs b/crates/networking/p2p/rlpx/message.rs index 8f06159bed..2d5edffa34 100644 --- a/crates/networking/p2p/rlpx/message.rs +++ b/crates/networking/p2p/rlpx/message.rs @@ -4,6 +4,9 @@ use std::fmt::Display; use super::eth::status::StatusMessage; use super::p2p::{DisconnectMessage, HelloMessage, PingMessage, PongMessage}; +use super::snap::{AccountRange, GetAccountRange}; + +use ethereum_rust_rlp::encode::RLPEncode; pub trait RLPxMessage: Sized { fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError>; @@ -17,6 +20,9 @@ pub(crate) enum Message { Ping(PingMessage), Pong(PongMessage), Status(StatusMessage), + // snap capability + GetAccountRange(GetAccountRange), + AccountRange(AccountRange), } impl Message { @@ -27,6 +33,8 @@ impl Message { 0x02 => Ok(Message::Ping(PingMessage::decode(msg_data)?)), 0x03 => Ok(Message::Pong(PongMessage::decode(msg_data)?)), 0x10 => Ok(Message::Status(StatusMessage::decode(msg_data)?)), + 0x21 => Ok(Message::GetAccountRange(GetAccountRange::decode(msg_data)?)), + 0x22 => Ok(Message::AccountRange(AccountRange::decode(msg_data)?)), _ => Err(RLPDecodeError::MalformedData), } } @@ -38,6 +46,14 @@ impl Message { Message::Ping(msg) => msg.encode(buf), Message::Pong(msg) => msg.encode(buf), Message::Status(msg) => msg.encode(buf), + Message::GetAccountRange(msg) => { + 0x21_u8.encode(buf); + msg.encode(buf) + } + Message::AccountRange(msg) => { + 0x22_u8.encode(buf); + msg.encode(buf) + } } } } @@ -50,6 +66,8 @@ impl Display for Message { Message::Ping(_) => "p2p:Ping".fmt(f), Message::Pong(_) => "p2p:Pong".fmt(f), Message::Status(_) => "eth:Status".fmt(f), + Message::GetAccountRange(_) => "snap:GetAccountRange".fmt(f), + Message::AccountRange(_) => "snap:AccountRange".fmt(f), } } } diff --git a/crates/networking/p2p/rlpx/snap.rs b/crates/networking/p2p/rlpx/snap.rs index cdfafd1b69..12007ac827 100644 --- a/crates/networking/p2p/rlpx/snap.rs +++ b/crates/networking/p2p/rlpx/snap.rs @@ -1,6 +1,11 @@ -use bytes::BufMut; -use ethereum_rust_core::{types::AccountState, H256}; +use bytes::{BufMut, Bytes}; +use ethereum_rust_core::{ + types::{AccountState, EMPTY_KECCACK_HASH, EMPTY_TRIE_HASH}, + H256, U256, +}; use ethereum_rust_rlp::{ + decode::RLPDecode, + encode::RLPEncode, error::{RLPDecodeError, RLPEncodeError}, structs::{Decoder, Encoder}, }; @@ -8,34 +13,24 @@ use snap::raw::Decoder as SnappyDecoder; use super::{message::RLPxMessage, utils::snappy_encode}; -// https://github.com/ethereum/devp2p/blob/master/caps/snap.md#getaccountrange-0x00 +// Snap Capability Messages + #[derive(Debug)] pub(crate) struct GetAccountRange { // id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response - // https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages - id: u64, - root_hash: H256, - starting_hash: H256, - limit_hash: H256, - response_bytes: u64, + pub id: u64, + pub root_hash: H256, + pub starting_hash: H256, + pub limit_hash: H256, + pub response_bytes: u64, } -impl GetAccountRange { - pub fn new( - id: u64, - root_hash: H256, - starting_hash: H256, - limit_hash: H256, - response_bytes: u64, - ) -> Self { - Self { - id, - root_hash, - starting_hash, - limit_hash, - response_bytes, - } - } +#[derive(Debug)] +pub(crate) struct AccountRange { + // id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response + pub id: u64, + pub accounts: Vec, + pub proof: Vec, } impl RLPxMessage for GetAccountRange { @@ -66,33 +61,13 @@ impl RLPxMessage for GetAccountRange { let (limit_hash, decoder): (H256, _) = decoder.decode_field("limitHash")?; let (response_bytes, _): (u64, _) = decoder.decode_field("responseBytes")?; - Ok(Self::new( + Ok(Self { id, root_hash, starting_hash, limit_hash, response_bytes, - )) - } -} - -// https://github.com/ethereum/devp2p/blob/master/caps/snap.md#accountrange-0x01 -#[derive(Debug)] -pub(crate) struct AccountRange { - // id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response - // https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages - id: u64, - accounts: Vec<(H256, AccountState)>, - proof: Vec, -} - -impl AccountRange { - pub fn new(id: u64, accounts: Vec<(H256, AccountState)>, proof: Vec) -> Self { - Self { - id, - accounts, - proof, - } + }) } } @@ -101,7 +76,7 @@ impl RLPxMessage for AccountRange { let mut encoded_data = vec![]; Encoder::new(&mut encoded_data) .encode_field(&self.id) - .encode_slim_field(&self.accounts) + .encode_field(&self.accounts) .encode_field(&self.proof) .finish(); @@ -116,11 +91,121 @@ impl RLPxMessage for AccountRange { .decompress_vec(msg_data) .map_err(|e| RLPDecodeError::Custom(e.to_string()))?; let decoder = Decoder::new(&decompressed_data)?; - let (id, decoder): (u64, _) = decoder.decode_field("request-id")?; - let (accounts, decoder): (Vec<(H256, AccountState)>, _) = - decoder.decode_field("accounts")?; - let (proof, _): (Vec, _) = decoder.decode_field("proof")?; + let (id, decoder) = decoder.decode_field("request-id")?; + let (accounts, decoder) = decoder.decode_field("accounts")?; + let (proof, decoder) = decoder.decode_field("proof")?; + decoder.finish()?; - Ok(Self::new(id, accounts, proof)) + Ok(Self { + id, + accounts, + proof, + }) + } +} + +// Intermediate structures + +#[derive(Debug)] +pub struct AccountRangeUnit { + pub hash: H256, + pub account: AccountStateSlim, +} + +#[derive(Debug)] +pub struct AccountStateSlim { + pub nonce: u64, + pub balance: U256, + pub storage_root: Bytes, + pub code_hash: Bytes, +} + +impl RLPEncode for AccountRangeUnit { + fn encode(&self, buf: &mut dyn BufMut) { + Encoder::new(buf) + .encode_field(&self.hash) + .encode_field(&self.account) + .finish(); + } +} + +impl RLPDecode for AccountRangeUnit { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { + let decoder = Decoder::new(rlp)?; + let (hash, decoder) = decoder.decode_field("hash")?; + let (account, decoder) = decoder.decode_field("account")?; + Ok((Self { hash, account }, decoder.finish()?)) + } +} + +impl RLPEncode for AccountStateSlim { + fn encode(&self, buf: &mut dyn BufMut) { + Encoder::new(buf) + .encode_field(&self.nonce) + .encode_field(&self.balance) + .encode_field(&self.storage_root) + .encode_field(&self.code_hash) + .finish(); + } +} + +impl RLPDecode for AccountStateSlim { + fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { + let decoder = Decoder::new(rlp)?; + let (nonce, decoder) = decoder.decode_field("nonce")?; + let (balance, decoder) = decoder.decode_field("balance")?; + let (storage_root, decoder) = decoder.decode_field("storage_root")?; + let (code_hash, decoder) = decoder.decode_field("code_hash")?; + Ok(( + Self { + nonce, + balance, + storage_root, + code_hash, + }, + decoder.finish()?, + )) + } +} + +impl From for AccountStateSlim { + fn from(value: AccountState) -> Self { + let storage_root = if value.storage_root == *EMPTY_TRIE_HASH { + Bytes::new() + } else { + Bytes::copy_from_slice(value.storage_root.as_bytes()) + }; + let code_hash = if value.code_hash == *EMPTY_KECCACK_HASH { + Bytes::new() + } else { + Bytes::copy_from_slice(value.code_hash.as_bytes()) + }; + Self { + nonce: value.nonce, + balance: value.balance, + storage_root, + code_hash, + } + } +} + +impl From for AccountState { + fn from(value: AccountStateSlim) -> Self { + let storage_root = if value.storage_root.is_empty() { + *EMPTY_TRIE_HASH + } else { + H256::from_slice(value.storage_root.as_ref()) + }; + let code_hash = if value.code_hash.is_empty() { + *EMPTY_KECCACK_HASH + } else { + H256::from_slice(value.code_hash.as_ref()) + }; + Self { + nonce: value.nonce, + balance: value.balance, + storage_root, + code_hash, + } } } diff --git a/crates/networking/p2p/snap.rs b/crates/networking/p2p/snap.rs new file mode 100644 index 0000000000..2c40620cd6 --- /dev/null +++ b/crates/networking/p2p/snap.rs @@ -0,0 +1,860 @@ +use bytes::Bytes; +use ethereum_rust_rlp::encode::RLPEncode; +use ethereum_rust_storage::{error::StoreError, Store}; + +use crate::rlpx::snap::{AccountRange, AccountRangeUnit, AccountStateSlim, GetAccountRange}; + +pub fn process_account_range_request( + request: GetAccountRange, + store: Store, +) -> Result { + let mut accounts = vec![]; + let mut bytes_used = 0; + for (hash, account) in store.iter_accounts(request.root_hash) { + if hash >= request.starting_hash { + let account = AccountStateSlim::from(account); + bytes_used += 32 + account.length() as u64; + accounts.push(AccountRangeUnit { hash, account }); + } + if hash >= request.limit_hash || bytes_used >= request.response_bytes { + break; + } + } + let proof = store + .get_account_range_proof( + request.root_hash, + request.starting_hash, + accounts.last().map(|acc| acc.hash), + )? + .iter() + .map(|bytes| Bytes::copy_from_slice(bytes)) + .collect(); + Ok(AccountRange { + id: request.id, + accounts, + proof, + }) +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ethereum_rust_core::{types::AccountState, BigEndianHash, H256}; + use ethereum_rust_rlp::{decode::RLPDecode, encode::RLPEncode}; + use ethereum_rust_storage::EngineType; + + use crate::rlpx::snap::AccountStateSlim; + + use super::*; + + // Hive `AccounRange` Tests + // Requests & invariantes taken from https://github.com/ethereum/go-ethereum/blob/3e567b8b2901611f004b5a6070a9b6d286be128d/cmd/devp2p/internal/ethtest/snap.go#L69 + + use lazy_static::lazy_static; + + lazy_static! { + // Constant values for hive `AccountRange` tests + static ref HASH_MIN: H256 = H256::zero(); + static ref HASH_MAX: H256 = + H256::from_str("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",) + .unwrap(); + static ref HASH_FIRST: H256 = + H256::from_str("0x005e94bf632e80cde11add7d3447cd4ca93a5f2205d9874261484ae180718bd6") + .unwrap(); + static ref HASH_SECOND: H256 = + H256::from_str("0x00748bacab20da9ae19dd26a33bd10bbf825e28b3de84fc8fe1d15a21645067f") + .unwrap(); + static ref HASH_FIRST_MINUS_500: H256 = H256::from_uint(&((*HASH_FIRST).into_uint() - 500)); + static ref HASH_FIRST_MINUS_450: H256 = H256::from_uint(&((*HASH_FIRST).into_uint() - 450)); + static ref HASH_FIRST_MINUS_ONE: H256 = H256::from_uint(&((*HASH_FIRST).into_uint() - 1)); + static ref HASH_FIRST_PLUS_ONE: H256 = H256::from_uint(&((*HASH_FIRST).into_uint() + 1)); + } + + #[test] + fn hive_account_range_a() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_MIN, + limit_hash: *HASH_MAX, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 86); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!( + res.accounts.last().unwrap().hash, + H256::from_str("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099") + .unwrap() + ); + } + + #[test] + fn hive_account_range_b() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_MIN, + limit_hash: *HASH_MAX, + response_bytes: 3000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 65); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!( + res.accounts.last().unwrap().hash, + H256::from_str("0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6") + .unwrap() + ); + } + + #[test] + fn hive_account_range_c() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_MIN, + limit_hash: *HASH_MAX, + response_bytes: 2000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 44); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!( + res.accounts.last().unwrap().hash, + H256::from_str("0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595") + .unwrap() + ); + } + + #[test] + fn hive_account_range_d() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_MIN, + limit_hash: *HASH_MAX, + response_bytes: 1, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 1); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_FIRST); + } + + #[test] + fn hive_account_range_e() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_MIN, + limit_hash: *HASH_MAX, + response_bytes: 0, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 1); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_FIRST); + } + + #[test] + fn hive_account_range_f() { + // In this test, we request a range where startingHash is before the first available + // account key, and limitHash is after. The server should return the first and second + // account of the state (because the second account is the 'next available'). + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_FIRST_MINUS_500, + limit_hash: *HASH_FIRST_PLUS_ONE, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 2); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_SECOND); + } + + #[test] + fn hive_account_range_g() { + // Here we request range where both bounds are before the first available account key. + // This should return the first account (even though it's out of bounds). + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_FIRST_MINUS_500, + limit_hash: *HASH_FIRST_MINUS_450, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 1); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_FIRST); + } + + #[test] + fn hive_account_range_h() { + // In this test, both startingHash and limitHash are zero. + // The server should return the first available account. + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_MIN, + limit_hash: *HASH_MIN, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 1); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_FIRST); + } + + #[test] + fn hive_account_range_i() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_FIRST, + limit_hash: *HASH_MAX, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 86); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!( + res.accounts.last().unwrap().hash, + H256::from_str("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099") + .unwrap() + ); + } + + #[test] + fn hive_account_range_j() { + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_FIRST_PLUS_ONE, + limit_hash: *HASH_MAX, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 86); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_SECOND); + assert_eq!( + res.accounts.last().unwrap().hash, + H256::from_str("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa") + .unwrap() + ); + } + + // Tests for different roots skipped (we don't have other state's data loaded) + + // Non-sensical requests + + #[test] + fn hive_account_range_k() { + // In this test, the startingHash is the first available key, and limitHash is + // a key before startingHash (wrong order). The server should return the first available key. + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_FIRST, + limit_hash: *HASH_FIRST_MINUS_ONE, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 1); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_FIRST); + } + + #[test] + fn hive_account_range_m() { + // In this test, the startingHash is the first available key and limitHash is zero. + // (wrong order). The server should return the first available key. + let (store, root) = setup_initial_state(); + let request = GetAccountRange { + id: 0, + root_hash: root, + starting_hash: *HASH_FIRST, + limit_hash: *HASH_MIN, + response_bytes: 4000, + }; + let res = process_account_range_request(request, store).unwrap(); + // Check test invariants + assert_eq!(res.accounts.len(), 1); + assert_eq!(res.accounts.first().unwrap().hash, *HASH_FIRST); + assert_eq!(res.accounts.last().unwrap().hash, *HASH_FIRST); + } + + // Initial state setup for hive snap tests + + fn setup_initial_state() -> (Store, H256) { + // We cannot process the old blocks that hive uses for the devp2p snap tests + // So I copied the state from a geth execution of the test suite + + // State was trimmed to only the first 100 accounts (as the furthest account used by the tests is account 87) + // If the full 408 account state is needed check out previous commits the PR that added this code + + let accounts: Vec<(&str, Vec)> = vec![ + ( + "0x005e94bf632e80cde11add7d3447cd4ca93a5f2205d9874261484ae180718bd6", + vec![ + 228_u8, 1, 128, 160, 223, 151, 249, 75, 196, 116, 113, 135, 6, 6, 246, 38, 251, + 122, 11, 66, 238, 210, 212, 95, 204, 132, 220, 18, 0, 206, 98, 247, 131, 29, + 169, 144, 128, + ], + ), + ( + "0x00748bacab20da9ae19dd26a33bd10bbf825e28b3de84fc8fe1d15a21645067f", + vec![196, 128, 1, 128, 128], + ), + ( + "0x00aa781aff39a8284ef43790e3a511b2caa50803613c5096bc782e8de08fa4c5", + vec![ + 211, 128, 143, 192, 151, 206, 123, 201, 7, 21, 179, 75, 159, 16, 0, 0, 0, 0, + 128, 128, + ], + ), + ( + "0x016d92531f4754834b0502de5b0342ceff21cde5bef386a83d2292f4445782c2", + vec![196, 128, 1, 128, 128], + ), + ( + "0x02547b56492bfe767f3d18be2aab96441c449cd945770ef7ef8555acc505b2e4", + vec![196, 128, 1, 128, 128], + ), + ( + "0x025f478d53bf78add6fa3708d9e061d59bfe14b21329b2a4cf1156d4f81b3d2d", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x0267c643f67b47cac9efacf6fcf0e4f4e1b273a727ded155db60eb9907939eb6", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x0304d8eaccf0b942c468074250cbcb625ec5c4688b6b5d17d2a9bdd8dd565d5a", + vec![ + 228, 1, 128, 160, 224, 12, 73, 166, 88, 73, 208, 92, 191, 39, 164, 215, 120, + 138, 104, 188, 123, 96, 19, 174, 51, 65, 29, 64, 188, 137, 40, 47, 192, 100, + 243, 61, 128, + ], + ), + ( + "0x0463e52cda557221b0b66bd7285b043071df4c2ab146260f4e010970f3a0cccf", + vec![196, 1, 128, 128, 128], + ), + ( + "0x04d9aa4f67f8b24d70a0ffd757e82456d9184113106b7d9e8eb6c3e8a8df27ee", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x053df2c3b574026812b154a99b13b626220af85cd01bb1693b1d42591054bce6", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x0579e46a5ed8a88504ac7d579b12eb346fbe4fd7e281bdd226b891f8abed4789", + vec![ + 228, 1, 128, 160, 61, 14, 43, 165, 55, 243, 89, 65, 6, 135, 9, 69, 15, 37, 254, + 228, 90, 175, 77, 198, 174, 46, 210, 42, 209, 46, 7, 67, 172, 124, 84, 167, + 128, + ], + ), + ( + "0x05f6de281d8c2b5d98e8e01cd529bd76416b248caf11e0552047c5f1d516aab6", + vec![196, 128, 1, 128, 128], + ), + ( + "0x07b49045c401bcc408f983d91a199c908cdf0d646049b5b83629a70b0117e295", + vec![ + 228, 1, 128, 160, 134, 154, 203, 146, 159, 89, 28, 84, 203, 133, 132, 42, 81, + 242, 150, 99, 94, 125, 137, 87, 152, 197, 71, 162, 147, 175, 228, 62, 123, 247, + 244, 23, 128, + ], + ), + ( + "0x0993fd5b750fe4414f93c7880b89744abb96f7af1171ed5f47026bdf01df1874", + vec![196, 128, 1, 128, 128], + ), + ( + "0x099d5081762b8b265e8ba4cd8e43f08be4715d903a0b1d96b3d9c4e811cbfb33", + vec![196, 128, 1, 128, 128], + ), + ( + "0x09d6e6745d272389182a510994e2b54d14b731fac96b9c9ef434bc1924315371", + vec![196, 128, 128, 128, 128], + ), + ( + "0x0a93a7231976ad485379a3b66c2d8983ba0b2ca87abaf0ca44836b2a06a2b102", + vec![196, 128, 1, 128, 128], + ), + ( + "0x0b564e4a0203cbcec8301709a7449e2e7371910778df64c89f48507390f2d129", + vec![196, 1, 128, 128, 128], + ), + ( + "0x0cd2a7c53c76f228ed3aa7a29644b1915fde9ec22e0433808bf5467d914e7c7a", + vec![ + 228, 1, 128, 160, 7, 84, 3, 90, 164, 7, 51, 129, 162, 17, 52, 43, 80, 125, 232, + 231, 117, 201, 124, 150, 16, 150, 230, 226, 39, 93, 240, 191, 203, 179, 160, + 28, 128, + ], + ), + ( + "0x0e0e4646090b881949ec9991e48dec768ccd1980896aefd0d51fd56fd5689790", + vec![ + 228, 1, 128, 160, 96, 252, 105, 16, 13, 142, 99, 38, 103, 200, 11, 148, 212, + 52, 0, 136, 35, 237, 117, 65, 107, 113, 203, 209, 18, 180, 208, 176, 47, 86, + 48, 39, 128, + ], + ), + ( + "0x0e27113c09de0a0cb0ff268c677aba17d39a3190fe15aec0ff7f54184955cba4", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x0e57ffa6cc6cbd96c1400150417dd9b30d958c58f63c36230a90a02b076f78b5", + vec![196, 128, 1, 128, 128], + ), + ( + "0x0f30822f90f33f1d1ba6d1521a00935630d2c81ab12fa03d4a0f4915033134f3", + vec![ + 228, 1, 128, 160, 128, 120, 243, 37, 157, 129, 153, 183, 202, 57, 213, 30, 53, + 213, 181, 141, 113, 255, 20, 134, 6, 115, 16, 96, 56, 109, 50, 60, 93, 25, 24, + 44, 128, + ], + ), + ( + "0x1017b10a7cc3732d729fe1f71ced25e5b7bc73dc62ca61309a8c7e5ac0af2f72", + vec![196, 1, 128, 128, 128], + ), + ( + "0x1098f06082dc467088ecedb143f9464ebb02f19dc10bd7491b03ba68d751ce45", + vec![196, 1, 128, 128, 128], + ), + ( + "0x11eb0304c1baa92e67239f6947cb93e485a7db05e2b477e1167a8960458fa8cc", + vec![196, 1, 128, 128, 128], + ), + ( + "0x12be3bf1f9b1dab5f908ca964115bee3bcff5371f84ede45bc60591b21117c51", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x12c1bb3dddf0f06f62d70ed5b7f7db7d89b591b3f23a838062631c4809c37196", + vec![196, 128, 1, 128, 128], + ), + ( + "0x12e394ad62e51261b4b95c431496e46a39055d7ada7dbf243f938b6d79054630", + vec![196, 1, 128, 128, 128], + ), + ( + "0x13cfc46f6bdb7a1c30448d41880d061c3b8d36c55a29f1c0c8d95a8e882b8c25", + vec![ + 228, 1, 128, 160, 148, 79, 9, 90, 251, 209, 56, 62, 93, 15, 145, 239, 2, 137, + 93, 57, 143, 79, 118, 253, 182, 216, 106, 223, 71, 101, 242, 91, 220, 48, 79, + 95, 128, + ], + ), + ( + "0x15293aec87177f6c88f58bc51274ba75f1331f5cb94f0c973b1deab8b3524dfe", + vec![196, 128, 1, 128, 128], + ), + ( + "0x170c927130fe8f1db3ae682c22b57f33f54eb987a7902ec251fe5dba358a2b25", + vec![196, 128, 1, 128, 128], + ), + ( + "0x17350c7adae7f08d7bbb8befcc97234462831638443cd6dfea186cbf5a08b7c7", + vec![ + 228, 1, 128, 160, 76, 231, 156, 217, 100, 86, 80, 240, 160, 14, 255, 168, 111, + 111, 234, 115, 60, 236, 234, 158, 162, 105, 100, 130, 143, 242, 92, 240, 87, + 123, 201, 116, 128, + ], + ), + ( + "0x174f1a19ff1d9ef72d0988653f31074cb59e2cf37cd9d2992c7b0dd3d77d84f9", + vec![196, 128, 1, 128, 128], + ), + ( + "0x17984cc4b4aac0492699d37662b53ec2acf8cbe540c968b817061e4ed27026d0", + vec![196, 128, 1, 128, 128], + ), + ( + "0x181abdd5e212171007e085fdc284a84d42d5bfc160960d881ccb6a10005ff089", + vec![196, 1, 128, 128, 128], + ), + ( + "0x188111c233bf6516bb9da8b5c4c31809a42e8604cd0158d933435cfd8e06e413", + vec![196, 1, 128, 128, 128], + ), + ( + "0x18f4256a59e1b2e01e96ac465e1d14a45d789ce49728f42082289fc25cf32b8d", + vec![196, 128, 1, 128, 128], + ), + ( + "0x1960414a11f8896c7fc4243aba7ed8179b0bc6979b7c25da7557b17f5dee7bf7", + vec![196, 1, 128, 128, 128], + ), + ( + "0x1a28912018f78f7e754df6b9fcec33bea25e5a232224db622e0c3343cf079eff", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x1bf7626cec5330a127e439e68e6ee1a1537e73b2de1aa6d6f7e06bc0f1e9d763", + vec![196, 128, 1, 128, 128], + ), + ( + "0x1c248f110218eaae2feb51bc82e9dcc2844bf93b88172c52afcb86383d262323", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595", + vec![ + 228, 1, 128, 160, 175, 134, 126, 108, 186, 232, 16, 202, 169, 36, 184, 182, + 172, 61, 140, 8, 145, 131, 20, 145, 166, 144, 109, 208, 190, 122, 211, 36, 220, + 209, 83, 61, 128, + ], + ), + ( + "0x1d38ada74301c31f3fd7d92dd5ce52dc37ae633e82ac29c4ef18dfc141298e26", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x1d6ee979097e29141ad6b97ae19bb592420652b7000003c55eb52d5225c3307d", + vec![ + 228, 1, 128, 160, 247, 53, 145, 231, 145, 175, 76, 124, 95, 160, 57, 195, 61, + 217, 209, 105, 202, 177, 75, 29, 155, 12, 167, 139, 204, 78, 116, 13, 85, 59, + 26, 207, 128, + ], + ), + ( + "0x1dff76635b74ddba16bba3054cc568eed2571ea6becaabd0592b980463f157e2", + vec![196, 1, 128, 128, 128], + ), + ( + "0x1ee7e0292fba90d9733f619f976a2655c484adb30135ef0c5153b5a2f32169df", + vec![196, 1, 128, 128, 128], + ), + ( + "0x209b102e507b8dfc6acfe2cf55f4133b9209357af679a6d507e6ee87112bfe10", + vec![196, 1, 128, 128, 128], + ), + ( + "0x210ce6d692a21d75de3764b6c0356c63a51550ebec2c01f56c154c24b1cf8888", + vec![196, 1, 128, 128, 128], + ), + ( + "0x2116ab29b4cb8547af547fe472b7ce30713f234ed49cb1801ea6d3cf9c796d57", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x2290ea88cc63f09ab5e8c989a67e2e06613311801e39c84aae3badd8bb38409c", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x2369a492b6cddcc0218617a060b40df0e7dda26abe48ba4e4108c532d3f2b84f", + vec![196, 1, 128, 128, 128], + ), + ( + "0x2374954008440ca3d17b1472d34cc52a6493a94fb490d5fb427184d7d5fd1cbf", + vec![196, 1, 128, 128, 128], + ), + ( + "0x23ddaac09188c12e5d88009afa4a34041175c5531f45be53f1560a1cbfec4e8a", + vec![ + 228, 1, 128, 160, 71, 250, 72, 226, 93, 54, 105, 169, 187, 25, 12, 89, 147, + 143, 75, 228, 157, 226, 208, 131, 105, 110, 185, 57, 195, 180, 7, 46, 198, 126, + 67, 177, 128, + ], + ), + ( + "0x246cc8a2b79a30ec71390d829d0cb37cce1b953e89cb14deae4945526714a71c", + vec![196, 128, 1, 128, 128], + ), + ( + "0x255ec86eac03ba59f6dfcaa02128adbb22c561ae0c49e9e62e4fff363750626e", + vec![ + 228, 1, 128, 160, 102, 235, 22, 7, 27, 163, 121, 191, 12, 99, 47, 203, 82, 249, + 23, 90, 101, 107, 239, 98, 173, 240, 190, 245, 52, 154, 127, 90, 106, 173, 93, + 136, 128, + ], + ), + ( + "0x26ce7d83dfb0ab0e7f15c42aeb9e8c0c5dba538b07c8e64b35fb64a37267dd96", + vec![ + 228, 1, 128, 160, 36, 52, 191, 198, 67, 236, 54, 65, 22, 205, 113, 81, 154, 57, + 118, 98, 178, 12, 82, 209, 173, 207, 240, 184, 48, 232, 10, 115, 142, 25, 243, + 14, 128, + ], + ), + ( + "0x2705244734f69af78e16c74784e1dc921cb8b6a98fe76f577cc441c831e973bf", + vec![196, 1, 128, 128, 128], + ), + ( + "0x28f25652ec67d8df6a2e33730e5d0983443e3f759792a0128c06756e8eb6c37f", + vec![ + 211, 128, 143, 192, 151, 206, 123, 201, 7, 21, 179, 75, 159, 16, 0, 0, 0, 0, + 128, 128, + ], + ), + ( + "0x2a248c1755e977920284c8054fceeb20530dc07cd8bbe876f3ce02000818cc3a", + vec![196, 1, 128, 128, 128], + ), + ( + "0x2a39afbe88f572c23c90da2d059af3de125f1da5c3753c530dc5619a4857119f", + vec![ + 228, 1, 128, 160, 130, 137, 181, 88, 134, 95, 44, 161, 245, 76, 152, 181, 255, + 93, 249, 95, 7, 194, 78, 198, 5, 226, 71, 181, 140, 119, 152, 96, 93, 205, 121, + 79, 128, + ], + ), + ( + "0x2b8d12301a8af18405b3c826b6edcc60e8e034810f00716ca48bebb84c4ce7ab", + vec![196, 1, 128, 128, 128], + ), + ( + "0x2baa718b760c0cbd0ec40a3c6df7f2948b40ba096e6e4b116b636f0cca023bde", + vec![196, 128, 1, 128, 128], + ), + ( + "0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6", + vec![ + 228, 128, 128, 128, 160, 142, 3, 136, 236, 246, 76, 250, 118, 179, 166, 175, + 21, 159, 119, 69, 21, 25, 167, 249, 187, 134, 46, 76, 206, 36, 23, 92, 121, 31, + 220, 176, 223, + ], + ), + ( + "0x2fe5767f605b7b821675b223a22e4e5055154f75e7f3041fdffaa02e4787fab8", + vec![196, 128, 1, 128, 128], + ), + ( + "0x303f57a0355c50bf1a0e1cf0fa8f9bdbc8d443b70f2ad93ac1c6b9c1d1fe29a2", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x30ce5b7591126d5464dfb4fc576a970b1368475ce097e244132b06d8cc8ccffe", + vec![196, 128, 1, 128, 128], + ), + ( + "0x315ccc15883d06b4e743f8252c999bf1ee994583ff6114d89c0f3ddee828302b", + vec![196, 1, 128, 128, 128], + ), + ( + "0x3197690074092fe51694bdb96aaab9ae94dac87f129785e498ab171a363d3b40", + vec![196, 128, 1, 128, 128], + ), + ( + "0x34a715e08b77afd68cde30b62e222542f3db90758370400c94d0563959a1d1a0", + vec![ + 228, 1, 128, 160, 79, 68, 99, 41, 181, 238, 61, 19, 212, 246, 181, 229, 242, + 16, 221, 194, 217, 15, 237, 186, 56, 75, 149, 14, 54, 161, 209, 154, 249, 92, + 92, 177, 128, + ], + ), + ( + "0x37310559ceaade42e45b3e3f05925aadca9e60aeeb9dd60d824875d9e9e71e26", + vec![ + 228, 1, 128, 160, 114, 200, 146, 33, 218, 237, 204, 221, 63, 187, 166, 108, 27, + 8, 27, 54, 52, 206, 137, 213, 160, 105, 190, 151, 255, 120, 50, 119, 143, 123, + 2, 58, 128, + ], + ), + ( + "0x37d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42", + vec![ + 248, 68, 128, 42, 160, 172, 49, 98, 168, 185, 219, 180, 49, 139, 132, 33, 159, + 49, 64, 231, 169, 236, 53, 18, 98, 52, 18, 2, 151, 221, 225, 15, 81, 178, 95, + 106, 38, 160, 245, 122, 205, 64, 37, 152, 114, 96, 109, 118, 25, 126, 240, 82, + 243, 211, 85, 136, 218, 223, 145, 158, 225, 240, 227, 203, 155, 98, 211, 244, + 176, 44, + ], + ), + ( + "0x37ddfcbcb4b2498578f90e0fcfef9965dcde4d4dfabe2f2836d2257faa169947", + vec![ + 228, 1, 128, 160, 82, 214, 210, 145, 58, 228, 75, 202, 17, 181, 161, 22, 2, 29, + 185, 124, 145, 161, 62, 56, 94, 212, 139, 160, 102, 40, 231, 66, 1, 35, 29, + 186, 128, + ], + ), + ( + "0x37e51740ad994839549a56ef8606d71ace79adc5f55c988958d1c450eea5ac2d", + vec![196, 1, 128, 128, 128], + ), + ( + "0x38152bce526b7e1c2bedfc9d297250fcead02818be7806638564377af145103b", + vec![ + 228, 1, 128, 160, 108, 0, 224, 145, 218, 227, 212, 34, 111, 172, 214, 190, 128, + 44, 134, 93, 93, 176, 245, 36, 117, 77, 34, 102, 100, 6, 19, 139, 84, 250, 176, + 230, 128, + ], + ), + ( + "0x3848b7da914222540b71e398081d04e3849d2ee0d328168a3cc173a1cd4e783b", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x389093badcaa24c3a8cbb4461f262fba44c4f178a162664087924e85f3d55710", + vec![196, 1, 128, 128, 128], + ), + ( + "0x3897cb9b6f68765022f3c74f84a9f2833132858f661f4bc91ccd7a98f4e5b1ee", + vec![196, 1, 128, 128, 128], + ), + ( + "0x395b92f75f8e06b5378a84ba03379f025d785d8b626b2b6a1c84b718244b9a91", + vec![ + 228, 1, 128, 160, 84, 70, 184, 24, 244, 198, 105, 102, 156, 211, 49, 71, 38, + 255, 19, 76, 241, 140, 88, 169, 165, 54, 223, 19, 199, 0, 97, 7, 5, 168, 183, + 200, 128, + ], + ), + ( + "0x3be526914a7d688e00adca06a0c47c580cb7aa934115ca26006a1ed5455dd2ce", + vec![196, 128, 1, 128, 128], + ), + ( + "0x3e57e37bc3f588c244ffe4da1f48a360fa540b77c92f0c76919ec4ee22b63599", + vec![196, 128, 1, 128, 128], + ), + ( + "0x415ded122ff7b6fe5862f5c443ea0375e372862b9001c5fe527d276a3a420280", + vec![196, 1, 128, 128, 128], + ), + ( + "0x419809ad1512ed1ab3fb570f98ceb2f1d1b5dea39578583cd2b03e9378bbe418", + vec![196, 1, 128, 128, 128], + ), + ( + "0x4363d332a0d4df8582a84932729892387c623fe1ec42e2cfcbe85c183ed98e0e", + vec![ + 213, 130, 1, 146, 143, 192, 151, 206, 123, 201, 7, 21, 179, 73, 233, 122, 138, + 101, 46, 31, 128, 128, + ], + ), + ( + "0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099", + vec![ + 228, 1, 1, 160, 190, 61, 117, 161, 114, 155, 225, 87, 231, 156, 59, 119, 240, + 2, 6, 219, 77, 84, 227, 234, 20, 55, 90, 1, 84, 81, 200, 142, 192, 103, 199, + 144, 128, + ], + ), + ( + "0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa", + vec![196, 1, 128, 128, 128], + ), + ( + "0x465311df0bf146d43750ed7d11b0451b5f6d5bfc69b8a216ef2f1c79c93cd848", + vec![196, 128, 1, 128, 128], + ), + ( + "0x47450e5beefbd5e3a3f80cbbac474bb3db98d5e609aa8d15485c3f0d733dea3a", + vec![ + 228, 1, 128, 160, 84, 66, 224, 39, 157, 63, 17, 73, 222, 76, 232, 217, 226, + 211, 240, 29, 24, 84, 117, 80, 56, 172, 26, 15, 174, 92, 72, 116, 155, 247, 31, + 32, 128, + ], + ), + ( + "0x482814ea8f103c39dcf6ba7e75df37145bde813964d82e81e5d7e3747b95303d", + vec![196, 128, 1, 128, 128], + ), + ( + "0x4845aac9f26fcd628b39b83d1ccb5c554450b9666b66f83aa93a1523f4db0ab6", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x48e291f8a256ab15da8401c8cae555d5417a992dff3848926fa5b71655740059", + vec![ + 228, 1, 128, 160, 162, 231, 8, 75, 169, 206, 193, 121, 81, 156, 126, 137, 80, + 198, 106, 211, 203, 168, 88, 106, 96, 207, 249, 244, 214, 12, 24, 141, 214, 33, + 82, 42, 128, + ], + ), + ( + "0x4973f6aa8cf5b1190fc95379aa01cff99570ee6b670725880217237fb49e4b24", + vec![ + 228, 1, 128, 160, 174, 46, 127, 28, 147, 60, 108, 168, 76, 232, 190, 129, 30, + 244, 17, 222, 231, 115, 251, 105, 80, 128, 86, 215, 36, 72, 4, 142, 161, 219, + 92, 71, 128, + ], + ), + ( + "0x4b238e08b80378d0815e109f350a08e5d41ec4094df2cfce7bc8b9e3115bda70", + vec![ + 228, 1, 128, 160, 17, 245, 211, 153, 202, 143, 183, 169, 175, 90, 212, 129, + 190, 96, 207, 97, 212, 84, 147, 205, 32, 32, 108, 157, 10, 35, 124, 231, 215, + 87, 30, 95, 128, + ], + ), + ( + "0x4b9f335ce0bdffdd77fdb9830961c5bc7090ae94703d0392d3f0ff10e6a4fbab", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x4bd8ef9873a5e85d4805dbcb0dbf6810e558ea175167549ef80545a9cafbb0e1", + vec![ + 228, 1, 128, 160, 161, 73, 19, 213, 72, 172, 29, 63, 153, 98, 162, 26, 86, 159, + 229, 47, 20, 54, 182, 210, 245, 234, 78, 54, 222, 19, 234, 133, 94, 222, 84, + 224, 128, + ], + ), + ( + "0x4c2765139cace1d217e238cc7ccfbb751ef200e0eae7ec244e77f37e92dfaee5", + vec![196, 1, 128, 128, 128], + ), + ( + "0x4c310e1f5d2f2e03562c4a5c473ae044b9ee19411f07097ced41e85bd99c3364", + vec![196, 128, 1, 128, 128], + ), + ( + "0x4ccd31891378d2025ef58980481608f11f5b35a988e877652e7cbb0a6127287c", + vec![201, 128, 133, 23, 72, 118, 232, 0, 128, 128], + ), + ( + "0x4ceaf2371fcfb54a4d8bc1c804d90b06b3c32c9f17112b57c29b30a25cf8ca12", + vec![196, 128, 1, 128, 128], + ), + ]; + + // Create a store and load it up with the accounts + let store = Store::new("null", EngineType::InMemory).unwrap(); + let mut state_trie = store.new_state_trie_for_test(); + for (address, account) in accounts { + let hashed_address = H256::from_str(address).unwrap().as_bytes().to_vec(); + let account = AccountState::from(AccountStateSlim::decode(&account).unwrap()); + state_trie + .insert(hashed_address, account.encode_to_vec()) + .unwrap(); + } + (store, state_trie.hash().unwrap()) + } +} diff --git a/crates/networking/rpc/eth/filter.rs b/crates/networking/rpc/eth/filter.rs index bbeb2f9b73..a5e8c8f130 100644 --- a/crates/networking/rpc/eth/filter.rs +++ b/crates/networking/rpc/eth/filter.rs @@ -558,7 +558,6 @@ mod tests { .await .unwrap(); - dbg!(&response); assert!( response.get("result").is_some(), "Response should have a 'result' field" diff --git a/crates/storage/store/storage.rs b/crates/storage/store/storage.rs index 7331126970..269286cf6f 100644 --- a/crates/storage/store/storage.rs +++ b/crates/storage/store/storage.rs @@ -690,6 +690,32 @@ impl Store { Ok(trie.get_proof(&hash_key(storage_key))?) } + // Returns an iterator across all accounts in the state trie given by the state_root + // Does not check that the state_root is valid + pub fn iter_accounts(&self, state_root: H256) -> impl Iterator { + self.engine + .open_state_trie(state_root) + .into_iter() + .content() + .map_while(|(path, value)| { + Some((H256::from_slice(&path), AccountState::decode(&value).ok()?)) + }) + } + + pub fn get_account_range_proof( + &self, + state_root: H256, + starting_hash: H256, + last_hash: Option, + ) -> Result>, StoreError> { + let state_trie = self.engine.open_state_trie(state_root); + let mut proof = state_trie.get_proof(&starting_hash.as_bytes().to_vec())?; + if let Some(last_hash) = last_hash { + proof.extend_from_slice(&state_trie.get_proof(&last_hash.as_bytes().to_vec())?); + } + Ok(proof) + } + pub fn add_payload(&self, payload_id: u64, block: Block) -> Result<(), StoreError> { self.engine.add_payload(payload_id, block) } @@ -697,6 +723,11 @@ impl Store { pub fn get_payload(&self, payload_id: u64) -> Result, StoreError> { self.engine.get_payload(payload_id) } + + /// Creates a new state trie with an empty state root, for testing purposes only + pub fn new_state_trie_for_test(&self) -> Trie { + self.engine.open_state_trie(*EMPTY_TRIE_HASH) + } } fn hash_address(address: &Address) -> Vec { diff --git a/crates/storage/trie/trie.rs b/crates/storage/trie/trie.rs index b43559a160..45b098f035 100644 --- a/crates/storage/trie/trie.rs +++ b/crates/storage/trie/trie.rs @@ -5,6 +5,7 @@ mod node; mod node_hash; mod rlp; mod state; +mod trie_iter; #[cfg(test)] mod test_utils; @@ -21,7 +22,7 @@ pub use self::db::{libmdbx::LibmdbxTrieDB, libmdbx_dupsort::LibmdbxDupsortTrieDB pub use self::db::{in_memory::InMemoryTrieDB, TrieDB}; pub use self::error::TrieError; -use self::{nibble::NibbleSlice, node::LeafNode, state::TrieState}; +use self::{nibble::NibbleSlice, node::LeafNode, state::TrieState, trie_iter::TrieIterator}; use lazy_static::lazy_static; @@ -147,11 +148,9 @@ impl Trie { if let NodeHash::Inline(node) = root { node_path.push(node.to_vec()); } - let root_node = self - .state - .get_node(root.clone())? - .expect("inconsistent tree structure"); - root_node.get_path(&self.state, NibbleSlice::new(path), &mut node_path)?; + if let Some(root_node) = self.state.get_node(root.clone())? { + root_node.get_path(&self.state, NibbleSlice::new(path), &mut node_path)?; + } Ok(node_path) } @@ -206,6 +205,16 @@ impl Trie { } } +impl IntoIterator for Trie { + type Item = Node; + + type IntoIter = TrieIterator; + + fn into_iter(self) -> Self::IntoIter { + TrieIterator::new(self) + } +} + #[cfg(test)] mod test { use cita_trie::{MemoryDB as CitaMemoryDB, PatriciaTrie as CitaTrie, Trie as CitaTrieTrait}; diff --git a/crates/storage/trie/trie_iter.rs b/crates/storage/trie/trie_iter.rs new file mode 100644 index 0000000000..e60d548dde --- /dev/null +++ b/crates/storage/trie/trie_iter.rs @@ -0,0 +1,58 @@ +use crate::{node::Node, node_hash::NodeHash, PathRLP, Trie, ValueRLP}; + +pub struct TrieIterator { + trie: Trie, + stack: Vec, +} + +impl TrieIterator { + pub(crate) fn new(trie: Trie) -> Self { + let stack = if let Some(root) = &trie.root { + vec![root.clone()] + } else { + vec![] + }; + Self { trie, stack } + } +} + +impl Iterator for TrieIterator { + type Item = Node; + + fn next(&mut self) -> Option { + if self.stack.is_empty() { + return None; + }; + // Fetch the last node in the stack + let next_node_hash = self.stack.pop()?; + let next_node = self.trie.state.get_node(next_node_hash).ok()??; + match &next_node { + Node::Branch(branch_node) => { + // Add all children to the stack (in reverse order so we process first child frist) + for child in branch_node.choices.iter().rev() { + if child.is_valid() { + self.stack.push(child.clone()) + } + } + } + Node::Extension(extension_node) => { + // Add child to the stack + self.stack.push(extension_node.child.clone()); + } + Node::Leaf(_) => {} + } + Some(next_node) + } +} + +impl TrieIterator { + pub fn content(self) -> impl Iterator { + self.filter_map(|n| match n { + Node::Branch(branch_node) => { + (!branch_node.path.is_empty()).then_some((branch_node.path, branch_node.value)) + } + Node::Extension(_) => None, + Node::Leaf(leaf_node) => Some((leaf_node.path, leaf_node.value)), + }) + } +} From ef9c51143dda041e0c8e4ca2a8833b0857e73bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Thu, 31 Oct 2024 19:00:49 +0100 Subject: [PATCH 6/6] refactor(l1): pass context to all RpcHandlers (#1039) Closes #1038 --- crates/networking/rpc/authentication.rs | 6 +- .../rpc/engine/exchange_transition_config.rs | 11 +- crates/networking/rpc/engine/fork_choice.rs | 12 +- crates/networking/rpc/engine/mod.rs | 4 +- crates/networking/rpc/engine/payload.rs | 13 +- crates/networking/rpc/eth/account.rs | 36 ++-- crates/networking/rpc/eth/block.rs | 57 +++--- crates/networking/rpc/eth/client.rs | 13 +- crates/networking/rpc/eth/fee_market.rs | 7 +- crates/networking/rpc/eth/filter.rs | 53 +++--- crates/networking/rpc/eth/gas_price.rs | 104 +++++++---- crates/networking/rpc/eth/logs.rs | 6 +- crates/networking/rpc/eth/transaction.rs | 57 +++--- crates/networking/rpc/rpc.rs | 172 +++++++++--------- 14 files changed, 304 insertions(+), 247 deletions(-) diff --git a/crates/networking/rpc/authentication.rs b/crates/networking/rpc/authentication.rs index 1aee371780..40bcbac308 100644 --- a/crates/networking/rpc/authentication.rs +++ b/crates/networking/rpc/authentication.rs @@ -16,7 +16,7 @@ pub enum AuthenticationError { } pub fn authenticate( - secret: Bytes, + secret: &Bytes, auth_header: Option>>, ) -> Result<(), RpcErr> { match auth_header { @@ -39,8 +39,8 @@ struct Claims { } /// Authenticates bearer jwt to check that authrpc calls are sent by the consensus layer -pub fn validate_jwt_authentication(token: &str, secret: Bytes) -> Result<(), AuthenticationError> { - let decoding_key = DecodingKey::from_secret(&secret); +pub fn validate_jwt_authentication(token: &str, secret: &Bytes) -> Result<(), AuthenticationError> { + let decoding_key = DecodingKey::from_secret(secret); let mut validation = Validation::new(Algorithm::HS256); validation.validate_exp = false; validation.set_required_spec_claims(&["iat"]); diff --git a/crates/networking/rpc/engine/exchange_transition_config.rs b/crates/networking/rpc/engine/exchange_transition_config.rs index 84ff9438a3..7d0faa2f1c 100644 --- a/crates/networking/rpc/engine/exchange_transition_config.rs +++ b/crates/networking/rpc/engine/exchange_transition_config.rs @@ -1,10 +1,9 @@ use ethereum_rust_core::{serde_utils, H256}; -use ethereum_rust_storage::Store; use serde::{Deserialize, Serialize}; use serde_json::Value; use tracing::{info, warn}; -use crate::{utils::RpcErr, RpcHandler}; +use crate::{utils::RpcErr, RpcApiContext, RpcHandler}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -45,11 +44,11 @@ impl RpcHandler for ExchangeTransitionConfigV1Req { Ok(ExchangeTransitionConfigV1Req { payload }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!("Received new engine request: {self}"); let payload = &self.payload; - let chain_config = storage.get_chain_config()?; + let chain_config = context.storage.get_chain_config()?; let terminal_total_difficulty = chain_config.terminal_total_difficulty; if terminal_total_difficulty.unwrap_or_default() != payload.terminal_total_difficulty { @@ -59,7 +58,9 @@ impl RpcHandler for ExchangeTransitionConfigV1Req { ); }; - let block = storage.get_block_header(payload.terminal_block_number)?; + let block = context + .storage + .get_block_header(payload.terminal_block_number)?; let terminal_block_hash = block.map_or(H256::zero(), |block| block.compute_block_hash()); serde_json::to_value(ExchangeTransitionConfigPayload { diff --git a/crates/networking/rpc/engine/fork_choice.rs b/crates/networking/rpc/engine/fork_choice.rs index 7824a6486a..78b7f7e892 100644 --- a/crates/networking/rpc/engine/fork_choice.rs +++ b/crates/networking/rpc/engine/fork_choice.rs @@ -4,7 +4,6 @@ use ethereum_rust_blockchain::{ latest_canonical_block_hash, payload::{create_payload, BuildPayloadArgs}, }; -use ethereum_rust_storage::Store; use serde_json::Value; use tracing::{info, warn}; @@ -14,7 +13,7 @@ use crate::{ payload::PayloadStatus, }, utils::RpcRequest, - RpcErr, RpcHandler, + RpcApiContext, RpcErr, RpcHandler, }; #[derive(Debug)] @@ -58,7 +57,8 @@ impl RpcHandler for ForkChoiceUpdatedV3 { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!( "New fork choice request with head: {}, safe: {}, finalized: {}.", self.fork_choice_state.head_block_hash, @@ -68,7 +68,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { let fork_choice_error_to_response = |error| { let response = match error { InvalidForkChoice::NewHeadAlreadyCanonical => ForkChoiceResponse::from( - PayloadStatus::valid_with_hash(latest_canonical_block_hash(&storage).unwrap()), + PayloadStatus::valid_with_hash(latest_canonical_block_hash(storage).unwrap()), ), InvalidForkChoice::Syncing => ForkChoiceResponse::from(PayloadStatus::syncing()), reason => { @@ -83,7 +83,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { }; let head_block = match apply_fork_choice( - &storage, + storage, self.fork_choice_state.head_block_hash, self.fork_choice_state.safe_block_hash, self.fork_choice_state.finalized_block_hash, @@ -125,7 +125,7 @@ impl RpcHandler for ForkChoiceUpdatedV3 { }; let payload_id = args.id(); response.set_id(payload_id); - let payload = match create_payload(&args, &storage) { + let payload = match create_payload(&args, storage) { Ok(payload) => payload, Err(ChainError::EvmError(error)) => return Err(error.into()), // Parent block is guaranteed to be present at this point, diff --git a/crates/networking/rpc/engine/mod.rs b/crates/networking/rpc/engine/mod.rs index 59e855a491..cd57a46ee0 100644 --- a/crates/networking/rpc/engine/mod.rs +++ b/crates/networking/rpc/engine/mod.rs @@ -2,7 +2,7 @@ pub mod exchange_transition_config; pub mod fork_choice; pub mod payload; -use crate::{utils::RpcRequest, RpcErr, RpcHandler, Store}; +use crate::{utils::RpcRequest, RpcApiContext, RpcErr, RpcHandler}; use serde_json::{json, Value}; pub type ExchangeCapabilitiesRequest = Vec; @@ -30,7 +30,7 @@ impl RpcHandler for ExchangeCapabilitiesRequest { }) } - fn handle(&self, _storage: Store) -> Result { + fn handle(&self, _context: RpcApiContext) -> Result { Ok(json!(*self)) } } diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 59e6e2ed55..b85188b7d8 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -3,12 +3,12 @@ use ethereum_rust_blockchain::error::ChainError; use ethereum_rust_blockchain::payload::build_payload; use ethereum_rust_core::types::Fork; use ethereum_rust_core::{H256, U256}; -use ethereum_rust_storage::Store; use serde_json::Value; use tracing::{error, info, warn}; use crate::types::payload::ExecutionPayloadResponse; use crate::utils::RpcRequest; +use crate::RpcApiContext; use crate::{ types::payload::{ExecutionPayloadV3, PayloadStatus}, RpcErr, RpcHandler, @@ -56,7 +56,8 @@ impl RpcHandler for NewPayloadV3Request { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; let block_hash = self.payload.block_hash; info!("Received new payload with block hash: {block_hash:#x}"); @@ -114,7 +115,7 @@ impl RpcHandler for NewPayloadV3Request { // Execute and store the block info!("Executing payload with block hash: {block_hash:#x}"); - let payload_status = match add_block(&block, &storage) { + let payload_status = match add_block(&block, storage) { Err(ChainError::ParentNotFound) => Ok(PayloadStatus::syncing()), // Under the current implementation this is not possible: we always calculate the state // transition of any new payload as long as the parent is present. If we received the @@ -185,15 +186,15 @@ impl RpcHandler for GetPayloadV3Request { Ok(GetPayloadV3Request { payload_id }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!("Requested payload with id: {:#018x}", self.payload_id); - let Some(mut payload) = storage.get_payload(self.payload_id)? else { + let Some(mut payload) = context.storage.get_payload(self.payload_id)? else { return Err(RpcErr::UnknownPayload(format!( "Payload with id {:#018x} not found", self.payload_id ))); }; - let (blobs_bundle, block_value) = build_payload(&mut payload, &storage) + let (blobs_bundle, block_value) = build_payload(&mut payload, &context.storage) .map_err(|err| RpcErr::Internal(err.to_string()))?; serde_json::to_value(ExecutionPayloadResponse { execution_payload: ExecutionPayloadV3::from_block(payload), diff --git a/crates/networking/rpc/eth/account.rs b/crates/networking/rpc/eth/account.rs index 953999daa3..060ec82784 100644 --- a/crates/networking/rpc/eth/account.rs +++ b/crates/networking/rpc/eth/account.rs @@ -1,9 +1,9 @@ -use ethereum_rust_storage::Store; use serde_json::Value; use tracing::info; use crate::types::account_proof::{AccountProof, StorageProof}; use crate::types::block_identifier::BlockIdentifierOrHash; +use crate::RpcApiContext; use crate::{utils::RpcErr, RpcHandler}; use ethereum_rust_core::{Address, BigEndianHash, H256, U256}; @@ -47,19 +47,21 @@ impl RpcHandler for GetBalanceRequest { block: BlockIdentifierOrHash::parse(params[1].clone(), 1)?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested balance of account {} at block {}", self.address, self.block ); - let Some(block_number) = self.block.resolve_block_number(&storage)? else { + let Some(block_number) = self.block.resolve_block_number(&context.storage)? else { return Err(RpcErr::Internal( "Could not resolve block number".to_owned(), )); // Should we return Null here? }; - let account = storage.get_account_info(block_number, self.address)?; + let account = context + .storage + .get_account_info(block_number, self.address)?; let balance = account.map(|acc| acc.balance).unwrap_or_default(); serde_json::to_value(format!("{:#x}", balance)) @@ -80,19 +82,20 @@ impl RpcHandler for GetCodeRequest { block: BlockIdentifierOrHash::parse(params[1].clone(), 1)?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested code of account {} at block {}", self.address, self.block ); - let Some(block_number) = self.block.resolve_block_number(&storage)? else { + let Some(block_number) = self.block.resolve_block_number(&context.storage)? else { return Err(RpcErr::Internal( "Could not resolve block number".to_owned(), )); // Should we return Null here? }; - let code = storage + let code = context + .storage .get_code_by_account_address(block_number, self.address)? .unwrap_or_default(); @@ -115,19 +118,20 @@ impl RpcHandler for GetStorageAtRequest { block: BlockIdentifierOrHash::parse(params[2].clone(), 2)?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested storage sot {} of account {} at block {}", self.storage_slot, self.address, self.block ); - let Some(block_number) = self.block.resolve_block_number(&storage)? else { + let Some(block_number) = self.block.resolve_block_number(&context.storage)? else { return Err(RpcErr::Internal( "Could not resolve block number".to_owned(), )); // Should we return Null here? }; - let storage_value = storage + let storage_value = context + .storage .get_storage_at(block_number, self.address, self.storage_slot)? .unwrap_or_default(); let storage_value = H256::from_uint(&storage_value); @@ -149,18 +153,19 @@ impl RpcHandler for GetTransactionCountRequest { block: BlockIdentifierOrHash::parse(params[1].clone(), 1)?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested nonce of account {} at block {}", self.address, self.block ); - let Some(block_number) = self.block.resolve_block_number(&storage)? else { + let Some(block_number) = self.block.resolve_block_number(&context.storage)? else { return serde_json::to_value("0x0") .map_err(|error| RpcErr::Internal(error.to_string())); }; - let nonce = storage + let nonce = context + .storage .get_nonce_by_account_address(block_number, self.address)? .unwrap_or_default(); @@ -186,12 +191,13 @@ impl RpcHandler for GetProofRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!( "Requested proof for account {} at block {} with storage keys: {:?}", self.address, self.block, self.storage_keys ); - let Some(block_number) = self.block.resolve_block_number(&storage)? else { + let Some(block_number) = self.block.resolve_block_number(storage)? else { return Ok(Value::Null); }; // Create account proof diff --git a/crates/networking/rpc/eth/block.rs b/crates/networking/rpc/eth/block.rs index fea369977f..74295d090e 100644 --- a/crates/networking/rpc/eth/block.rs +++ b/crates/networking/rpc/eth/block.rs @@ -10,7 +10,7 @@ use crate::{ receipt::{RpcReceipt, RpcReceiptBlockInfo, RpcReceiptTxInfo}, }, utils::RpcErr, - RpcHandler, + RpcApiContext, RpcHandler, }; use ethereum_rust_core::{ types::{ @@ -68,9 +68,10 @@ impl RpcHandler for GetBlockByNumberRequest { hydrated: serde_json::from_value(params[1].clone())?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!("Requested block with number: {}", self.block); - let block_number = match self.block.resolve_block_number(&storage)? { + let block_number = match self.block.resolve_block_number(storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; @@ -109,7 +110,8 @@ impl RpcHandler for GetBlockByHashRequest { hydrated: serde_json::from_value(params[1].clone())?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!("Requested block with hash: {:#x}", self.block); let block_number = match storage.get_block_number(self.block)? { Some(number) => number, @@ -149,16 +151,16 @@ impl RpcHandler for GetBlockTransactionCountRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested transaction count for block with number: {}", self.block ); - let block_number = match self.block.resolve_block_number(&storage)? { + let block_number = match self.block.resolve_block_number(&context.storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; - let block_body = match storage.get_block_body(block_number)? { + let block_body = match context.storage.get_block_body(block_number)? { Some(block_body) => block_body, _ => return Ok(Value::Null), }; @@ -182,9 +184,10 @@ impl RpcHandler for GetBlockReceiptsRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!("Requested receipts for block with number: {}", self.block); - let block_number = match self.block.resolve_block_number(&storage)? { + let block_number = match self.block.resolve_block_number(storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; @@ -195,7 +198,7 @@ impl RpcHandler for GetBlockReceiptsRequest { // Block not found _ => return Ok(Value::Null), }; - let receipts = get_all_block_rpc_receipts(block_number, header, body, &storage)?; + let receipts = get_all_block_rpc_receipts(block_number, header, body, storage)?; serde_json::to_value(&receipts).map_err(|error| RpcErr::Internal(error.to_string())) } @@ -214,16 +217,17 @@ impl RpcHandler for GetRawHeaderRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested raw header for block with identifier: {}", self.block ); - let block_number = match self.block.resolve_block_number(&storage)? { + let block_number = match self.block.resolve_block_number(&context.storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; - let header = storage + let header = context + .storage .get_block_header(block_number)? .ok_or(RpcErr::BadParams("Header not found".to_owned()))?; @@ -246,14 +250,14 @@ impl RpcHandler for GetRawBlockRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!("Requested raw block: {}", self.block); - let block_number = match self.block.resolve_block_number(&storage)? { + let block_number = match self.block.resolve_block_number(&context.storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; - let header = storage.get_block_header(block_number)?; - let body = storage.get_block_body(block_number)?; + let header = context.storage.get_block_header(block_number)?; + let body = context.storage.get_block_body(block_number)?; let (header, body) = match (header, body) { (Some(header), Some(body)) => (header, body), _ => return Ok(Value::Null), @@ -279,8 +283,9 @@ impl RpcHandler for GetRawReceipts { }) } - fn handle(&self, storage: Store) -> Result { - let block_number = match self.block.resolve_block_number(&storage)? { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; + let block_number = match self.block.resolve_block_number(storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; @@ -290,7 +295,7 @@ impl RpcHandler for GetRawReceipts { (Some(header), Some(body)) => (header, body), _ => return Ok(Value::Null), }; - let receipts: Vec = get_all_block_receipts(block_number, header, body, &storage)? + let receipts: Vec = get_all_block_receipts(block_number, header, body, storage)? .iter() .map(|receipt| format!("0x{}", hex::encode(receipt.encode_to_vec()))) .collect(); @@ -303,9 +308,9 @@ impl RpcHandler for BlockNumberRequest { Ok(Self {}) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!("Requested latest block number"); - match storage.get_latest_block_number() { + match context.storage.get_latest_block_number() { Ok(Some(block_number)) => serde_json::to_value(format!("{:#x}", block_number)) .map_err(|error| RpcErr::Internal(error.to_string())), Ok(None) => Err(RpcErr::Internal("No blocks found".to_owned())), @@ -319,15 +324,15 @@ impl RpcHandler for GetBlobBaseFee { Ok(Self {}) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!("Requested blob gas price"); - match storage.get_latest_block_number() { + match context.storage.get_latest_block_number() { Ok(Some(block_number)) => { - let header = match storage.get_block_header(block_number)? { + let header = match context.storage.get_block_header(block_number)? { Some(header) => header, _ => return Err(RpcErr::Internal("Could not get block header".to_owned())), }; - let parent_header = match find_parent_header(&header, &storage) { + let parent_header = match find_parent_header(&header, &context.storage) { Ok(header) => header, Err(error) => return Err(RpcErr::Internal(error.to_string())), }; diff --git a/crates/networking/rpc/eth/client.rs b/crates/networking/rpc/eth/client.rs index a52eece66a..f090b0c701 100644 --- a/crates/networking/rpc/eth/client.rs +++ b/crates/networking/rpc/eth/client.rs @@ -1,9 +1,7 @@ -use tracing::info; - -use ethereum_rust_storage::Store; use serde_json::Value; +use tracing::info; -use crate::{utils::RpcErr, RpcHandler}; +use crate::{utils::RpcErr, RpcApiContext, RpcHandler}; pub struct ChainId; impl RpcHandler for ChainId { @@ -11,9 +9,10 @@ impl RpcHandler for ChainId { Ok(Self {}) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!("Requested chain id"); - let chain_spec = storage + let chain_spec = context + .storage .get_chain_config() .map_err(|error| RpcErr::Internal(error.to_string()))?; serde_json::to_value(format!("{:#x}", chain_spec.chain_id)) @@ -27,7 +26,7 @@ impl RpcHandler for Syncing { Ok(Self {}) } - fn handle(&self, _storage: Store) -> Result { + fn handle(&self, _context: RpcApiContext) -> Result { Ok(Value::Bool(false)) } } diff --git a/crates/networking/rpc/eth/fee_market.rs b/crates/networking/rpc/eth/fee_market.rs index cc83a1f73e..e1b8cf663c 100644 --- a/crates/networking/rpc/eth/fee_market.rs +++ b/crates/networking/rpc/eth/fee_market.rs @@ -4,7 +4,7 @@ use serde::Serialize; use serde_json::Value; use tracing::info; -use crate::{types::block_identifier::BlockIdentifier, utils::RpcErr, RpcHandler}; +use crate::{types::block_identifier::BlockIdentifier, utils::RpcErr, RpcApiContext, RpcHandler}; use ethereum_rust_core::types::calculate_base_fee_per_blob_gas; use ethereum_rust_storage::Store; @@ -67,7 +67,8 @@ impl RpcHandler for FeeHistoryRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!( "Requested fee history for {} blocks starting from {}", self.block_count, self.newest_block @@ -79,7 +80,7 @@ impl RpcHandler for FeeHistoryRequest { } let (start_block, end_block) = - Self::get_range(&storage, self.block_count, &self.newest_block)?; + Self::get_range(storage, self.block_count, &self.newest_block)?; let oldest_block = start_block; let block_count = (end_block - start_block) as usize; let mut base_fee_per_gas = Vec::::with_capacity(block_count + 1); diff --git a/crates/networking/rpc/eth/filter.rs b/crates/networking/rpc/eth/filter.rs index a5e8c8f130..c0e6018fda 100644 --- a/crates/networking/rpc/eth/filter.rs +++ b/crates/networking/rpc/eth/filter.rs @@ -266,7 +266,7 @@ mod tests { }, map_http_requests, utils::test_utils::{self, start_test_api}, - FILTER_DURATION, + RpcApiContext, FILTER_DURATION, }; use crate::{ types::block_identifier::BlockIdentifier, @@ -436,18 +436,22 @@ mod tests { json_req: serde_json::Value, filters_pointer: ActiveFilters, ) -> u64 { - let node = example_p2p_node(); + let context = RpcApiContext { + storage: Store::new("in-mem", EngineType::InMemory) + .expect("Fatal: could not create in memory test db"), + jwt_secret: Default::default(), + local_p2p_node: example_p2p_node(), + active_filters: filters_pointer.clone(), + }; let request: RpcRequest = serde_json::from_value(json_req).expect("Test json is incorrect"); let genesis_config: Genesis = serde_json::from_str(TEST_GENESIS).expect("Fatal: non-valid genesis test config"); - let store = Store::new("in-mem", EngineType::InMemory) - .expect("Fatal: could not create in memory test db"); - store + + context + .storage .add_initial_state(genesis_config) .expect("Fatal: could not add test genesis in test"); - let response = map_http_requests(&request, store, node, filters_pointer.clone()) - .unwrap() - .to_string(); + let response = map_http_requests(&request, context).unwrap().to_string(); let trimmed_id = response.trim().trim_matches('"'); assert!(trimmed_id.starts_with("0x")); let hex = trimmed_id.trim_start_matches("0x"); @@ -485,13 +489,15 @@ mod tests { ), ); let active_filters = Arc::new(Mutex::new(HashMap::from([filter]))); - map_http_requests( - &uninstall_filter_req, - Store::new("in-mem", EngineType::InMemory).unwrap(), - example_p2p_node(), - active_filters.clone(), - ) - .unwrap(); + let context = RpcApiContext { + storage: Store::new("in-mem", EngineType::InMemory).unwrap(), + local_p2p_node: example_p2p_node(), + jwt_secret: Default::default(), + active_filters: active_filters.clone(), + }; + + map_http_requests(&uninstall_filter_req, context).unwrap(); + assert!( active_filters.clone().lock().unwrap().len() == 0, "Expected filter map to be empty after request" @@ -500,6 +506,14 @@ mod tests { #[test] fn removing_non_existing_filter_returns_false() { + let active_filters = Arc::new(Mutex::new(HashMap::new())); + + let context = RpcApiContext { + storage: Store::new("in-mem", EngineType::InMemory).unwrap(), + local_p2p_node: example_p2p_node(), + active_filters: active_filters.clone(), + jwt_secret: Default::default(), + }; let uninstall_filter_req: RpcRequest = serde_json::from_value(json!( { "jsonrpc":"2.0", @@ -511,14 +525,7 @@ mod tests { ,"id":1 })) .expect("Json for test is not a valid request"); - let active_filters = Arc::new(Mutex::new(HashMap::new())); - let res = map_http_requests( - &uninstall_filter_req, - Store::new("in-mem", EngineType::InMemory).unwrap(), - example_p2p_node(), - active_filters.clone(), - ) - .unwrap(); + let res = map_http_requests(&uninstall_filter_req, context).unwrap(); assert!(matches!(res, serde_json::Value::Bool(false))); } diff --git a/crates/networking/rpc/eth/gas_price.rs b/crates/networking/rpc/eth/gas_price.rs index 8fc9951461..fe3f184175 100644 --- a/crates/networking/rpc/eth/gas_price.rs +++ b/crates/networking/rpc/eth/gas_price.rs @@ -1,9 +1,8 @@ use ethereum_rust_blockchain::constants::MIN_GAS_LIMIT; -use ethereum_rust_storage::Store; use tracing::error; use crate::utils::RpcErr; -use crate::RpcHandler; +use crate::{RpcApiContext, RpcHandler}; use serde_json::Value; // TODO: This does not need a struct, @@ -41,8 +40,8 @@ impl RpcHandler for GasPrice { // we can look into more sophisticated estimation methods, if needed. /// Estimate Gas Price based on already accepted transactions, /// as per the spec, this will be returned in wei. - fn handle(&self, storage: Store) -> Result { - let Some(latest_block_number) = storage.get_latest_block_number()? else { + fn handle(&self, context: RpcApiContext) -> Result { + let Some(latest_block_number) = context.storage.get_latest_block_number()? else { error!("FATAL: LATEST BLOCK NUMBER IS MISSING"); return Err(RpcErr::Internal("Error calculating gas price".to_string())); }; @@ -64,7 +63,7 @@ impl RpcHandler for GasPrice { // caching this result, also we can have a specific DB method // that returns a block range to not query them one-by-one. for block_num in block_range { - let Some(block_body) = storage.get_block_body(block_num)? else { + let Some(block_body) = context.storage.get_block_body(block_num)? else { error!("Block body for block number {block_num} is missing but is below the latest known block!"); return Err(RpcErr::Internal( "Error calculating gas price: missing data".to_string(), @@ -93,7 +92,7 @@ mod tests { use crate::{ map_http_requests, utils::{parse_json_hex, test_utils::example_p2p_node, RpcRequest}, - RpcHandler, + RpcApiContext, RpcHandler, }; use bytes::Bytes; use ethereum_rust_blockchain::constants::MIN_GAS_LIMIT; @@ -104,10 +103,11 @@ mod tests { }, Address, Bloom, H256, U256, }; + use ethereum_rust_net::types::Node; use ethereum_rust_storage::{EngineType, Store}; use hex_literal::hex; use serde_json::json; - use std::str::FromStr; + use std::{net::Ipv4Addr, str::FromStr}; // Base price for each test transaction. const BASE_PRICE_IN_WEI: u64 = 10_u64.pow(9); fn test_header(block_num: u64) -> BlockHeader { @@ -198,7 +198,7 @@ mod tests { } #[test] fn test_for_legacy_txs() { - let store = setup_store(); + let context = default_context(); for block_num in 1..100 { let mut txs = vec![]; for nonce in 1..=3 { @@ -212,21 +212,25 @@ mod tests { }; let block_header = test_header(block_num); let block = Block::new(block_header.clone(), block_body); - store.add_block(block).unwrap(); - store + context.storage.add_block(block).unwrap(); + context + .storage .set_canonical_block(block_num, block_header.compute_block_hash()) .unwrap(); - store.update_latest_block_number(block_num).unwrap(); + context + .storage + .update_latest_block_number(block_num) + .unwrap(); } let gas_price = GasPrice {}; - let response = gas_price.handle(store).unwrap(); + let response = gas_price.handle(context).unwrap(); let parsed_result = parse_json_hex(&response).unwrap(); assert_eq!(parsed_result, 2000000000); } #[test] fn test_for_eip_1559_txs() { - let store = setup_store(); + let context = default_context(); for block_num in 1..100 { let mut txs = vec![]; for nonce in 1..=3 { @@ -239,20 +243,24 @@ mod tests { }; let block_header = test_header(block_num); let block = Block::new(block_header.clone(), block_body); - store.add_block(block).unwrap(); - store + context.storage.add_block(block).unwrap(); + context + .storage .set_canonical_block(block_num, block_header.compute_block_hash()) .unwrap(); - store.update_latest_block_number(block_num).unwrap(); + context + .storage + .update_latest_block_number(block_num) + .unwrap(); } let gas_price = GasPrice {}; - let response = gas_price.handle(store).unwrap(); + let response = gas_price.handle(context).unwrap(); let parsed_result = parse_json_hex(&response).unwrap(); assert_eq!(parsed_result, 2000000000); } #[test] fn test_with_mixed_transactions() { - let store = setup_store(); + let context = default_context(); for block_num in 1..100 { let txs = vec![ legacy_tx_for_test(1), @@ -267,20 +275,24 @@ mod tests { }; let block_header = test_header(block_num); let block = Block::new(block_header.clone(), block_body); - store.add_block(block).unwrap(); - store + context.storage.add_block(block).unwrap(); + context + .storage .set_canonical_block(block_num, block_header.compute_block_hash()) .unwrap(); - store.update_latest_block_number(block_num).unwrap(); + context + .storage + .update_latest_block_number(block_num) + .unwrap(); } let gas_price = GasPrice {}; - let response = gas_price.handle(store).unwrap(); + let response = gas_price.handle(context).unwrap(); let parsed_result = parse_json_hex(&response).unwrap(); assert_eq!(parsed_result, 2000000000); } #[test] fn test_with_not_enough_blocks_or_transactions() { - let store = setup_store(); + let context = default_context(); for block_num in 1..10 { let txs = vec![legacy_tx_for_test(1)]; let block_body = BlockBody { @@ -290,23 +302,27 @@ mod tests { }; let block_header = test_header(block_num); let block = Block::new(block_header.clone(), block_body); - store.add_block(block).unwrap(); - store + context.storage.add_block(block).unwrap(); + context + .storage .set_canonical_block(block_num, block_header.compute_block_hash()) .unwrap(); - store.update_latest_block_number(block_num).unwrap(); + context + .storage + .update_latest_block_number(block_num) + .unwrap(); } let gas_price = GasPrice {}; - let response = gas_price.handle(store).unwrap(); + let response = gas_price.handle(context).unwrap(); let parsed_result = parse_json_hex(&response).unwrap(); assert_eq!(parsed_result, 1000000000); } #[test] fn test_with_no_blocks_but_genesis() { - let store = setup_store(); + let context = default_context(); let gas_price = GasPrice {}; let expected_gas_price = MIN_GAS_LIMIT; - let response = gas_price.handle(store).unwrap(); + let response = gas_price.handle(context).unwrap(); let parsed_result = parse_json_hex(&response).unwrap(); assert_eq!(parsed_result, expected_gas_price); } @@ -320,7 +336,8 @@ mod tests { }); let expected_response = json!("0x3b9aca00"); let request: RpcRequest = serde_json::from_value(raw_json).expect("Test json is not valid"); - let storage = setup_store(); + let mut context = default_context(); + context.local_p2p_node = example_p2p_node(); for block_num in 1..100 { let txs = vec![legacy_tx_for_test(1)]; @@ -331,14 +348,31 @@ mod tests { }; let block_header = test_header(block_num); let block = Block::new(block_header.clone(), block_body); - storage.add_block(block).unwrap(); - storage + context.storage.add_block(block).unwrap(); + context + .storage .set_canonical_block(block_num, block_header.compute_block_hash()) .unwrap(); - storage.update_latest_block_number(block_num).unwrap(); + context + .storage + .update_latest_block_number(block_num) + .unwrap(); } - let response = - map_http_requests(&request, storage, example_p2p_node(), Default::default()).unwrap(); + let response = map_http_requests(&request, context).unwrap(); assert_eq!(response, expected_response) } + + fn default_context() -> RpcApiContext { + RpcApiContext { + storage: setup_store(), + jwt_secret: Default::default(), + local_p2p_node: Node { + ip: std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + udp_port: Default::default(), + tcp_port: Default::default(), + node_id: Default::default(), + }, + active_filters: Default::default(), + } + } } diff --git a/crates/networking/rpc/eth/logs.rs b/crates/networking/rpc/eth/logs.rs index cefeb83d62..b45034e70b 100644 --- a/crates/networking/rpc/eth/logs.rs +++ b/crates/networking/rpc/eth/logs.rs @@ -4,7 +4,7 @@ // - Ethereum's reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_newfilter use crate::{ types::{block_identifier::BlockIdentifier, receipt::RpcLog}, - RpcErr, RpcHandler, + RpcApiContext, RpcErr, RpcHandler, }; use ethereum_rust_core::{H160, H256}; use ethereum_rust_storage::Store; @@ -84,8 +84,8 @@ impl RpcHandler for LogsFilter { )), } } - fn handle(&self, storage: Store) -> Result { - let filtered_logs = fetch_logs_with_filter(self, storage)?; + fn handle(&self, context: RpcApiContext) -> Result { + let filtered_logs = fetch_logs_with_filter(self, context.storage)?; serde_json::to_value(filtered_logs).map_err(|error| { tracing::error!("Log filtering request failed with: {error}"); RpcErr::Internal("Failed to filter logs".to_string()) diff --git a/crates/networking/rpc/eth/transaction.rs b/crates/networking/rpc/eth/transaction.rs index d0cbcca825..a88dd98324 100644 --- a/crates/networking/rpc/eth/transaction.rs +++ b/crates/networking/rpc/eth/transaction.rs @@ -5,7 +5,7 @@ use crate::{ transaction::{RpcTransaction, SendRawTransactionRequest}, }, utils::RpcErr, - RpcHandler, + RpcApiContext, RpcHandler, }; use ethereum_rust_core::{ types::{AccessListEntry, BlockHash, BlockHeader, BlockNumber, GenericTransaction, TxKind}, @@ -96,16 +96,16 @@ impl RpcHandler for CallRequest { block, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { let block = self.block.clone().unwrap_or_default(); info!("Requested call on block: {}", block); - let header = match block.resolve_block_header(&storage)? { + let header = match block.resolve_block_header(&context.storage)? { Some(header) => header, // Block not found _ => return Ok(Value::Null), }; // Run transaction - let result = simulate_tx(&self.transaction, &header, storage, SpecId::CANCUN)?; + let result = simulate_tx(&self.transaction, &header, context.storage, SpecId::CANCUN)?; serde_json::to_value(format!("0x{:#x}", result.output())) .map_err(|error| RpcErr::Internal(error.to_string())) } @@ -132,20 +132,20 @@ impl RpcHandler for GetTransactionByBlockNumberAndIndexRequest { }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested transaction at index: {} of block with number: {}", self.transaction_index, self.block, ); - let block_number = match self.block.resolve_block_number(&storage)? { + let block_number = match self.block.resolve_block_number(&context.storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; - let block_body = match storage.get_block_body(block_number)? { + let block_body = match context.storage.get_block_body(block_number)? { Some(block_body) => block_body, _ => return Ok(Value::Null), }; - let block_header = match storage.get_block_header(block_number)? { + let block_header = match context.storage.get_block_header(block_number)? { Some(block_body) => block_body, _ => return Ok(Value::Null), }; @@ -183,16 +183,16 @@ impl RpcHandler for GetTransactionByBlockHashAndIndexRequest { .map_err(|error| RpcErr::BadParams(error.to_string()))?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { info!( "Requested transaction at index: {} of block with hash: {:#x}", self.transaction_index, self.block, ); - let block_number = match storage.get_block_number(self.block)? { + let block_number = match context.storage.get_block_number(self.block)? { Some(number) => number, _ => return Ok(Value::Null), }; - let block_body = match storage.get_block_body(block_number)? { + let block_body = match context.storage.get_block_body(block_number)? { Some(block_body) => block_body, _ => return Ok(Value::Null), }; @@ -221,7 +221,8 @@ impl RpcHandler for GetTransactionByHashRequest { transaction_hash: serde_json::from_value(params[0].clone())?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!( "Requested transaction with hash: {:#x}", self.transaction_hash, @@ -259,7 +260,8 @@ impl RpcHandler for GetTransactionReceiptRequest { transaction_hash: serde_json::from_value(params[0].clone())?, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; info!( "Requested receipt for transaction {:#x}", self.transaction_hash, @@ -274,7 +276,7 @@ impl RpcHandler for GetTransactionReceiptRequest { None => return Ok(Value::Null), }; let receipts = - block::get_all_block_rpc_receipts(block_number, block.header, block.body, &storage)?; + block::get_all_block_rpc_receipts(block_number, block.header, block.body, storage)?; serde_json::to_value(receipts.get(index as usize)) .map_err(|error| RpcErr::Internal(error.to_string())) } @@ -304,14 +306,14 @@ impl RpcHandler for CreateAccessListRequest { block, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { let block = self.block.clone().unwrap_or_default(); info!("Requested access list creation for tx on block: {}", block); - let block_number = match block.resolve_block_number(&storage)? { + let block_number = match block.resolve_block_number(&context.storage)? { Some(block_number) => block_number, _ => return Ok(Value::Null), }; - let header = match storage.get_block_header(block_number)? { + let header = match context.storage.get_block_header(block_number)? { Some(header) => header, // Block not found _ => return Ok(Value::Null), @@ -320,7 +322,7 @@ impl RpcHandler for CreateAccessListRequest { let (gas_used, access_list, error) = match ethereum_rust_vm::create_access_list( &self.transaction, &header, - &mut evm_state(storage, header.compute_block_hash()), + &mut evm_state(context.storage, header.compute_block_hash()), SpecId::CANCUN, )? { ( @@ -386,8 +388,10 @@ impl RpcHandler for GetRawTransaction { }) } - fn handle(&self, storage: Store) -> Result { - let tx = storage.get_transaction_by_hash(self.transaction_hash)?; + fn handle(&self, context: RpcApiContext) -> Result { + let tx = context + .storage + .get_transaction_by_hash(self.transaction_hash)?; let tx = match tx { Some(tx) => tx, @@ -423,10 +427,11 @@ impl RpcHandler for EstimateGasRequest { block, }) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { + let storage = &context.storage; let block = self.block.clone().unwrap_or_default(); info!("Requested estimate on block: {}", block); - let block_header = match block.resolve_block_header(&storage)? { + let block_header = match block.resolve_block_header(storage)? { Some(header) => header, // Block not found _ => return Ok(Value::Null), @@ -464,7 +469,7 @@ impl RpcHandler for EstimateGasRequest { highest_gas_limit = recap_with_account_balances( highest_gas_limit, &self.transaction, - &storage, + storage, block_header.number, )?; } @@ -570,15 +575,15 @@ impl RpcHandler for SendRawTransactionRequest { Ok(transaction) } - fn handle(&self, storage: Store) -> Result { + fn handle(&self, context: RpcApiContext) -> Result { let hash = if let SendRawTransactionRequest::EIP4844(wrapped_blob_tx) = self { mempool::add_blob_transaction( wrapped_blob_tx.tx.clone(), wrapped_blob_tx.blobs_bundle.clone(), - storage, + context.storage, ) } else { - mempool::add_transaction(self.to_transaction(), storage) + mempool::add_transaction(self.to_transaction(), context.storage) }?; serde_json::to_value(format!("{:#x}", hash)) .map_err(|error| RpcErr::Internal(error.to_string())) diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index 71d14787d9..645386c291 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -70,12 +70,12 @@ pub struct RpcApiContext { trait RpcHandler: Sized { fn parse(params: &Option>) -> Result; - fn call(req: &RpcRequest, storage: Store) -> Result { + fn call(req: &RpcRequest, context: RpcApiContext) -> Result { let request = Self::parse(&req.params)?; - request.handle(storage) + request.handle(context) } - fn handle(&self, storage: Store) -> Result; + fn handle(&self, context: RpcApiContext) -> Result; } const FILTER_DURATION: Duration = { @@ -149,15 +149,8 @@ pub async fn handle_http_request( State(service_context): State, body: String, ) -> Json { - let storage = service_context.storage; - let local_p2p_node = service_context.local_p2p_node; let req: RpcRequest = serde_json::from_str(&body).unwrap(); - let res = map_http_requests( - &req, - storage, - local_p2p_node, - service_context.active_filters, - ); + let res = map_http_requests(&req, service_context); rpc_response(req.id, res) } @@ -166,129 +159,116 @@ pub async fn handle_authrpc_request( auth_header: Option>>, body: String, ) -> Json { - let storage = service_context.storage; - let secret = service_context.jwt_secret; let req: RpcRequest = serde_json::from_str(&body).unwrap(); - match authenticate(secret, auth_header) { + match authenticate(&service_context.jwt_secret, auth_header) { Err(error) => rpc_response(req.id, Err(error)), Ok(()) => { // Proceed with the request - let res = map_authrpc_requests(&req, storage, service_context.active_filters); + let res = map_authrpc_requests(&req, service_context); rpc_response(req.id, res) } } } /// Handle requests that can come from either clients or other users -pub fn map_http_requests( - req: &RpcRequest, - storage: Store, - local_p2p_node: Node, - filters: ActiveFilters, -) -> Result { +pub fn map_http_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.namespace() { - Ok(RpcNamespace::Eth) => map_eth_requests(req, storage, filters), - Ok(RpcNamespace::Admin) => map_admin_requests(req, storage, local_p2p_node), - Ok(RpcNamespace::Debug) => map_debug_requests(req, storage), - Ok(RpcNamespace::Web3) => map_web3_requests(req, storage), + Ok(RpcNamespace::Eth) => map_eth_requests(req, context), + Ok(RpcNamespace::Admin) => map_admin_requests(req, context), + Ok(RpcNamespace::Debug) => map_debug_requests(req, context), + Ok(RpcNamespace::Web3) => map_web3_requests(req, context), _ => Err(RpcErr::MethodNotFound(req.method.clone())), } } /// Handle requests from consensus client -pub fn map_authrpc_requests( - req: &RpcRequest, - storage: Store, - active_filters: ActiveFilters, -) -> Result { +pub fn map_authrpc_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.namespace() { - Ok(RpcNamespace::Engine) => map_engine_requests(req, storage), - Ok(RpcNamespace::Eth) => map_eth_requests(req, storage, active_filters), + Ok(RpcNamespace::Engine) => map_engine_requests(req, context), + Ok(RpcNamespace::Eth) => map_eth_requests(req, context), _ => Err(RpcErr::MethodNotFound(req.method.clone())), } } -pub fn map_eth_requests( - req: &RpcRequest, - storage: Store, - filters: ActiveFilters, -) -> Result { +pub fn map_eth_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.method.as_str() { - "eth_chainId" => ChainId::call(req, storage), - "eth_syncing" => Syncing::call(req, storage), - "eth_getBlockByNumber" => GetBlockByNumberRequest::call(req, storage), - "eth_getBlockByHash" => GetBlockByHashRequest::call(req, storage), - "eth_getBalance" => GetBalanceRequest::call(req, storage), - "eth_getCode" => GetCodeRequest::call(req, storage), - "eth_getStorageAt" => GetStorageAtRequest::call(req, storage), + "eth_chainId" => ChainId::call(req, context), + "eth_syncing" => Syncing::call(req, context), + "eth_getBlockByNumber" => GetBlockByNumberRequest::call(req, context), + "eth_getBlockByHash" => GetBlockByHashRequest::call(req, context), + "eth_getBalance" => GetBalanceRequest::call(req, context), + "eth_getCode" => GetCodeRequest::call(req, context), + "eth_getStorageAt" => GetStorageAtRequest::call(req, context), "eth_getBlockTransactionCountByNumber" => { - GetBlockTransactionCountRequest::call(req, storage) + GetBlockTransactionCountRequest::call(req, context) } - "eth_getBlockTransactionCountByHash" => GetBlockTransactionCountRequest::call(req, storage), + "eth_getBlockTransactionCountByHash" => GetBlockTransactionCountRequest::call(req, context), "eth_getTransactionByBlockNumberAndIndex" => { - GetTransactionByBlockNumberAndIndexRequest::call(req, storage) + GetTransactionByBlockNumberAndIndexRequest::call(req, context) } "eth_getTransactionByBlockHashAndIndex" => { - GetTransactionByBlockHashAndIndexRequest::call(req, storage) + GetTransactionByBlockHashAndIndexRequest::call(req, context) } - "eth_getBlockReceipts" => GetBlockReceiptsRequest::call(req, storage), - "eth_getTransactionByHash" => GetTransactionByHashRequest::call(req, storage), - "eth_getTransactionReceipt" => GetTransactionReceiptRequest::call(req, storage), - "eth_createAccessList" => CreateAccessListRequest::call(req, storage), - "eth_blockNumber" => BlockNumberRequest::call(req, storage), - "eth_call" => CallRequest::call(req, storage), - "eth_blobBaseFee" => GetBlobBaseFee::call(req, storage), - "eth_getTransactionCount" => GetTransactionCountRequest::call(req, storage), - "eth_feeHistory" => FeeHistoryRequest::call(req, storage), - "eth_estimateGas" => EstimateGasRequest::call(req, storage), - "eth_getLogs" => LogsFilter::call(req, storage), - "eth_newFilter" => NewFilterRequest::stateful_call(req, storage, filters), - "eth_uninstallFilter" => DeleteFilterRequest::stateful_call(req, storage, filters), - "eth_getFilterChanges" => FilterChangesRequest::stateful_call(req, storage, filters), - "eth_sendRawTransaction" => SendRawTransactionRequest::call(req, storage), - "eth_getProof" => GetProofRequest::call(req, storage), - "eth_gasPrice" => GasPrice::call(req, storage), + "eth_getBlockReceipts" => GetBlockReceiptsRequest::call(req, context), + "eth_getTransactionByHash" => GetTransactionByHashRequest::call(req, context), + "eth_getTransactionReceipt" => GetTransactionReceiptRequest::call(req, context), + "eth_createAccessList" => CreateAccessListRequest::call(req, context), + "eth_blockNumber" => BlockNumberRequest::call(req, context), + "eth_call" => CallRequest::call(req, context), + "eth_blobBaseFee" => GetBlobBaseFee::call(req, context), + "eth_getTransactionCount" => GetTransactionCountRequest::call(req, context), + "eth_feeHistory" => FeeHistoryRequest::call(req, context), + "eth_estimateGas" => EstimateGasRequest::call(req, context), + "eth_getLogs" => LogsFilter::call(req, context), + "eth_newFilter" => { + NewFilterRequest::stateful_call(req, context.storage, context.active_filters) + } + "eth_uninstallFilter" => { + DeleteFilterRequest::stateful_call(req, context.storage, context.active_filters) + } + "eth_getFilterChanges" => { + FilterChangesRequest::stateful_call(req, context.storage, context.active_filters) + } + "eth_sendRawTransaction" => SendRawTransactionRequest::call(req, context), + "eth_getProof" => GetProofRequest::call(req, context), + "eth_gasPrice" => GasPrice::call(req, context), unknown_eth_method => Err(RpcErr::MethodNotFound(unknown_eth_method.to_owned())), } } -pub fn map_debug_requests(req: &RpcRequest, storage: Store) -> Result { +pub fn map_debug_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.method.as_str() { - "debug_getRawHeader" => GetRawHeaderRequest::call(req, storage), - "debug_getRawBlock" => GetRawBlockRequest::call(req, storage), - "debug_getRawTransaction" => GetRawTransaction::call(req, storage), - "debug_getRawReceipts" => GetRawReceipts::call(req, storage), + "debug_getRawHeader" => GetRawHeaderRequest::call(req, context), + "debug_getRawBlock" => GetRawBlockRequest::call(req, context), + "debug_getRawTransaction" => GetRawTransaction::call(req, context), + "debug_getRawReceipts" => GetRawReceipts::call(req, context), unknown_debug_method => Err(RpcErr::MethodNotFound(unknown_debug_method.to_owned())), } } -pub fn map_engine_requests(req: &RpcRequest, storage: Store) -> Result { +pub fn map_engine_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.method.as_str() { - "engine_exchangeCapabilities" => ExchangeCapabilitiesRequest::call(req, storage), - "engine_forkchoiceUpdatedV3" => ForkChoiceUpdatedV3::call(req, storage), - "engine_newPayloadV3" => NewPayloadV3Request::call(req, storage), + "engine_exchangeCapabilities" => ExchangeCapabilitiesRequest::call(req, context), + "engine_forkchoiceUpdatedV3" => ForkChoiceUpdatedV3::call(req, context), + "engine_newPayloadV3" => NewPayloadV3Request::call(req, context), "engine_exchangeTransitionConfigurationV1" => { - ExchangeTransitionConfigV1Req::call(req, storage) + ExchangeTransitionConfigV1Req::call(req, context) } - "engine_getPayloadV3" => GetPayloadV3Request::call(req, storage), + "engine_getPayloadV3" => GetPayloadV3Request::call(req, context), unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())), } } -pub fn map_admin_requests( - req: &RpcRequest, - storage: Store, - local_p2p_node: Node, -) -> Result { +pub fn map_admin_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.method.as_str() { - "admin_nodeInfo" => admin::node_info(storage, local_p2p_node), + "admin_nodeInfo" => admin::node_info(context.storage, context.local_p2p_node), unknown_admin_method => Err(RpcErr::MethodNotFound(unknown_admin_method.to_owned())), } } -pub fn map_web3_requests(req: &RpcRequest, storage: Store) -> Result { +pub fn map_web3_requests(req: &RpcRequest, context: RpcApiContext) -> Result { match req.method.as_str() { - "web3_clientVersion" => web3::client_version(req, storage), + "web3_clientVersion" => web3::client_version(req, context.storage), unknown_web3_method => Err(RpcErr::MethodNotFound(unknown_web3_method.to_owned())), } } @@ -340,7 +320,13 @@ mod tests { let storage = Store::new("temp.db", EngineType::InMemory).expect("Failed to create test DB"); storage.set_chain_config(&example_chain_config()).unwrap(); - let result = map_http_requests(&request, storage, local_p2p_node, Default::default()); + let context = RpcApiContext { + local_p2p_node, + storage, + jwt_secret: Default::default(), + active_filters: Default::default(), + }; + let result = map_http_requests(&request, context); let rpc_response = rpc_response(request.id, result); let expected_response = to_rpc_response_success_value( r#"{"jsonrpc":"2.0","id":1,"result":{"enode":"enode://d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666@127.0.0.1:30303","id":"d860a01f9722d78051619d1e2351aba3f43f943f6f00718d1b9baa4101932a1f5011f16bb2b1bb35db20d6fe28fa0bf09636d26a87d31de9ec6203eeedb1f666","ip":"127.0.0.1","name":"ethereum_rust/0.1.0/rust1.80","ports":{"discovery":30303,"listener":30303},"protocols":{"eth":{"chainId":3151908,"homesteadBlock":0,"daoForkBlock":null,"daoForkSupport":false,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":null,"berlinBlock":0,"londonBlock":0,"arrowGlacierBlock":null,"grayGlacierBlock":null,"mergeNetsplitBlock":0,"shanghaiTime":0,"cancunTime":0,"pragueTime":1718232101,"verkleTime":null,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true}}}}"#, @@ -371,7 +357,13 @@ mod tests { .expect("Failed to add genesis block to DB"); let local_p2p_node = example_p2p_node(); // Process request - let result = map_http_requests(&request, storage, local_p2p_node, Default::default()); + let context = RpcApiContext { + local_p2p_node, + storage, + jwt_secret: Default::default(), + active_filters: Default::default(), + }; + let result = map_http_requests(&request, context); let response = rpc_response(request.id, result); let expected_response = to_rpc_response_success_value( r#"{"jsonrpc":"2.0","id":1,"result":{"accessList":[],"gasUsed":"0x5208"}}"#, @@ -394,7 +386,13 @@ mod tests { .expect("Failed to add genesis block to DB"); let local_p2p_node = example_p2p_node(); // Process request - let result = map_http_requests(&request, storage, local_p2p_node, Default::default()); + let context = RpcApiContext { + local_p2p_node, + storage, + jwt_secret: Default::default(), + active_filters: Default::default(), + }; + let result = map_http_requests(&request, context); let response = serde_json::from_value::(rpc_response(request.id, result).0) .expect("Request failed");