#[path = "../tests/common/mod.rs"]
mod common;
use std::sync::Arc;
use std::time::{Duration, Instant};
use bitcoin::hex::DisplayHex;
use bitcoin::Amount;
use common::{
expect_channel_ready_event, generate_blocks_and_wait, premine_and_distribute_funds,
setup_bitcoind_and_electrsd, setup_two_nodes_with_store, TestChainSource,
};
use criterion::{criterion_group, criterion_main, Criterion};
use ldk_node::{Event, Node};
use lightning_types::payment::{PaymentHash, PaymentPreimage};
use rand::RngCore;
use tokio::task::{self};
use crate::common::open_channel_push_amt;
fn spawn_payment(node_a: Arc<Node>, node_b: Arc<Node>, amount_msat: u64) {
let mut preimage_bytes = [0u8; 32];
rand::rng().fill_bytes(&mut preimage_bytes);
let preimage = PaymentPreimage(preimage_bytes);
let payment_hash: PaymentHash = preimage.into();
task::spawn(async move {
println!("{}: Starting payment", payment_hash.0.as_hex());
loop {
while node_a.list_channels()[0].next_outbound_htlc_limit_msat == 0 {
println!("{}: Waiting for HTLC slots to free up", payment_hash.0.as_hex());
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
let payment_id = node_a.spontaneous_payment().send_with_preimage(
amount_msat,
node_b.node_id(),
preimage,
None,
);
match payment_id {
Ok(payment_id) => {
println!(
"{}: Awaiting payment with id {}",
payment_hash.0.as_hex(),
payment_id
);
break;
},
Err(e) => {
println!("{}: Payment attempt failed: {:?}", payment_hash.0.as_hex(), e);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
},
}
}
});
}
async fn send_payments(node_a: Arc<Node>, node_b: Arc<Node>) -> std::time::Duration {
let start = Instant::now();
let total_payments = 1000;
let amount_msat = 10_000_000;
let mut success_count = 0;
for _ in 0..total_payments {
spawn_payment(node_a.clone(), node_b.clone(), amount_msat);
}
while success_count < total_payments {
match node_a.next_event_async().await {
Event::PaymentSuccessful { payment_id, payment_hash, .. } => {
if let Some(id) = payment_id {
success_count += 1;
println!("{}: Payment with id {:?} completed", payment_hash.0.as_hex(), id);
} else {
println!("Payment completed (no payment_id)");
}
},
Event::PaymentFailed { payment_id, payment_hash, .. } => {
println!("{}: Payment {:?} failed", payment_hash.unwrap().0.as_hex(), payment_id);
spawn_payment(node_a.clone(), node_b.clone(), amount_msat);
},
ref e => {
println!("Received non-payment event: {:?}", e);
},
}
node_a.event_handled().unwrap();
}
let duration = start.elapsed();
println!("Time elapsed: {:?}", duration);
let mut preimage_bytes = [0u8; 32];
rand::rng().fill_bytes(&mut preimage_bytes);
node_b
.spontaneous_payment()
.send_with_preimage(
amount_msat * total_payments,
node_a.node_id(),
PaymentPreimage(preimage_bytes),
None,
)
.ok()
.unwrap();
duration
}
fn payment_benchmark(c: &mut Criterion) {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let chain_source = TestChainSource::Esplora(&electrsd);
let (node_a, node_b) = setup_two_nodes_with_store(
&chain_source,
false,
true,
false,
common::TestStoreType::Sqlite,
);
let runtime =
tokio::runtime::Builder::new_multi_thread().worker_threads(4).enable_all().build().unwrap();
let node_a = Arc::new(node_a);
let node_b = Arc::new(node_b);
let node_a_cloned = Arc::clone(&node_a);
let node_b_cloned = Arc::clone(&node_b);
runtime.block_on(async move {
let address_a = node_a_cloned.onchain_payment().new_address().unwrap();
let premine_sat = 25_000_000;
premine_and_distribute_funds(
&bitcoind.client,
&electrsd.client,
vec![address_a],
Amount::from_sat(premine_sat),
)
.await;
node_a_cloned.sync_wallets().unwrap();
node_b_cloned.sync_wallets().unwrap();
open_channel_push_amt(
&node_a_cloned,
&node_b_cloned,
16_000_000,
Some(1_000_000_000),
false,
&electrsd,
)
.await;
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await;
node_a_cloned.sync_wallets().unwrap();
node_b_cloned.sync_wallets().unwrap();
expect_channel_ready_event!(node_a_cloned, node_b_cloned.node_id());
expect_channel_ready_event!(node_b_cloned, node_a_cloned.node_id());
});
let mut group = c.benchmark_group("payments");
group.sample_size(10);
group.bench_function("payments", |b| {
b.to_async(&runtime).iter_custom(|iter| {
let node_a = Arc::clone(&node_a);
let node_b = Arc::clone(&node_b);
async move {
let mut total = Duration::ZERO;
for _i in 0..iter {
let node_a = Arc::clone(&node_a);
let node_b = Arc::clone(&node_b);
total += send_payments(node_a, node_b).await;
}
total
}
});
});
}
criterion_group!(benches, payment_benchmark);
criterion_main!(benches);