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-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/receipt/tests.rs b/axiom-query/src/components/subqueries/receipt/tests.rs index 2e35f2e9..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, transaction::get_tx_key_from_index}, + 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::{ @@ -42,10 +46,7 @@ use crate::components::{ 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 @@ -65,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, @@ -107,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 6744d93b..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, transaction::get_tx_key_from_index}, + providers::receipt::{get_receipt_rlp, TransactionReceipt}, receipt::{calc_max_val_len as rc_calc_max_val_len, EthReceiptInput}, utils::{ build_utils::dummy::DummyFrom, @@ -23,7 +22,7 @@ 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}; @@ -68,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(), @@ -135,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) -}