Skip to content

Commit 36d53b5

Browse files
committed
Benchmark payments across stores
Run the existing payments benchmark once per configured store backend so filesystem, SQLite, and optional PostgreSQL results are reported under the same payment flow. AI-assisted-by: OpenAI Codex
1 parent 8e556e1 commit 36d53b5

3 files changed

Lines changed: 149 additions & 59 deletions

File tree

benches/payments.rs

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use bitcoin::hex::DisplayHex;
88
use bitcoin::Amount;
99
use common::{
1010
expect_channel_ready_event, generate_blocks_and_wait, premine_and_distribute_funds,
11-
random_chain_source, setup_bitcoind_and_electrsd, setup_two_nodes_with_store,
11+
setup_bitcoind_and_electrsd, setup_two_nodes_with_store, store_bench_configs,
12+
wait_for_payment_success, TestChainSource,
1213
};
1314
use criterion::{criterion_group, criterion_main, Criterion};
1415
use ldk_node::{Event, Node};
@@ -102,7 +103,7 @@ async fn send_payments(node_a: Arc<Node>, node_b: Arc<Node>) -> std::time::Durat
102103
// Send back the money for the next iteration.
103104
let mut preimage_bytes = [0u8; 32];
104105
rand::rng().fill_bytes(&mut preimage_bytes);
105-
node_b
106+
let return_payment_id = node_b
106107
.spontaneous_payment()
107108
.send_with_preimage(
108109
amount_msat * total_payments,
@@ -112,78 +113,92 @@ async fn send_payments(node_a: Arc<Node>, node_b: Arc<Node>) -> std::time::Durat
112113
)
113114
.ok()
114115
.unwrap();
116+
wait_for_payment_success(&node_b, return_payment_id).await;
115117

116118
duration
117119
}
118120

