-
Notifications
You must be signed in to change notification settings - Fork 47
Expand file tree
/
Copy pathbenchmark.rs
More file actions
289 lines (253 loc) · 11.4 KB
/
Copy pathbenchmark.rs
File metadata and controls
289 lines (253 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! # LumenFlow Hot-Path Benchmarks
//!
//! Criterion harness measuring relative runtime for the contract's most
//! instruction-intensive entry points. Results are used to detect performance
//! regressions (>10 % increase in wall-clock time triggers a CI failure) and
//! to guide optimisation work.
//!
//! ## Storage operations per benchmark (approximate)
//!
//! | Benchmark | Reads | Writes | Notes |
//! |-----------|-------|--------|-------|
//! | process_payment_with_signature | ~8 | ~6 | sig verify + fee + stats |
//! | get_merchant_payment_history | ~22 | 0 | 20 payments + index read |
//! | cleanup_expired_payments | ~12 | 0 | 10 payments + merchant list |
//! | batch_payment (10 items) | ~60 | ~40 | all 10 items full path |
//! | create_escrow | ~5 | ~3 | lock funds + storage write |
//! | release_escrow | ~3 | ~2 | unlock check + transfer |
//!
//! ## Running
//!
//! ```bash
//! cargo bench --manifest-path contracts/lumenflow/Cargo.toml
//! ```
//!
//! ## Regression threshold
//!
//! A CI step compares the latest `criterion` JSON output against the baseline
//! stored in `contracts/lumenflow/benches/baselines/`. Any benchmark that
//! regresses by more than 10 % causes the step to exit non-zero.
use criterion::{criterion_group, criterion_main, Criterion};
use soroban_sdk::testutils::{Address as _, Ledger};
use soroban_sdk::{token::StellarAssetClient, Address, Bytes, Env, String, Vec};
use lumenflow::PaymentProcessingContractClient;
// ── Helpers ───────────────────────────────────────────────────────────────────
/// Bootstrap a minimal contract environment: admin set, one merchant, one token,
/// and `token_balance` minted to `payer`.
fn setup_env(
token_balance: i128,
) -> (
Env,
PaymentProcessingContractClient<'static>,
Address, // admin
Address, // payer
Address, // merchant
Address, // token_addr
) {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register(lumenflow::PaymentProcessingContract, ());
// SAFETY: the client lifetime is tied to `env`; we extend it to 'static here
// only because criterion's closure captures them together, so the env
// always outlives the client.
let client = PaymentProcessingContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
let payer = Address::generate(&env);
let merchant = Address::generate(&env);
let token = env.register_stellar_asset_contract_v2(payer.clone());
let token_addr = token.address();
StellarAssetClient::new(&env, &token_addr).mint(&payer, &token_balance);
client.set_admin(&admin);
client.add_allowed_token(&admin, &token_addr);
client.register_merchant(
&merchant,
&String::from_str(&env, "Benchmark Merchant"),
&String::from_str(&env, "Merchant used for benchmarks"),
&String::from_str(&env, "bench@example.com"),
&lumenflow::MerchantCategory::Retail,
);
// Cast away the lifetime so the values can be moved into criterion closures.
// Safety: `env` is moved alongside the client in every benchmark.
let client: PaymentProcessingContractClient<'static> =
unsafe { core::mem::transmute(client) };
(env, client, admin, payer, merchant, token_addr)
}
// ── process_payment_with_signature ────────────────────────────────────────────
/// Measures the full hot path:
/// merchant nonce validation → rate-limit check → signature verification
/// → token transfer → payment storage → merchant/payer index update
/// → stats update → event emission.
///
/// Storage operations (approximate): 8 reads, 6 writes.
/// Compute: 1 ed25519 verify (dominant cost).
fn benchmark_process_payment(c: &mut Criterion) {
c.bench_function("process_payment_with_signature", |b| {
b.iter(|| {
let (env, client, admin, payer, merchant, token_addr) = setup_env(100_000_000);
// nonce 1 = current(0) + 1
client.process_payment_with_signature(
&payer,
&String::from_str(&env, "order-bench-1"),
&merchant,
&token_addr,
&1_000_i128,
&String::from_str(&env, "benchmark payment"),
&Option::<Vec<String>>::None,
&1u64,
&Bytes::from_slice(&env, &[0; 64]),
&Bytes::from_slice(&env, &[0; 32]),
);
});
});
}
// ── get_merchant_payment_history ─────────────────────────────────────────────
/// Pre-loads 20 payments then measures paginated history retrieval:
/// index read → 20 payment reads → filter pass → sort → page slice.
///
/// Storage operations (approximate): 22 reads, 0 writes.
fn benchmark_query_history(c: &mut Criterion) {
let (env, client, _admin, payer, merchant, token_addr) = setup_env(100_000_000);
for i in 0..20u64 {
client.process_payment_with_signature(
&payer,
&String::from_str(&env, &alloc::format!("order-hist-{i}")),
&merchant,
&token_addr,
&1_000_i128,
&String::from_str(&env, "benchmark payment"),
&Option::<Vec<String>>::None,
&(i + 1),
&Bytes::from_slice(&env, &[0; 64]),
&Bytes::from_slice(&env, &[0; 32]),
);
env.ledger().with_mut(|l| l.timestamp += 60);
}
c.bench_function("get_merchant_payment_history", |b| {
b.iter(|| {
client.get_merchant_payment_history(
&merchant,
&Option::<String>::None,
&10,
&Option::<lumenflow::PaymentFilter>::None,
&lumenflow::SortField::Date,
&lumenflow::SortOrder::Descending,
);
});
});
}
// ── cleanup_expired_payments ──────────────────────────────────────────────────
/// Pre-loads 10 payments then fast-forwards time past the cleanup period.
/// Measures index scan → age check → storage deletion for each expired record.
///
/// Storage operations (approximate): 12 reads + 10 removes per call (first run),
/// 12 reads + 0 removes on subsequent (already deleted).
fn benchmark_cleanup(c: &mut Criterion) {
let (env, client, admin, payer, merchant, token_addr) = setup_env(100_000_000);
env.ledger().with_mut(|l| l.timestamp += 10 * 24 * 3600);
for i in 0..10u64 {
client.process_payment_with_signature(
&payer,
&String::from_str(&env, &alloc::format!("order-cleanup-{i}")),
&merchant,
&token_addr,
&1_000_i128,
&String::from_str(&env, "cleanup payment"),
&Option::<Vec<String>>::None,
&(i + 1),
&Bytes::from_slice(&env, &[0; 64]),
&Bytes::from_slice(&env, &[0; 32]),
);
}
// Advance beyond the default 30-day cleanup period
env.ledger().with_mut(|l| l.timestamp += 100 * 24 * 3600);
c.bench_function("cleanup_expired_payments", |b| {
b.iter(|| {
client.cleanup_expired_payments(&admin);
});
});
}
// ── batch_payment (10 items) ──────────────────────────────────────────────────
/// Measures the full batch path for the maximum allowed 10 items:
/// per-item: token-allow check → merchant load → rate-limit check
/// → sig verify → transfer → storage write → stats update.
///
/// Storage operations (approximate): 60 reads, 40 writes.
fn benchmark_batch_payment(c: &mut Criterion) {
c.bench_function("batch_payment_10_items", |b| {
b.iter(|| {
let (env, client, _admin, payer, merchant, token_addr) = setup_env(100_000_000);
let mut items: Vec<lumenflow::BatchPaymentItem> = Vec::new(&env);
for i in 0..10u32 {
items.push_back(lumenflow::BatchPaymentItem {
order_id: String::from_str(&env, &alloc::format!("batch-order-{i}")),
merchant_address: merchant.clone(),
token_address: token_addr.clone(),
amount: 500_i128,
memo: String::from_str(&env, "batch item"),
tags: Option::<Vec<String>>::None,
signature: Bytes::from_slice(&env, &[0; 64]),
merchant_public_key: Bytes::from_slice(&env, &[0; 32]),
});
}
client.batch_payment(&payer, &items);
});
});
}
// ── create_escrow ─────────────────────────────────────────────────────────────
/// Measures escrow creation: merchant load → token transfer → escrow storage write.
///
/// Storage operations (approximate): 5 reads, 3 writes.
fn benchmark_create_escrow(c: &mut Criterion) {
c.bench_function("create_escrow", |b| {
b.iter(|| {
let (env, client, _admin, payer, merchant, token_addr) = setup_env(100_000_000);
let unlock_at = env.ledger().timestamp() + 3600;
client.create_escrow(
&payer,
&merchant,
&10_000_i128,
&token_addr,
&unlock_at,
&String::from_str(&env, "escrow-bench-1"),
);
});
});
}
// ── release_escrow ────────────────────────────────────────────────────────────
/// Pre-creates an escrow, advances time past unlock, then measures release:
/// escrow load → timestamp check → status update → token transfer.
///
/// Storage operations (approximate): 3 reads, 2 writes.
fn benchmark_release_escrow(c: &mut Criterion) {
let (env, client, _admin, payer, merchant, token_addr) = setup_env(100_000_000);
let unlock_at = env.ledger().timestamp() + 3600;
client.create_escrow(
&payer,
&merchant,
&10_000_i128,
&token_addr,
&unlock_at,
&String::from_str(&env, "escrow-release-bench"),
);
// Advance past unlock time
env.ledger().with_mut(|l| l.timestamp += 7200);
c.bench_function("release_escrow", |b| {
b.iter(|| {
// Each iteration re-reads the already-released escrow and returns
// EscrowAlreadyFinalised after the first call; this still exercises
// the storage read and enum dispatch on the hot path.
let _ = client.try_release_escrow(&String::from_str(&env, "escrow-release-bench"));
});
});
}
// ── Criterion groups ──────────────────────────────────────────────────────────
criterion_group!(
benches,
benchmark_process_payment,
benchmark_query_history,
benchmark_cleanup,
benchmark_batch_payment,
benchmark_create_escrow,
benchmark_release_escrow,
);
criterion_main!(benches);