Skip to content

Commit 0b5a13f

Browse files
chore(test-suite): gateway-stress upgrade for fhevm 0.10 (#1314)
* chore(test-suite): upgrade for fhevm 0.9.11 * chore(test-suite): improved nonce manager * chore(test-suite): gateway-stress updates for v0.10
1 parent ec27950 commit 0b5a13f

File tree

14 files changed

+986
-958
lines changed

14 files changed

+986
-958
lines changed

test-suite/gateway-stress/Cargo.lock

Lines changed: 711 additions & 860 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test-suite/gateway-stress/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
[package]
22
name = "gateway-stress"
3-
version = "0.9.0"
3+
version = "0.10.0"
44
edition = "2024"
55

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

10-
alloy = { version = "1.0", default-features = false, features = [
10+
alloy = { version = "1.0.38", default-features = false, features = [
1111
"essentials",
12+
"json-rpc",
1213
"reqwest-rustls-tls",
1314
"std",
1415
"provider-ws",
@@ -53,7 +54,7 @@ tokio = { version = "1.47.0", default-features = false, features = [
5354
] }
5455
tokio-util = "0.7.15"
5556
tracing = "0.1.41"
56-
tracing-subscriber = { version = "0.3.19", default-features = true, features = [
57+
tracing-subscriber = { version = "0.3.20", default-features = true, features = [
5758
"env-filter",
5859
] }
5960
rand = "0.9.2"

test-suite/gateway-stress/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ decrypts at the time of writing), at a given frequency and for a specified durat
1313
- [Stress testing](#stress-testing)
1414
- [Benchmarking](#benchmarking)
1515
- [Tracing](#tracing)
16+
- [Local e2e setup](#local-e2e-setup)
1617
- [Bonus: Generating handles via coprocessor stress-test-generator](#bonus-generating-handles-via-coprocessor-stress-test-generator)
1718

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

123+
## Local e2e setup
124+
125+
To play with the tool in a local e2e setup, follow these steps:
126+
- from the root of the `fehvm` repo: `cd test-suite/fhevm`
127+
- deploy the e2e setup using the `./fhevm-cli deploy` command
128+
- 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)):
129+
- `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"`
130+
- generate ciphertext handles
131+
- `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)
132+
- 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
133+
- run the tool. Ex:
134+
- `cd ../gateway_stress`
135+
- `cargo run -- -c config/config.toml -p 1 -d "1s" gw -t user`
136+
122137
## Bonus: Generating handles via coprocessor stress-test-generator
123138

124139
To use this tool, you would need already existing handles to decrypt. You could use coprocessor's

test-suite/gateway-stress/config/config.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ parallel_requests = 1
1616
# # Defaults to false, if set to true, then `tests_interval` is ignored
1717
# sequential = false
1818

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

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

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

4444
# Address of the Decryption contract (required)
45-
decryption_address = "0xF0bFB159C7381F7CB332586004d8247252C5b816"
45+
decryption_address = "0x35760912360E875DA50D40a74305575c23D55783"
4646

4747
# Wallet's private key as a hex string
4848
private_key = "24af7cb5f6cd0f29df22c6f3e2f18ee5b3949f5a489a14b0674bef8fd89bfe91"

test-suite/gateway-stress/src/blockchain/manager.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,11 @@ impl GatewayTestManager {
210210
for result in results.iter() {
211211
w.serialize(result)?;
212212
}
213+
w.flush()?;
213214
}
214215
let bench_result = BenchAverageResult::new(bench_record, results);
215216
average_results_writer.serialize(bench_result)?;
217+
average_results_writer.flush()?;
216218
}
217219

218220
Ok(())

test-suite/gateway-stress/src/blockchain/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod manager;
2+
pub mod nonce_manager;
23
pub mod provider;
34
pub mod wallet;
45

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use alloy::{
2+
network::Network,
3+
primitives::Address,
4+
providers::{Provider, fillers::NonceManager},
5+
transports::TransportResult,
6+
};
7+
use async_trait::async_trait;
8+
use futures::lock::{Mutex, MutexGuard};
9+
use std::collections::{BTreeSet, HashMap, hash_map::Entry};
10+
use std::sync::Arc;
11+
use tracing::trace;
12+
13+
/// A robust, in-memory nonce manager for a scalable transaction engine.
14+
#[derive(Clone, Debug, Default)]
15+
pub struct ZamaNonceManager {
16+
/// Nonce state for each account, shared across all tasks/threads using the nonce manager.
17+
accounts: Arc<Mutex<HashMap<Address, AccountState>>>,
18+
}
19+
20+
/// Represents the complete nonce state for a single account.
21+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
22+
pub struct AccountState {
23+
/// The "high-water mark" nonce. Used only when no gaps are available.
24+
pub next_nonce: u64,
25+
/// Nonces that have been dispatched but not yet confirmed or rejected.
26+
pub locked_nonces: BTreeSet<u64>,
27+
/// Nonces that were previously locked but have been released, creating gaps.
28+
pub available_nonces: BTreeSet<u64>,
29+
}
30+
31+
impl ZamaNonceManager {
32+
pub fn new() -> Self {
33+
Self::default()
34+
}
35+
36+
/// The primary logic for acquiring and locking the next valid nonce.
37+
///
38+
/// The logic prioritizes filling gaps from `available_nonces` before
39+
/// incrementing the main `next_nonce` counter.
40+
pub async fn get_increase_and_lock_nonce<P, N>(
41+
&self,
42+
provider: &P,
43+
address: Address,
44+
) -> TransportResult<u64>
45+
where
46+
P: Provider<N>,
47+
N: Network,
48+
{
49+
let mut accounts_guard = self.accounts.lock().await;
50+
let account =
51+
Self::get_or_init_account_state(&mut accounts_guard, provider, address).await?;
52+
let nonce_to_use =
53+
if let Some(available_nonce) = account.available_nonces.iter().next().copied() {
54+
account.available_nonces.remove(&available_nonce);
55+
trace!(%address, nonce = available_nonce, "Reusing available nonce");
56+
available_nonce
57+
} else {
58+
let next = account.next_nonce;
59+
account.next_nonce += 1;
60+
trace!(%address, nonce = next, "Using next sequential nonce");
61+
next
62+
};
63+
64+
account.locked_nonces.insert(nonce_to_use);
65+
Ok(nonce_to_use)
66+
}
67+
68+
/// Releases a locked nonce, making it available for reuse.
69+
pub async fn release_nonce(&self, address: Address, nonce: u64) {
70+
let mut accounts = self.accounts.lock().await;
71+
if let Some(account) = accounts.get_mut(&address)
72+
&& account.locked_nonces.remove(&nonce)
73+
{
74+
account.available_nonces.insert(nonce);
75+
}
76+
}
77+
78+
/// Confirms a nonce has been used on-chain, removing it permanently.
79+
pub async fn confirm_nonce(&self, address: Address, nonce: u64) {
80+
let mut accounts = self.accounts.lock().await;
81+
if let Some(account) = accounts.get_mut(&address) {
82+
account.locked_nonces.remove(&nonce);
83+
}
84+
}
85+
86+
/// Helper to retrieve or initialize the `AccountState` for an address.
87+
async fn get_or_init_account_state<'a, P, N>(
88+
accounts_guard: &'a mut MutexGuard<'_, HashMap<Address, AccountState>>,
89+
provider: &P,
90+
address: Address,
91+
) -> TransportResult<&'a mut AccountState>
92+
where
93+
P: Provider<N>,
94+
N: Network,
95+
{
96+
let account = match accounts_guard.entry(address) {
97+
Entry::Occupied(entry) => entry.into_mut(),
98+
Entry::Vacant(entry) => {
99+
let initial_nonce = provider.get_transaction_count(address).await?;
100+
entry.insert(AccountState {
101+
next_nonce: initial_nonce,
102+
..Default::default()
103+
})
104+
}
105+
};
106+
Ok(account)
107+
}
108+
}
109+
110+
// Implements the `NonceManager` trait for seamless integration with Alloy's provider stack.
111+
#[async_trait]
112+
impl NonceManager for ZamaNonceManager {
113+
async fn get_next_nonce<P, N>(&self, provider: &P, address: Address) -> TransportResult<u64>
114+
where
115+
P: Provider<N>,
116+
N: Network,
117+
{
118+
self.get_increase_and_lock_nonce(provider, address).await
119+
}
120+
}

0 commit comments

Comments
 (0)