Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,571 changes: 711 additions & 860 deletions test-suite/gateway-stress/Cargo.lock

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions test-suite/gateway-stress/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
[package]
name = "gateway-stress"
version = "0.9.0"
version = "0.10.0"
edition = "2024"

[dependencies]
fhevm_gateway_bindings = { git = "https://github.com/zama-ai/fhevm.git", tag = "v0.9.1-1", default-features = false }
gateway-sdk = { git = "https://github.com/zama-ai/fhevm.git", tag = "v0.9.1-1", default-features = false }
fhevm_gateway_bindings = { git = "https://github.com/zama-ai/fhevm.git", tag = "v0.10.0-4", default-features = false }
gateway-sdk = { git = "https://github.com/zama-ai/fhevm.git", tag = "v0.10.0-4", default-features = false }

alloy = { version = "1.0", default-features = false, features = [
alloy = { version = "1.0.38", default-features = false, features = [
"essentials",
"json-rpc",
"reqwest-rustls-tls",
"std",
"provider-ws",
Expand Down Expand Up @@ -53,7 +54,7 @@ tokio = { version = "1.47.0", default-features = false, features = [
] }
tokio-util = "0.7.15"
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", default-features = true, features = [
tracing-subscriber = { version = "0.3.20", default-features = true, features = [
"env-filter",
] }
rand = "0.9.2"
15 changes: 15 additions & 0 deletions test-suite/gateway-stress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ decrypts at the time of writing), at a given frequency and for a specified durat
- [Stress testing](#stress-testing)
- [Benchmarking](#benchmarking)
- [Tracing](#tracing)
- [Local e2e setup](#local-e2e-setup)
- [Bonus: Generating handles via coprocessor stress-test-generator](#bonus-generating-handles-via-coprocessor-stress-test-generator)

## Build
Expand Down Expand Up @@ -119,6 +120,20 @@ RUST_LOG="gateway_stress=debug,alloy=debug" ./gateway-stress -c config/config.to
RUST_LOG="debug" ./gateway-stress -c config/config.toml gw -t public
```

## Local e2e setup

To play with the tool in a local e2e setup, follow these steps:
- from the root of the `fehvm` repo: `cd test-suite/fhevm`
- deploy the e2e setup using the `./fhevm-cli deploy` command
- if using `fhevm` version > 0.10.0, fund the account the tool is using with ERC20 token (use the same private key as in [gateway-stress config](../gateway-stress/config/config.toml)):
- `docker run --env-file env/staging/.env.gateway-mocked-payment.local -e TX_SENDER_PRIVATE_KEY="0x24af7cb5f6cd0f29df22c6f3e2f18ee5b3949f5a489a14b0674bef8fd89bfe91" -it --rm --network fhevm_default ghcr.io/zama-ai/fhevm/gateway-contracts:v0.10.0-4 "npx hardhat task:setTxSenderMockedPayment"`
- generate ciphertext handles
- `cd ../../coprocessor/fhevm-engine/stress-test-generator; rm -f data/handles_for*; EVGEN_SCENARIO=data/minitest_003_generate_handles_for_decryption.csv make run; cd -` (see [this section](#bonus-generating-handles-via-coprocessor-stress-test-generator) for more details)
- update the `[[public_ct]]` and `[[user_ct]]` sections of the [gateway-stress config](../gateway-stress/config/config.toml) with one of the value of the `../../coprocessor/fhevm-engine/stress-test-generator/data/handles_for_pub_decryption` and `../../coprocessor/fhevm-engine/stress-test-generator/data/handles_for_usr_decryption` files
- run the tool. Ex:
- `cd ../gateway_stress`
- `cargo run -- -c config/config.toml -p 1 -d "1s" gw -t user`

## Bonus: Generating handles via coprocessor stress-test-generator

To use this tool, you would need already existing handles to decrypt. You could use coprocessor's
Expand Down
18 changes: 9 additions & 9 deletions test-suite/gateway-stress/config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ parallel_requests = 1
# # Defaults to false, if set to true, then `tests_interval` is ignored
# sequential = false

# The ciphertexts to use for user decryptions
[[user_ct]]
# The ciphertexts to use for public decryptions
[[public_ct]]
# The handle used of the ciphertext
handle = "0xc07ece86473ba540de4dbef63889f0233f5481dd7dff00000000000030390500"
# The digest of the ciphertext
handle = "0x1f9b79819b2581535a80f267862540c5eac12ee504ff00000000000030390500"
# The digest of the ciphertext. Used only for DB tests, it can be a dummy value otherwise
digest = "0x134b4f97e70be0e71a26e38304e096fd370188b5c11b89feb8a61176b73d1b5a"

# The ciphertexts to use for public decryptions
[[public_ct]]
# The ciphertexts to use for user decryptions
[[user_ct]]
# The handle used of the ciphertext
handle = "0x57539e394a6ae1dc4b4f1b00248777ce265eb6d08bff00000000000030390500"
# The digest of the ciphertext
handle = "0x1d01f83fd1b14d84e9b8680790d993d6848aeb6462ff00000000000030390500"
# The digest of the ciphertext. Used only for DB tests, it can be a dummy value otherwise
digest = "0x134b4f97e70be0e71a26e38304e096fd370188b5c11b89feb8a61176b73d1b5a"

# Configuration section for stress test using the Gateway chain
Expand All @@ -42,7 +42,7 @@ host_chain_id = 12345
gateway_chain_id = 54321

# Address of the Decryption contract (required)
decryption_address = "0xF0bFB159C7381F7CB332586004d8247252C5b816"
decryption_address = "0x35760912360E875DA50D40a74305575c23D55783"

# Wallet's private key as a hex string
private_key = "24af7cb5f6cd0f29df22c6f3e2f18ee5b3949f5a489a14b0674bef8fd89bfe91"
Expand Down
2 changes: 2 additions & 0 deletions test-suite/gateway-stress/src/blockchain/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,11 @@ impl GatewayTestManager {
for result in results.iter() {
w.serialize(result)?;
}
w.flush()?;
}
let bench_result = BenchAverageResult::new(bench_record, results);
average_results_writer.serialize(bench_result)?;
average_results_writer.flush()?;
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions test-suite/gateway-stress/src/blockchain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod manager;
pub mod nonce_manager;
pub mod provider;
pub mod wallet;

Expand Down
120 changes: 120 additions & 0 deletions test-suite/gateway-stress/src/blockchain/nonce_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use alloy::{
network::Network,
primitives::Address,
providers::{Provider, fillers::NonceManager},
transports::TransportResult,
};
use async_trait::async_trait;
use futures::lock::{Mutex, MutexGuard};
use std::collections::{BTreeSet, HashMap, hash_map::Entry};
use std::sync::Arc;
use tracing::trace;

/// A robust, in-memory nonce manager for a scalable transaction engine.
#[derive(Clone, Debug, Default)]
pub struct ZamaNonceManager {
/// Nonce state for each account, shared across all tasks/threads using the nonce manager.
accounts: Arc<Mutex<HashMap<Address, AccountState>>>,
}

/// Represents the complete nonce state for a single account.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AccountState {
/// The "high-water mark" nonce. Used only when no gaps are available.
pub next_nonce: u64,
/// Nonces that have been dispatched but not yet confirmed or rejected.
pub locked_nonces: BTreeSet<u64>,
/// Nonces that were previously locked but have been released, creating gaps.
pub available_nonces: BTreeSet<u64>,
}

impl ZamaNonceManager {
pub fn new() -> Self {
Self::default()
}

/// The primary logic for acquiring and locking the next valid nonce.
///
/// The logic prioritizes filling gaps from `available_nonces` before
/// incrementing the main `next_nonce` counter.
pub async fn get_increase_and_lock_nonce<P, N>(
&self,
provider: &P,
address: Address,
) -> TransportResult<u64>
where
P: Provider<N>,
N: Network,
{
let mut accounts_guard = self.accounts.lock().await;
let account =
Self::get_or_init_account_state(&mut accounts_guard, provider, address).await?;
let nonce_to_use =
if let Some(available_nonce) = account.available_nonces.iter().next().copied() {
account.available_nonces.remove(&available_nonce);
trace!(%address, nonce = available_nonce, "Reusing available nonce");
available_nonce
} else {
let next = account.next_nonce;
account.next_nonce += 1;
trace!(%address, nonce = next, "Using next sequential nonce");
next
};

account.locked_nonces.insert(nonce_to_use);
Ok(nonce_to_use)
}

/// Releases a locked nonce, making it available for reuse.
pub async fn release_nonce(&self, address: Address, nonce: u64) {
let mut accounts = self.accounts.lock().await;
if let Some(account) = accounts.get_mut(&address)
&& account.locked_nonces.remove(&nonce)
{
account.available_nonces.insert(nonce);
}
}

/// Confirms a nonce has been used on-chain, removing it permanently.
pub async fn confirm_nonce(&self, address: Address, nonce: u64) {
let mut accounts = self.accounts.lock().await;
if let Some(account) = accounts.get_mut(&address) {
account.locked_nonces.remove(&nonce);
}
}

/// Helper to retrieve or initialize the `AccountState` for an address.
async fn get_or_init_account_state<'a, P, N>(
accounts_guard: &'a mut MutexGuard<'_, HashMap<Address, AccountState>>,
provider: &P,
address: Address,
) -> TransportResult<&'a mut AccountState>
where
P: Provider<N>,
N: Network,
{
let account = match accounts_guard.entry(address) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let initial_nonce = provider.get_transaction_count(address).await?;
entry.insert(AccountState {
next_nonce: initial_nonce,
..Default::default()
})
}
};
Ok(account)
}
}

// Implements the `NonceManager` trait for seamless integration with Alloy's provider stack.
#[async_trait]
impl NonceManager for ZamaNonceManager {
async fn get_next_nonce<P, N>(&self, provider: &P, address: Address) -> TransportResult<u64>
where
P: Provider<N>,
N: Network,
{
self.get_increase_and_lock_nonce(provider, address).await
}
}
Loading