From ed8e6ee7033e0a6bfa330c9fa2d11037ad935a2e Mon Sep 17 00:00:00 2001 From: Petar Ivanov <29689712+dartdart26@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:01:06 +0300 Subject: [PATCH] fix(txn-sender): use a WS provider instead of HTTP Since txn-sender waits for txn receipts, it is better to use WS instead of HTTP and polling. This also makes it faster to get the receipts. Resolves https://github.com/zama-ai/httpz-backend/issues/443 --- fhevm-engine/Cargo.lock | 1 + fhevm-engine/fhevm-listener/.gitignore | 2 + fhevm-engine/fhevm-listener/Cargo.toml | 1 + fhevm-engine/fhevm-listener/build.rs | 14 +++- .../gw-listener/src/bin/gw_listener.rs | 3 +- .../src/bin/transaction_sender.rs | 14 ++-- .../tests/add_ciphertext_tests.rs | 17 +++-- .../transaction-sender/tests/allow_handle.rs | 6 +- .../transaction-sender/tests/common.rs | 19 ++++- .../tests/verify_proof_tests.rs | 72 +++++++++++-------- 10 files changed, 98 insertions(+), 51 deletions(-) create mode 100644 fhevm-engine/fhevm-listener/.gitignore diff --git a/fhevm-engine/Cargo.lock b/fhevm-engine/Cargo.lock index cbd2e6a2..b459b8ed 100644 --- a/fhevm-engine/Cargo.lock +++ b/fhevm-engine/Cargo.lock @@ -3135,6 +3135,7 @@ dependencies = [ "fhevm-engine-common", "foundry-compilers", "futures-util", + "semver 1.0.26", "serde", "serial_test", "sqlx", diff --git a/fhevm-engine/fhevm-listener/.gitignore b/fhevm-engine/fhevm-listener/.gitignore new file mode 100644 index 00000000..dc2c85de --- /dev/null +++ b/fhevm-engine/fhevm-listener/.gitignore @@ -0,0 +1,2 @@ +artifacts +cache diff --git a/fhevm-engine/fhevm-listener/Cargo.toml b/fhevm-engine/fhevm-listener/Cargo.toml index acfc33e6..28f50774 100644 --- a/fhevm-engine/fhevm-listener/Cargo.toml +++ b/fhevm-engine/fhevm-listener/Cargo.toml @@ -34,3 +34,4 @@ serial_test = "3.2.0" [build-dependencies] foundry-compilers = { version = "0.13.0", features = ["svm-solc"] } +semver = "1.0.26" diff --git a/fhevm-engine/fhevm-listener/build.rs b/fhevm-engine/fhevm-listener/build.rs index 92a2700c..fade70eb 100644 --- a/fhevm-engine/fhevm-listener/build.rs +++ b/fhevm-engine/fhevm-listener/build.rs @@ -1,13 +1,23 @@ -use foundry_compilers::{Project, ProjectPathsConfig}; +use foundry_compilers::{ + multi::MultiCompiler, + solc::{Solc, SolcCompiler}, + Project, ProjectPathsConfig, +}; +use semver::Version; use std::path::Path; fn main() { println!("cargo::warning=build.rs run ..."); let paths = ProjectPathsConfig::hardhat(Path::new(env!("CARGO_MANIFEST_DIR"))) .unwrap(); + // Use a specific version due to an issue with libc and libstdc++ in the rust Docker image we use to run it. + let solc = Solc::find_or_install(&Version::new(0, 8, 28)).unwrap(); let project = Project::builder() .paths(paths) - .build(Default::default()) + .build( + MultiCompiler::new(Some(SolcCompiler::Specific(solc)), None) + .unwrap(), + ) .unwrap(); let output = project.compile().unwrap(); if output.has_compiler_errors() { diff --git a/fhevm-engine/gw-listener/src/bin/gw_listener.rs b/fhevm-engine/gw-listener/src/bin/gw_listener.rs index f241ad6f..f71c3d9d 100644 --- a/fhevm-engine/gw-listener/src/bin/gw_listener.rs +++ b/fhevm-engine/gw-listener/src/bin/gw_listener.rs @@ -57,8 +57,7 @@ async fn main() -> anyhow::Result<()> { let provider = ProviderBuilder::new() .on_ws(WsConnect::new(conf.gw_url.clone())) - .await - .expect("should have valid provider"); + .await?; let cancel_token = CancellationToken::new(); let gw_listener = GatewayListener::new( diff --git a/fhevm-engine/transaction-sender/src/bin/transaction_sender.rs b/fhevm-engine/transaction-sender/src/bin/transaction_sender.rs index 8089e7f1..31949f36 100644 --- a/fhevm-engine/transaction-sender/src/bin/transaction_sender.rs +++ b/fhevm-engine/transaction-sender/src/bin/transaction_sender.rs @@ -1,8 +1,11 @@ use std::str::FromStr; use alloy::{ - network::EthereumWallet, primitives::Address, providers::ProviderBuilder, - signers::local::PrivateKeySigner, transports::http::reqwest::Url, + network::EthereumWallet, + primitives::Address, + providers::{ProviderBuilder, WsConnect}, + signers::local::PrivateKeySigner, + transports::http::reqwest::Url, }; use clap::Parser; use tokio::signal::unix::{signal, SignalKind}; @@ -100,10 +103,11 @@ async fn main() -> anyhow::Result<()> { .clone() .unwrap_or_else(|| std::env::var("DATABASE_URL").expect("DATABASE_URL is undefined")); let cancel_token = CancellationToken::new(); - let provider = ProviderBuilder::new() - .filler(ProviderFillers::default()) + let provider = ProviderBuilder::default() .wallet(wallet) - .on_http(conf.gateway_url); + .filler(ProviderFillers::default()) + .on_ws(WsConnect::new(conf.gateway_url)) + .await?; let sender = TransactionSender::new( conf.zkpok_manager_address, conf.ciphertext_manager_address, diff --git a/fhevm-engine/transaction-sender/tests/add_ciphertext_tests.rs b/fhevm-engine/transaction-sender/tests/add_ciphertext_tests.rs index 395823ce..c13b74b9 100644 --- a/fhevm-engine/transaction-sender/tests/add_ciphertext_tests.rs +++ b/fhevm-engine/transaction-sender/tests/add_ciphertext_tests.rs @@ -1,4 +1,4 @@ -use alloy::providers::{Provider, ProviderBuilder, WalletProvider}; +use alloy::providers::{Provider, ProviderBuilder, WsConnect}; use alloy::signers::local::PrivateKeySigner; use common::{CiphertextManager, TestEnvironment}; @@ -17,8 +17,10 @@ mod common; async fn test_add_ciphertext_digests() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -40,9 +42,7 @@ async fn test_add_ciphertext_digests() -> anyhow::Result<()> { // Add a ciphertext digest to database let handle = random::<[u8; 32]>().to_vec(); // Record initial transaction count. - let initial_tx_count = provider - .get_transaction_count(provider.default_signer_address()) - .await?; + let initial_tx_count = provider.get_transaction_count(env.signer.address()).await?; // Insert a ciphertext digest into the database. insert_ciphertext_digest( @@ -95,9 +95,7 @@ async fn test_add_ciphertext_digests() -> anyhow::Result<()> { ); // Verify that a transaction has been sent. - let tx_count = provider - .get_transaction_count(provider.default_signer_address()) - .await?; + let tx_count = provider.get_transaction_count(env.signer.address()).await?; assert_eq!( tx_count, initial_tx_count + 1, @@ -122,7 +120,8 @@ async fn test_retry_mechanism() -> anyhow::Result<()> { // Create a provider without a wallet. let provider = ProviderBuilder::default() .filler(ProviderFillers::default()) - .on_anvil(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let txn_sender = TransactionSender::new( PrivateKeySigner::random().address(), PrivateKeySigner::random().address(), diff --git a/fhevm-engine/transaction-sender/tests/allow_handle.rs b/fhevm-engine/transaction-sender/tests/allow_handle.rs index 310b62cb..950d19de 100644 --- a/fhevm-engine/transaction-sender/tests/allow_handle.rs +++ b/fhevm-engine/transaction-sender/tests/allow_handle.rs @@ -1,6 +1,6 @@ -use alloy::primitives::Address; use alloy::providers::ProviderBuilder; use alloy::signers::local::PrivateKeySigner; +use alloy::{primitives::Address, providers::WsConnect}; use common::{ACLManager, TestEnvironment}; use rand::random; @@ -18,8 +18,10 @@ mod common; async fn test_allow_handle() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let acl_manager = ACLManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( diff --git a/fhevm-engine/transaction-sender/tests/common.rs b/fhevm-engine/transaction-sender/tests/common.rs index 0889d39b..059128ca 100644 --- a/fhevm-engine/transaction-sender/tests/common.rs +++ b/fhevm-engine/transaction-sender/tests/common.rs @@ -1,4 +1,10 @@ -use alloy::{primitives::Address, signers::local::PrivateKeySigner, sol}; +use alloy::{ + network::EthereumWallet, + node_bindings::{Anvil, AnvilInstance}, + primitives::Address, + signers::local::PrivateKeySigner, + sol, +}; use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; use tokio_util::sync::CancellationToken; use tracing::Level; @@ -31,6 +37,10 @@ pub struct TestEnvironment { pub contract_address: Address, #[allow(dead_code)] pub user_address: Address, + #[allow(dead_code)] + pub anvil: AnvilInstance, + #[allow(dead_code)] + pub wallet: EthereumWallet, } impl TestEnvironment { @@ -57,13 +67,18 @@ impl TestEnvironment { ) .await?; + let anvil = Anvil::new().try_spawn()?; + let signer: PrivateKeySigner = anvil.keys()[0].clone().into(); + let wallet = signer.clone().into(); Ok(Self { - signer: PrivateKeySigner::random(), + signer, conf, cancel_token: CancellationToken::new(), db_pool, contract_address: PrivateKeySigner::random().address(), user_address: PrivateKeySigner::random().address(), + anvil, + wallet, }) } } diff --git a/fhevm-engine/transaction-sender/tests/verify_proof_tests.rs b/fhevm-engine/transaction-sender/tests/verify_proof_tests.rs index 194ceb2f..49012e42 100644 --- a/fhevm-engine/transaction-sender/tests/verify_proof_tests.rs +++ b/fhevm-engine/transaction-sender/tests/verify_proof_tests.rs @@ -1,5 +1,5 @@ use alloy::primitives::FixedBytes; -use alloy::providers::{Provider, WalletProvider}; +use alloy::providers::{Provider, WsConnect}; use alloy::signers::local::PrivateKeySigner; use alloy::{primitives::U256, sol_types::eip712_domain}; use alloy::{providers::ProviderBuilder, signers::SignerSync, sol, sol_types::SolStruct}; @@ -28,8 +28,10 @@ sol! { async fn verify_proof_response_success() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, false).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -134,8 +136,10 @@ async fn verify_proof_response_success() -> anyhow::Result<()> { async fn verify_proof_response_concurrent_success() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, false).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -179,7 +183,7 @@ async fn verify_proof_response_concurrent_success() -> anyhow::Result<()> { b.push_bind(contract_chain_id as i64); b.push_bind(env.contract_address.to_string()); b.push_bind(env.user_address.to_string()); - b.push_bind(&[1u8; 64]); + b.push_bind([1u8; 64]); b.push_bind(true); }); query_builder.push(")"); @@ -192,7 +196,7 @@ async fn verify_proof_response_concurrent_success() -> anyhow::Result<()> { .await?; let events = events_handle.await?; - for i in 0..count { + for (i, event) in events.iter().enumerate().take(count) { let proof_id = i; let expected_proof_id = U256::from(proof_id); let expected_handles: Vec> = @@ -213,9 +217,9 @@ async fn verify_proof_response_concurrent_success() -> anyhow::Result<()> { let expected_sig = env.signer.sign_hash_sync(&signing_hash)?; // Make sure data in the event is correct, including the deterministic ECDSA signature. - assert_eq!(events[i].0._0, expected_proof_id); - assert_eq!(events[i].0._1, expected_handles); - assert_eq!(events[i].0._2.as_ref(), expected_sig.as_bytes()); + assert_eq!(event.0._0, expected_proof_id); + assert_eq!(event.0._1, expected_handles); + assert_eq!(event.0._2.as_ref(), expected_sig.as_bytes()); } // Make sure the proofs are removed from the database. @@ -242,8 +246,10 @@ async fn verify_proof_response_concurrent_success() -> anyhow::Result<()> { async fn verify_proof_response_reversal_already_responded() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, true, false).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -263,9 +269,7 @@ async fn verify_proof_response_reversal_already_responded() -> anyhow::Result<() let run_handle = tokio::spawn(async move { txn_sender.run().await }); // Record initial transaction count. - let initial_tx_count = provider - .get_transaction_count(provider.default_signer_address()) - .await?; + let initial_tx_count = provider.get_transaction_count(env.signer.address()).await?; // Insert a proof into the database and notify the sender. sqlx::query!( @@ -301,9 +305,7 @@ async fn verify_proof_response_reversal_already_responded() -> anyhow::Result<() } // Verify that no transaction has been sent. - let final_tx_count = provider - .get_transaction_count(provider.default_signer_address()) - .await?; + let final_tx_count = provider.get_transaction_count(env.signer.address()).await?; assert_eq!( final_tx_count, initial_tx_count, "Expected no new transaction to be sent" @@ -319,8 +321,10 @@ async fn verify_proof_response_reversal_already_responded() -> anyhow::Result<() async fn verify_proof_response_other_reversal_gas_estimation() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, true).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -392,8 +396,10 @@ async fn verify_proof_response_other_reversal_gas_estimation() -> anyhow::Result async fn verify_proof_response_other_reversal_receipt() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, true).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; // Create the sender with a gas limit such that no gas estimation is done, forcing failure at receipt (after the txn has been sent). @@ -468,8 +474,10 @@ async fn verify_proof_max_retries_remove_entry() -> anyhow::Result<()> { env.conf.verify_proof_remove_after_max_retries = true; env.conf.verify_proof_resp_max_retries = 2; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, true).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -533,8 +541,10 @@ async fn verify_proof_max_retries_do_not_remove_entry() -> anyhow::Result<()> { env.conf.verify_proof_remove_after_max_retries = false; env.conf.verify_proof_resp_max_retries = 2; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, true).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -609,8 +619,10 @@ async fn verify_proof_max_retries_do_not_remove_entry() -> anyhow::Result<()> { async fn reject_proof_response_success() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, false).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -694,8 +706,10 @@ async fn reject_proof_response_success() -> anyhow::Result<()> { async fn reject_proof_response_reversal_already_responded() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, true, false).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new( @@ -715,9 +729,7 @@ async fn reject_proof_response_reversal_already_responded() -> anyhow::Result<() let run_handle = tokio::spawn(async move { txn_sender.run().await }); // Record initial transaction count. - let initial_tx_count = provider - .get_transaction_count(provider.default_signer_address()) - .await?; + let initial_tx_count = provider.get_transaction_count(env.signer.address()).await?; sqlx::query!( "WITH ins AS ( @@ -752,9 +764,7 @@ async fn reject_proof_response_reversal_already_responded() -> anyhow::Result<() } // Verify that no transaction has been sent. - let final_tx_count = provider - .get_transaction_count(provider.default_signer_address()) - .await?; + let final_tx_count = provider.get_transaction_count(env.signer.address()).await?; assert_eq!( final_tx_count, initial_tx_count, "Expected no new transaction to be sent" @@ -770,8 +780,10 @@ async fn reject_proof_response_reversal_already_responded() -> anyhow::Result<() async fn reject_proof_response_other_reversal_receipt() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, true).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; // Create the sender with a gas limit such that no gas estimation is done, forcing failure at receipt (after the txn has been sent). @@ -840,8 +852,10 @@ async fn reject_proof_response_other_reversal_receipt() -> anyhow::Result<()> { async fn reject_proof_response_other_reversal_gas_estimation() -> anyhow::Result<()> { let env = TestEnvironment::new().await?; let provider = ProviderBuilder::default() + .wallet(env.wallet) .filler(ProviderFillers::default()) - .on_anvil_with_wallet(); + .on_ws(WsConnect::new(env.anvil.ws_endpoint_url())) + .await?; let zkpok_manager = ZKPoKManager::deploy(&provider, false, true).await?; let ciphertext_manager = CiphertextManager::deploy(&provider).await?; let txn_sender = TransactionSender::new(