diff --git a/axiom-core/Cargo.toml b/axiom-core/Cargo.toml index 639a2f36..31de50af 100644 --- a/axiom-core/Cargo.toml +++ b/axiom-core/Cargo.toml @@ -29,10 +29,10 @@ anyhow = "1.0" hex = "0.4.3" # halo2, features turned on by axiom-eth -axiom-eth = { version = "=0.4.1", path = "../axiom-eth", default-features = false, features = ["providers", "aggregation", "evm"] } +axiom-eth = { version = "0.4.1", path = "../axiom-eth", default-features = false, features = ["providers", "aggregation", "evm"] } # crypto -ethers-core = { version = "=2.0.10" } +ethers-core = { version = "=2.0.14" } # keygen clap = { version = "=4.4.7", features = ["derive"], optional = true } diff --git a/axiom-eth/Cargo.toml b/axiom-eth/Cargo.toml index 3ddb7cb3..a99864e7 100644 --- a/axiom-eth/Cargo.toml +++ b/axiom-eth/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axiom-eth" -version = "0.4.1" +version = "0.4.2" authors = ["Intrinsic Technologies"] license = "MIT" edition = "2021" @@ -35,7 +35,7 @@ zkevm-hashes = { git = "https://github.com/axiom-crypto/halo2-lib.git", tag = "v # crypto rlp = "=0.5.2" -ethers-core = { version = "=2.0.10" } # fix the version as it affects what fields are included in the `Block` struct +ethers-core = { version = "=2.0.14", features = ["optimism"] } # fix the version as it affects what fields are included in the `Block` struct # mpt implementation hasher = { version = "=0.1", features = ["hash-keccak"] } cita_trie = "=5.0.0" @@ -53,7 +53,7 @@ snark-verifier = { git = "https://github.com/axiom-crypto/snark-verifier.git", t snark-verifier-sdk = { git = "https://github.com/axiom-crypto/snark-verifier.git", tag = "v0.1.7-git", default-features = false } # generating circuit inputs from blockchain -ethers-providers = { version = "=2.0.10", optional = true } +ethers-providers = { version = "=2.0.14", optional = true, features = ["optimism"] } tokio = { version = "1.28", default-features = false, features = ["rt", "rt-multi-thread", "macros"], optional = true } futures = { version = "0.3", optional = true } diff --git a/axiom-eth/src/providers/block.rs b/axiom-eth/src/providers/block.rs index d326d975..5f861097 100644 --- a/axiom-eth/src/providers/block.rs +++ b/axiom-eth/src/providers/block.rs @@ -1,5 +1,5 @@ use ethers_core::{ - types::{Block, H256, U64}, + types::{Block, H256}, utils::keccak256, }; use ethers_providers::{JsonRpcClient, Middleware, Provider, ProviderError}; @@ -36,22 +36,10 @@ pub fn get_block_rlp(block: &Block) -> Vec { let base_fee = block.base_fee_per_gas; let withdrawals_root: Option = block.withdrawals_root; // EIP-4844: - let other = &block.other; - let mut blob_gas_used = other.get("blobGasUsed"); // EIP-4844 spec - if blob_gas_used.is_none() { - blob_gas_used = other.get("dataGasUsed"); // EIP-4788 spec - } - let blob_gas_used: Option = - blob_gas_used.map(|v| serde_json::from_value(v.clone()).unwrap()); - let mut excess_blob_gas = other.get("excessBlobGas"); // EIP-4844 spec - if excess_blob_gas.is_none() { - excess_blob_gas = other.get("excessDataGas"); // EIP-4788 spec - } - let excess_blob_gas: Option = - excess_blob_gas.map(|v| serde_json::from_value(v.clone()).unwrap()); + let blob_gas_used = block.blob_gas_used; + let excess_blob_gas = block.excess_blob_gas; // EIP-4788: - let parent_beacon_block_root: Option = - other.get("parentBeaconBlockRoot").map(|v| serde_json::from_value(v.clone()).unwrap()); + let parent_beacon_block_root: Option = block.parent_beacon_block_root; let mut rlp_len = 15; for opt in [ @@ -116,25 +104,32 @@ mod tests { use super::*; - #[test] - fn test_retry_provider() { + #[tokio::test] + async fn test_retry_provider() { let provider = setup_provider(Chain::Mainnet); + let latest = provider.get_block_number().await.unwrap().as_u64(); + for block_num in [5_000_050, 5_000_051, 17_034_973, 19_426_587, 19_426_589, latest] { + let block = provider.get_block(block_num).await.unwrap().unwrap(); + get_block_rlp(&block); + } + } - let rt = Runtime::new().unwrap(); - for block_num in [5000050, 5000051, 17034973] { - let block = rt.block_on(provider.get_block(block_num)).unwrap().unwrap(); + #[tokio::test] + async fn test_retry_provider_base() { + let provider = setup_provider(Chain::Base); + let latest = provider.get_block_number().await.unwrap().as_u64(); + for block_num in [0, 100, 100_000, 5_000_050, 7_000_000, 8_000_000, 11_864_572, latest] { + let block = provider.get_block(block_num).await.unwrap().unwrap(); get_block_rlp(&block); } } - #[test] - fn test_retry_provider_sepolia() { + #[tokio::test] + async fn test_retry_provider_sepolia() { let provider = setup_provider(Chain::Sepolia); - - let rt = Runtime::new().unwrap(); - let latest = rt.block_on(provider.get_block_number()).unwrap(); - for block_num in [0, 5000050, 5187023, 5187810, 5187814, latest.as_u64()] { - let block = rt.block_on(provider.get_block(block_num)).unwrap().unwrap(); + let latest = provider.get_block_number().await.unwrap().as_u64(); + for block_num in [0, 5000050, 5187023, 5187810, 5187814, latest] { + let block = provider.get_block(block_num).await.unwrap().unwrap(); get_block_rlp(&block); } } diff --git a/axiom-eth/src/providers/mod.rs b/axiom-eth/src/providers/mod.rs index 9ceb455e..5c842a73 100644 --- a/axiom-eth/src/providers/mod.rs +++ b/axiom-eth/src/providers/mod.rs @@ -12,7 +12,18 @@ pub mod transaction; pub fn get_provider_uri(chain: Chain) -> String { let key = var("ALCHEMY_KEY").expect("ALCHEMY_KEY environmental variable not set"); - format!("https://eth-{chain}.g.alchemy.com/v2/{key}") + match chain { + Chain::Base | Chain::Optimism | Chain::Arbitrum => { + format!("https://{chain}-mainnet.g.alchemy.com/v2/{key}") + } + Chain::Mainnet | Chain::Sepolia => { + format!("https://eth-{chain}.g.alchemy.com/v2/{key}") + } + _ => { + // Catch-all, may not always work + format!("https://{chain}.g.alchemy.com/v2/{key}") + } + } } pub fn setup_provider(chain: Chain) -> Provider> { diff --git a/axiom-eth/src/providers/receipt.rs b/axiom-eth/src/providers/receipt.rs index eb2284e4..a2a689c4 100644 --- a/axiom-eth/src/providers/receipt.rs +++ b/axiom-eth/src/providers/receipt.rs @@ -1,39 +1,245 @@ +use std::collections::HashMap; use std::sync::Arc; +use anyhow::anyhow; use cita_trie::{MemoryDB, PatriciaTrie, Trie}; -use ethers_core::types::{TransactionReceipt, H256}; +use ethers_core::types::{Address, Bloom, Bytes, Log, OtherFields, H256, U128, U256, U64}; use ethers_providers::{JsonRpcClient, Middleware, Provider}; +use futures::future::join_all; use hasher::HasherKeccak; use rlp::RlpStream; +use serde::{Deserialize, Serialize}; +#[cfg(test)] use tokio::runtime::Runtime; -use crate::receipt::{calc_max_val_len as rc_calc_max_val_len, EthBlockReceiptInput}; -use crate::{mpt::MPTInput, providers::from_hex, receipt::EthReceiptInput}; +#[cfg(test)] +use crate::{ + mpt::MPTInput, + providers::block::{get_block_rlp, get_blocks}, + providers::from_hex, + receipt::EthReceiptInput, + receipt::{calc_max_val_len as rc_calc_max_val_len, EthBlockReceiptInput}, +}; + +use super::transaction::get_tx_key_from_index; + +// Issue: https://github.com/gakonst/ethers-rs/issues/2768 +// Copying from: https://github.com/alloy-rs/alloy/blob/410850b305a28297483d819b669b04ba31796359/crates/rpc-types/src/eth/transaction/receipt.rs#L8 +/// "Receipt" of an executed transaction: details of its execution. +/// Transaction receipt +#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionReceipt { + /// Transaction Hash. + pub transaction_hash: Option, + /// Index within the block. + pub transaction_index: U64, + /// Hash of the block this transaction was included within. + pub block_hash: Option, + /// Number of the block this transaction was included within. + pub block_number: Option, + /// Address of the sender + pub from: Address, + /// Address of the receiver. None when its a contract creation transaction. + pub to: Option
, + /// Cumulative gas used within the block after this was executed. + pub cumulative_gas_used: U256, + /// Gas used by this transaction alone. + /// + /// Gas used is `None` if the the client is running in light client mode. + pub gas_used: Option, + /// Contract address created, or None if not a deployment. + pub contract_address: Option
, + /// Logs emitted by this transaction. + pub logs: Vec, + /// Status: either 1 (success) or 0 (failure). Only present after activation of EIP-658 + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub root: Option, + /// Logs bloom + pub logs_bloom: Bloom, + /// EIP-2718 Transaction type, Some(1) for AccessList transaction, None for Legacy + #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, + /// The price paid post-execution by the transaction (i.e. base fee + priority fee). Both + /// fields in 1559-style transactions are maximums (max fee + max priority fee), the amount + /// that's actually paid by users can only be determined post-execution + #[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")] + pub effective_gas_price: Option, + + // Note: blob_gas_used and blob_gas_price are not part of the EIP-2718 ReceiptPayload + /// Blob gas used by the eip-4844 transaction + /// + /// This is None for non eip-4844 transactions + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_gas_used: Option, + /// The price paid by the eip-4844 transaction per blob gas. + #[serde(skip_serializing_if = "Option::is_none")] + pub blob_gas_price: Option, + + /// Arbitrary extra fields. Contains fields specific to, e.g., L2s. + #[serde(flatten)] + pub other: OtherFields, +} + +// Copied from https://github.com/alloy-rs/alloy/blob/410850b305a28297483d819b669b04ba31796359/crates/rpc-types/src/eth/transaction/optimism.rs#L25 +/// Additional fields for Optimism transaction receipts +#[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OptimismTransactionReceiptFields { + /// Deposit nonce for deposit transactions post-regolith + #[serde(skip_serializing_if = "Option::is_none")] + pub deposit_nonce: Option, + /// Deposit receipt version for deposit transactions post-canyon + #[serde(skip_serializing_if = "Option::is_none")] + pub deposit_receipt_version: Option, + /// L1 fee for the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_fee: Option, + /// L1 fee scalar for the transaction + #[serde(default, skip_serializing_if = "Option::is_none", with = "l1_fee_scalar_serde")] + pub l1_fee_scalar: Option, + /// L1 gas price for the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_gas_price: Option, + /// L1 gas used for the transaction + #[serde(skip_serializing_if = "Option::is_none")] + pub l1_gas_used: Option, +} + +/// Serialize/Deserialize l1FeeScalar to/from string +mod l1_fee_scalar_serde { + use serde::{de, Deserialize}; + + pub(super) fn serialize(value: &Option, s: S) -> Result + where + S: serde::Serializer, + { + if let Some(v) = value { + return s.serialize_str(&v.to_string()); + } + s.serialize_none() + } + + pub(super) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let s: Option = Option::deserialize(deserializer)?; + if let Some(s) = s { + return Ok(Some(s.parse::().map_err(de::Error::custom)?)); + } -use super::block::{get_block_rlp, get_blocks}; + Ok(None) + } +} -/// This is a fix to -pub fn rlp_bytes(receipt: TransactionReceipt) -> Vec { +/// This is a fix to . +/// Also fixes the OP stack deposit receipt RLP encoding. +/// `reth` reference: https://github.com/paradigmxyz/reth/blob/4e1c56f8d0baf7282b8ceb5ff8c93da66961ca2a/crates/primitives/src/receipt.rs#L486 +pub fn get_receipt_rlp(receipt: &TransactionReceipt) -> anyhow::Result { let mut s = RlpStream::new(); - s.begin_list(4); + s.begin_unbounded_list(); if let Some(post_state) = receipt.root { s.append(&post_state); } else { - s.append(&receipt.status.expect("No post-state or status in receipt")); + s.append(&receipt.status.ok_or(anyhow!("No post-state or status in receipt"))?); } s.append(&receipt.cumulative_gas_used); s.append(&receipt.logs_bloom); s.append_list(&receipt.logs); - let bytesa = s.out(); - let mut rlp = bytesa.to_vec(); + + // OP stack deposit transaction + // https://specs.optimism.io/protocol/deposits.html#deposit-receipt + if receipt.transaction_type == Some(U64::from(0x7E)) { + let op_fields: OptimismTransactionReceiptFields = + serde_json::from_value(serde_json::to_value(&receipt.other)?)?; + // https://github.com/paradigmxyz/reth/blob/4e1c56f8d0baf7282b8ceb5ff8c93da66961ca2a/crates/primitives/src/receipt.rs#L40 + if let Some(deposit_receipt_version) = op_fields.deposit_receipt_version { + // RPC providers seem to provide depositNonce even before Canyon, so we use receipt version as indicator + let deposit_nonce = op_fields + .deposit_nonce + .ok_or(anyhow!("Canyon deposit receipt without depositNonce"))?; + s.append(&deposit_nonce); + // This is denoted as "depositReceiptVersion" in RPC responses, not "depositNonceVersion" like in the docs + s.append(&deposit_receipt_version); + } + } + + s.finalize_unbounded_list(); + let rlp_bytes: Bytes = s.out().freeze().into(); + let mut encoded = vec![]; if let Some(tx_type) = receipt.transaction_type { - if tx_type.as_u32() > 0 { - rlp = [vec![tx_type.as_u32() as u8], rlp].concat(); + let tx_type = u8::try_from(tx_type.as_u64()) + .map_err(|_| anyhow!("Transaction type is not a byte"))?; + if tx_type > 0 { + encoded.extend_from_slice(&[tx_type]); } } - rlp + encoded.extend_from_slice(rlp_bytes.as_ref()); + Ok(encoded.into()) +} + +// ========================= Receipts Trie Construction ========================= +pub struct BlockReceiptsDb { + pub trie: PatriciaTrie, + pub root: H256, + pub rc_rlps: Vec>, } +impl BlockReceiptsDb { + pub fn new( + trie: PatriciaTrie, + root: H256, + rc_rlps: Vec>, + ) -> Self { + Self { trie, root, rc_rlps } + } +} + +// ===== Block with Receipts ===== +/// A block with all receipts. We require the receiptsRoot to be provided for a safety check. +/// Deserialization should still work on an object with extra fields. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct BlockWithReceipts { + /// Block number + pub number: U64, + /// Receipts root hash + pub receipts_root: H256, + /// All receipts in the block + pub receipts: Vec, +} + +pub fn construct_rc_tries_from_full_blocks( + blocks: Vec, +) -> anyhow::Result> { + let mut tries = HashMap::new(); + for block in blocks { + let mut trie = + PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); + let mut rc_rlps = Vec::with_capacity(block.receipts.len()); + for (idx, rc) in block.receipts.into_iter().enumerate() { + let tx_key = get_tx_key_from_index(idx); + let rc_rlp = get_receipt_rlp(&rc)?.to_vec(); + rc_rlps.push(rc_rlp.clone()); + trie.insert(tx_key, rc_rlp)?; + } + // safety check: + let root = trie.root()?; + if root != block.receipts_root.as_bytes() { + anyhow::bail!("Transactions trie incorrectly constructed for block {}", block.number); + } + let root = block.receipts_root; + tries.insert(block.number.as_u64(), BlockReceiptsDb::new(trie, root, rc_rlps)); + } + Ok(tries) +} + +// This function only for testing use +#[cfg(test)] async fn get_receipt_query( receipt_pf_max_depth: usize, receipts: Vec, // all receipts in the block @@ -53,7 +259,7 @@ async fn get_receipt_query( if idx == 0 { rc_key = vec![0x80]; } - let rc_rlp = rlp_bytes(receipt); + let rc_rlp = get_receipt_rlp(&receipt).unwrap().to_vec(); trie.insert(rc_key, rc_rlp.clone()).unwrap(); vals_cache.push(rc_rlp); } @@ -82,26 +288,49 @@ async fn get_receipt_query( EthReceiptInput { idx: tx_idx, proof } } -pub fn get_block_receipts( +/// Default is 3 retries for each receipt. +pub async fn get_block_with_receipts( provider: &Provider