119121
fn payment_benchmark(c: &mut Criterion) {
120122
// Set up two nodes. Because this is slow, we reuse the same nodes for each sample.
121123
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
122-
let chain_source = random_chain_source(&bitcoind, &electrsd);
123-
124-
let (node_a, node_b) =
125-
setup_two_nodes_with_store(&chain_source, false, false, common::TestStoreType::Sqlite);
126-
127-
let runtime =
128-
tokio::runtime::Builder::new_multi_thread().worker_threads(4).enable_all().build().unwrap();
129-
130-
let node_a = Arc::new(node_a);
131-
let node_b = Arc::new(node_b);
132-
133-
// Fund the nodes and setup a channel between them. The criterion function cannot be async, so we need to execute
134-
// the setup using a runtime.
135-
let node_a_cloned = Arc::clone(&node_a);
136-
let node_b_cloned = Arc::clone(&node_b);
137-
runtime.block_on(async move {
138-
let address_a = node_a_cloned.onchain_payment().new_address().unwrap();
139-
let premine_sat = 25_000_000;
140-
premine_and_distribute_funds(
141-
&bitcoind.client,
142-
&electrsd.client,
143-
vec![address_a],
144-
Amount::from_sat(premine_sat),
145-
)
146-
.await;
147-
node_a_cloned.sync_wallets().unwrap();
148-
node_b_cloned.sync_wallets().unwrap();
149-
open_channel_push_amt(
150-
&node_a_cloned,
151-
&node_b_cloned,
152-
16_000_000,
153-
Some(1_000_000_000),
154-
false,
155-
&electrsd,
156-
)
157-
.await;
158-
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
159-
node_a_cloned.sync_wallets().unwrap();
160-
node_b_cloned.sync_wallets().unwrap();
161-
expect_channel_ready_event!(node_a_cloned, node_b_cloned.node_id());
162-
expect_channel_ready_event!(node_b_cloned, node_a_cloned.node_id());
163-
});
124+
let chain_source = TestChainSource::BitcoindRpcSync(&bitcoind);
125+
126+
let store_configs = store_bench_configs();
127+
128+
let runtime = benchmark_runtime();
164129

165130
let mut group = c.benchmark_group("payments");
166131
group.sample_size(10);
167132

168-
group.bench_function("payments", |b| {
169-
// Use custom timing so that sending back the money at the end of each iteration isn't included in the
170-
// measurement.
171-
b.to_async(&runtime).iter_custom(|iter| {
133+
for store_config in store_configs {
134+
let (node_a, node_b) =
135+
setup_two_nodes_with_store(&chain_source, false, false, store_config.store_type);
136+
137+
let node_a = Arc::new(node_a);
138+
let node_b = Arc::new(node_b);
139+
140+
// Fund the nodes and setup a channel between them. The criterion function cannot be async,
141+
// so we need to execute the setup using a runtime.
142+
let node_a_cloned = Arc::clone(&node_a);
143+
let node_b_cloned = Arc::clone(&node_b);
144+
runtime.block_on(async {
145+
let address_a = node_a_cloned.onchain_payment().new_address().unwrap();
146+
let premine_sat = 25_000_000;
147+
premine_and_distribute_funds(
148+
&bitcoind.client,
149+
&electrsd.client,
150+
vec![address_a],
151+
Amount::from_sat(premine_sat),
152+
)
153+
.await;
154+
node_a_cloned.sync_wallets().unwrap();
155+
node_b_cloned.sync_wallets().unwrap();
156+
open_channel_push_amt(
157+
&node_a_cloned,
158+
&node_b_cloned,
159+
16_000_000,
160+
Some(1_000_000_000),
161+
false,
162+
&electrsd,
163+
)
164+
.await;
165+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
166+
node_a_cloned.sync_wallets().unwrap();
167+
node_b_cloned.sync_wallets().unwrap();
168+
expect_channel_ready_event!(node_a_cloned, node_b_cloned.node_id());
169+
expect_channel_ready_event!(node_b_cloned, node_a_cloned.node_id());
170+
});
171+
172+
group.bench_function(store_config.name, |b| {
173+
// Use custom timing so that sending back the money at the end of each iteration isn't
174+
// included in the measurement.
172175
let node_a = Arc::clone(&node_a);
173176
let node_b = Arc::clone(&node_b);
174-
175-
async move {
176-
let mut total = Duration::ZERO;
177-
for _i in 0..iter {
178-
let node_a = Arc::clone(&node_a);
179-
let node_b = Arc::clone(&node_b);
180-
181-
total += send_payments(node_a, node_b).await;
177+
b.to_async(&runtime).iter_custom(|iter| {
178+
let node_a = Arc::clone(&node_a);
179+
let node_b = Arc::clone(&node_b);
180+
181+
async move {
182+
let mut total = Duration::ZERO;
183+
for _i in 0..iter {
184+
let node_a = Arc::clone(&node_a);
185+
let node_b = Arc::clone(&node_b);
186+
187+
total += send_payments(node_a, node_b).await;
188+
}
189+
total
182190
}
183-
total
184-
}
191+
});
185192
});
186-
});
193+
}
194+
}
195+
196+
fn benchmark_runtime() -> tokio::runtime::Runtime {
197+
let mut builder = tokio::runtime::Builder::new_multi_thread();
198+
builder.worker_threads(4).enable_all();
199+
#[cfg(tokio_unstable)]
200+
builder.enable_eager_driver_handoff();
201+
builder.build().unwrap()
187202
}
188203

189204
criterion_group!(benches, payment_benchmark);

tests/common/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use ldk_node::{
5252
PendingSweepBalance, UserChannelId,
5353
};
5454
use lightning::io;
55+
use lightning::ln::channelmanager::PaymentId;
5556
use lightning::ln::msgs::SocketAddress;
5657
use lightning::routing::gossip::NodeAlias;
5758
use lightning::util::persist::{KVStore, PageToken, PaginatedKVStore, PaginatedListResponse};
@@ -308,6 +309,26 @@ macro_rules! expect_payment_successful_event {
308309

309310
pub(crate) use expect_payment_successful_event;
310311

312+
pub async fn wait_for_payment_success(node: &Node, expected_payment_id: PaymentId) {
313+
loop {
314+
match node.next_event_async().await {
315+
Event::PaymentSuccessful { payment_id: Some(payment_id), .. }
316+
if payment_id == expected_payment_id =>
317+
{
318+
node.event_handled().unwrap();
319+
break;
320+
},
321+
Event::PaymentFailed { payment_id, payment_hash, .. } => {
322+
node.event_handled().unwrap();
323+
panic!("Return payment {:?} failed with hash {:?}", payment_id, payment_hash);
324+
},
325+
_ => {
326+
node.event_handled().unwrap();
327+
},
328+
}
329+
}
330+
}
331+
311332
pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
312333
let bitcoind_exe =
313334
env::var("BITCOIND_EXE").ok().or_else(|| corepc_node::downloaded_exe_path().ok()).expect(
@@ -535,6 +556,33 @@ pub(crate) enum TestStoreType {
535556
TestSyncStore,
536557
Sqlite,
537558
FilesystemStore,
559+
#[cfg(feature = "postgres")]
560+
Postgres,
561+
}
562+
563+
#[derive(Clone, Copy)]
564+
pub(crate) struct StoreBenchConfig {
565+
pub(crate) name: &'static str,
566+
pub(crate) store_type: TestStoreType,
567+
}
568+
569+
pub(crate) fn store_bench_configs() -> Vec<StoreBenchConfig> {
570+
#[cfg(not(feature = "postgres"))]
571+
{
572+
vec![
573+
StoreBenchConfig { name: "sqlite", store_type: TestStoreType::Sqlite },
574+
StoreBenchConfig { name: "filesystem", store_type: TestStoreType::FilesystemStore },
575+
]
576+
}
577+
578+
#[cfg(feature = "postgres")]
579+
{
580+
vec![
581+
StoreBenchConfig { name: "sqlite", store_type: TestStoreType::Sqlite },
582+
StoreBenchConfig { name: "filesystem", store_type: TestStoreType::FilesystemStore },
583+
StoreBenchConfig { name: "postgres", store_type: TestStoreType::Postgres },
584+
]
585+
}
538586
}
539587

540588
impl Default for TestStoreType {
@@ -729,6 +777,31 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) ->
729777
TestStoreType::FilesystemStore => {
730778
builder.build_with_fs_store(config.node_entropy.into()).unwrap()
731779
},
780+
#[cfg(feature = "postgres")]
781+
TestStoreType::Postgres => {
782+
use ldk_node::io::postgres_store::POSTGRES_TEST_URL_ENV_VAR;
783+
784+
let table_name = format!(
785+
"test_{}",
786+
config
787+
.node_config
788+
.storage_dir_path
789+
.chars()
790+
.filter(|c| c.is_ascii_alphanumeric())
791+
.collect::<String>()
792+
);
793+
let connection_string = std::env::var(POSTGRES_TEST_URL_ENV_VAR)
794+
.unwrap_or_else(|_| "host=localhost user=postgres password=postgres".to_string());
795+
builder
796+
.build_with_postgres_store(
797+
config.node_entropy.into(),
798+
connection_string,
799+
None,
800+
Some(table_name),
801+
None,
802+
)
803+
.unwrap()
804+
},
732805
};
733806

734807
node.start().unwrap();

tests/integration_tests_rust.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,6 +3714,8 @@ async fn build_0_7_0_node(
37143714
TestStoreType::FilesystemStore => builder_old.build_with_fs_store().unwrap(),
37153715
TestStoreType::Sqlite => builder_old.build().unwrap(),
37163716
TestStoreType::TestSyncStore => panic!("TestSyncStore not supported in v0.7.0 builder"),
3717+
#[cfg(feature = "postgres")]
3718+
TestStoreType::Postgres => panic!("Postgres not supported in v0.7.0 builder"),
37173719
};
37183720

37193721
node_old.start().unwrap();

0 commit comments

Comments
 (0)