From 40183060085ac14f0d0e60f9a5c641cfee255f8f Mon Sep 17 00:00:00 2001 From: Ratan Kaliani Date: Tue, 29 Aug 2023 23:32:05 -0700 Subject: [PATCH] feat: eth_getTransactionLog (#99) * wip * feat: test transaction log * refactor: update offset explicitly * lint * fix order * refactor: update order of blockhash * lint * lint * add generator to macro, dummy serialzie in circuit * add storage key gen from main * chore: cargo fmt --- plonky2x/src/backend/circuit/utils.rs | 5 +- plonky2x/src/frontend/eth/storage/builder.rs | 87 +++++++++++-- .../eth/storage/generators/storage.rs | 121 +++++++++++++++++- .../src/frontend/eth/storage/vars/block.rs | 8 +- .../src/frontend/eth/storage/vars/storage.rs | 11 +- 5 files changed, 208 insertions(+), 24 deletions(-) diff --git a/plonky2x/src/backend/circuit/utils.rs b/plonky2x/src/backend/circuit/utils.rs index ef7db7093..ef986e1a0 100644 --- a/plonky2x/src/backend/circuit/utils.rs +++ b/plonky2x/src/backend/circuit/utils.rs @@ -30,7 +30,7 @@ use plonky2::util::serialization::{Buffer, IoResult, WitnessGeneratorSerializer} use crate::frontend::eth::storage::generators::block::EthBlockGenerator; use crate::frontend::eth::storage::generators::storage::{ - EthStorageKeyGenerator, EthStorageProofGenerator, + EthLogGenerator, EthStorageKeyGenerator, EthStorageProofGenerator, }; use crate::frontend::hash::keccak::keccak256::Keccack256Generator; @@ -144,8 +144,9 @@ where SplitGenerator, "SplitGenerator", WireSplitGenerator, "WireSplitGenerator", EthStorageProofGenerator, "EthStorageProofGenerator", - EthStorageKeyGenerator, "EthStorageKeyGenerator", + EthLogGenerator, "EthLogGenerator", EthBlockGenerator, "EthBlockGenerator", + EthStorageKeyGenerator, "EthStorageKeyGenerator", Keccack256Generator, "Keccak256Generator" } } diff --git a/plonky2x/src/frontend/eth/storage/builder.rs b/plonky2x/src/frontend/eth/storage/builder.rs index 05369f56a..4a3cc8161 100644 --- a/plonky2x/src/frontend/eth/storage/builder.rs +++ b/plonky2x/src/frontend/eth/storage/builder.rs @@ -3,7 +3,9 @@ use plonky2::field::extension::Extendable; use plonky2::hash::hash_types::RichField; use super::generators::block::EthBlockGenerator; -use super::generators::storage::{EthStorageKeyGenerator, EthStorageProofGenerator}; +use super::generators::storage::{ + EthLogGenerator, EthStorageKeyGenerator, EthStorageProofGenerator, +}; use super::vars::{EthAccountVariable, EthHeaderVariable, EthLogVariable}; use crate::frontend::builder::CircuitBuilder; use crate::frontend::eth::vars::AddressVariable; @@ -24,11 +26,11 @@ impl, const D: usize> CircuitBuilder { #[allow(non_snake_case)] pub fn eth_get_storage_at( &mut self, + block_hash: Bytes32Variable, address: AddressVariable, storage_key: Bytes32Variable, - block_hash: Bytes32Variable, ) -> Bytes32Variable { - let generator = EthStorageProofGenerator::new(self, address, storage_key, block_hash); + let generator = EthStorageProofGenerator::new(self, block_hash, address, storage_key); self.add_simple_generator(&generator); generator.value } @@ -50,13 +52,15 @@ impl, const D: usize> CircuitBuilder { } #[allow(non_snake_case)] - pub fn eth_get_transaction_receipt( + pub fn eth_get_transaction_log( &mut self, - _transaction_hash: Bytes32Variable, - _block_hash: Bytes32Variable, - _log_index: usize, + transaction_hash: Bytes32Variable, + block_hash: Bytes32Variable, + log_index: u64, ) -> EthLogVariable { - todo!() + let generator = EthLogGenerator::new(self, transaction_hash, block_hash, log_index); + self.add_simple_generator(&generator); + generator.value } } @@ -70,7 +74,7 @@ mod tests { use super::*; use crate::frontend::eth::storage::utils::get_map_storage_location; - use crate::frontend::eth::storage::vars::EthHeader; + use crate::frontend::eth::storage::vars::{EthHeader, EthLog}; use crate::prelude::CircuitBuilderX; use crate::utils::{address, bytes32}; @@ -88,7 +92,7 @@ mod tests { let block_hash = builder.evm_read::(); let address = builder.evm_read::(); let location = builder.evm_read::(); - let value = builder.eth_get_storage_at(address, location, block_hash); + let value = builder.eth_get_storage_at(block_hash, address, location); builder.evm_write(value); // Build your circuit. @@ -97,10 +101,13 @@ mod tests { // Write to the circuit input. // These values are taken from Ethereum block https://etherscan.io/block/17880427 let mut input = circuit.input(); + // block hash input.evm_write::(bytes32!( "0x281dc31bb78779a1ede7bf0f4d2bc5f07ddebc9f9d1155e413d8804384604bbe" )); + // address input.evm_write::(address!("0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5")); + // location input.evm_write::(bytes32!( "0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5" )); @@ -236,4 +243,64 @@ mod tests { let _ = circuit.serialize().unwrap(); } + + #[test] + #[cfg_attr(feature = "ci", ignore)] + #[allow(non_snake_case)] + fn test_eth_get_transaction_log() { + dotenv::dotenv().ok(); + let rpc_url = env::var("RPC_1").unwrap(); + let provider = Provider::::try_from(rpc_url).unwrap(); + + // This is the circuit definition + let mut builder = CircuitBuilderX::new(); + builder.set_execution_client(provider); + let transaction_hash = builder.read::(); + let block_hash = builder.read::(); + let log_index = 0u64; + + let value = builder.eth_get_transaction_log(transaction_hash, block_hash, log_index); + builder.write(value); + + // Build your circuit. + let circuit = builder.build::(); + + // Write to the circuit input. + // These values are taken from Ethereum block https://etherscan.io/block/17880427 + let mut input = circuit.input(); + // transaction hash + input.write::(bytes32!( + "0xead2251970404128e6f9bdff0133badb7338c5fa7ea4eec24e88af85a6d03cf2" + )); + // block hash + input.write::(bytes32!( + "0x281dc31bb78779a1ede7bf0f4d2bc5f07ddebc9f9d1155e413d8804384604bbe" + )); + + // Generate a proof. + let (proof, output) = circuit.prove(&input); + + // Verify proof. + circuit.verify(&proof, &input, &output); + + // Read output. + let circuit_value = output.read::(); + println!("{:?}", circuit_value); + assert_eq!( + circuit_value, + EthLog { + address: address!("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), + topics: [ + bytes32!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), + bytes32!("0x00000000000000000000000059b4bb1f5d943cf71a10df63f6b743ee4a4489ee"), + bytes32!("0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff") + ], + data_hash: bytes32!( + "0x5cdda96947975d4afbc971c9aa8bb2cc684e158d10a0d878b3a5b8b0f895262c" + ) + } + ); + + let _ = circuit.serialize().unwrap(); + } } diff --git a/plonky2x/src/frontend/eth/storage/generators/storage.rs b/plonky2x/src/frontend/eth/storage/generators/storage.rs index 9e2f88265..79f8220a5 100644 --- a/plonky2x/src/frontend/eth/storage/generators/storage.rs +++ b/plonky2x/src/frontend/eth/storage/generators/storage.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use core::marker::PhantomData; use ethers::providers::Middleware; -use ethers::types::EIP1186ProofResponse; +use ethers::types::{EIP1186ProofResponse, TransactionReceipt}; use plonky2::field::extension::Extendable; use plonky2::hash::hash_types::RichField; use plonky2::iop::generator::{GeneratedValues, SimpleGenerator}; @@ -10,10 +10,12 @@ use plonky2::iop::target::Target; use plonky2::iop::witness::PartitionWitness; use plonky2::plonk::circuit_data::CommonCircuitData; use plonky2::util::serialization::{Buffer, IoResult, Read, Write}; +use sha2::Digest; use tokio::runtime::Runtime; use crate::frontend::builder::CircuitBuilder; use crate::frontend::eth::storage::utils::get_map_storage_location; +use crate::frontend::eth::storage::vars::{EthLog, EthLogVariable}; use crate::frontend::eth::utils::u256_to_h256_be; use crate::frontend::eth::vars::AddressVariable; use crate::frontend::uint::uint256::U256Variable; @@ -22,9 +24,9 @@ use crate::utils::eth::get_provider; #[derive(Debug, Clone)] pub struct EthStorageProofGenerator, const D: usize> { + block_hash: Bytes32Variable, address: AddressVariable, storage_key: Bytes32Variable, - block_hash: Bytes32Variable, pub value: Bytes32Variable, chain_id: u64, _phantom: PhantomData, @@ -33,16 +35,16 @@ pub struct EthStorageProofGenerator, const D: usize impl, const D: usize> EthStorageProofGenerator { pub fn new( builder: &mut CircuitBuilder, + block_hash: Bytes32Variable, address: AddressVariable, storage_key: Bytes32Variable, - block_hash: Bytes32Variable, ) -> EthStorageProofGenerator { let chain_id = builder.get_chain_id(); let value = builder.init::(); EthStorageProofGenerator { + block_hash, address, storage_key, - block_hash, value, chain_id, _phantom: PhantomData::, @@ -193,3 +195,114 @@ impl, const D: usize> SimpleGenerator }) } } + +#[derive(Debug, Clone)] +pub struct EthLogGenerator, const D: usize> { + transaction_hash: Bytes32Variable, + block_hash: Bytes32Variable, + log_index: u64, + pub value: EthLogVariable, + chain_id: u64, + _phantom: PhantomData, +} + +impl, const D: usize> EthLogGenerator { + pub fn new( + builder: &mut CircuitBuilder, + transaction_hash: Bytes32Variable, + block_hash: Bytes32Variable, + log_index: u64, + ) -> EthLogGenerator { + let chain_id = builder.get_chain_id(); + let value = builder.init::(); + EthLogGenerator { + transaction_hash, + block_hash, + log_index, + value, + chain_id, + _phantom: PhantomData::, + } + } +} + +impl, const D: usize> SimpleGenerator for EthLogGenerator { + fn id(&self) -> String { + "EthLogGenerator".to_string() + } + + fn dependencies(&self) -> Vec { + let mut targets = Vec::new(); + targets.extend(self.transaction_hash.targets()); + targets.extend(self.block_hash.targets()); + targets + } + + fn run_once(&self, witness: &PartitionWitness, buffer: &mut GeneratedValues) { + let transaction_hash = self.transaction_hash.get(witness); + // block_hash is unused + let _block_hash = self.block_hash.get(witness); + + let provider = get_provider(self.chain_id); + let rt = Runtime::new().expect("failed to create tokio runtime"); + let result: TransactionReceipt = rt + .block_on(async { + provider + .get_transaction_receipt(transaction_hash) + .await + .expect("Failed to call get_transaction_receipt") + }) + .expect("No transaction receipt found"); + + let log = &result.logs[self.log_index as usize]; + let value = EthLog { + address: log.address, + topics: [log.topics[0], log.topics[1], log.topics[2]], + data_hash: ethers::types::H256::from_slice(sha2::Sha256::digest(&log.data).as_ref()), + }; + self.value.set(buffer, value); + } + + #[allow(unused_variables)] + fn serialize(&self, dst: &mut Vec, common_data: &CommonCircuitData) -> IoResult<()> { + let chain_id_bytes = self.chain_id.to_be_bytes(); + dst.write_all(&chain_id_bytes)?; + + dst.write_target_vec(&self.transaction_hash.targets())?; + dst.write_target_vec(&self.block_hash.targets())?; + + let log_index_bytes = self.log_index.to_be_bytes(); + dst.write_all(&log_index_bytes)?; + + dst.write_target_vec(&self.value.targets()) + } + + #[allow(unused_variables)] + fn deserialize(src: &mut Buffer, common_data: &CommonCircuitData) -> IoResult { + let mut chain_id_bytes = [0u8; 8]; + src.read_exact(&mut chain_id_bytes)?; + let chain_id = u64::from_be_bytes(chain_id_bytes); + + let transaction_hash_targets = src.read_target_vec()?; + let transaction_hash = Bytes32Variable::from_targets(&transaction_hash_targets); + + let block_hash_targets = src.read_target_vec()?; + let block_hash = Bytes32Variable::from_targets(&block_hash_targets); + + let mut log_index_bytes = [0u8; 8]; + src.read_exact(&mut log_index_bytes)?; + let log_index = u64::from_be_bytes(log_index_bytes); + + let value_targets = src.read_target_vec()?; + let value = EthLogVariable::from_targets(&value_targets); + + Ok(Self { + block_hash, + transaction_hash, + log_index, + value, + chain_id, + _phantom: PhantomData::, + }) + } +} diff --git a/plonky2x/src/frontend/eth/storage/vars/block.rs b/plonky2x/src/frontend/eth/storage/vars/block.rs index 300500e4b..7914a312d 100644 --- a/plonky2x/src/frontend/eth/storage/vars/block.rs +++ b/plonky2x/src/frontend/eth/storage/vars/block.rs @@ -102,9 +102,11 @@ impl CircuitVariable for EthHeaderVariable { #[allow(unused_variables)] fn from_variables(variables: &[Variable]) -> Self { let parent_hash = Bytes32Variable::from_variables(&variables[0..32 * 8]); - let uncle_hash = Bytes32Variable::from_variables(&variables[32 * 8..64 * 8]); - let coinbase = AddressVariable::from_variables(&variables[64 * 8..64 * 8 + 8 * 20]); - let mut offset = 64 * 8 + 8 * 20; + let mut offset = 32 * 8; + let uncle_hash = Bytes32Variable::from_variables(&variables[offset..offset + 32 * 8]); + offset += 32 * 8; + let coinbase = AddressVariable::from_variables(&variables[offset..offset + 8 * 20]); + offset += 8 * 20; let root = Bytes32Variable::from_variables(&variables[offset..offset + 32 * 8]); offset += 32 * 8; diff --git a/plonky2x/src/frontend/eth/storage/vars/storage.rs b/plonky2x/src/frontend/eth/storage/vars/storage.rs index 5b375f3c5..3a82d46ec 100644 --- a/plonky2x/src/frontend/eth/storage/vars/storage.rs +++ b/plonky2x/src/frontend/eth/storage/vars/storage.rs @@ -115,11 +115,12 @@ impl CircuitVariable for EthAccountVariable { fn from_variables(variables: &[Variable]) -> Self { let balance = U256Variable::from_variables(&variables[0..4]); - let code_hash = Bytes32Variable::from_variables(&variables[4..4 + 32 * 8]); - let offset = 4 + 32 * 8; + let mut offset = 4; + let code_hash = Bytes32Variable::from_variables(&variables[offset..offset + 32 * 8]); + offset += 32 * 8; let nonce = U256Variable::from_variables(&variables[offset..offset + 4]); - let storage_hash = - Bytes32Variable::from_variables(&variables[offset + 4..offset + 4 + 32 * 8]); + offset += 4; + let storage_hash = Bytes32Variable::from_variables(&variables[offset..offset + 32 * 8]); Self { balance, code_hash, @@ -145,7 +146,7 @@ impl CircuitVariable for EthAccountVariable { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct EthLog { pub address: Address, pub topics: [H256; 3],