, - block_number: u32, -) -> Vec { - let rt = Runtime::new().unwrap(); - let block2 = rt - .block_on(provider.get_block(block_number as u64)) - .unwrap() - .unwrap_or_else(|| panic!("Block {block_number} not found")); - let mut receipts = Vec::new(); - for transaction in block2.transactions { - let receipt = rt - .block_on(provider.get_transaction_receipt(transaction)) - .unwrap() - .unwrap_or_else(|| panic!("Transaction {transaction} not found")); - receipts.push(receipt); - } - receipts + block_number: u64, + retries: Option, +) -> anyhow::Result { + let default_retries = 3; + let block = provider.get_block(block_number).await?.ok_or(anyhow!("Failed to get block"))?; + let receipts = join_all(block.transactions.iter().map(|&tx_hash| { + let mut retries = retries.unwrap_or(default_retries); + async move { + loop { + let receipt = provider.request("eth_getTransactionReceipt", [tx_hash]).await; + match receipt { + Ok(Some(receipt)) => return Ok(receipt), + Ok(None) => { + if retries == 0 { + return Err(anyhow!("Receipt not found after {}", retries)); + } + retries -= 1; + } + Err(e) => { + if retries == 0 { + return Err(e.into()); + } + retries -= 1; + } + } + } + } + })) + .await + .into_iter() + .collect::>>()?; + + Ok(BlockWithReceipts { + number: block_number.into(), + receipts_root: block.receipts_root, + receipts, + }) } +#[cfg(test)] pub fn get_block_receipt_input( provider: &Provider

