From a265dbdf4ba5957115ebcb5993b8b02a6dbf1a60 Mon Sep 17 00:00:00 2001 From: Zack Youell Date: Tue, 31 Dec 2024 12:27:25 +0000 Subject: [PATCH 1/6] Receipt value extraction prover --- mp2-common/src/eth.rs | 38 +++ mp2-v1/src/values_extraction/api.rs | 20 +- mp2-v1/src/values_extraction/leaf_receipt.rs | 27 +- mp2-v1/src/values_extraction/planner.rs | 260 ++++++++++++++++++- mp2-v1/tests/common/cases/indexing.rs | 2 +- ryhope/src/storage/updatetree.rs | 17 +- 6 files changed, 339 insertions(+), 25 deletions(-) diff --git a/mp2-common/src/eth.rs b/mp2-common/src/eth.rs index 9bef7f92..9dcccc55 100644 --- a/mp2-common/src/eth.rs +++ b/mp2-common/src/eth.rs @@ -72,6 +72,44 @@ pub fn extract_child_hashes(rlp_data: &[u8]) -> Vec> { hashes } +/// Enum used to distinguish between different types of node in an MPT. +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum NodeType { + Branch, + Extension, + Leaf, +} + +/// Function that returns the [`NodeType`] of an RLP encoded MPT node +pub fn node_type(rlp_data: &[u8]) -> Result { + let rlp = Rlp::new(rlp_data); + + let item_count = rlp.item_count()?; + + if item_count == 17 { + Ok(NodeType::Branch) + } else if item_count == 2 { + // The first item is the encoded path, if it begins with a 2 or 3 it is a leaf, else it is an extension node + let first_item = rlp.at(0)?; + + // We want the first byte + let first_byte = first_item.as_raw()[0]; + + // The we divide by 16 to get the first nibble + match first_byte / 16 { + 0 | 1 => Ok(NodeType::Extension), + 2 | 3 => Ok(NodeType::Leaf), + _ => Err(anyhow!( + "Expected compact encoding beginning with 0,1,2 or 3" + )), + } + } else { + Err(anyhow!( + "RLP encoded Node item count was {item_count}, expected either 17 or 2" + )) + } +} + pub fn left_pad32(slice: &[u8]) -> [u8; 32] { left_pad::<32>(slice) } diff --git a/mp2-v1/src/values_extraction/api.rs b/mp2-v1/src/values_extraction/api.rs index bc628792..9f2bc22b 100644 --- a/mp2-v1/src/values_extraction/api.rs +++ b/mp2-v1/src/values_extraction/api.rs @@ -13,7 +13,7 @@ use anyhow::{bail, ensure, Result}; use log::debug; use mp2_common::{ default_config, - eth::{EventLogInfo, ReceiptProofInfo}, + eth::EventLogInfo, mpt_sequential::PAD_LEN, proof::{ProofInputSerialized, ProofWithVK}, storage_key::{MappingSlot, SimpleSlot}, @@ -79,11 +79,13 @@ impl CircuitInput { /// Create a circuit input for proving a leaf MPT node of a transaction receipt. pub fn new_receipt_leaf( - info: &ReceiptProofInfo, + last_node: &[u8], + tx_index: u64, event: &EventLogInfo, ) -> Self { CircuitInput::LeafReceipt( - ReceiptLeafCircuit::new(info, event).expect("Could not construct Receipt Leaf Circuit"), + ReceiptLeafCircuit::new(last_node, tx_index, event) + .expect("Could not construct Receipt Leaf Circuit"), ) } @@ -750,7 +752,11 @@ mod tests { let params = build_circuits_params(); println!("Proving leaf 1..."); - let leaf_input_1 = CircuitInput::new_receipt_leaf(info_one, &query.event); + let leaf_input_1 = CircuitInput::new_receipt_leaf( + info_one.mpt_proof.last().unwrap(), + info_one.tx_index, + &query.event, + ); let now = std::time::Instant::now(); let leaf_proof1 = generate_proof(¶ms, leaf_input_1).unwrap(); { @@ -765,7 +771,11 @@ mod tests { ); println!("Proving leaf 2..."); - let leaf_input_2 = CircuitInput::new_receipt_leaf(info_two, &query.event); + let leaf_input_2 = CircuitInput::new_receipt_leaf( + info_two.mpt_proof.last().unwrap(), + info_two.tx_index, + &query.event, + ); let now = std::time::Instant::now(); let leaf_proof2 = generate_proof(¶ms, leaf_input_2).unwrap(); println!( diff --git a/mp2-v1/src/values_extraction/leaf_receipt.rs b/mp2-v1/src/values_extraction/leaf_receipt.rs index 01260333..97ecbc1a 100644 --- a/mp2-v1/src/values_extraction/leaf_receipt.rs +++ b/mp2-v1/src/values_extraction/leaf_receipt.rs @@ -11,10 +11,10 @@ use alloy::{ primitives::{Address, Log, B256}, rlp::Decodable, }; -use anyhow::{anyhow, Result}; +use anyhow::Result; use mp2_common::{ array::{Array, Vector, VectorWire}, - eth::{EventLogInfo, ReceiptProofInfo}, + eth::EventLogInfo, group_hashing::CircuitBuilderGroupHashing, keccak::{InputData, KeccakCircuit, KeccakWires, HASH_LEN}, mpt_sequential::{MPTKeyWire, MPTReceiptLeafNode, PAD_LEN}, @@ -321,17 +321,10 @@ where { /// Create a new [`ReceiptLeafCircuit`] from a [`ReceiptProofInfo`] and a [`EventLogInfo`] pub fn new( - proof_info: &ReceiptProofInfo, + last_node: &[u8], + tx_index: u64, event: &EventLogInfo, ) -> Result { - // Since the compact encoding of the key is stored first plus an additional list header and - // then the first element in the receipt body is the transaction type we calculate the offset to that point - - let last_node = proof_info - .mpt_proof - .last() - .ok_or(anyhow!("Could not get last node in receipt trie proof"))?; - // Convert to Rlp form so we can use provided methods. let node_rlp = rlp::Rlp::new(last_node); @@ -429,8 +422,8 @@ where }); Ok(Self { - node: last_node.clone(), - tx_index: proof_info.tx_index, + node: last_node.to_vec(), + tx_index, size, address, rel_add_offset: add_rel_offset, @@ -760,8 +753,12 @@ mod tests { let info = proofs.first().unwrap(); let query = receipt_proof_infos.query(); - let c = - ReceiptLeafCircuit::::new::(info, &query.event).unwrap(); + let c = ReceiptLeafCircuit::::new::( + info.mpt_proof.last().unwrap(), + info.tx_index, + &query.event, + ) + .unwrap(); let test_circuit = TestReceiptLeafCircuit { c }; let node = info.mpt_proof.last().unwrap().clone(); diff --git a/mp2-v1/src/values_extraction/planner.rs b/mp2-v1/src/values_extraction/planner.rs index ca013cb2..135f31c5 100644 --- a/mp2-v1/src/values_extraction/planner.rs +++ b/mp2-v1/src/values_extraction/planner.rs @@ -6,10 +6,13 @@ use alloy::{ transports::Transport, }; use anyhow::Result; -use mp2_common::eth::{EventLogInfo, ReceiptQuery}; -use ryhope::storage::updatetree::UpdateTree; +use mp2_common::eth::{node_type, EventLogInfo, NodeType, ReceiptQuery}; +use ryhope::storage::updatetree::{Next, UpdateTree}; use std::future::Future; +use std::collections::HashMap; + +use super::{generate_proof, CircuitInput, PublicParameters}; /// Trait that is implemented for all data that we can provably extract. pub trait Extractable { fn create_update_tree( @@ -18,6 +21,33 @@ pub trait Extractable { epoch: u64, provider: &RootProvider, ) -> impl Future>>; + + fn prove_value_extraction( + &self, + contract: Address, + epoch: u64, + pp: &PublicParameters, + provider: &RootProvider, + ) -> impl Future>>; +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct ProofData { + node: Vec, + node_type: NodeType, + tx_index: Option, + proof: Option>, +} + +impl ProofData { + pub fn new(node: Vec, node_type: NodeType, tx_index: Option) -> ProofData { + ProofData { + node, + node_type, + tx_index, + proof: None, + } + } } impl Extractable @@ -45,6 +75,158 @@ impl Extractable // Now we make the UpdateTree Ok(UpdateTree::::from_paths(key_paths, epoch as i64)) } + + async fn prove_value_extraction( + &self, + contract: Address, + epoch: u64, + pp: &PublicParameters, + provider: &RootProvider, + ) -> Result> { + let query = ReceiptQuery:: { + contract, + event: *self, + }; + + let proofs = query.query_receipt_proofs(provider, epoch.into()).await?; + + let mut data_store = HashMap::::new(); + + // Convert the paths into their keys using keccak + let key_paths = proofs + .iter() + .map(|input| { + let tx_index = input.tx_index; + input + .mpt_proof + .iter() + .map(|node| { + let node_key = keccak256(node); + let node_type = node_type(node)?; + let tx = if let NodeType::Leaf = node_type { + Some(tx_index) + } else { + None + }; + data_store.insert(node_key, ProofData::new(node.clone(), node_type, tx)); + + Ok(node_key) + }) + .collect::>>() + }) + .collect::>>>()?; + + let update_tree = UpdateTree::::from_paths(key_paths, epoch as i64); + + let mut update_plan = update_tree.clone().into_workplan(); + + while let Some(Next::Ready(work_plan_item)) = update_plan.next() { + let node_type = data_store + .get(work_plan_item.k()) + .ok_or(anyhow::anyhow!( + "No ProofData found for key: {:?}", + work_plan_item.k() + ))? + .node_type; + + let update_tree_node = + update_tree + .get_node(work_plan_item.k()) + .ok_or(anyhow::anyhow!( + "No UpdateTreeNode found for key: {:?}", + work_plan_item.k() + ))?; + + match node_type { + NodeType::Leaf => { + let proof_data = + data_store + .get_mut(work_plan_item.k()) + .ok_or(anyhow::anyhow!( + "No ProofData found for key: {:?}", + work_plan_item.k() + ))?; + let input = CircuitInput::new_receipt_leaf( + &proof_data.node, + proof_data.tx_index.unwrap(), + self, + ); + let proof = generate_proof(pp, input)?; + proof_data.proof = Some(proof); + update_plan.done(&work_plan_item)?; + } + NodeType::Extension => { + let child_key = update_tree.get_child_keys(update_tree_node); + if child_key.len() != 1 { + return Err(anyhow::anyhow!("When proving extension node had {} many child keys when we should only have 1", child_key.len())); + } + let child_proof = data_store + .get(&child_key[0]) + .ok_or(anyhow::anyhow!( + "Extension node child had no proof data for key: {:?}", + child_key[0] + ))? + .clone(); + let proof_data = + data_store + .get_mut(work_plan_item.k()) + .ok_or(anyhow::anyhow!( + "No ProofData found for key: {:?}", + work_plan_item.k() + ))?; + let input = CircuitInput::new_extension( + proof_data.node.clone(), + child_proof.proof.ok_or(anyhow::anyhow!( + "Extension node child proof was a None value" + ))?, + ); + let proof = generate_proof(pp, input)?; + proof_data.proof = Some(proof); + update_plan.done(&work_plan_item)?; + } + NodeType::Branch => { + let child_keys = update_tree.get_child_keys(update_tree_node); + let child_proofs = child_keys + .iter() + .map(|key| { + data_store + .get(key) + .ok_or(anyhow::anyhow!( + "Branch child data could not be found for key: {:?}", + key + ))? + .clone() + .proof + .ok_or(anyhow::anyhow!("No proof found in brnach node child")) + }) + .collect::>>>()?; + let proof_data = + data_store + .get_mut(work_plan_item.k()) + .ok_or(anyhow::anyhow!( + "No ProofData found for key: {:?}", + work_plan_item.k() + ))?; + let input = CircuitInput::new_mapping_variable_branch( + proof_data.node.clone(), + child_proofs, + ); + let proof = generate_proof(pp, input)?; + proof_data.proof = Some(proof); + update_plan.done(&work_plan_item)?; + } + } + } + + let final_data = data_store + .get(update_tree.root()) + .ok_or(anyhow::anyhow!("No data for root of update tree found"))? + .clone(); + + final_data + .proof + .ok_or(anyhow::anyhow!("No proof stored for final data")) + } } #[cfg(test)] @@ -52,10 +234,21 @@ pub mod tests { use alloy::{eips::BlockNumberOrTag, primitives::Address, providers::ProviderBuilder, sol}; use anyhow::anyhow; - use mp2_common::eth::BlockUtil; + use eth_trie::Trie; + use mp2_common::{ + digest::Digest, + eth::BlockUtil, + proof::ProofWithVK, + utils::{Endianness, Packer}, + }; use mp2_test::eth::get_mainnet_url; use std::str::FromStr; + use crate::values_extraction::{ + api::build_circuits_params, compute_receipt_leaf_metadata_digest, + compute_receipt_leaf_value_digest, PublicInputs, + }; + use super::*; #[tokio::test] @@ -80,6 +273,67 @@ pub mod tests { Ok(()) } + #[tokio::test] + async fn test_receipt_proving() -> Result<()> { + // First get the info we will feed in to our function + let event_info = test_receipt_trie_helper().await?; + + let contract = Address::from_str("0xbd3531da5cf5857e7cfaa92426877b022e612cf8")?; + let epoch: u64 = 21362445; + + let url = get_mainnet_url(); + // get some tx and receipt + let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); + + let pp = build_circuits_params(); + let final_proof_bytes = event_info + .prove_value_extraction(contract, epoch, &pp, &provider) + .await?; + + let final_proof = ProofWithVK::deserialize(&final_proof_bytes)?; + let query = ReceiptQuery::<2, 1> { + contract, + event: event_info, + }; + + let metadata_digest = compute_receipt_leaf_metadata_digest(&event_info); + + let value_digest = query + .query_receipt_proofs(&provider, epoch.into()) + .await? + .iter() + .fold(Digest::NEUTRAL, |acc, info| { + acc + compute_receipt_leaf_value_digest(info, &event_info) + }); + + let pi = PublicInputs::new(&final_proof.proof.public_inputs); + + let mut block_util = build_test_data().await; + // Check the output hash + { + assert_eq!( + pi.root_hash(), + block_util + .receipts_trie + .root_hash()? + .0 + .to_vec() + .pack(Endianness::Little) + ); + } + + // Check value digest + { + assert_eq!(pi.values_digest(), value_digest.to_weierstrass()); + } + + // Check metadata digest + { + assert_eq!(pi.metadata_digest(), metadata_digest.to_weierstrass()); + } + Ok(()) + } + /// Function that fetches a block together with its transaction trie and receipt trie for testing purposes. async fn build_test_data() -> BlockUtil { let url = get_mainnet_url(); diff --git a/mp2-v1/tests/common/cases/indexing.rs b/mp2-v1/tests/common/cases/indexing.rs index d9ff7d7d..591d7df7 100644 --- a/mp2-v1/tests/common/cases/indexing.rs +++ b/mp2-v1/tests/common/cases/indexing.rs @@ -738,7 +738,7 @@ impl TableIndexing { } }; - let table_id = &self.table.public_name.clone(); + let table_id = &self.table.public_name; // we construct the proof key for both mappings and single variable in the same way since // it is derived from the table id which should be different for any tables we create. let value_key = ProofKey::ValueExtraction((table_id.clone(), bn as BlockPrimaryIndex)); diff --git a/ryhope/src/storage/updatetree.rs b/ryhope/src/storage/updatetree.rs index 80a65781..b83e8672 100644 --- a/ryhope/src/storage/updatetree.rs +++ b/ryhope/src/storage/updatetree.rs @@ -41,9 +41,13 @@ impl UpdateTreeNode { fn is_leaf(&self) -> bool { self.children.is_empty() } + + pub fn k(&self) -> K { + self.k.clone() + } } -impl UpdateTree { +impl UpdateTree { pub fn root(&self) -> &K { &self.nodes[0].k } @@ -55,6 +59,17 @@ impl UpdateTree { fn node_mut(&mut self, i: usize) -> &mut UpdateTreeNode { &mut self.nodes[i] } + + pub fn get_node(&self, key: &K) -> Option<&UpdateTreeNode> { + self.idx.get(key).map(|idx| self.node(*idx)) + } + + pub fn get_child_keys(&self, node: &UpdateTreeNode) -> Vec { + node.children + .iter() + .map(|idx| self.node(*idx).k()) + .collect() + } } impl UpdateTree { From d509713a1543815fe3023727418163c068b919bc Mon Sep 17 00:00:00 2001 From: Zack Youell Date: Tue, 31 Dec 2024 14:11:55 +0000 Subject: [PATCH 2/6] Resolves CRY-26 --- mp2-v1/src/api.rs | 4 ++ mp2-v1/tests/common/cases/indexing.rs | 5 +- mp2-v1/tests/common/cases/table_source.rs | 78 ++++++++++++++++++++--- mp2-v1/tests/common/final_extraction.rs | 6 ++ mp2-v1/tests/integrated_tests.rs | 10 ++- 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/mp2-v1/src/api.rs b/mp2-v1/src/api.rs index 4f813b8e..46a049ef 100644 --- a/mp2-v1/src/api.rs +++ b/mp2-v1/src/api.rs @@ -75,6 +75,10 @@ impl PublicParameters { pub fn get_params_info(&self) -> Result> { self.tree_creation.get_params_info() } + + pub fn get_value_extraction_params(&self) -> &values_extraction::PublicParameters { + &self.values_extraction + } } /// Instantiate the circuits employed for the pre-processing stage of LPN, diff --git a/mp2-v1/tests/common/cases/indexing.rs b/mp2-v1/tests/common/cases/indexing.rs index 591d7df7..5bfc86c4 100644 --- a/mp2-v1/tests/common/cases/indexing.rs +++ b/mp2-v1/tests/common/cases/indexing.rs @@ -1031,15 +1031,16 @@ where self.apply_update(ctx, contract).await } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum ChangeType { Deletion, Insertion, Update(UpdateType), Silent, + Receipt(usize, usize), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum UpdateType { SecondaryIndex, Rest, diff --git a/mp2-v1/tests/common/cases/table_source.rs b/mp2-v1/tests/common/cases/table_source.rs index 6704a49c..de624525 100644 --- a/mp2-v1/tests/common/cases/table_source.rs +++ b/mp2-v1/tests/common/cases/table_source.rs @@ -34,7 +34,7 @@ use mp2_v1::{ values_extraction::{ compute_all_receipt_coulmn_ids, compute_receipt_leaf_metadata_digest, identifier_for_mapping_key_column, identifier_for_mapping_value_column, - identifier_single_var_column, + identifier_single_var_column, planner::Extractable, }, }; use plonky2::field::types::PrimeField64; @@ -444,6 +444,9 @@ impl SingleValuesExtractionArgs { current_values.s2 = next_value(); } }, + ChangeType::Receipt(..) => { + panic!("Can't process a Receipt update type for a Simple variable") + } }; let contract_update = UpdateSimpleStorage::Single(current_values); @@ -718,6 +721,7 @@ impl MappingValuesExtractionArgs { } } } + ChangeType::Receipt(..) => panic!("Can't process Receipt update type for a Mapping"), }; // small iteration to always have a good updated list of mapping keys for update in mapping_updates.iter() { @@ -1344,20 +1348,76 @@ where async fn generate_extraction_proof_inputs( &self, - _ctx: &mut TestContext, - _contract: &Contract, - _value_key: ProofKey, + ctx: &mut TestContext, + contract: &Contract, + value_key: ProofKey, ) -> Result<(ExtractionProofInput, HashOutput)> { - todo!("Implement as part of CRY-25") + let event = self.get_event(); + + let ProofKey::ValueExtraction((_, bn)) = value_key else { + bail!("key wrong"); + }; + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + + let value_proof = event + .prove_value_extraction( + contract.address(), + bn as u64, + ctx.params().get_value_extraction_params(), + provider.root(), + ) + .await?; + Ok(( + ExtractionProofInput::Receipt(value_proof), + self.metadata_hash(contract.address(), contract.chain_id()), + )) } fn random_contract_update<'a>( &'a mut self, - _ctx: &'a mut TestContext, - _contract: &'a Contract, - _c: ChangeType, + ctx: &'a mut TestContext, + contract: &'a Contract, + c: ChangeType, ) -> BoxFuture<'a, Vec>> { - todo!("Implement as part of CRY-25") + let event = self.get_event(); + async move { + let ChangeType::Receipt(relevant, others) = c else { + panic!("Need ChangeType::Receipt, got: {:?}", c); + }; + let contract_update = + ReceiptUpdate::new((R::NO_TOPICS as u8, R::MAX_DATA as u8), relevant, others); + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + + let event_emitter = EventContract::new(contract.address(), provider.root()); + event_emitter + .apply_update(ctx, &contract_update) + .await + .unwrap(); + + let block_number = ctx.block_number().await; + let new_block_number = block_number as BlockPrimaryIndex; + + let query = ReceiptQuery::<{ R::NO_TOPICS }, { R::MAX_DATA }> { + contract: contract.address(), + event, + }; + + let proof_infos = query + .query_receipt_proofs(provider.root(), block_number.into()) + .await + .unwrap(); + + R::to_table_rows(&proof_infos, &event, new_block_number) + } + .boxed() } fn metadata_hash(&self, _contract_address: Address, _chain_id: u64) -> MetadataHash { diff --git a/mp2-v1/tests/common/final_extraction.rs b/mp2-v1/tests/common/final_extraction.rs index 0ae8db58..4190aae1 100644 --- a/mp2-v1/tests/common/final_extraction.rs +++ b/mp2-v1/tests/common/final_extraction.rs @@ -22,10 +22,13 @@ pub struct MergeExtractionProof { pub mapping: ExtractionTableProof, } +type ReceiptExtractionProof = Vec; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum ExtractionProofInput { Single(ExtractionTableProof), Merge(MergeExtractionProof), + Receipt(ReceiptExtractionProof), } impl TestContext { @@ -57,6 +60,9 @@ impl TestContext { inputs.single.value_proof, inputs.mapping.value_proof, ), + ExtractionProofInput::Receipt(input) => { + CircuitInput::new_receipt_input(block_proof, input) + } }?; let params = self.params(); let proof = self diff --git a/mp2-v1/tests/integrated_tests.rs b/mp2-v1/tests/integrated_tests.rs index 5ec84cc9..684bf540 100644 --- a/mp2-v1/tests/integrated_tests.rs +++ b/mp2-v1/tests/integrated_tests.rs @@ -90,9 +90,15 @@ async fn integrated_indexing() -> Result<()> { ctx.build_params(ParamsType::Indexing).unwrap(); info!("Params built"); - // For now we test that we can start a receipt case only. - let (_receipt, _genesis) = + + let (mut receipt, genesis) = TableIndexing::>::receipt_test_case(0, 0, &mut ctx).await?; + let changes = vec![ + ChangeType::Receipt(1, 10), + ChangeType::Receipt(10, 1), + ChangeType::Receipt(0, 10), + ]; + receipt.run(&mut ctx, genesis, changes.clone()).await?; // NOTE: to comment to avoid very long tests... let (mut single, genesis) = From cddb4d1d9e498b0915243f8f4be6d4c3e731a4ac Mon Sep 17 00:00:00 2001 From: Zack Youell Date: Thu, 2 Jan 2025 09:39:13 +0000 Subject: [PATCH 3/6] Fixed topic cell id discrepancy --- mp2-v1/src/values_extraction/planner.rs | 4 ++++ mp2-v1/tests/common/cases/table_source.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mp2-v1/src/values_extraction/planner.rs b/mp2-v1/src/values_extraction/planner.rs index 135f31c5..279ba09b 100644 --- a/mp2-v1/src/values_extraction/planner.rs +++ b/mp2-v1/src/values_extraction/planner.rs @@ -96,6 +96,10 @@ impl Extractable let key_paths = proofs .iter() .map(|input| { + let digest = + crate::values_extraction::compute_receipt_leaf_value_digest(input, self) + .to_weierstrass(); + println!("extraction proof values digest: {:?}", digest); let tx_index = input.tx_index; input .mpt_proof diff --git a/mp2-v1/tests/common/cases/table_source.rs b/mp2-v1/tests/common/cases/table_source.rs index de624525..1a67312f 100644 --- a/mp2-v1/tests/common/cases/table_source.rs +++ b/mp2-v1/tests/common/cases/table_source.rs @@ -1193,7 +1193,7 @@ pub trait ReceiptExtractionArgs: .skip(1) .map(|(j, topic)| { Cell::new( - *column_ids.get(&format!("topic_{}", j)).unwrap(), + *column_ids.get(&format!("topic_{}", j + 1)).unwrap(), topic.into(), ) }) From a810894c13807d6bb8705d91d6c6ca8cf31604bb Mon Sep 17 00:00:00 2001 From: Zack Youell Date: Thu, 2 Jan 2025 12:44:40 +0000 Subject: [PATCH 4/6] Fixed receipt value digest --- mp2-v1/src/values_extraction/leaf_receipt.rs | 25 ++++++++++-- mp2-v1/src/values_extraction/mod.rs | 43 +++++++++++++++----- mp2-v1/src/values_extraction/planner.rs | 1 + 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/mp2-v1/src/values_extraction/leaf_receipt.rs b/mp2-v1/src/values_extraction/leaf_receipt.rs index 97ecbc1a..62982355 100644 --- a/mp2-v1/src/values_extraction/leaf_receipt.rs +++ b/mp2-v1/src/values_extraction/leaf_receipt.rs @@ -22,6 +22,7 @@ use mp2_common::{ public_inputs::PublicInputCommon, rlp::MAX_KEY_NIBBLE_LEN, types::{CBuilder, GFp}, + u256::UInt256Target, utils::{less_than, less_than_or_equal_to_unsafe, Endianness, PackerTarget, ToTargets}, D, F, }; @@ -205,9 +206,20 @@ impl EventWires { b.mul(tmp, scalar) }); - // Map the gas used to a curve point for the value digest, gas used is the first column so use one as its column id. - let gas_digest = b.map_to_curve_point(&[gas_used_column_id, gas_used]); - let tx_index_digest = b.map_to_curve_point(&[tx_index_column_id, tx_index]); + // Convert gas into a UInt256Target as we know it fits into a u32 (We summed 3 bytes, there are 4 bytes in a u32) + let gas_used_u256 = UInt256Target::new_from_target_unsafe(b, gas_used); + + let gas_values = iter::once(gas_used_column_id) + .chain(gas_used_u256.to_targets()) + .collect::>(); + let gas_digest = b.map_to_curve_point(&gas_values); + + // For transaction index digest we know the transaction index will always fit in a u32 + let tx_index_uint256 = UInt256Target::new_from_target_unsafe(b, tx_index); + let tx_index_values = iter::once(tx_index_column_id) + .chain(tx_index_uint256.to_targets()) + .collect::>(); + let tx_index_digest = b.map_to_curve_point(&tx_index_values); let initial_row_digest = b.add_curve_point(&[gas_digest, tx_index_digest]); // We also keep track of the number of real logs we process as each log forms a row in our table @@ -263,7 +275,12 @@ impl EventWires { // We also keep track of which log this is in the receipt to avoid having identical rows in the table in the case // that the event we are tracking can be emitted multiple times in the same transaction but has no topics or data. let log_number = b.constant(F::from_canonical_usize(index + 1)); - let log_no_digest = b.map_to_curve_point(&[log_number_column_id, log_number]); + // This number will always fit into a u32 so we can directly call `UInt256Target::from_target_unsafe` + let log_number_uint256 = UInt256Target::new_from_target_unsafe(b, log_number); + let log_no_values = iter::once(log_number_column_id) + .chain(log_number_uint256.to_targets()) + .collect::>(); + let log_no_digest = b.map_to_curve_point(&log_no_values); points.push(log_no_digest); let increment = b.select(dummy, zero, one); diff --git a/mp2-v1/src/values_extraction/mod.rs b/mp2-v1/src/values_extraction/mod.rs index e78f857a..4b87c566 100644 --- a/mp2-v1/src/values_extraction/mod.rs +++ b/mp2-v1/src/values_extraction/mod.rs @@ -1,6 +1,6 @@ use alloy::{ consensus::TxReceipt, - primitives::{Address, IntoLogData}, + primitives::{Address, IntoLogData, B256, U256}, }; use mp2_common::{ eth::{left_pad32, EventLogInfo, ReceiptProofInfo}, @@ -297,7 +297,7 @@ pub fn compute_receipt_leaf_value_digest Digest { let receipt = receipt_proof_info.to_receipt().unwrap(); let gas_used = receipt.cumulative_gas_used(); - + let gas_used_u256: B256 = U256::from(gas_used).into(); // Only use events that we are indexing let address = event.address; let sig = event.event_signature; @@ -336,13 +336,26 @@ pub fn compute_receipt_leaf_value_digest>(); let gas_used_column_id = H::hash_no_pad(&gas_used_input).elements[0]; - let index_digest = map_to_curve_point(&[ - tx_index_column_id, - GFp::from_canonical_u64(receipt_proof_info.tx_index), - ]); - - let gas_digest = - map_to_curve_point(&[gas_used_column_id, GFp::from_noncanonical_u128(gas_used)]); + let index_256: B256 = U256::from(receipt_proof_info.tx_index).into(); + let index_values = iter::once(tx_index_column_id) + .chain( + index_256 + .0 + .pack(mp2_common::utils::Endianness::Big) + .to_fields(), + ) + .collect::>(); + let index_digest = map_to_curve_point(&index_values); + + let gas_used_values = iter::once(gas_used_column_id) + .chain( + gas_used_u256 + .0 + .pack(mp2_common::utils::Endianness::Big) + .to_fields(), + ) + .collect::>(); + let gas_digest = map_to_curve_point(&gas_used_values); let mut n = 0; receipt .logs() @@ -396,8 +409,16 @@ pub fn compute_receipt_leaf_value_digest>(); - let log_no_digest = - map_to_curve_point(&[log_number_column_id, GFp::from_canonical_usize(n)]); + let log_no_256: B256 = U256::from(n).into(); + let log_no_values = iter::once(log_number_column_id) + .chain( + log_no_256 + .0 + .pack(mp2_common::utils::Endianness::Big) + .to_fields(), + ) + .collect::>(); + let log_no_digest = map_to_curve_point(&log_no_values); let initial_digest = index_digest + gas_digest + log_no_digest; let row_value = iter::once(initial_digest) diff --git a/mp2-v1/src/values_extraction/planner.rs b/mp2-v1/src/values_extraction/planner.rs index 279ba09b..02b6ea93 100644 --- a/mp2-v1/src/values_extraction/planner.rs +++ b/mp2-v1/src/values_extraction/planner.rs @@ -101,6 +101,7 @@ impl Extractable .to_weierstrass(); println!("extraction proof values digest: {:?}", digest); let tx_index = input.tx_index; + println!("tx index: {}", tx_index); input .mpt_proof .iter() From 841e488ec6c764511bef840984b0c945a7af824e Mon Sep 17 00:00:00 2001 From: Zack Youell Date: Thu, 2 Jan 2025 13:49:28 +0000 Subject: [PATCH 5/6] RowTreeUpdate Debugging --- mp2-v1/src/values_extraction/planner.rs | 5 ----- mp2-v1/tests/common/table.rs | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mp2-v1/src/values_extraction/planner.rs b/mp2-v1/src/values_extraction/planner.rs index 02b6ea93..135f31c5 100644 --- a/mp2-v1/src/values_extraction/planner.rs +++ b/mp2-v1/src/values_extraction/planner.rs @@ -96,12 +96,7 @@ impl Extractable let key_paths = proofs .iter() .map(|input| { - let digest = - crate::values_extraction::compute_receipt_leaf_value_digest(input, self) - .to_weierstrass(); - println!("extraction proof values digest: {:?}", digest); let tx_index = input.tx_index; - println!("tx index: {}", tx_index); input .mpt_proof .iter() diff --git a/mp2-v1/tests/common/table.rs b/mp2-v1/tests/common/table.rs index 19a72f30..7c73f2ab 100644 --- a/mp2-v1/tests/common/table.rs +++ b/mp2-v1/tests/common/table.rs @@ -430,6 +430,9 @@ impl Table { .map(|plan| RowUpdateResult { updates: plan }); { // debugging + if out.is_err() { + println!("Out was an error: {:?}", out); + } println!("\n+++++++++++++++++++++++++++++++++\n"); let root = self.row.root_data().await.unwrap(); let new_epoch = self.row.current_epoch(); @@ -491,7 +494,7 @@ pub enum TreeRowUpdate { Deletion(RowTreeKey), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RowUpdateResult { // There is only a single row key for a table that we update continuously // so no need to track all the rows that have been updated in the result From 7fce69604cb51cd120167a1418d8dc6bfb4333c9 Mon Sep 17 00:00:00 2001 From: Zack Youell Date: Thu, 2 Jan 2025 14:50:52 +0000 Subject: [PATCH 6/6] Correct Receipt Row Tree --- mp2-v1/tests/common/cases/indexing.rs | 17 +++++++++++++++++ mp2-v1/tests/common/cases/table_source.rs | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mp2-v1/tests/common/cases/indexing.rs b/mp2-v1/tests/common/cases/indexing.rs index 5bfc86c4..6f7af0fa 100644 --- a/mp2-v1/tests/common/cases/indexing.rs +++ b/mp2-v1/tests/common/cases/indexing.rs @@ -508,7 +508,24 @@ impl TableIndexing { .source .random_contract_update(ctx, &self.contract, ut) .await; + // If we are dealing with receipts we need to remove everything already in the row tree let bn = ctx.block_number().await as BlockPrimaryIndex; + + let table_row_updates = if let ChangeType::Receipt(..) = ut { + let current_row_epoch = self.table.row.current_epoch(); + let current_row_keys = self + .table + .row + .keys_at(current_row_epoch) + .await + .into_iter() + .map(TableRowUpdate::::Deletion) + .collect::>(); + [current_row_keys, table_row_updates].concat() + } else { + table_row_updates + }; + log::info!("Applying follow up updates to contract done - now at block {bn}",); // we first run the initial preprocessing and db creation. // NOTE: we don't show copy on write here - the fact of only reproving what has been diff --git a/mp2-v1/tests/common/cases/table_source.rs b/mp2-v1/tests/common/cases/table_source.rs index 1a67312f..47cbe932 100644 --- a/mp2-v1/tests/common/cases/table_source.rs +++ b/mp2-v1/tests/common/cases/table_source.rs @@ -1141,7 +1141,7 @@ pub trait ReceiptExtractionArgs: fn get_index(&self) -> u64; - fn to_table_rows( + fn to_table_rows( proof_infos: &[ReceiptProofInfo], event: &EventLogInfo<{ Self::NO_TOPICS }, { Self::MAX_DATA }>, block: PrimaryIndex, @@ -1221,7 +1221,7 @@ pub trait ReceiptExtractionArgs: data_cells, ] .concat(), - primary: block, + primary: block.clone(), }; TableRowUpdate::::Insertion(collection, secondary)