From 0dc8b873ed51db0af2140d6672e790a27f5daa4f Mon Sep 17 00:00:00 2001 From: Rodion Lim Date: Thu, 3 Oct 2024 10:47:50 +0800 Subject: [PATCH 1/5] feat: support quorum private transaction during cast run --- crates/cast/bin/cmd/run.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 7d7c922b4b6d..f17b66c25c8c 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -1,6 +1,7 @@ -use alloy_primitives::U256; +use alloy_primitives::{Bytes, Uint, U256}; use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; +use alloy_transport::TransportResult; use cast::{revm::primitives::EnvWithHandlerCfg, traces::TraceKind}; use clap::Parser; use eyre::{Result, WrapErr}; @@ -109,7 +110,7 @@ impl RunArgs { .build()?; let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?; - let tx = provider + let mut tx = provider .get_transaction_by_hash(tx_hash) .await .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))? @@ -231,6 +232,23 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); + if tx.inner.signature.is_some() { + // check if recovery id corresponds to a quorum private transaction (https://docs.goquorum.conssensys.io/concepts/privacy/private-and-public#private-transactions) + let v = tx.inner.signature.unwrap().v; + let is_private_quorum_txn = v >= Uint::from(37) && v <= Uint::from(38); + if is_private_quorum_txn { + println!("Private quorum transaction detected."); + let result: TransportResult = provider + .client() + .request("eth_getQuorumPayload", (tx.input.clone(),)) + .await; + trace!(quorum_private_payload=?result, "fetch eth_getQuorumPayload"); + if result.is_ok() { + tx.input = result.unwrap(); + } + } + } + configure_tx_env(&mut env, &tx.inner); if let Some(to) = tx.to { From 581c3af59b6fd8cd9f72d41510e78863eb68e0a9 Mon Sep 17 00:00:00 2001 From: Matthew Alexander Date: Mon, 7 Oct 2024 09:07:36 +0800 Subject: [PATCH 2/5] feat: rust error handling * feat: rust error handling * feat: pass through eth_getQuorumPayload errors * chore: replace logs with println macro --- crates/cast/bin/cmd/run.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index f17b66c25c8c..1c43e704d78b 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -189,10 +189,10 @@ impl RunArgs { tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) { pb.set_position((index + 1) as u64); - continue; + continue } if tx.hash == tx_hash { - break; + break } configure_tx_env(&mut env, &tx.inner); @@ -232,19 +232,30 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - if tx.inner.signature.is_some() { - // check if recovery id corresponds to a quorum private transaction (https://docs.goquorum.conssensys.io/concepts/privacy/private-and-public#private-transactions) - let v = tx.inner.signature.unwrap().v; - let is_private_quorum_txn = v >= Uint::from(37) && v <= Uint::from(38); + if let Some(signature) = tx.inner.signature { + let v = signature.v; + let is_private_quorum_txn = v == Uint::from(37) || v == Uint::from(38); if is_private_quorum_txn { println!("Private quorum transaction detected."); + + // Ideally, this should be a method implemented in alloy_provider let result: TransportResult = provider .client() .request("eth_getQuorumPayload", (tx.input.clone(),)) .await; - trace!(quorum_private_payload=?result, "fetch eth_getQuorumPayload"); - if result.is_ok() { - tx.input = result.unwrap(); + + match result { + Ok(tessera_input) => { + // If non-empty, use tessera payload + if !tessera_input.is_empty() { + tx.input = tessera_input; + } else { + println!("eth_getQuorumPayload returned empty bytes, using original tx.input instead"); + } + } + Err(e) => { + println!("eth_getQuorumPayload threw an error: {e}, proceeding with original tx.input"); + } } } } From 41ba49fe91eace73c443d13932202ead12ddc9ff Mon Sep 17 00:00:00 2001 From: Matthew Alexander Date: Mon, 7 Oct 2024 15:23:07 +0800 Subject: [PATCH 3/5] Refactor cast private txn checks + error handling --- crates/cast/bin/cmd/run.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 1c43e704d78b..0ab38cd33079 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -234,11 +234,14 @@ impl RunArgs { if let Some(signature) = tx.inner.signature { let v = signature.v; - let is_private_quorum_txn = v == Uint::from(37) || v == Uint::from(38); + + // 37/38 for private quorum transactions, tessera hash is 64 bytes + let is_private_quorum_txn = + (v == Uint::from(37) || v == Uint::from(38)) && tx.input.len() == 64; + if is_private_quorum_txn { println!("Private quorum transaction detected."); - // Ideally, this should be a method implemented in alloy_provider let result: TransportResult = provider .client() .request("eth_getQuorumPayload", (tx.input.clone(),)) @@ -246,15 +249,14 @@ impl RunArgs { match result { Ok(tessera_input) => { - // If non-empty, use tessera payload - if !tessera_input.is_empty() { - tx.input = tessera_input; - } else { - println!("eth_getQuorumPayload returned empty bytes, using original tx.input instead"); - } + println!( + "Executing private quorum transaction with quorum payload: {:?}", + tessera_input.to_string() + ); + tx.input = tessera_input; } Err(e) => { - println!("eth_getQuorumPayload threw an error: {e}, proceeding with original tx.input"); + return Err(eyre::eyre!("eth_getQuorumPayload threw an error: {e}, cannot execute transaction {:?}", tx.hash)); } } } From 5e30117012b0b2222d33ac1cb3ae5b09cb60668d Mon Sep 17 00:00:00 2001 From: Rodion Lim Date: Tue, 8 Oct 2024 10:34:41 +0800 Subject: [PATCH 4/5] feat: error on quorum calls should revert to default behaviour instead of early exit --- crates/cast/bin/cmd/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 0ab38cd33079..ebc3b738368a 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -256,7 +256,7 @@ impl RunArgs { tx.input = tessera_input; } Err(e) => { - return Err(eyre::eyre!("eth_getQuorumPayload threw an error: {e}, cannot execute transaction {:?}", tx.hash)); + println!("eth_getQuorumPayload threw an error: {e}, cannot fetch transaction input {:?}", tx.hash); } } } From 56ef7cbcd6731a3b7f3ad7a2d213c598d9a0dbf2 Mon Sep 17 00:00:00 2001 From: Rodion Lim Date: Sat, 12 Oct 2024 14:48:54 +0800 Subject: [PATCH 5/5] feat: refactor configuring for quorum transaction to a separate function Signed-off-by: Rodion Lim --- crates/cast/bin/cmd/run.rs | 36 +++------------------------ crates/evm/core/src/utils.rs | 47 +++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index ebc3b738368a..d97ac005fdb1 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -1,7 +1,6 @@ -use alloy_primitives::{Bytes, Uint, U256}; +use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; -use alloy_transport::TransportResult; use cast::{revm::primitives::EnvWithHandlerCfg, traces::TraceKind}; use clap::Parser; use eyre::{Result, WrapErr}; @@ -22,7 +21,7 @@ use foundry_config::{ use foundry_evm::{ executors::{EvmError, TracingExecutor}, opts::EvmOpts, - utils::configure_tx_env, + utils::{configure_quorum, configure_tx_env}, }; /// CLI arguments for `cast run`. @@ -232,36 +231,7 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - if let Some(signature) = tx.inner.signature { - let v = signature.v; - - // 37/38 for private quorum transactions, tessera hash is 64 bytes - let is_private_quorum_txn = - (v == Uint::from(37) || v == Uint::from(38)) && tx.input.len() == 64; - - if is_private_quorum_txn { - println!("Private quorum transaction detected."); - - let result: TransportResult = provider - .client() - .request("eth_getQuorumPayload", (tx.input.clone(),)) - .await; - - match result { - Ok(tessera_input) => { - println!( - "Executing private quorum transaction with quorum payload: {:?}", - tessera_input.to_string() - ); - tx.input = tessera_input; - } - Err(e) => { - println!("eth_getQuorumPayload threw an error: {e}, cannot fetch transaction input {:?}", tx.hash); - } - } - } - } - + configure_quorum(&mut tx, provider).await; configure_tx_env(&mut env, &tx.inner); if let Some(to) = tx.to { diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index bd17aae44292..8e633ee1abe9 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -4,12 +4,15 @@ use crate::{ InspectorExt, }; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Selector, TxKind, U256}; +use alloy_primitives::{Address, Bytes, Selector, TxKind, Uint, U256}; use alloy_provider::{ - network::{BlockResponse, HeaderResponse}, - Network, + network::{AnyNetwork, BlockResponse, HeaderResponse}, + Network, Provider, RootProvider, }; use alloy_rpc_types::{Transaction, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_transport::layers::RetryBackoffService; +use foundry_common::provider::runtime_transport::RuntimeTransport; use foundry_config::NamedChain; use foundry_fork_db::DatabaseError; use revm::{ @@ -142,6 +145,44 @@ pub fn configure_tx_req_env( Ok(()) } +/// Configures the input to the raw payload for the given RPC transaction if it is a quorum private +/// transaction. +pub async fn configure_quorum( + tx: &mut WithOtherFields, + provider: RootProvider, AnyNetwork>, +) { + if let Some(signature) = tx.inner.signature { + let v = signature.v; + + let is_private_quorum_txn = + (v == Uint::from(37) || v == Uint::from(38)) && tx.input.len() == 64; + + if is_private_quorum_txn { + println!("Private quorum transaction detected."); + + let result: alloy_transport::TransportResult = + provider.client().request("eth_getQuorumPayload", (tx.input.clone(),)).await; + + match result { + Ok(tessera_input) => { + println!( + "Executing private quorum transaction with quorum payload: {:?}", + tessera_input.to_string() + ); + tx.input = tessera_input; + } + Err(e) => { + println!( + "eth_getQuorumPayload threw an error: {e}, cannot fetch transaction + input {:?}", + tx.hash + ); + } + } + } + } +} + /// Get the gas used, accounting for refunds pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 };