, tx_hash: H256, @@ -115,11 +344,12 @@ pub fn get_block_receipt_input( .block_on(provider.get_transaction(tx_hash)) .unwrap() .unwrap_or_else(|| panic!("Transaction {tx_hash} not found")); - let block_number = tx.block_number.unwrap().as_u32(); - let block = get_blocks(provider, [block_number as u64]).unwrap().pop().unwrap().unwrap(); + let block_number = tx.block_number.unwrap().as_u64(); + let block = get_blocks(provider, [block_number]).unwrap().pop().unwrap().unwrap(); let block_hash = block.hash.unwrap(); let block_header = get_block_rlp(&block); - let receipts = get_block_receipts(provider, block_number); + let receipts = + rt.block_on(get_block_with_receipts(provider, block_number, None)).unwrap().receipts; // requested receipt pf let receipt = rt.block_on(get_receipt_query( receipt_pf_max_depth, @@ -130,64 +360,62 @@ pub fn get_block_receipt_input( max_log_num, topic_num_bounds, )); - EthBlockReceiptInput { block_number, block_hash, block_header, receipt } + EthBlockReceiptInput { block_number: block_number as u32, block_hash, block_header, receipt } } #[cfg(test)] mod tests { - use std::{env::var, sync::Arc}; - - use cita_trie::{MemoryDB, PatriciaTrie, Trie}; use ethers_core::types::Chain; use ethers_providers::Middleware; - use hasher::HasherKeccak; - use tokio::runtime::Runtime; - use crate::providers::{from_hex, receipt::rlp_bytes, setup_provider}; + use crate::providers::{ + receipt::{ + construct_rc_tries_from_full_blocks, get_block_with_receipts, BlockWithReceipts, + }, + setup_provider, + }; - #[test] - fn correct_root() { - let block_number = (var("BLOCK_NUM").expect("BLOCK_NUM environmental variable not set")) - .parse::() - .unwrap(); + // Tests some fixed blocks as well as the 10 latest blocks + #[tokio::test] + async fn test_reconstruct_receipt_trie_mainnet() -> anyhow::Result<()> { let provider = setup_provider(Chain::Mainnet); - let rt = Runtime::new().unwrap(); - let block2 = rt - .block_on(provider.get_block(block_number as u64)) - .unwrap() - .unwrap_or_else(|| panic!("Block {block_number} not found")); - let mut receipts = Vec::new(); - for transaction in block2.transactions { - receipts.push(rt.block_on(provider.get_transaction_receipt(transaction)).unwrap()) + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = vec![ + 50_000, 500_000, 5_000_050, 5_000_051, 17_000_000, 17_034_973, 19_426_587, 19_426_589, + ]; + for i in 0..10 { + block_nums.push(latest_block - i); } - let receipts_root = block2.receipts_root; - let memdb = Arc::new(MemoryDB::new(true)); - let hasher = Arc::new(HasherKeccak::new()); - let mut trie = PatriciaTrie::new(Arc::clone(&memdb), Arc::clone(&hasher)); - let num_rcs = receipts.len(); - let mut vals_cache = Vec::new(); - #[allow(clippy::needless_range_loop)] - for idx in 0..num_rcs { - let rc = receipts[idx].clone(); - match rc { - None => {} - Some(rc) => { - let mut rc_key = - rlp::encode(&from_hex(&format!("{idx:x}").to_string())).to_vec(); - if idx == 0 { - rc_key = vec![0x80]; - } - let rc_rlp = rlp_bytes(rc); - println!("RC_RLP: {rc_rlp:02x?}"); - trie.insert(rc_key.clone(), rc_rlp.clone()).unwrap(); - vals_cache.push(rc_rlp); - } - } + let mut full_blocks = Vec::new(); + for block_num in block_nums { + let block = get_block_with_receipts(&provider, block_num, None).await?; + let block: BlockWithReceipts = serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); } - let root = trie.root().unwrap(); - trie = PatriciaTrie::from(Arc::clone(&memdb), Arc::clone(&hasher), &root).unwrap(); - let root = trie.root().unwrap(); - assert!(root == receipts_root.as_bytes().to_vec()); + // Will panic if any tx root does not match trie root: + construct_rc_tries_from_full_blocks(full_blocks)?; + Ok(()) + } + + // Tests some fixed blocks as well as the 10 latest blocks + // Tests OP stack deposit transactions + #[tokio::test] + async fn test_reconstruct_receipt_trie_base() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Base); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = vec![10, 100_000, 5_000_050, 8_000_000, 8578617, 11_864_572]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + for block_num in block_nums { + let block = get_block_with_receipts(&provider, block_num, None).await?; + let block: BlockWithReceipts = serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_rc_tries_from_full_blocks(full_blocks)?; + Ok(()) } } @@ -203,11 +431,15 @@ mod data_analysis { #[test] #[ignore] pub fn find_receipt_lens() -> Result<(), Box> { + let rt = Runtime::new().unwrap(); let provider = setup_provider(Chain::Mainnet); let mut data_file = File::create("data.txt").expect("creation failed"); for i in 0..100 { - let receipts = get_block_receipts(&provider, 17578525 - i); + let receipts = rt + .block_on(get_block_with_receipts(&provider, 17578525 - i, None)) + .unwrap() + .receipts; for (j, receipt) in receipts.into_iter().enumerate() { let _len = { let mut s = RlpStream::new(); diff --git a/axiom-eth/src/providers/storage.rs b/axiom-eth/src/providers/storage.rs index bca855ba..01973eb8 100644 --- a/axiom-eth/src/providers/storage.rs +++ b/axiom-eth/src/providers/storage.rs @@ -6,6 +6,7 @@ use ethers_providers::{JsonRpcClient, Middleware, Provider}; use futures::future::join_all; use rlp::{Encodable, Rlp, RlpStream}; use tokio::runtime::Runtime; +use zkevm_hashes::util::eth_types::ToBigEndian; use crate::{ mpt::{MPTInput, KECCAK_RLP_EMPTY_STRING}, @@ -87,7 +88,7 @@ pub fn json_to_mpt_input( .storage_proof .into_iter() .map(|storage_pf| { - let path = H256(keccak256(storage_pf.key)); + let path = H256(keccak256(storage_pf.key.to_be_bytes())); let (new_proof, mut value) = get_new_proof_and_value(&path, &storage_pf.proof); let new_proof_bytes: Vec = new_proof.clone().into_iter().map(Bytes::from_iter).collect(); diff --git a/axiom-eth/src/providers/transaction.rs b/axiom-eth/src/providers/transaction.rs index 50479da0..c915a54a 100644 --- a/axiom-eth/src/providers/transaction.rs +++ b/axiom-eth/src/providers/transaction.rs @@ -1,22 +1,190 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; +use anyhow::{anyhow, bail}; use cita_trie::{MemoryDB, PatriciaTrie, Trie}; -use ethers_core::types::{Transaction, H256}; +use ethers_core::types::{Block, Bytes, Transaction, H256, U128, U64}; use ethers_providers::{JsonRpcClient, Middleware, Provider}; use hasher::HasherKeccak; +use rlp::RlpStream; +use serde::{Deserialize, Serialize}; use tokio::runtime::Runtime; +use crate::providers::from_hex; +#[cfg(test)] use crate::{ mpt::MPTInput, - providers::from_hex, transaction::{ calc_max_val_len as tx_calc_max_val_len, EthBlockTransactionsInput, EthTransactionLenProof, EthTransactionProof, }, }; -use super::block::get_block_rlp; +// ========== Raw Transaction RLP computations ============= +// These are not yet implemented in alloy, and ethers_core does not support EIP-4844 transactions, so we keep a custom implementation here. + +/// The new fields in a EIP 4844 blob transaction. +/// This object is meant to be transmuted from the `other` field in [`Transaction`]. +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlobTransactionFields { + /// Configured max fee per blob gas for eip-4844 transactions + pub max_fee_per_blob_gas: U128, + /// Contains the blob hashes for eip-4844 transactions. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec, +} + +/// Computes the RLP encoding, in bytes, of a raw transaction. +/// Updates `ethers_core` to support EIP-4844 transactions. +pub fn get_transaction_rlp(transaction: &Transaction) -> anyhow::Result { + match transaction.transaction_type { + // EIP-4844 blob transaction + Some(x) if x == U64::from(3) => { + let other = serde_json::to_value(&transaction.other)?; + let blob_fields: BlobTransactionFields = serde_json::from_value(other)?; + let mut rlp = RlpStream::new(); + rlp.begin_unbounded_list(); + let chain_id = transaction.chain_id.ok_or(anyhow!("Eip4844 tx missing chainId"))?; + let max_priority_fee_per_gas = transaction + .max_priority_fee_per_gas + .ok_or(anyhow!("Eip4844 tx missing maxPriorityFeePerGas"))?; + let max_fee_per_gas = + transaction.max_fee_per_gas.ok_or(anyhow!("Eip4844 tx missing maxFeePerGas"))?; + let to = transaction.to.ok_or(anyhow!("Eip4844 tx `to` MUST NOT be nil"))?; + rlp.append(&chain_id); + rlp.append(&transaction.nonce); + rlp.append(&max_priority_fee_per_gas); + rlp.append(&max_fee_per_gas); + rlp.append(&transaction.gas); + rlp.append(&to); + rlp.append(&transaction.value); + rlp.append(&transaction.input.as_ref()); + rlp_opt_list(&mut rlp, &transaction.access_list); + rlp.append(&blob_fields.max_fee_per_blob_gas); + rlp.append_list(&blob_fields.blob_versioned_hashes); + rlp.append(&normalize_v(transaction.v.as_u64(), chain_id.as_u64())); + rlp.append(&transaction.r); + rlp.append(&transaction.s); + rlp.finalize_unbounded_list(); + let rlp_bytes: Bytes = rlp.out().freeze().into(); + let mut encoded = vec![]; + encoded.extend_from_slice(&[0x3]); + encoded.extend_from_slice(rlp_bytes.as_ref()); + Ok(encoded.into()) + } + // Legacy, EIP-2718, or EIP-155 transactions are handled by ethers_core + // So are Optimism Deposited Transactions + _ => Ok(transaction.rlp()), + } +} + +// Copied from https://github.com/gakonst/ethers-rs/blob/5394d899adca736a602e316e6f0c06fdb5aa64b9/ethers-core/src/types/transaction/mod.rs#L22 +/// RLP encode a value if it exists or else encode an empty string. +pub fn rlp_opt(rlp: &mut rlp::RlpStream, opt: &Option) { + if let Some(inner) = opt { + rlp.append(inner); + } else { + rlp.append(&""); + } +} + +/// RLP encode a value if it exists or else encode an empty list. +pub fn rlp_opt_list(rlp: &mut rlp::RlpStream, opt: &Option) { + if let Some(inner) = opt { + rlp.append(inner); + } else { + // Choice of `u8` type here is arbitrary as all empty lists are encoded the same. + rlp.append_list::(&[]); + } +} + +/// normalizes the signature back to 0/1 +pub fn normalize_v(v: u64, chain_id: u64) -> u64 { + if v > 1 { + v - chain_id * 2 - 35 + } else { + v + } +} + +// ========================= Transactions Trie Construction ========================= +pub struct BlockTransactionsDb { + pub trie: PatriciaTrie, + pub root: H256, + pub tx_rlps: Vec>, +} + +impl BlockTransactionsDb { + pub fn new( + trie: PatriciaTrie, + root: H256, + tx_rlps: Vec>, + ) -> Self { + Self { trie, root, tx_rlps } + } +} + +// ===== Block with Transactions ===== +/// A block with all transactions. We require the transactionsRoot to be provided for a safety check. +/// Deserialization should still work on an object with extra fields. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct BlockWithTransactions { + /// Block number + pub number: U64, + /// Transactions root hash + pub transactions_root: H256, + /// All transactions in the block + pub transactions: Vec, +} + +impl TryFrom> for BlockWithTransactions { + type Error = &'static str; + fn try_from(block: Block) -> Result { + Ok(Self { + number: block.number.ok_or("Block not in chain")?, + transactions_root: block.transactions_root, + transactions: block.transactions, + }) + } +} + +/// For each block with all raw transactions, constructs the Merkle Patricia trie and returns a map from block number to the trie as well as flat vector of transactions. +pub fn construct_tx_tries_from_full_blocks( + blocks: Vec, +) -> anyhow::Result> { + let mut tries = HashMap::new(); + for block in blocks { + let mut trie = + PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); + let mut tx_rlps = Vec::with_capacity(block.transactions.len()); + for (idx, tx) in block.transactions.into_iter().enumerate() { + let tx_key = get_tx_key_from_index(idx); + let tx_rlp = get_transaction_rlp(&tx)?.to_vec(); + tx_rlps.push(tx_rlp.clone()); + trie.insert(tx_key, tx_rlp)?; + } + // safety check: + let root = trie.root()?; + if root != block.transactions_root.as_bytes() { + bail!("Transactions trie incorrectly constructed"); + } + let root = block.transactions_root; + tries.insert(block.number.as_u64(), BlockTransactionsDb::new(trie, root, tx_rlps)); + } + Ok(tries) +} + +pub fn get_tx_key_from_index(idx: usize) -> Vec { + let mut tx_key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); + if idx == 0 { + tx_key = vec![0x80]; + } + tx_key +} +// This function only for testing use +#[cfg(test)] /// Creates transaction proof by reconstructing the transaction MPTrie. /// `transactions` should be all transactions in the block async fn get_transaction_query( @@ -42,7 +210,7 @@ async fn get_transaction_query( if idx == 0 { tx_key = vec![0x80]; } - let tx_rlp = tx.rlp().to_vec(); + let tx_rlp = get_transaction_rlp(&tx).unwrap().to_vec(); trie.insert(tx_key.clone(), tx_rlp.clone()).unwrap(); vals_cache.push(tx_rlp); } @@ -152,6 +320,7 @@ async fn get_transaction_query( (pfs, len_proof) } +#[cfg(test)] pub fn get_block_transaction_input( provider: &Provider

, idxs: Vec, @@ -177,7 +346,7 @@ pub fn get_block_transaction_input( } let block_hash = block.hash.unwrap(); - let block_header = get_block_rlp(&block); + let block_header = super::block::get_block_rlp(&block); let (tx_proofs, len_proof) = rt.block_on(get_transaction_query( transaction_pf_max_depth, @@ -251,12 +420,61 @@ mod data_analysis { use std::{fs::File, io::Write}; use ethers_core::types::Chain; + use ethers_providers::Middleware; use rlp::RlpStream; - use crate::providers::setup_provider; + use crate::providers::{ + setup_provider, + transaction::{construct_tx_tries_from_full_blocks, BlockWithTransactions}, + }; use super::{get_block_access_list_num, get_block_transaction_len, get_block_transactions}; + // Tests some fixed blocks as well as the 10 latest blocks + #[tokio::test] + async fn test_reconstruct_tx_trie_mainnet() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Mainnet); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = + vec![500_000, 5_000_050, 5_000_051, 17_034_973, 19_426_587, 19_426_589]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + for block_num in block_nums { + let block = provider.get_block_with_txs(block_num).await?.unwrap(); + let block: BlockWithTransactions = + serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_tx_tries_from_full_blocks(full_blocks)?; + Ok(()) + } + + // Tests some fixed blocks as well as the 10 latest blocks + // Tests OP stack deposit transactions + #[tokio::test] + async fn test_reconstruct_tx_trie_base() -> anyhow::Result<()> { + let provider = setup_provider(Chain::Base); + let latest_block = provider.get_block_number().await.unwrap().as_u64(); + let mut block_nums = vec![10, 100_000, 5_000_050, 8_000_000, 11_864_572]; + for i in 0..10 { + block_nums.push(latest_block - i); + } + let mut full_blocks = Vec::new(); + dbg!(&block_nums); + for block_num in block_nums { + let block = provider.get_block_with_txs(block_num).await?.unwrap(); + let block: BlockWithTransactions = + serde_json::from_value(serde_json::to_value(block)?)?; + full_blocks.push(block); + } + // Will panic if any tx root does not match trie root: + construct_tx_tries_from_full_blocks(full_blocks)?; + Ok(()) + } + #[test] #[ignore] pub fn find_good_block256() { diff --git a/axiom-eth/src/storage/circuit.rs b/axiom-eth/src/storage/circuit.rs index c40f9bea..36b617eb 100644 --- a/axiom-eth/src/storage/circuit.rs +++ b/axiom-eth/src/storage/circuit.rs @@ -5,6 +5,7 @@ use halo2_base::{gates::GateInstructions, Context}; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; +use zkevm_hashes::util::eth_types::ToBigEndian; use crate::{ block_header::get_block_header_rlp_max_lens, @@ -27,7 +28,7 @@ pub struct EthStorageInput { pub acct_pf: MPTInput, pub acct_state: Vec>, /// A vector of (slot, value, proof) tuples - pub storage_pfs: Vec<(H256, U256, MPTInput)>, + pub storage_pfs: Vec<(U256, U256, MPTInput)>, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -48,7 +49,7 @@ impl EthStorageInput { .storage_pfs .into_iter() .map(|(slot, _, pf)| { - let slot = encode_h256_to_hilo(&slot).hi_lo(); + let slot = encode_h256_to_hilo(&H256(slot.to_be_bytes())).hi_lo(); let slot = slot.map(|slot| ctx.load_witness(slot)); let pf = pf.assign(ctx); (slot, pf) diff --git a/axiom-query/Cargo.toml b/axiom-query/Cargo.toml index 1338f925..f25a3d30 100644 --- a/axiom-query/Cargo.toml +++ b/axiom-query/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "axiom-query" -version = "2.0.15" +version = "2.0.16" authors = ["Intrinsic Technologies"] license = "MIT" edition = "2021" @@ -35,12 +35,12 @@ rand = "0.8" rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } # halo2, features turned on by axiom-eth -axiom-eth = { version = "=0.4.1", path = "../axiom-eth", default-features = false, features = ["providers", "aggregation", "evm"] } +axiom-eth = { version = "=0.4.2", path = "../axiom-eth", default-features = false, features = ["providers", "aggregation", "evm"] } axiom-codec = { version = "0.2.1", path = "../axiom-codec", default-features = false } # crypto rlp = "0.5.2" -ethers-core = { version = "=2.0.10" } +ethers-core = { version = "=2.0.14", features = ["optimism"] } # mpt implementation hasher = { version = "=0.1", features = ["hash-keccak"] } cita_trie = "=5.0.0" @@ -61,7 +61,7 @@ rand_chacha = "0.3.1" tokio = { version = "1.33", features = ["macros"] } reqwest = { version = "0.11", features = ["json"] } # generating circuit inputs from blockchain -ethers-providers = { version = "2.0.10" } +ethers-providers = { version = "2.0.14" } futures = { version = "0.3" } blake3 = { version = "=1.5" } diff --git a/axiom-query/src/components/subqueries/account/tests.rs b/axiom-query/src/components/subqueries/account/tests.rs index f066b756..83deda7f 100644 --- a/axiom-query/src/components/subqueries/account/tests.rs +++ b/axiom-query/src/components/subqueries/account/tests.rs @@ -209,10 +209,11 @@ async fn test_mock_empty_account_subqueries() { let _circuit = test_mock_account_subqueries(18, network, subqueries, 50).await; } -#[tokio::test] -async fn test_mock_empty_account_subqueries2() { - let network = Chain::Goerli; - // non-inclusion ends in extension node - let subqueries: Vec<_> = vec![(9173678, "0x8dde5d4a8384f403f888e1419672d94c570440c9", 0)]; - let _circuit = test_mock_account_subqueries(18, network, subqueries, 50).await; -} +// Goerli is dead +// #[tokio::test] +// async fn test_mock_empty_account_subqueries2() { +// let network = Chain::Goerli; +// // non-inclusion ends in extension node +// let subqueries: Vec<_> = vec![(9173678, "0x8dde5d4a8384f403f888e1419672d94c570440c9", 0)]; +// let _circuit = test_mock_account_subqueries(18, network, subqueries, 50).await; +// } diff --git a/axiom-query/src/components/subqueries/account/types.rs b/axiom-query/src/components/subqueries/account/types.rs index 49e1e509..9653449e 100644 --- a/axiom-query/src/components/subqueries/account/types.rs +++ b/axiom-query/src/components/subqueries/account/types.rs @@ -109,7 +109,7 @@ impl From for CircuitOutputAccountShard { } } -pub const GENESIS_ADDRESS_0_ACCOUNT_PROOF: &str = r#"{"address":"0x0000000000000000000000000000000000000000","balance":"0x0","codeHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","storageHash":"0x0000000000000000000000000000000000000000000000000000000000000000","accountProof":["0xf90211a090dcaf88c40c7bbc95a912cbdde67c175767b31173df9ee4b0d733bfdd511c43a0babe369f6b12092f49181ae04ca173fb68d1a5456f18d20fa32cba73954052bda0473ecf8a7e36a829e75039a3b055e51b8332cbf03324ab4af2066bbd6fbf0021a0bbda34753d7aa6c38e603f360244e8f59611921d9e1f128372fec0d586d4f9e0a04e44caecff45c9891f74f6a2156735886eedf6f1a733628ebc802ec79d844648a0a5f3f2f7542148c973977c8a1e154c4300fec92f755f7846f1b734d3ab1d90e7a0e823850f50bf72baae9d1733a36a444ab65d0a6faaba404f0583ce0ca4dad92da0f7a00cbe7d4b30b11faea3ae61b7f1f2b315b61d9f6bd68bfe587ad0eeceb721a07117ef9fc932f1a88e908eaead8565c19b5645dc9e5b1b6e841c5edbdfd71681a069eb2de283f32c11f859d7bcf93da23990d3e662935ed4d6b39ce3673ec84472a0203d26456312bbc4da5cd293b75b840fc5045e493d6f904d180823ec22bfed8ea09287b5c21f2254af4e64fca76acc5cd87399c7f1ede818db4326c98ce2dc2208a06fc2d754e304c48ce6a517753c62b1a9c1d5925b89707486d7fc08919e0a94eca07b1c54f15e299bd58bdfef9741538c7828b5d7d11a489f9c20d052b3471df475a051f9dd3739a927c89e357580a4c97b40234aa01ed3d5e0390dc982a7975880a0a089d613f26159af43616fd9455bb461f4869bfede26f2130835ed067a8b967bfb80","0xf90211a06f499aafd2fcc95db6ac85b9ec36ce16b3747180d51b7ba72babdbceaef0cac8a034ffbe94cc9f4ac7e43bbd0ab875ce079e5d131f72f33974c09525bad37da4b4a026ac19ac1e99055b84ef53fad0ff4bf76a54af485b399dac5d91e55320941c16a0a33d103a92ff6f95c081309f83f474a009048614d5d40e14067dbae0cf9ed084a046a0e834a4f3482cb37f70f1f188d7c749c33fb8b94854b16dcebe840fc9390aa0a5a914013e15472dc3ae08f774e9d5ac3127419a2c81bec98963f40dde42ebaaa0b5740bdfa8ecf2b4d0b560f72474846788a3e19f9e0894c6bd2eb46255d222e9a04aa4e4ebe1930364ae283e8f1fa4de7ef1867a3f7fb89c23e068b807464eac14a0f84e5e71db73c15fc0bfa5566fae5e687e8eed398ef68e0d8229a7bc2eb333fda0551d35fa9c76d23bbbc1feb30a16e6ee1087c96aa2c31a8be297c4904c37373ba0f25b1be3ea53f222e17985dde60b04716bc342232874af3ad0af1652165138f2a0e50848e903b54f966851f4cbac1deb5b1d1beb42b4223379bb911f68001747f8a021d90bccf615ff6349cc5fdf8604ee52789c0e977fe12c2401b1cc229a9e7e47a0ade009f37dd2907895900d853eefbcf26af8f1665c8802804584241d825a6b49a09fe500ded938f686589ab2f42caad51341441980ea20e9fcb69e62b774c9990fa087888bb118be98fa5dfd57a76d0b59af08d7977fe87bad7c0d68ef82f2c9a92880","0xf901b1a0ec652a529bfb6f625879b961b8f7986b8294cfb1082d24b2c27f9a5b3fbccece80a088a3bacf48a0d00e3b36c3274ca2ab8d9d8f54c90e03724b3f6f5137c5a350c1a0a3c84954aad8408ed44eed138573a4db917e19d49e6cb716c14c7dedcb7a0051a069d3ae295c988b5e52f9d86b3aa85e9167a2d59a5ad47b6d1f8faaae9cd3aee4a0252dbbed1d3b713b43b6b8745d1d60605bbc4474746bfffe16375acbf42c0ec080a0a886f03399a8e32312b65d59b2a5d97ba7bb078aa5dab7aeb08d1fbd721a0944a0e9b89be70399650793c37b4aca1779e5adf4d8a07cea63dab9a9f5ef6b7dc66fa0b352a156bda0e42ce192bc430f8513e827b0aaa70002a21fef3a4df416be93e9a00665ba82ae23119a4a244be15e42e23589490995236c43bac11b5628613c337ba0b45176ce952dda9f523f244d921805f005c11b2027f53d12dda0e069278cf908a0eefa94d2ecf8946494c634277eac048823f35f7820d354c6e9352176c9b44e4da046443df5febce492f17eed4f98f2ad755fec20cd9eede857dc951757ef85b51aa0fc14ff2dbb3675d852fb37d7796eb1f303282d3466aef865a17da813d22bfc028080","0xf8718080a06f69700f636d81db1793bcee2562dcf0a4a06f2525fb2f55f5c00aab81b4b86880a00f13a02c0878c787e7f9fdcfbb3b3169b42c2a0595c7afecf86dbb46fbcb567b80808080a05c80e25e034b9cc0f079b1226a97c22434851b86c6b55be77eae89b096462afd80808080808080"],"storageProof":[{"key":"0x0000000000000000000000000000000000000000000000000000000000000000","proof":[],"value":"0x0"}]}"#; +pub const GENESIS_ADDRESS_0_ACCOUNT_PROOF: &str = r#"{"address":"0x0000000000000000000000000000000000000000","balance":"0x0","codeHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","storageHash":"0x0000000000000000000000000000000000000000000000000000000000000000","accountProof":["0xf90211a090dcaf88c40c7bbc95a912cbdde67c175767b31173df9ee4b0d733bfdd511c43a0babe369f6b12092f49181ae04ca173fb68d1a5456f18d20fa32cba73954052bda0473ecf8a7e36a829e75039a3b055e51b8332cbf03324ab4af2066bbd6fbf0021a0bbda34753d7aa6c38e603f360244e8f59611921d9e1f128372fec0d586d4f9e0a04e44caecff45c9891f74f6a2156735886eedf6f1a733628ebc802ec79d844648a0a5f3f2f7542148c973977c8a1e154c4300fec92f755f7846f1b734d3ab1d90e7a0e823850f50bf72baae9d1733a36a444ab65d0a6faaba404f0583ce0ca4dad92da0f7a00cbe7d4b30b11faea3ae61b7f1f2b315b61d9f6bd68bfe587ad0eeceb721a07117ef9fc932f1a88e908eaead8565c19b5645dc9e5b1b6e841c5edbdfd71681a069eb2de283f32c11f859d7bcf93da23990d3e662935ed4d6b39ce3673ec84472a0203d26456312bbc4da5cd293b75b840fc5045e493d6f904d180823ec22bfed8ea09287b5c21f2254af4e64fca76acc5cd87399c7f1ede818db4326c98ce2dc2208a06fc2d754e304c48ce6a517753c62b1a9c1d5925b89707486d7fc08919e0a94eca07b1c54f15e299bd58bdfef9741538c7828b5d7d11a489f9c20d052b3471df475a051f9dd3739a927c89e357580a4c97b40234aa01ed3d5e0390dc982a7975880a0a089d613f26159af43616fd9455bb461f4869bfede26f2130835ed067a8b967bfb80","0xf90211a06f499aafd2fcc95db6ac85b9ec36ce16b3747180d51b7ba72babdbceaef0cac8a034ffbe94cc9f4ac7e43bbd0ab875ce079e5d131f72f33974c09525bad37da4b4a026ac19ac1e99055b84ef53fad0ff4bf76a54af485b399dac5d91e55320941c16a0a33d103a92ff6f95c081309f83f474a009048614d5d40e14067dbae0cf9ed084a046a0e834a4f3482cb37f70f1f188d7c749c33fb8b94854b16dcebe840fc9390aa0a5a914013e15472dc3ae08f774e9d5ac3127419a2c81bec98963f40dde42ebaaa0b5740bdfa8ecf2b4d0b560f72474846788a3e19f9e0894c6bd2eb46255d222e9a04aa4e4ebe1930364ae283e8f1fa4de7ef1867a3f7fb89c23e068b807464eac14a0f84e5e71db73c15fc0bfa5566fae5e687e8eed398ef68e0d8229a7bc2eb333fda0551d35fa9c76d23bbbc1feb30a16e6ee1087c96aa2c31a8be297c4904c37373ba0f25b1be3ea53f222e17985dde60b04716bc342232874af3ad0af1652165138f2a0e50848e903b54f966851f4cbac1deb5b1d1beb42b4223379bb911f68001747f8a021d90bccf615ff6349cc5fdf8604ee52789c0e977fe12c2401b1cc229a9e7e47a0ade009f37dd2907895900d853eefbcf26af8f1665c8802804584241d825a6b49a09fe500ded938f686589ab2f42caad51341441980ea20e9fcb69e62b774c9990fa087888bb118be98fa5dfd57a76d0b59af08d7977fe87bad7c0d68ef82f2c9a92880","0xf901b1a0ec652a529bfb6f625879b961b8f7986b8294cfb1082d24b2c27f9a5b3fbccece80a088a3bacf48a0d00e3b36c3274ca2ab8d9d8f54c90e03724b3f6f5137c5a350c1a0a3c84954aad8408ed44eed138573a4db917e19d49e6cb716c14c7dedcb7a0051a069d3ae295c988b5e52f9d86b3aa85e9167a2d59a5ad47b6d1f8faaae9cd3aee4a0252dbbed1d3b713b43b6b8745d1d60605bbc4474746bfffe16375acbf42c0ec080a0a886f03399a8e32312b65d59b2a5d97ba7bb078aa5dab7aeb08d1fbd721a0944a0e9b89be70399650793c37b4aca1779e5adf4d8a07cea63dab9a9f5ef6b7dc66fa0b352a156bda0e42ce192bc430f8513e827b0aaa70002a21fef3a4df416be93e9a00665ba82ae23119a4a244be15e42e23589490995236c43bac11b5628613c337ba0b45176ce952dda9f523f244d921805f005c11b2027f53d12dda0e069278cf908a0eefa94d2ecf8946494c634277eac048823f35f7820d354c6e9352176c9b44e4da046443df5febce492f17eed4f98f2ad755fec20cd9eede857dc951757ef85b51aa0fc14ff2dbb3675d852fb37d7796eb1f303282d3466aef865a17da813d22bfc028080","0xf8718080a06f69700f636d81db1793bcee2562dcf0a4a06f2525fb2f55f5c00aab81b4b86880a00f13a02c0878c787e7f9fdcfbb3b3169b42c2a0595c7afecf86dbb46fbcb567b80808080a05c80e25e034b9cc0f079b1226a97c22434851b86c6b55be77eae89b096462afd80808080808080"],"storageProof":[{"key":"0x0","proof":[],"value":"0x0"}]}"#; #[cfg(test)] mod test { diff --git a/axiom-query/src/components/subqueries/receipt/tests.rs b/axiom-query/src/components/subqueries/receipt/tests.rs index 3a246b94..6f4bb452 100644 --- a/axiom-query/src/components/subqueries/receipt/tests.rs +++ b/axiom-query/src/components/subqueries/receipt/tests.rs @@ -17,7 +17,11 @@ use axiom_eth::{ halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, mpt::MPTInput, - providers::{get_provider_uri, setup_provider}, + providers::{ + receipt::{construct_rc_tries_from_full_blocks, get_block_with_receipts}, + setup_provider, + transaction::get_tx_key_from_index, + }, receipt::{calc_max_val_len, EthReceiptChipParams, EthReceiptInput}, utils::component::{ promise_loader::single::PromiseLoaderParams, ComponentCircuit, @@ -25,11 +29,11 @@ use axiom_eth::{ }, }; use cita_trie::Trie; -use ethers_core::types::{Chain, TransactionReceipt, H256}; +use ethers_core::types::{Chain, H256}; use ethers_providers::Middleware; use futures::future::join_all; use itertools::Itertools; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use tokio; use crate::components::{ @@ -37,16 +41,12 @@ use crate::components::{ subqueries::{ block_header::types::{ComponentTypeHeaderSubquery, OutputHeaderShard}, common::shard_into_component_promise_results, - transaction::types::get_tx_key_from_index, }, }; use super::{ circuit::{ComponentCircuitReceiptSubquery, CoreParamsReceiptSubquery}, - types::{ - construct_rc_tries_from_full_blocks, BlockWithReceipts, CircuitInputReceiptShard, - CircuitInputReceiptSubquery, - }, + types::{CircuitInputReceiptShard, CircuitInputReceiptSubquery}, }; /// transaction index is within u16, so rlp(txIndex) is at most 3 bytes => 6 nibbles @@ -66,17 +66,6 @@ struct Request { params: Vec, } -#[derive(Deserialize)] -struct Response { - // id: u8, - // jsonrpc: String, - result: ResponseBody, -} -#[derive(Deserialize)] -struct ResponseBody { - receipts: Vec, -} - async fn test_mock_receipt_subqueries( k: u32, network: Chain, @@ -108,35 +97,9 @@ async fn test_mock_receipt_subqueries( )) .await; - // assuming this is Alchemy for now - let _provider_uri = get_provider_uri(network); - let provider_uri = _provider_uri.as_str(); // async moves are weird - let _client = reqwest::Client::new(); - let client = &_client; let block_nums = requests.iter().map(|r| r.block_number as u64).sorted().dedup().collect_vec(); let blocks = join_all(block_nums.iter().map(|&block_num| async move { - let block = provider.get_block(block_num).await.unwrap().unwrap(); - let req = Request { - id: 1, - jsonrpc: "2.0".to_string(), - method: "alchemy_getTransactionReceipts".to_string(), - params: vec![Params { block_number: format!("0x{block_num:x}") }], - }; - // println!("{}", serde_json::to_string(&req).unwrap()); - let res = client - .post(provider_uri) - .json(&req) - .send() - .await - .unwrap() - .json::() - .await - .unwrap(); - BlockWithReceipts { - number: block_num.into(), - receipts_root: block.receipts_root, - receipts: res.result.receipts, - } + get_block_with_receipts(provider, block_num, None).await.unwrap() })) .await; diff --git a/axiom-query/src/components/subqueries/receipt/types.rs b/axiom-query/src/components/subqueries/receipt/types.rs index 98f6d6a3..ace2f394 100644 --- a/axiom-query/src/components/subqueries/receipt/types.rs +++ b/axiom-query/src/components/subqueries/receipt/types.rs @@ -4,9 +4,8 @@ //! - The in-circuit formatted versions of logical inputs and outputs. These include formatting in terms of field elements and accounting for all lengths needing to be fixed at compile time. //! - We then provide conversion functions from human-readable to circuit formats. //! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. -use std::{collections::HashMap, marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc}; -use anyhow::Result; use axiom_codec::{ types::{field_elements::FieldReceiptSubquery, native::ReceiptSubquery}, HiLo, @@ -15,7 +14,7 @@ use axiom_eth::{ halo2_base::AssignedValue, impl_fix_len_call_witness, mpt::MPTInput, - providers::receipt::rlp_bytes, + providers::receipt::{get_receipt_rlp, TransactionReceipt}, receipt::{calc_max_val_len as rc_calc_max_val_len, EthReceiptInput}, utils::{ build_utils::dummy::DummyFrom, @@ -23,15 +22,12 @@ use axiom_eth::{ }, }; use cita_trie::{MemoryDB, PatriciaTrie, Trie}; -use ethers_core::types::{TransactionReceipt, H256, U64}; +use ethers_core::types::H256; use hasher::HasherKeccak; use serde::{Deserialize, Serialize}; use crate::{ - components::subqueries::{ - common::OutputSubqueryShard, transaction::types::get_tx_key_from_index, - }, - utils::codec::AssignedReceiptSubquery, + components::subqueries::common::OutputSubqueryShard, utils::codec::AssignedReceiptSubquery, Field, }; @@ -71,7 +67,7 @@ impl DummyFrom for CircuitInputReceiptShard let mut trie = PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); let rc = TransactionReceipt { status: Some(0x1.into()), ..Default::default() }; - let rc_rlp = rlp_bytes(rc); + let rc_rlp = get_receipt_rlp(&rc).unwrap().to_vec(); trie.insert(vec![0x80], rc_rlp.clone()).unwrap(); let mpt_input = MPTInput { path: (&[0x80]).into(), @@ -138,58 +134,3 @@ impl From for CircuitOutputReceiptShard { output.convert_into() } } - -// ===== Block with Receipts ===== -/// A block with all receipts. We require the receiptsRoot to be provided for a safety check. -/// Deserialization should still work on an object with extra fields. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct BlockWithReceipts { - /// Block number - pub number: U64, - /// Receipts root hash - pub receipts_root: H256, - /// All receipts in the block - pub receipts: Vec, -} - -pub struct BlockReceiptsDb { - pub trie: PatriciaTrie, - pub root: H256, - pub rc_rlps: Vec>, -} - -impl BlockReceiptsDb { - pub fn new( - trie: PatriciaTrie, - root: H256, - rc_rlps: Vec>, - ) -> Self { - Self { trie, root, rc_rlps } - } -} - -pub fn construct_rc_tries_from_full_blocks( - blocks: Vec, -) -> Result> { - let mut tries = HashMap::new(); - for block in blocks { - let mut trie = - PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); - let mut rc_rlps = Vec::with_capacity(block.receipts.len()); - for (idx, rc) in block.receipts.into_iter().enumerate() { - let tx_key = get_tx_key_from_index(idx); - let rc_rlp = rlp_bytes(rc); - rc_rlps.push(rc_rlp.clone()); - trie.insert(tx_key, rc_rlp)?; - } - // safety check: - let root = trie.root()?; - if root != block.receipts_root.as_bytes() { - anyhow::bail!("Transactions trie incorrectly constructed"); - } - let root = block.receipts_root; - tries.insert(block.number.as_u64(), BlockReceiptsDb::new(trie, root, rc_rlps)); - } - Ok(tries) -} diff --git a/axiom-query/src/components/subqueries/storage/circuit.rs b/axiom-query/src/components/subqueries/storage/circuit.rs index d57dfe19..e692333c 100644 --- a/axiom-query/src/components/subqueries/storage/circuit.rs +++ b/axiom-query/src/components/subqueries/storage/circuit.rs @@ -8,8 +8,7 @@ use axiom_eth::{ halo2_proofs::plonk::ConstraintSystem, keccak::{types::ComponentTypeKeccak, KeccakChip}, mpt::MPTChip, - rlc::circuit::builder::RlcCircuitBuilder, - rlc::circuit::builder::RlcContextPair, + rlc::circuit::builder::{RlcCircuitBuilder, RlcContextPair}, rlp::RlpChip, storage::{EthStorageChip, EthStorageWitness}, utils::{ @@ -30,6 +29,7 @@ use axiom_eth::{ }, constrain_vec_equal, unsafe_bytes_to_assigned, }, + zkevm_hashes::util::eth_types::ToBigEndian, }; use serde::{Deserialize, Serialize}; @@ -215,7 +215,7 @@ pub fn handle_single_storage_subquery_phase0( // should have already validated input so storage_pfs has length 1 let (slot, _value, mpt_proof) = subquery.proof.storage_pfs[0].clone(); // assign `slot` as `SafeBytes32` - let unsafe_slot = unsafe_bytes_to_assigned(ctx, slot.as_bytes()); + let unsafe_slot = unsafe_bytes_to_assigned(ctx, &slot.to_be_bytes()); let slot_bytes = safe.raw_bytes_to(ctx, unsafe_slot); // convert slot to HiLo to save for later let slot = safe_bytes32_to_hi_lo(ctx, gate, &slot_bytes); diff --git a/axiom-query/src/components/subqueries/transaction/tests.rs b/axiom-query/src/components/subqueries/transaction/tests.rs index 21498771..504c5dcb 100644 --- a/axiom-query/src/components/subqueries/transaction/tests.rs +++ b/axiom-query/src/components/subqueries/transaction/tests.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, marker::PhantomData, str::FromStr, sync::Arc}; +use std::{marker::PhantomData, str::FromStr}; use axiom_codec::{ special_values::{TX_CALLDATA_IDX_OFFSET, TX_FUNCTION_SELECTOR_FIELD_IDX}, @@ -12,19 +12,22 @@ use axiom_eth::{ halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}, keccak::{promise::generate_keccak_shards_from_calls, types::ComponentTypeKeccak}, mpt::MPTInput, - providers::setup_provider, - transaction::EthTransactionChipParams, - transaction::{calc_max_val_len, EthTransactionProof}, + providers::{ + setup_provider, + transaction::{ + construct_tx_tries_from_full_blocks, get_tx_key_from_index, BlockWithTransactions, + }, + }, + transaction::{calc_max_val_len, EthTransactionChipParams, EthTransactionProof}, utils::component::{ promise_loader::single::PromiseLoaderParams, ComponentCircuit, ComponentPromiseResultsInMerkle, ComponentType, }, }; -use cita_trie::{MemoryDB, PatriciaTrie, Trie}; +use cita_trie::Trie; use ethers_core::types::{Chain, H256}; use ethers_providers::Middleware; use futures::future::join_all; -use hasher::HasherKeccak; use itertools::Itertools; use tokio; @@ -38,55 +41,12 @@ use crate::components::{ use super::{ circuit::{ComponentCircuitTxSubquery, CoreParamsTxSubquery}, - types::{ - get_tx_key_from_index, BlockWithTransactions, CircuitInputTxShard, CircuitInputTxSubquery, - }, + types::{CircuitInputTxShard, CircuitInputTxSubquery}, }; /// transaction index is within u16, so rlp(txIndex) is at most 3 bytes => 6 nibbles pub const TRANSACTION_PROOF_MAX_DEPTH: usize = 6; -pub struct BlockTransactionsDb { - pub trie: PatriciaTrie, - pub root: H256, - pub tx_rlps: Vec>, -} - -impl BlockTransactionsDb { - pub fn new( - trie: PatriciaTrie, - root: H256, - tx_rlps: Vec>, - ) -> Self { - Self { trie, root, tx_rlps } - } -} - -pub fn construct_tx_tries_from_full_blocks( - blocks: Vec, -) -> anyhow::Result> { - let mut tries = HashMap::new(); - for block in blocks { - let mut trie = - PatriciaTrie::new(Arc::new(MemoryDB::new(true)), Arc::new(HasherKeccak::new())); - let mut tx_rlps = Vec::with_capacity(block.transactions.len()); - for (idx, tx) in block.transactions.into_iter().enumerate() { - let tx_key = get_tx_key_from_index(idx); - let tx_rlp = tx.rlp().to_vec(); - tx_rlps.push(tx_rlp.clone()); - trie.insert(tx_key, tx_rlp)?; - } - // safety check: - let root = trie.root()?; - if root != block.transactions_root.as_bytes() { - anyhow::bail!("Transactions trie incorrectly constructed"); - } - let root = block.transactions_root; - tries.insert(block.number.as_u64(), BlockTransactionsDb::new(trie, root, tx_rlps)); - } - Ok(tries) -} - async fn test_mock_tx_subqueries( k: u32, network: Chain, @@ -246,12 +206,22 @@ async fn test_mock_tx_subqueries_simple() { async fn test_mock_tx_subqueries_legacy_vrs() { let k = 18; let subqueries = vec![ - ("0xbfcf97782f59c868a4f3248fce750488f567f312da4aa6632c64c2fe3cca4d8c", 8), - ("0xbfcf97782f59c868a4f3248fce750488f567f312da4aa6632c64c2fe3cca4d8c", 9), - ("0xbfcf97782f59c868a4f3248fce750488f567f312da4aa6632c64c2fe3cca4d8c", 10), - ("0xbfcf97782f59c868a4f3248fce750488f567f312da4aa6632c64c2fe3cca4d8c", 11), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 8), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 9), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 10), + ("0xb522100fc065547683da6b3fa5e755e721ffe5cd80f73153327cd5f403e6223c", 11), ]; - test_mock_tx_subqueries(k, Chain::Goerli, subqueries, 4000, 0).await; + test_mock_tx_subqueries(k, Chain::Sepolia, subqueries, 4000, 0).await; +} + +#[tokio::test] +async fn test_mock_tx_subqueries_eip4844() { + let k = 18; + let subqueries = vec![( + "0x740bbfb65e00b16e496757dfb1c8df1eea101cf9f559f519096d25904b7fa79b", + TX_FUNCTION_SELECTOR_FIELD_IDX, + )]; + test_mock_tx_subqueries(k, Chain::Mainnet, subqueries, 200, 0).await; } #[cfg(feature = "keygen")] diff --git a/axiom-query/src/components/subqueries/transaction/types.rs b/axiom-query/src/components/subqueries/transaction/types.rs index 80bb3942..b282cb8e 100644 --- a/axiom-query/src/components/subqueries/transaction/types.rs +++ b/axiom-query/src/components/subqueries/transaction/types.rs @@ -6,7 +6,6 @@ //! - This circuit has no public instances (IO) other than the circuit's own component commitment and the promise commitments from any component calls. use std::{marker::PhantomData, sync::Arc}; -use anyhow::Result; use axiom_codec::{ types::{field_elements::FieldTxSubquery, native::TxSubquery}, HiLo, @@ -15,7 +14,6 @@ use axiom_eth::{ halo2_base::AssignedValue, impl_fix_len_call_witness, mpt::MPTInput, - providers::from_hex, transaction::{calc_max_val_len as tx_calc_max_val_len, EthTransactionProof}, utils::{ build_utils::dummy::DummyFrom, @@ -23,7 +21,7 @@ use axiom_eth::{ }, }; use cita_trie::{MemoryDB, PatriciaTrie, Trie}; -use ethers_core::types::{Block, Transaction, H256, U64}; +use ethers_core::types::{Transaction, H256}; use hasher::HasherKeccak; use serde::{Deserialize, Serialize}; @@ -123,36 +121,3 @@ impl From for CircuitOutputTxShard { output.convert_into() } } - -// ===== Block with Transactions ===== -/// A block with all transactions. We require the transactionsRoot to be provided for a safety check. -/// Deserialization should still work on an object with extra fields. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct BlockWithTransactions { - /// Block number - pub number: U64, - /// Transactions root hash - pub transactions_root: H256, - /// All transactions in the block - pub transactions: Vec, -} - -impl TryFrom> for BlockWithTransactions { - type Error = &'static str; - fn try_from(block: Block) -> Result { - Ok(Self { - number: block.number.ok_or("Block not in chain")?, - transactions_root: block.transactions_root, - transactions: block.transactions, - }) - } -} - -pub fn get_tx_key_from_index(idx: usize) -> Vec { - let mut tx_key = rlp::encode(&from_hex(&format!("{idx:x}"))).to_vec(); - if idx == 0 { - tx_key = vec![0x80]; - } - tx_key -}