|  | 
|  | 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. 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 runtime = | 
|  | 132 | +		tokio::runtime::Builder::new_multi_thread().worker_threads(4).enable_all().build().unwrap(); | 
|  | 133 | + | 
|  | 134 | +	let node_a = Arc::new(node_a); | 
|  | 135 | +	let node_b = Arc::new(node_b); | 
|  | 136 | + | 
|  | 137 | +	// Fund the nodes and setup a channel between them. The criterion function cannot be async, so we need to execute | 
|  | 138 | +	// the setup using a runtime. | 
|  | 139 | +	let node_a_cloned = Arc::clone(&node_a); | 
|  | 140 | +	let node_b_cloned = Arc::clone(&node_b); | 
|  | 141 | +	runtime.block_on(async move { | 
|  | 142 | +		let address_a = node_a_cloned.onchain_payment().new_address().unwrap(); | 
|  | 143 | +		let premine_sat = 25_000_000; | 
|  | 144 | +		premine_and_distribute_funds( | 
|  | 145 | +			&bitcoind.client, | 
|  | 146 | +			&electrsd.client, | 
|  | 147 | +			vec![address_a], | 
|  | 148 | +			Amount::from_sat(premine_sat), | 
|  | 149 | +		) | 
|  | 150 | +		.await; | 
|  | 151 | +		node_a_cloned.sync_wallets().unwrap(); | 
|  | 152 | +		node_b_cloned.sync_wallets().unwrap(); | 
|  | 153 | +		open_channel_push_amt( | 
|  | 154 | +			&node_a_cloned, | 
|  | 155 | +			&node_b_cloned, | 
|  | 156 | +			16_000_000, | 
|  | 157 | +			Some(1_000_000_000), | 
|  | 158 | +			false, | 
|  | 159 | +			&electrsd, | 
|  | 160 | +		) | 
|  | 161 | +		.await; | 
|  | 162 | +		generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; | 
|  | 163 | +		node_a_cloned.sync_wallets().unwrap(); | 
|  | 164 | +		node_b_cloned.sync_wallets().unwrap(); | 
|  | 165 | +		expect_channel_ready_event!(node_a_cloned, node_b_cloned.node_id()); | 
|  | 166 | +		expect_channel_ready_event!(node_b_cloned, node_a_cloned.node_id()); | 
|  | 167 | +	}); | 
|  | 168 | + | 
|  | 169 | +	let mut group = c.benchmark_group("payments"); | 
|  | 170 | +	group.sample_size(10); | 
|  | 171 | + | 
|  | 172 | +	group.bench_function("payments", |b| { | 
|  | 173 | +		// Use custom timing so that sending back the money at the end of each iteration isn't included in the | 
|  | 174 | +		// measurement. | 
|  | 175 | +		b.to_async(&runtime).iter_custom(|iter| { | 
|  | 176 | +			let node_a = Arc::clone(&node_a); | 
|  | 177 | +			let node_b = Arc::clone(&node_b); | 
|  | 178 | + | 
|  | 179 | +			async move { | 
|  | 180 | +				let mut total = Duration::ZERO; | 
|  | 181 | +				for _i in 0..iter { | 
|  | 182 | +					let node_a = Arc::clone(&node_a); | 
|  | 183 | +					let node_b = Arc::clone(&node_b); | 
|  | 184 | + | 
|  | 185 | +					total += send_payments(node_a, node_b).await; | 
|  | 186 | +				} | 
|  | 187 | +				total | 
|  | 188 | +			} | 
|  | 189 | +		}); | 
|  | 190 | +	}); | 
|  | 191 | +} | 
|  | 192 | + | 
|  | 193 | +criterion_group!(benches, payment_benchmark); | 
|  | 194 | +criterion_main!(benches); | 
0 commit comments