Skip to content

Commit e294f87

Browse files
committed
Add forwarding operations benchmark
Add an operations bench target with a forwarding benchmark that compares sqlite, filesystem, and postgres stores over a settled multi-hop payment. AI-assisted-by: OpenAI Codex
1 parent c60b8ca commit e294f87

2 files changed

Lines changed: 348 additions & 0 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ check-cfg = [
141141
name = "payments"
142142
harness = false
143143

144+
[[bench]]
145+
name = "operations"
146+
harness = false
147+
144148
[[bench]]
145149
name = "database"
146150
harness = false

benches/operations.rs

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
#[path = "../tests/common/mod.rs"]
9+
mod common;
10+
11+
use std::sync::Arc;
12+
use std::time::{Duration, Instant};
13+
14+
use bitcoin::Amount;
15+
use common::{
16+
expect_event, generate_blocks_and_wait, premine_and_distribute_funds, random_config,
17+
setup_bitcoind_and_electrsd, setup_node,
18+
};
19+
use criterion::{criterion_group, criterion_main, Criterion};
20+
use electrsd::corepc_node::Node as BitcoinD;
21+
use ldk_node::{Event, Node};
22+
use lightning::ln::channelmanager::PaymentId;
23+
use lightning::routing::router::RouteParametersConfig;
24+
use lightning_invoice::{Bolt11InvoiceDescription, Description};
25+
26+
use crate::common::{open_channel_push_amt, TestChainSource, TestStoreType};
27+
28+
#[derive(Clone, Copy)]
29+
struct StoreBenchConfig {
30+
name: &'static str,
31+
store_type: TestStoreType,
32+
}
33+
34+
fn operations_benchmark(c: &mut Criterion) {
35+
forwarding_benchmark(c);
36+
}
37+
38+
fn forwarding_benchmark(c: &mut Criterion) {
39+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
40+
let chain_source = TestChainSource::Esplora(&electrsd);
41+
let runtime =
42+
tokio::runtime::Builder::new_multi_thread().worker_threads(4).enable_all().build().unwrap();
43+
44+
let mut group = c.benchmark_group("forwarding");
45+
group.sample_size(10);
46+
47+
for store_config in store_bench_configs() {
48+
if !should_register_bench("forwarding", store_config.name) {
49+
continue;
50+
}
51+
let nodes = setup_forwarding_nodes(
52+
&chain_source,
53+
&bitcoind,
54+
&electrsd,
55+
store_config.store_type,
56+
&runtime,
57+
);
58+
let nodes = Arc::new(nodes);
59+
60+
group.bench_function(store_config.name, |b| {
61+
b.to_async(&runtime).iter_custom(|iter| {
62+
let nodes = Arc::clone(&nodes);
63+
64+
async move {
65+
let mut total = Duration::ZERO;
66+
for _ in 0..iter {
67+
total += send_forwarded_payments(Arc::clone(&nodes)).await;
68+
}
69+
total
70+
}
71+
});
72+
});
73+
}
74+
}
75+
76+
fn should_register_bench(group: &str, name: &str) -> bool {
77+
let target = format!("{}/{}", group, name);
78+
let filters: Vec<String> =
79+
std::env::args().skip(1).filter(|arg| !arg.starts_with('-')).collect();
80+
filters.is_empty()
81+
|| filters.iter().any(|filter| {
82+
target.contains(filter) || (filter == group && target.starts_with(&format!("{group}/")))
83+
})
84+
}
85+
86+
fn setup_forwarding_nodes(
87+
chain_source: &TestChainSource, bitcoind: &BitcoinD, electrsd: &electrsd::ElectrsD,
88+
store_type: TestStoreType, runtime: &tokio::runtime::Runtime,
89+
) -> Vec<Arc<Node>> {
90+
let mut nodes = Vec::new();
91+
for _ in 0..5 {
92+
let mut config = random_config(true);
93+
config.store_type = store_type;
94+
nodes.push(Arc::new(setup_node(chain_source, config)));
95+
}
96+
97+
runtime.block_on(async {
98+
let addresses =
99+
nodes.iter().map(|node| node.onchain_payment().new_address().unwrap()).collect();
100+
premine_and_distribute_funds(
101+
&bitcoind.client,
102+
&electrsd.client,
103+
addresses,
104+
Amount::from_sat(5_000_000),
105+
)
106+
.await;
107+
for node in &nodes {
108+
node.sync_wallets().unwrap();
109+
}
110+
111+
let funding_amount_sat = 1_000_000;
112+
let push_amount_msat = None;
113+
open_channel_push_amt(
114+
&nodes[0],
115+
&nodes[1],
116+
funding_amount_sat,
117+
push_amount_msat,
118+
true,
119+
electrsd,
120+
)
121+
.await;
122+
open_channel_push_amt(
123+
&nodes[1],
124+
&nodes[2],
125+
funding_amount_sat,
126+
push_amount_msat,
127+
true,
128+
electrsd,
129+
)
130+
.await;
131+
nodes[1].sync_wallets().unwrap();
132+
open_channel_push_amt(
133+
&nodes[1],
134+
&nodes[3],
135+
funding_amount_sat,
136+
push_amount_msat,
137+
true,
138+
electrsd,
139+
)
140+
.await;
141+
open_channel_push_amt(
142+
&nodes[2],
143+
&nodes[4],
144+
funding_amount_sat,
145+
push_amount_msat,
146+
true,
147+
electrsd,
148+
)
149+
.await;
150+
open_channel_push_amt(
151+
&nodes[3],
152+
&nodes[4],
153+
funding_amount_sat,
154+
push_amount_msat,
155+
true,
156+
electrsd,
157+
)
158+
.await;
159+
160+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
161+
for node in &nodes {
162+
node.sync_wallets().unwrap();
163+
}
164+
165+
expect_event!(nodes[0], ChannelReady);
166+
expect_event!(nodes[1], ChannelReady);
167+
expect_event!(nodes[1], ChannelReady);
168+
expect_event!(nodes[1], ChannelReady);
169+
expect_event!(nodes[2], ChannelReady);
170+
expect_event!(nodes[2], ChannelReady);
171+
expect_event!(nodes[3], ChannelReady);
172+
expect_event!(nodes[3], ChannelReady);
173+
expect_event!(nodes[4], ChannelReady);
174+
expect_event!(nodes[4], ChannelReady);
175+
176+
tokio::time::sleep(Duration::from_secs(1)).await;
177+
warm_up_forwarding_route(&nodes).await;
178+
});
179+
180+
nodes
181+
}
182+
183+
async fn send_forwarded_payments(nodes: Arc<Vec<Arc<Node>>>) -> Duration {
184+
let start = Instant::now();
185+
186+
let total_payments = 1;
187+
let amount_msat = 2_500_000;
188+
let route_params = route_parameters();
189+
190+
for _ in 0..total_payments {
191+
let invoice_description =
192+
Bolt11InvoiceDescription::Direct(Description::new("forwarding".to_string()).unwrap());
193+
let invoice = nodes[4]
194+
.bolt11_payment()
195+
.receive(amount_msat, &invoice_description.into(), 9217)
196+
.unwrap();
197+
let payment_id =
198+
nodes[0].bolt11_payment().send(&invoice, Some(route_params.clone())).unwrap();
199+
wait_for_forwarded_payment(&nodes, payment_id).await;
200+
}
201+
202+
let duration = start.elapsed();
203+
204+
for _ in 0..total_payments {
205+
let invoice_description =
206+
Bolt11InvoiceDescription::Direct(Description::new("return".to_string()).unwrap());
207+
let invoice = nodes[0]
208+
.bolt11_payment()
209+
.receive(amount_msat - 100_000, &invoice_description.into(), 9217)
210+
.unwrap();
211+
match nodes[4].bolt11_payment().send(&invoice, Some(route_params.clone())) {
212+
Ok(return_payment_id) => wait_for_payment_success(&nodes[4], return_payment_id).await,
213+
Err(_) => break,
214+
}
215+
}
216+
tokio::time::sleep(Duration::from_millis(10)).await;
217+
for node in nodes.iter() {
218+
drain_events(node);
219+
}
220+
221+
duration
222+
}
223+
224+
async fn wait_for_forwarded_payment(nodes: &[Arc<Node>], expected_payment_id: PaymentId) {
225+
let mut payment_successful = false;
226+
let mut first_hop_forwarded = false;
227+
let mut second_hop_forwarded = false;
228+
229+
while !payment_successful || !first_hop_forwarded || !second_hop_forwarded {
230+
tokio::select! {
231+
event = nodes[0].next_event_async(), if !payment_successful => {
232+
match event {
233+
Event::PaymentSuccessful { payment_id: Some(payment_id), .. }
234+
if payment_id == expected_payment_id =>
235+
{
236+
payment_successful = true;
237+
},
238+
Event::PaymentFailed { payment_id, payment_hash, .. } => {
239+
nodes[0].event_handled().unwrap();
240+
panic!("Forwarded payment {:?} failed with hash {:?}", payment_id, payment_hash);
241+
},
242+
_ => {},
243+
}
244+
nodes[0].event_handled().unwrap();
245+
},
246+
event = nodes[1].next_event_async(), if !first_hop_forwarded => {
247+
if matches!(event, Event::PaymentForwarded { .. }) {
248+
first_hop_forwarded = true;
249+
}
250+
nodes[1].event_handled().unwrap();
251+
},
252+
event = nodes[2].next_event_async(), if !second_hop_forwarded => {
253+
if matches!(event, Event::PaymentForwarded { .. }) {
254+
second_hop_forwarded = true;
255+
}
256+
nodes[2].event_handled().unwrap();
257+
},
258+
event = nodes[3].next_event_async(), if !second_hop_forwarded => {
259+
if matches!(event, Event::PaymentForwarded { .. }) {
260+
second_hop_forwarded = true;
261+
}
262+
nodes[3].event_handled().unwrap();
263+
},
264+
}
265+
}
266+
}
267+
268+
async fn warm_up_forwarding_route(nodes: &[Arc<Node>]) {
269+
for _ in 0..30 {
270+
let invoice_description = Bolt11InvoiceDescription::Direct(
271+
Description::new("forwarding warmup".to_string()).unwrap(),
272+
);
273+
let invoice = nodes[4]
274+
.bolt11_payment()
275+
.receive(2_500_000, &invoice_description.into(), 9217)
276+
.unwrap();
277+
if let Ok(payment_id) = nodes[0].bolt11_payment().send(&invoice, Some(route_parameters())) {
278+
wait_for_payment_success(&nodes[0], payment_id).await;
279+
tokio::time::sleep(Duration::from_millis(50)).await;
280+
for node in nodes {
281+
drain_events(node);
282+
}
283+
return;
284+
}
285+
tokio::time::sleep(Duration::from_secs(1)).await;
286+
}
287+
288+
panic!("Timed out warming up forwarding route");
289+
}
290+
291+
fn route_parameters() -> RouteParametersConfig {
292+
RouteParametersConfig {
293+
max_total_routing_fee_msat: Some(75_000),
294+
max_total_cltv_expiry_delta: 1000,
295+
max_path_count: 10,
296+
max_channel_saturation_power_of_half: 2,
297+
}
298+
}
299+
300+
async fn wait_for_payment_success(node: &Node, expected_payment_id: PaymentId) {
301+
loop {
302+
match node.next_event_async().await {
303+
Event::PaymentSuccessful { payment_id: Some(payment_id), .. }
304+
if payment_id == expected_payment_id =>
305+
{
306+
node.event_handled().unwrap();
307+
break;
308+
},
309+
Event::PaymentFailed { payment_id, payment_hash, .. } => {
310+
node.event_handled().unwrap();
311+
panic!("Return payment {:?} failed with hash {:?}", payment_id, payment_hash);
312+
},
313+
_ => node.event_handled().unwrap(),
314+
}
315+
}
316+
}
317+
318+
fn drain_events(node: &Node) {
319+
while node.next_event().is_some() {
320+
node.event_handled().unwrap();
321+
}
322+
}
323+
324+
fn store_bench_configs() -> Vec<StoreBenchConfig> {
325+
#[cfg(not(feature = "postgres"))]
326+
{
327+
vec![
328+
StoreBenchConfig { name: "sqlite", store_type: TestStoreType::Sqlite },
329+
StoreBenchConfig { name: "filesystem", store_type: TestStoreType::FilesystemStore },
330+
]
331+
}
332+
333+
#[cfg(feature = "postgres")]
334+
{
335+
vec![
336+
StoreBenchConfig { name: "sqlite", store_type: TestStoreType::Sqlite },
337+
StoreBenchConfig { name: "filesystem", store_type: TestStoreType::FilesystemStore },
338+
StoreBenchConfig { name: "postgres", store_type: TestStoreType::Postgres },
339+
]
340+
}
341+
}
342+
343+
criterion_group!(benches, operations_benchmark);
344+
criterion_main!(benches);

0 commit comments

Comments
 (0)