From f2bbd86a5d1a455b5ad5547971e3373d6b4e2532 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 15 Feb 2025 00:28:06 +0300 Subject: [PATCH] Implement eth_getProof for Fork2 (#1881) --- bin/citrea/tests/common/helpers.rs | 3 +- bin/citrea/tests/mock/evm/archival_state.rs | 1 + bin/citrea/tests/mock/evm/fee.rs | 1 + bin/citrea/tests/mock/evm/gas_price.rs | 1 + bin/citrea/tests/mock/evm/mod.rs | 251 ++++++++++++++---- bin/citrea/tests/mock/evm/subscription.rs | 1 + bin/citrea/tests/mock/evm/tracing.rs | 1 + bin/citrea/tests/mock/mempool/mod.rs | 1 + bin/citrea/tests/mock/mod.rs | 7 + bin/citrea/tests/mock/proving.rs | 6 + bin/citrea/tests/mock/reopen.rs | 9 + bin/citrea/tests/mock/sequencer_behaviour.rs | 5 + .../tests/mock/sequencer_replacement.rs | 8 + .../soft_confirmation_rule_enforcer/mod.rs | 1 + bin/citrea/tests/mock/syncing.rs | 8 + crates/ethereum-rpc/src/lib.rs | 243 ++++++++++++++--- crates/evm/src/lib.rs | 2 +- crates/evm/src/provider_functions.rs | 14 +- 18 files changed, 462 insertions(+), 101 deletions(-) diff --git a/bin/citrea/tests/common/helpers.rs b/bin/citrea/tests/common/helpers.rs index 84c66f9cfa..ddbb4fb8af 100644 --- a/bin/citrea/tests/common/helpers.rs +++ b/bin/citrea/tests/common/helpers.rs @@ -57,6 +57,7 @@ pub async fn start_rollup( light_client_prover_config: Option, rollup_config: FullNodeConfig, sequencer_config: Option, + network: Option, ) -> TaskManager<()> { // create rollup config default creator function and use them here for the configs @@ -64,7 +65,7 @@ pub async fn start_rollup( // Fake receipts are receipts without the proof, they only include the journal, which makes them suitable for testing and development std::env::set_var("RISC0_DEV_MODE", "1"); - let mock_demo_rollup = MockDemoRollup::new(Network::Nightly); + let mock_demo_rollup = MockDemoRollup::new(network.unwrap_or(Network::Nightly)); if sequencer_config.is_some() && rollup_prover_config.is_some() { panic!("Both sequencer and batch prover config cannot be set at the same time"); diff --git a/bin/citrea/tests/mock/evm/archival_state.rs b/bin/citrea/tests/mock/evm/archival_state.rs index dd01305374..868d8d2ec4 100644 --- a/bin/citrea/tests/mock/evm/archival_state.rs +++ b/bin/citrea/tests/mock/evm/archival_state.rs @@ -42,6 +42,7 @@ async fn test_archival_state() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/evm/fee.rs b/bin/citrea/tests/mock/evm/fee.rs index 56ffe98b4c..7feb1d50ec 100644 --- a/bin/citrea/tests/mock/evm/fee.rs +++ b/bin/citrea/tests/mock/evm/fee.rs @@ -36,6 +36,7 @@ async fn test_minimum_base_fee() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/evm/gas_price.rs b/bin/citrea/tests/mock/evm/gas_price.rs index 274b00047e..0cbaafd64b 100644 --- a/bin/citrea/tests/mock/evm/gas_price.rs +++ b/bin/citrea/tests/mock/evm/gas_price.rs @@ -44,6 +44,7 @@ async fn test_gas_price_increase() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/evm/mod.rs b/bin/citrea/tests/mock/evm/mod.rs index 9f928b1877..2d379e890a 100644 --- a/bin/citrea/tests/mock/evm/mod.rs +++ b/bin/citrea/tests/mock/evm/mod.rs @@ -11,7 +11,8 @@ use citrea_evm::smart_contracts::{LogsContract, SimpleStorageContract, TestContr use citrea_evm::system_contracts::BitcoinLightClient; use citrea_stf::genesis_config::GenesisPaths; use reth_primitives::{BlockId, BlockNumberOrTag}; -use sov_rollup_interface::CITREA_VERSION; +use sha2::Digest; +use sov_rollup_interface::{Network, CITREA_VERSION}; use sov_state::KeyHash; use tokio::time::sleep; @@ -57,6 +58,7 @@ async fn web3_rpc_tests() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequener_config), + None, ) .await; }); @@ -118,6 +120,7 @@ async fn evm_tx_tests() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -159,6 +162,7 @@ async fn test_eth_get_logs() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -201,6 +205,7 @@ async fn test_genesis_contract_call() -> Result<(), Box> None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -256,70 +261,178 @@ fn check_proof(acc_proof: &EIP1186AccountProofResponse, account_address: Address let expected_root_hash = acc_proof.storage_hash.0.into(); // construct account key/values to be verified - let account_key = [b"Evm/a/\x14", account_address.as_slice()].concat(); - let account_hash = KeyHash::with::(account_key.clone()); - let proved_account = if acc_proof.account_proof[1] == Bytes::from("y") { - // Account exists and it's serialized form is: - let code_hash_bytes = if acc_proof.code_hash != KECCAK_EMPTY { - // 1 for Some and 32 for length - [&[1, 32], acc_proof.code_hash.0.as_slice()].concat() + if acc_proof.account_proof[0] == Bytes::from("fork1") { + dbg!("verify proof acc fork1"); + let account_key = [b"Evm/a/\x14", account_address.as_slice()].concat(); + let account_hash = KeyHash::with::(account_key.clone()); + let proved_account = if acc_proof.account_proof[2] == Bytes::from("y") { + // Account exists and it's serialized form is: + let code_hash_bytes = if acc_proof.code_hash != KECCAK_EMPTY { + // 1 for Some and 32 for length + [&[1, 32], acc_proof.code_hash.0.as_slice()].concat() + } else { + // 0 for None + vec![0] + }; + let bytes = [ + &[32], // balance length + acc_proof.balance.as_le_slice(), + &acc_proof.nonce.to_le_bytes(), + &code_hash_bytes, + ] + .concat(); + Some(bytes) } else { - // 0 for None - vec![0] + // Account does not exist + dbg!("fork1: acc does not exist"); + None }; - let bytes = [ - &[32], // balance length - acc_proof.balance.as_le_slice(), - &acc_proof.nonce.to_le_bytes(), - &code_hash_bytes, - ] - .concat(); - Some(bytes) - } else { - // Account does not exist - None - }; - let acc_storage_proof: jmt::proof::SparseMerkleProof = - borsh::from_slice(&acc_proof.account_proof[0]).unwrap(); + let acc_storage_proof: jmt::proof::SparseMerkleProof = + borsh::from_slice(&acc_proof.account_proof[1]).unwrap(); - acc_storage_proof - .verify(expected_root_hash, account_hash, proved_account) - .expect("Account proof must be valid"); + acc_storage_proof + .verify(expected_root_hash, account_hash, proved_account) + .expect("Account proof must be valid"); + } else if acc_proof.account_proof[0] == Bytes::from("fork2") { + dbg!("verify proof acc fork2"); + let account_key = [b"Evm/i/\x14", account_address.as_slice()].concat(); + let account_hash = KeyHash::with::(account_key.clone()); - for storage_proof in &acc_proof.storage_proof { - let storage_key = [ - b"Evm/s/", - acc_proof.address.as_slice(), - &[32], - U256::from_le_slice(storage_proof.key.0.as_slice()) - .to_be_bytes::<32>() - .as_slice(), - ] - .concat(); - let key_hash = KeyHash::with::(storage_key.clone()); + if acc_proof.account_proof.len() == 3 { + // Neither account index nor account exist + assert_eq!(acc_proof.account_proof[2], Bytes::from("n")); - let proved_value = if storage_proof.proof[1] == Bytes::from("y") { - // Storage value exists and it's serialized form is: - let bytes = [&[32], storage_proof.value.to_be_bytes::<32>().as_slice()].concat(); - Some(bytes) - } else { - // Storage value does not exist - None - }; + let acc_storage_proof: jmt::proof::SparseMerkleProof = + borsh::from_slice(&acc_proof.account_proof[1]).unwrap(); - let storage_proof: jmt::proof::SparseMerkleProof = - borsh::from_slice(&storage_proof.proof[0]).unwrap(); + acc_storage_proof + .verify(expected_root_hash, account_hash, None::>) + .expect("Account proof must be valid"); + } else { + // fork, index_proof, index_bytes, account_proof, account_exists + assert_eq!(acc_proof.account_proof.len(), 5); + + let proved_index_value = acc_proof.account_proof[2].to_vec(); + let account_idx = usize::from_le_bytes( + proved_index_value + .clone() + .try_into() + .expect("Must be exactly 8 bytes"), + ); + + let acc_index_proof: jmt::proof::SparseMerkleProof = + borsh::from_slice(&acc_proof.account_proof[1]).unwrap(); + + acc_index_proof + .verify(expected_root_hash, account_hash, Some(proved_index_value)) + .expect("Account proof index must be valid"); + + let proved_account = if acc_proof.account_proof[4] == Bytes::from("y") { + // Account exists and it's serialized form is: + let code_hash_bytes = if acc_proof.code_hash != KECCAK_EMPTY { + // 1 for Some and 32 for length + [&[1, 32], acc_proof.code_hash.0.as_slice()].concat() + } else { + // 0 for None + vec![0] + }; + let bytes = [ + &[32], // balance length + acc_proof.balance.as_le_slice(), + &acc_proof.nonce.to_le_bytes(), + &code_hash_bytes, + ] + .concat(); + Some(bytes) + } else { + // Account does not exist + dbg!("fork2: acc does not exist"); + None + }; + + let index_key = [b"Evm/t/", account_idx.to_le_bytes().as_slice()].concat(); + let index_hash = KeyHash::with::(index_key.clone()); + + let acc_proof: jmt::proof::SparseMerkleProof = + borsh::from_slice(&acc_proof.account_proof[3]).unwrap(); + + acc_proof + .verify(expected_root_hash, index_hash, proved_account) + .expect("Account proof must be valid"); + } + } else { + panic!("Unknown fork {}", acc_proof.account_proof[0]); + } - storage_proof - .verify(expected_root_hash, key_hash, proved_value) - .expect("Account storage proof must be valid"); + for storage_proof in &acc_proof.storage_proof { + if storage_proof.proof[0] == Bytes::from("fork1") { + let storage_key = [ + b"Evm/s/".as_slice(), + acc_proof.address.as_slice(), + &[32], + U256::from_le_slice(storage_proof.key.0.as_slice()) + .to_be_bytes::<32>() + .as_slice(), + ] + .concat(); + let key_hash = KeyHash::with::(storage_key.clone()); + + let proved_value = if storage_proof.proof[2] == Bytes::from("y") { + // Storage value exists and it's serialized form is: + let bytes = [&[32], storage_proof.value.to_be_bytes::<32>().as_slice()].concat(); + Some(bytes) + } else { + // Storage value does not exist + dbg!("fork1: storage does not exist"); + None + }; + + let storage_proof: jmt::proof::SparseMerkleProof = + borsh::from_slice(&storage_proof.proof[1]).unwrap(); + + storage_proof + .verify(expected_root_hash, key_hash, proved_value) + .expect("Account storage proof must be valid"); + } else if storage_proof.proof[0] == Bytes::from("fork2") { + let kaddr = { + let mut hasher: sha2::Sha256 = + sha2::Digest::new_with_prefix(account_address.as_slice()); + hasher.update(storage_proof.key.0.as_slice()); + let arr = hasher.finalize(); + U256::from_le_slice(&arr) + }; + let storage_key = [ + b"Evm/s/".as_slice(), + &[32], + kaddr.to_be_bytes::<32>().as_slice(), + ] + .concat(); + let key_hash = KeyHash::with::(storage_key.clone()); + + let proved_value = if storage_proof.proof[2] == Bytes::from("y") { + // Storage value exists and it's serialized form is: + let bytes = [&[32], storage_proof.value.to_be_bytes::<32>().as_slice()].concat(); + Some(bytes) + } else { + // Storage value does not exist + dbg!("fork2: storage does not exist"); + None + }; + + let storage_proof: jmt::proof::SparseMerkleProof = + borsh::from_slice(&storage_proof.proof[1]).unwrap(); + + storage_proof + .verify(expected_root_hash, key_hash, proved_value) + .expect("Account storage proof must be valid"); + } else { + panic!("Unknown fork {}", storage_proof.proof[0]); + } } } -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_eth_get_proof() -> Result<(), Box> { +async fn test_eth_get_proof_on(network: Network) -> Result<(), Box> { // citrea::initialize_logging(::tracing::Level::INFO); let (seq_port_tx, seq_port_rx) = tokio::sync::oneshot::channel(); @@ -338,7 +451,7 @@ async fn test_eth_get_proof() -> Result<(), Box> { min_soft_confirmations_per_commitment: 123456, ..Default::default() }; - let seq_task = tokio::spawn(async { + let seq_task = tokio::spawn(async move { start_rollup( seq_port_tx, GenesisPaths::from_dir("../../resources/genesis/mock-dockerized/"), @@ -346,6 +459,7 @@ async fn test_eth_get_proof() -> Result<(), Box> { None, rollup_config, Some(sequencer_config), + Some(network), ) .await; }); @@ -388,8 +502,19 @@ async fn test_eth_get_proof() -> Result<(), Box> { .unwrap() ); + let non_existing_field = U256::from(42); + let non_existing_value = seq_test_client + .eth_get_storage_at(contract_address, non_existing_field, None) + .await + .unwrap(); + assert_eq!(non_existing_value, U256::ZERO); + let acc_proof_latest = seq_test_client - .eth_get_proof(contract_address, vec![contract_field], None) + .eth_get_proof( + contract_address, + vec![contract_field, non_existing_field], + None, + ) .await .unwrap(); @@ -413,7 +538,7 @@ async fn test_eth_get_proof() -> Result<(), Box> { let acc_proof_1 = seq_test_client .eth_get_proof( contract_address, - vec![contract_field], + vec![contract_field, non_existing_field], Some(BlockNumberOrTag::Number(block_num)), ) .await @@ -422,7 +547,7 @@ async fn test_eth_get_proof() -> Result<(), Box> { let acc_proof_2 = seq_test_client .eth_get_proof( contract_address, - vec![contract_field], + vec![contract_field, non_existing_field], Some(BlockNumberOrTag::Number(block_num - 1)), ) .await @@ -462,6 +587,16 @@ async fn test_eth_get_proof() -> Result<(), Box> { Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn test_eth_get_proof_fork0() -> Result<(), Box> { + test_eth_get_proof_on(Network::Devnet).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth_get_proof_fork2() -> Result<(), Box> { + test_eth_get_proof_on(Network::Nightly).await +} + #[allow(clippy::borrowed_box)] async fn test_getlogs(client: &Box) -> Result<(), Box> { let (contract_address, contract) = { diff --git a/bin/citrea/tests/mock/evm/subscription.rs b/bin/citrea/tests/mock/evm/subscription.rs index f259b7978a..67bb26e252 100644 --- a/bin/citrea/tests/mock/evm/subscription.rs +++ b/bin/citrea/tests/mock/evm/subscription.rs @@ -50,6 +50,7 @@ async fn test_eth_subscriptions() -> Result<(), Box> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/evm/tracing.rs b/bin/citrea/tests/mock/evm/tracing.rs index a9ed9bb97c..e6d9bae8e2 100644 --- a/bin/citrea/tests/mock/evm/tracing.rs +++ b/bin/citrea/tests/mock/evm/tracing.rs @@ -44,6 +44,7 @@ async fn tracing_tests() -> Result<(), Box> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/mempool/mod.rs b/bin/citrea/tests/mock/mempool/mod.rs index ff6997611f..d882c9e0f5 100644 --- a/bin/citrea/tests/mock/mempool/mod.rs +++ b/bin/citrea/tests/mock/mempool/mod.rs @@ -38,6 +38,7 @@ async fn initialize_test( None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/mod.rs b/bin/citrea/tests/mock/mod.rs index 2d44e56b9b..df44a7020f 100644 --- a/bin/citrea/tests/mock/mod.rs +++ b/bin/citrea/tests/mock/mod.rs @@ -89,6 +89,7 @@ async fn test_all_flow() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -118,6 +119,7 @@ async fn test_all_flow() { None, rollup_config, None, + None, ) .await; }); @@ -143,6 +145,7 @@ async fn test_all_flow() { None, rollup_config, None, + None, ) .await; }); @@ -406,6 +409,7 @@ async fn test_ledger_get_head_soft_confirmation() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -475,6 +479,7 @@ async fn initialize_test( None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -499,6 +504,7 @@ async fn initialize_test( None, rollup_config, None, + None, ) .await; }); @@ -630,6 +636,7 @@ async fn test_offchain_contract_storage() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/proving.rs b/bin/citrea/tests/mock/proving.rs index eade183832..b194a9c060 100644 --- a/bin/citrea/tests/mock/proving.rs +++ b/bin/citrea/tests/mock/proving.rs @@ -46,6 +46,7 @@ async fn full_node_verify_proof_and_store() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -77,6 +78,7 @@ async fn full_node_verify_proof_and_store() { None, rollup_config, None, + None, ) .await; }); @@ -102,6 +104,7 @@ async fn full_node_verify_proof_and_store() { None, rollup_config, None, + None, ) .await; }); @@ -241,6 +244,7 @@ async fn test_batch_prover_prove_rpc() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -273,6 +277,7 @@ async fn test_batch_prover_prove_rpc() { None, rollup_config, None, + None, ) .await; }); @@ -298,6 +303,7 @@ async fn test_batch_prover_prove_rpc() { None, rollup_config, None, + None, ) .await; }); diff --git a/bin/citrea/tests/mock/reopen.rs b/bin/citrea/tests/mock/reopen.rs index d94002041a..0685843fba 100644 --- a/bin/citrea/tests/mock/reopen.rs +++ b/bin/citrea/tests/mock/reopen.rs @@ -46,6 +46,7 @@ async fn test_reopen_full_node() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -70,6 +71,7 @@ async fn test_reopen_full_node() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -151,6 +153,7 @@ async fn test_reopen_full_node() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -211,6 +214,7 @@ async fn test_reopen_sequencer() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -260,6 +264,7 @@ async fn test_reopen_sequencer() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -325,6 +330,7 @@ async fn test_reopen_prover() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -357,6 +363,7 @@ async fn test_reopen_prover() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -427,6 +434,7 @@ async fn test_reopen_prover() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -481,6 +489,7 @@ async fn test_reopen_prover() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); diff --git a/bin/citrea/tests/mock/sequencer_behaviour.rs b/bin/citrea/tests/mock/sequencer_behaviour.rs index c29688713f..ee81969f0c 100644 --- a/bin/citrea/tests/mock/sequencer_behaviour.rs +++ b/bin/citrea/tests/mock/sequencer_behaviour.rs @@ -57,6 +57,7 @@ async fn test_sequencer_fill_missing_da_blocks() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -164,6 +165,7 @@ async fn test_sequencer_commitment_threshold() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -372,6 +374,7 @@ async fn test_gas_limit_too_high() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -396,6 +399,7 @@ async fn test_gas_limit_too_high() { None, rollup_config, None, + None, ) .await; }); @@ -511,6 +515,7 @@ async fn test_system_tx_effect_on_block_gas_limit() -> Result<(), anyhow::Error> None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/sequencer_replacement.rs b/bin/citrea/tests/mock/sequencer_replacement.rs index 1d621039d8..6e6733b92a 100644 --- a/bin/citrea/tests/mock/sequencer_replacement.rs +++ b/bin/citrea/tests/mock/sequencer_replacement.rs @@ -62,6 +62,7 @@ async fn test_sequencer_crash_and_replace_full_node() -> Result<(), anyhow::Erro None, rollup_config, Some(config1), + None, ) .await; }); @@ -87,6 +88,7 @@ async fn test_sequencer_crash_and_replace_full_node() -> Result<(), anyhow::Erro None, rollup_config, None, + None, ) .await; }); @@ -149,6 +151,7 @@ async fn test_sequencer_crash_and_replace_full_node() -> Result<(), anyhow::Erro None, rollup_config, Some(config1), + None, ) .await; }); @@ -221,6 +224,7 @@ async fn test_sequencer_crash_restore_mempool() -> Result<(), anyhow::Error> { None, rollup_config, Some(config1), + None, ) .await; }); @@ -313,6 +317,7 @@ async fn test_sequencer_crash_restore_mempool() -> Result<(), anyhow::Error> { None, rollup_config, Some(config1), + None, ) .await; }); @@ -419,6 +424,7 @@ async fn test_soft_confirmation_save() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -443,6 +449,7 @@ async fn test_soft_confirmation_save() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -467,6 +474,7 @@ async fn test_soft_confirmation_save() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); diff --git a/bin/citrea/tests/mock/soft_confirmation_rule_enforcer/mod.rs b/bin/citrea/tests/mock/soft_confirmation_rule_enforcer/mod.rs index 831cca37c4..a438311cd8 100644 --- a/bin/citrea/tests/mock/soft_confirmation_rule_enforcer/mod.rs +++ b/bin/citrea/tests/mock/soft_confirmation_rule_enforcer/mod.rs @@ -49,6 +49,7 @@ async fn too_many_l2_block_per_l1_block() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); diff --git a/bin/citrea/tests/mock/syncing.rs b/bin/citrea/tests/mock/syncing.rs index 724dd08210..6434d74229 100644 --- a/bin/citrea/tests/mock/syncing.rs +++ b/bin/citrea/tests/mock/syncing.rs @@ -55,6 +55,7 @@ async fn test_delayed_sync_ten_blocks() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -91,6 +92,7 @@ async fn test_delayed_sync_ten_blocks() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -292,6 +294,7 @@ async fn test_prover_sync_with_commitments() -> Result<(), anyhow::Error> { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -320,6 +323,7 @@ async fn test_prover_sync_with_commitments() -> Result<(), anyhow::Error> { None, rollup_config, None, + None, ) .await; }); @@ -439,6 +443,7 @@ async fn test_full_node_sync_status() { None, rollup_config, Some(sequencer_config), + None, ) .await; }); @@ -475,6 +480,7 @@ async fn test_full_node_sync_status() { None, rollup_config, None, + None, ) .await; }); @@ -565,6 +571,7 @@ async fn test_healthcheck() { None, rollup_config, Some(sequencer_config), + None, ) .await; @@ -587,6 +594,7 @@ async fn test_healthcheck() { None, rollup_config, None, + None, ) .await; diff --git a/crates/ethereum-rpc/src/lib.rs b/crates/ethereum-rpc/src/lib.rs index ff11ac2d93..18a919b543 100644 --- a/crates/ethereum-rpc/src/lib.rs +++ b/crates/ethereum-rpc/src/lib.rs @@ -10,7 +10,7 @@ use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64}; use alloy_rpc_types::serde_helpers::JsonStorageKey; use alloy_rpc_types::{EIP1186AccountProofResponse, EIP1186StorageProof, FeeHistory, Index}; use alloy_rpc_types_trace::geth::{GethDebugTracingOptions, GethTrace, TraceResult}; -use citrea_evm::{Evm, Filter}; +use citrea_evm::{DbAccount, Evm, Filter}; use citrea_primitives::forks::fork_from_block_number; use citrea_sequencer::SequencerRpcClient; pub use ethereum::{EthRpcConfig, Ethereum}; @@ -29,7 +29,7 @@ use sov_db::ledger_db::{LedgerDB, SharedLedgerOps}; use sov_ledger_rpc::LedgerRpcClient; use sov_modules_api::da::BlockHeaderTrait; use sov_modules_api::utils::to_jsonrpsee_error_object; -use sov_modules_api::{SpecId as CitreaSpecId, WorkingSet}; +use sov_modules_api::{SpecId as CitreaSpecId, StateMapAccessor, WorkingSet}; use sov_rollup_interface::services::da::DaService; use sov_state::storage::NativeStorage; use tokio::join; @@ -250,7 +250,14 @@ where .map_err(to_eth_rpc_error) } - // Implemented for Genesis and Fork1 only. Not for Fork2 yet. + // For account for fork1 we return: + // fork1, account_proof, account_exists (y/n) + // For account for fork2 we return one of: + // - fork2, index_proof, n + // - fork2, index_proof, index (little endian, 8 bytes), account_proof, account_exists (y) + // + // For storages we return: + // fork1/fork2, value_proof, value_exists (y/n) fn eth_get_proof( &self, address: Address, @@ -277,12 +284,6 @@ where let citrea_spec = fork_from_block_number(block_id_internal).spec_id; - if citrea_spec >= CitreaSpecId::Fork2 { - return Err(EthApiError::EvmCustom( - "Method not implemented yet for >= Fork2".into(), - ))?; - } - evm.set_state_to_end_of_evm_block_by_block_id(block_id, &mut working_set)?; let version = block_id_internal @@ -293,36 +294,167 @@ where .get_root_hash(version) .map_err(|_| EthApiError::EvmCustom("Root hash not found".into()))?; - let account = evm - .account_info(&address, citrea_spec, &mut working_set) - .unwrap_or_default(); + let account_in_fork1 = evm.account_info_prefork2(&address, &mut working_set); + let account_in_fork2 = evm.account_info_postfork2(&address, &mut working_set); + let account_should_gen_prefork2_proof = citrea_spec < CitreaSpecId::Fork2 + || (account_in_fork1.is_some() && account_in_fork2.is_none()); + + let account = if account_should_gen_prefork2_proof { + account_in_fork1.unwrap_or_default() + } else { + account_in_fork2.unwrap_or_default() + }; let balance = account.balance; let nonce = account.nonce; let code_hash = account.code_hash.unwrap_or(KECCAK_EMPTY); - let account_key = StorageKey::new( - evm.accounts_prefork2.prefix(), - &address, - evm.accounts_prefork2.codec().key_codec(), - ); + // Remove before mainet + fn generate_account_proof_prefork2( + evm: &Evm, + account: &Address, + version: u64, + working_set: &mut WorkingSet, + ) -> Vec + where + C: sov_modules_api::Context, + C::Storage: NativeStorage, + { + let account_key = StorageKey::new( + evm.accounts_prefork2.prefix(), + &account, + evm.accounts_prefork2.codec().key_codec(), + ); + + let account_proof = working_set.get_with_proof(account_key, version); + let fork = Bytes::from("fork1"); // Remove before mainet + let account_exists = if account_proof.value.is_some() { + Bytes::from("y") + } else { + Bytes::from("n") + }; + let account_proof = + borsh::to_vec(&account_proof.proof).expect("Serialization shouldn't fail"); + let account_proof = Bytes::from(account_proof); + vec![fork, account_proof, account_exists] + } - let account_proof = working_set.get_with_proof(account_key, version); - let account_exists = if account_proof.value.is_some() { - Bytes::from("y") - } else { - Bytes::from("n") - }; - let account_proof = - borsh::to_vec(&account_proof.proof).expect("Serialization shouldn't fail"); - let account_proof = Bytes::from(account_proof); + fn generate_account_proof_postfork2( + evm: &Evm, + account: &Address, + version: u64, + working_set: &mut WorkingSet, + ) -> Vec + where + C: sov_modules_api::Context, + C::Storage: NativeStorage, + { + let fork = Bytes::from("fork2"); // Remove before mainet + + let index_key = StorageKey::new( + evm.account_idxs.prefix(), + &account, + evm.account_idxs.codec().key_codec(), + ); + let index_proof = working_set.get_with_proof(index_key, version); + let index_proof_exists = index_proof.value.is_some(); + let index_proof = + borsh::to_vec(&index_proof.proof).expect("Serialization shouldn't fail"); + let index_proof = Bytes::from(index_proof); + + if index_proof_exists { + // we have to generate another proof for idx -> account + let index = evm + .account_idxs + .get(account, working_set) + .expect("Account index exists"); + let index_bytes = Bytes::from_iter(index.to_le_bytes()); + + let account_key = StorageKey::new( + evm.accounts_postfork2.prefix(), + &index, + evm.accounts_postfork2.codec().key_codec(), + ); + + let account_proof = working_set.get_with_proof(account_key, version); + let account_exists = if account_proof.value.is_some() { + Bytes::from("y") + } else { + Bytes::from("n") + }; + let account_proof = + borsh::to_vec(&account_proof.proof).expect("Serialization shouldn't fail"); + let account_proof = Bytes::from(account_proof); + vec![ + fork, + index_proof, + index_bytes, + account_proof, + account_exists, + ] + } else { + let index_exists = Bytes::from("n"); - let mut storage_proof = vec![]; - for key in keys { - let key: U256 = key.0.into(); - let storage_key = - StorageKey::new(evm.storage.prefix(), &key, evm.storage.codec().key_codec()); - let value = evm.storage_get(&address, &key, citrea_spec, &mut working_set); + vec![fork, index_proof, index_exists] + } + } + + // Remove before mainet + fn generate_storage_proof_prefork2( + evm: &Evm, + account: &Address, + key: &U256, + citrea_spec: CitreaSpecId, + version: u64, + working_set: &mut WorkingSet, + ) -> EIP1186StorageProof + where + C: sov_modules_api::Context, + C::Storage: NativeStorage, + { + let db_account = DbAccount::new(account); + let storage_key = StorageKey::new( + db_account.storage.prefix(), + key, + evm.storage.codec().key_codec(), + ); + let value = evm.storage_get(account, key, citrea_spec, working_set); + let proof = working_set.get_with_proof(storage_key, version); + let fork = Bytes::from("fork1"); // Remove before mainet + let value_exists = if proof.value.is_some() { + Bytes::from("y") + } else { + Bytes::from("n") + }; + let value_proof = borsh::to_vec(&proof.proof).expect("Serialization shouldn't fail"); + let value_proof = Bytes::from(value_proof); + EIP1186StorageProof { + key: JsonStorageKey(key.to_le_bytes().into()), + value: value.unwrap_or_default(), + proof: vec![fork, value_proof, value_exists], + } + } + + fn generate_storage_proof_postfork2( + evm: &Evm, + account: &Address, + key: &U256, + citrea_spec: CitreaSpecId, + version: u64, + working_set: &mut WorkingSet, + ) -> EIP1186StorageProof + where + C: sov_modules_api::Context, + C::Storage: NativeStorage, + { + let kaddr = Evm::::get_storage_address(account, key); + let storage_key = StorageKey::new( + evm.storage.prefix(), + &kaddr, + evm.storage.codec().key_codec(), + ); + let value = evm.storage_get(account, key, citrea_spec, working_set); let proof = working_set.get_with_proof(storage_key, version); + let fork = Bytes::from("fork2"); // Remove before mainet let value_exists = if proof.value.is_some() { Bytes::from("y") } else { @@ -330,11 +462,50 @@ where }; let value_proof = borsh::to_vec(&proof.proof).expect("Serialization shouldn't fail"); let value_proof = Bytes::from(value_proof); - storage_proof.push(EIP1186StorageProof { + EIP1186StorageProof { key: JsonStorageKey(key.to_le_bytes().into()), value: value.unwrap_or_default(), - proof: vec![value_proof, value_exists], - }); + proof: vec![fork, value_proof, value_exists], + } + } + + let account_proof = if account_should_gen_prefork2_proof { + generate_account_proof_prefork2(&evm, &address, version, &mut working_set) + } else { + generate_account_proof_postfork2(&evm, &address, version, &mut working_set) + }; + + let mut storage_proof = vec![]; + for key in keys { + let key: U256 = key.0.into(); + let in_fork1 = evm + .storage_get_prefork2(&address, &key, &mut working_set) + .is_some(); + let in_fork2 = evm + .storage_get_postfork2(&address, &key, &mut working_set) + .is_some(); + let should_gen_prefork2_proof = + citrea_spec < CitreaSpecId::Fork2 || (in_fork1 && !in_fork2); + let proof = if should_gen_prefork2_proof { + generate_storage_proof_prefork2( + &evm, + &address, + &key, + citrea_spec, + version, + &mut working_set, + ) + } else { + generate_storage_proof_postfork2( + &evm, + &address, + &key, + citrea_spec, + version, + &mut working_set, + ) + }; + storage_proof.push(proof); } Ok(EIP1186AccountProofResponse { @@ -343,7 +514,7 @@ where nonce, code_hash, storage_hash: root_hash.into(), - account_proof: vec![account_proof, account_exists], + account_proof, storage_proof, }) } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index bc523e6f50..c161674ae3 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -82,7 +82,7 @@ pub struct Evm { /// Mapping from account address to account id. #[state(rename = "i")] - pub(crate) account_idxs: sov_modules_api::StateMap, + pub account_idxs: sov_modules_api::StateMap, /// Mapping from account address to account state. #[state(rename = "a")] diff --git a/crates/evm/src/provider_functions.rs b/crates/evm/src/provider_functions.rs index d413c4cb8d..7d299a47cb 100644 --- a/crates/evm/src/provider_functions.rs +++ b/crates/evm/src/provider_functions.rs @@ -33,7 +33,8 @@ impl Evm { } } - fn account_info_prefork2( + /// Get account info < Fork2 + pub fn account_info_prefork2( &self, address: &Address, working_set: &mut WorkingSet, @@ -41,7 +42,8 @@ impl Evm { self.accounts_prefork2.get(address, working_set) } - fn account_info_postfork2( + /// Get account info >= Fork2 + pub fn account_info_postfork2( &self, address: &Address, working_set: &mut WorkingSet, @@ -131,14 +133,15 @@ impl Evm { } /// Get the address of a storage key for the given account - fn get_storage_address(account: &Address, key: &U256) -> U256 { + pub fn get_storage_address(account: &Address, key: &U256) -> U256 { let mut hasher: sha2::Sha256 = sha2::Digest::new_with_prefix(account.as_slice()); hasher.update(key.as_le_slice()); let arr = hasher.finalize(); U256::from_le_slice(&arr) } - fn storage_get_prefork2( + /// Get storage value < Fork2 + pub fn storage_get_prefork2( &self, account: &Address, key: &U256, @@ -148,7 +151,8 @@ impl Evm { db_account.storage.get(key, working_set) } - fn storage_get_postfork2( + /// Get storage value >= Fork2 + pub fn storage_get_postfork2( &self, account: &Address, key: &U256,