| 
 | 1 | +mod common;  | 
 | 2 | + | 
 | 3 | +use std::time::Instant;  | 
 | 4 | +use std::{sync::Arc, time::Duration};  | 
 | 5 | + | 
 | 6 | +use bitcoin::hex::DisplayHex;  | 
 | 7 | +use bitcoin::Amount;  | 
 | 8 | +use common::{  | 
 | 9 | +	expect_channel_ready_event, generate_blocks_and_wait, premine_and_distribute_funds,  | 
 | 10 | +	setup_bitcoind_and_electrsd, setup_two_nodes_with_store, TestChainSource,  | 
 | 11 | +};  | 
 | 12 | +use criterion::{criterion_group, criterion_main, Criterion};  | 
 | 13 | +use ldk_node::{Event, Node};  | 
 | 14 | +use lightning_types::payment::{PaymentHash, PaymentPreimage};  | 
 | 15 | +use rand::RngCore;  | 
 | 16 | +use tokio::task::{self};  | 
 | 17 | + | 
 | 18 | +use crate::common::open_channel_push_amt;  | 
 | 19 | + | 
 | 20 | +fn spawn_payment(node_a: Arc<Node>, node_b: Arc<Node>, amount_msat: u64) {  | 
 | 21 | +	let mut preimage_bytes = [0u8; 32];  | 
 | 22 | +	rand::thread_rng().fill_bytes(&mut preimage_bytes);  | 
 | 23 | +	let preimage = PaymentPreimage(preimage_bytes);  | 
 | 24 | +	let payment_hash: PaymentHash = preimage.into();  | 
 | 25 | + | 
 | 26 | +	// Spawn each payment as a separate async task  | 
 | 27 | +	task::spawn(async move {  | 
 | 28 | +		println!("{}: Starting payment", payment_hash.0.as_hex());  | 
 | 29 | + | 
 | 30 | +		loop {  | 
 | 31 | +			// Pre-check the HTLC slots to try to avoid the performance impact of a failed payment.  | 
 | 32 | +			while node_a.list_channels()[0].next_outbound_htlc_limit_msat == 0 {  | 
 | 33 | +				println!("{}: Waiting for HTLC slots to free up", payment_hash.0.as_hex());  | 
 | 34 | +				tokio::time::sleep(std::time::Duration::from_millis(100)).await;  | 
 | 35 | +			}  | 
 | 36 | + | 
 | 37 | +			let payment_id = node_a.spontaneous_payment().send_with_preimage(  | 
 | 38 | +				amount_msat,  | 
 | 39 | +				node_b.node_id(),  | 
 | 40 | +				preimage,  | 
 | 41 | +				None,  | 
 | 42 | +			);  | 
 | 43 | + | 
 | 44 | +			match payment_id {  | 
 | 45 | +				Ok(payment_id) => {  | 
 | 46 | +					println!(  | 
 | 47 | +						"{}: Awaiting payment with id {}",  | 
 | 48 | +						payment_hash.0.as_hex(),  | 
 | 49 | +						payment_id  | 
 | 50 | +					);  | 
 | 51 | +					break;  | 
 | 52 | +				},  | 
 | 53 | +				Err(e) => {  | 
 | 54 | +					println!("{}: Payment attempt failed: {:?}", payment_hash.0.as_hex(), e);  | 
 | 55 | + | 
 | 56 | +					tokio::time::sleep(std::time::Duration::from_millis(100)).await;  | 
 | 57 | +				},  | 
 | 58 | +			}  | 
 | 59 | +		}  | 
 | 60 | +	});  | 
 | 61 | +}  | 
 | 62 | + | 
 | 63 | +async fn send_payments(node_a: Arc<Node>, node_b: Arc<Node>) -> std::time::Duration {  | 
 | 64 | +	let start = Instant::now();  | 
 | 65 | + | 
 | 66 | +	let total_payments = 1000;  | 
 | 67 | +	let amount_msat = 10_000_000;  | 
 | 68 | + | 
 | 69 | +	let mut success_count = 0;  | 
 | 70 | +	for _ in 0..total_payments {  | 
 | 71 | +		spawn_payment(node_a.clone(), node_b.clone(), amount_msat);  | 
 | 72 | +	}  | 
 | 73 | + | 
 | 74 | +	while success_count < total_payments {  | 
 | 75 | +		match node_a.next_event_async().await {  | 
 | 76 | +			Event::PaymentSuccessful { payment_id, payment_hash, .. } => {  | 
 | 77 | +				if let Some(id) = payment_id {  | 
 | 78 | +					success_count += 1;  | 
 | 79 | +					println!("{}: Payment with id {:?} completed", payment_hash.0.as_hex(), id);  | 
 | 80 | +				} else {  | 
 | 81 | +					println!("Payment completed (no payment_id)");  | 
 | 82 | +				}  | 
 | 83 | +			},  | 
 | 84 | +			Event::PaymentFailed { payment_id, payment_hash, .. } => {  | 
 | 85 | +				println!("{}: Payment {:?} failed", payment_hash.unwrap().0.as_hex(), payment_id);  | 
 | 86 | + | 
 | 87 | +				// The payment failed, so we need to respawn it.  | 
 | 88 | +				spawn_payment(node_a.clone(), node_b.clone(), amount_msat);  | 
 | 89 | +			},  | 
 | 90 | +			ref e => {  | 
 | 91 | +				println!("Received non-payment event: {:?}", e);  | 
 | 92 | +			},  | 
 | 93 | +		}  | 
 | 94 | + | 
 | 95 | +		node_a.event_handled().unwrap();  | 
 | 96 | +	}  | 
 | 97 | + | 
 | 98 | +	let duration = start.elapsed();  | 
 | 99 | +	println!("Time elapsed: {:?}", duration);  | 
 | 100 | + | 
 | 101 | +	// Send back the money for the next iteration.  | 
 | 102 | +	let mut preimage_bytes = [0u8; 32];  | 
 | 103 | +	rand::thread_rng().fill_bytes(&mut preimage_bytes);  | 
 | 104 | +	node_b  | 
 | 105 | +		.spontaneous_payment()  | 
 | 106 | +		.send_with_preimage(  | 
 | 107 | +			amount_msat * total_payments,  | 
 | 108 | +			node_a.node_id(),  | 
 | 109 | +			PaymentPreimage(preimage_bytes),  | 
 | 110 | +			None,  | 
 | 111 | +		)  | 
 | 112 | +		.ok()  | 
 | 113 | +		.unwrap();  | 
 | 114 | + | 
 | 115 | +	duration  | 
 | 116 | +}  | 
 | 117 | + | 
 | 118 | +fn payment_benchmark(c: &mut Criterion) {  | 
 | 119 | +	// Set up two nodes with a channel between them. Because this is slow, we reuse the same nodes for each sample.  | 
 | 120 | +	let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();  | 
 | 121 | +	let chain_source = TestChainSource::Esplora(&electrsd);  | 
 | 122 | + | 
 | 123 | +	let (node_a, node_b) = setup_two_nodes_with_store(  | 
 | 124 | +		&chain_source,  | 
 | 125 | +		false,  | 
 | 126 | +		true,  | 
 | 127 | +		false,  | 
 | 128 | +		common::TestStoreType::Sqlite,  | 
 | 129 | +	);  | 
 | 130 | + | 
 | 131 | +	let address_a = node_a.onchain_payment().new_address().unwrap();  | 
 | 132 | +	let premine_sat = 25_000_000;  | 
 | 133 | +	premine_and_distribute_funds(  | 
 | 134 | +		&bitcoind.client,  | 
 | 135 | +		&electrsd.client,  | 
 | 136 | +		vec![address_a],  | 
 | 137 | +		Amount::from_sat(premine_sat),  | 
 | 138 | +	);  | 
 | 139 | +	node_a.sync_wallets().unwrap();  | 
 | 140 | +	node_b.sync_wallets().unwrap();  | 
 | 141 | +	open_channel_push_amt(&node_a, &node_b, 16_000_000, Some(1_000_000_000), false, &electrsd);  | 
 | 142 | +	generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);  | 
 | 143 | +	node_a.sync_wallets().unwrap();  | 
 | 144 | +	node_b.sync_wallets().unwrap();  | 
 | 145 | +	expect_channel_ready_event!(node_a, node_b.node_id());  | 
 | 146 | +	expect_channel_ready_event!(node_b, node_a.node_id());  | 
 | 147 | + | 
 | 148 | +	let node_a = Arc::new(node_a);  | 
 | 149 | +	let node_b = Arc::new(node_b);  | 
 | 150 | + | 
 | 151 | +	let mut group = c.benchmark_group("payments");  | 
 | 152 | +	group.sample_size(10);  | 
 | 153 | + | 
 | 154 | +	group.bench_function("payments", |b| {  | 
 | 155 | +		let runtime = tokio::runtime::Builder::new_multi_thread()  | 
 | 156 | +			.worker_threads(4)  | 
 | 157 | +			.enable_all()  | 
 | 158 | +			.build()  | 
 | 159 | +			.unwrap();  | 
 | 160 | + | 
 | 161 | +		// Use custom timing so that sending back the money at the end of each iteration isn't included in the  | 
 | 162 | +		// measurement.  | 
 | 163 | +		b.to_async(runtime).iter_custom(|iter| {  | 
 | 164 | +			let node_a = Arc::clone(&node_a);  | 
 | 165 | +			let node_b = Arc::clone(&node_b);  | 
 | 166 | + | 
 | 167 | +			async move {  | 
 | 168 | +				let mut total = Duration::ZERO;  | 
 | 169 | +				for _i in 0..iter {  | 
 | 170 | +					let node_a = Arc::clone(&node_a);  | 
 | 171 | +					let node_b = Arc::clone(&node_b);  | 
 | 172 | + | 
 | 173 | +					total += send_payments(node_a, node_b).await;  | 
 | 174 | +				}  | 
 | 175 | +				total  | 
 | 176 | +			}  | 
 | 177 | +		});  | 
 | 178 | +	});  | 
 | 179 | +}  | 
 | 180 | + | 
 | 181 | +criterion_group!(benches, payment_benchmark);  | 
 | 182 | +criterion_main!(benches);  | 
0 commit comments