Skip to content

Commit ceab99f

Browse files
randomloginclaude
andcommitted
Add probing service tests
Add integration tests that verify the probing service fires probes on the configured interval and respects the locked-msat budget cap. Shared helpers in tests/common are extended with probing-aware setup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 068c0db commit ceab99f

2 files changed

Lines changed: 540 additions & 7 deletions

File tree

tests/common/mod.rs

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use ldk_node::config::{
4343
use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy};
4444
use ldk_node::io::sqlite_store::SqliteStore;
4545
use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus};
46+
use ldk_node::probing::ProbingConfig;
4647
use ldk_node::{
4748
Builder, ChannelShutdownState, CustomTlvRecord, Event, LightningBalance, Node, NodeError,
4849
PendingSweepBalance, UserChannelId,
@@ -403,9 +404,9 @@ pub(crate) fn random_config(anchor_channels: bool) -> TestConfig {
403404
}
404405

405406
#[cfg(feature = "uniffi")]
406-
type TestNode = Arc<Node>;
407+
pub(crate) type TestNode = Arc<Node>;
407408
#[cfg(not(feature = "uniffi"))]
408-
type TestNode = Node;
409+
pub(crate) type TestNode = Node;
409410

410411
#[derive(Clone)]
411412
pub(crate) enum TestChainSource<'a> {
@@ -437,6 +438,7 @@ pub(crate) struct TestConfig {
437438
pub async_payments_role: Option<AsyncPaymentsRole>,
438439
pub wallet_rescan_from_height: Option<u32>,
439440
pub force_wallet_full_scan: bool,
441+
pub probing: Option<ProbingConfig>,
440442
}
441443

442444
impl Default for TestConfig {
@@ -458,6 +460,7 @@ impl Default for TestConfig {
458460
async_payments_role,
459461
wallet_rescan_from_height,
460462
force_wallet_full_scan,
463+
probing: None,
461464
}
462465
}
463466
}
@@ -598,6 +601,10 @@ pub(crate) fn setup_node(chain_source: &TestChainSource, config: TestConfig) ->
598601

599602
builder.set_async_payments_role(config.async_payments_role).unwrap();
600603

604+
if let Some(probing) = config.probing {
605+
builder.set_probing_config(probing.into());
606+
}
607+
601608
let node = match config.store_type {
602609
TestStoreType::TestSyncStore => {
603610
let kv_store = TestSyncStore::new(config.node_config.storage_dir_path.into());
@@ -699,6 +706,37 @@ pub(crate) async fn wait_for_outpoint_spend<E: ElectrumApi>(electrs: &E, outpoin
699706
.await;
700707
}
701708

709+
/// Polls the channel from `source_node` to `counterparty_node` until it reports `is_usable`
710+
/// and can carry an HTLC of `min_amount_msat` from `source_node`'s side.
711+
///
712+
/// After `ChannelReady`, channel-monitor persistence can lag for tens of seconds on slow
713+
/// CI runners; during that window `send_probe`/`send_payment` reject with
714+
/// `ParameterError("...monitor update is in progress...")`. This helper gives tests a
715+
/// deterministic readiness gate instead of racing the monitor-update pipeline.
716+
pub(crate) async fn wait_for_channel_ready_to_send(
717+
source_node: &TestNode, counterparty_node: &TestNode, min_amount_msat: u64,
718+
) {
719+
let counterparty = counterparty_node.node_id();
720+
let deadline = tokio::time::Instant::now() + Duration::from_secs(180);
721+
while tokio::time::Instant::now() < deadline {
722+
let ready = source_node.list_channels().iter().any(|c| {
723+
c.counterparty_node_id == counterparty
724+
&& c.is_usable
725+
&& c.next_outbound_htlc_limit_msat >= min_amount_msat
726+
});
727+
if ready {
728+
return;
729+
}
730+
tokio::time::sleep(Duration::from_millis(100)).await;
731+
}
732+
panic!(
733+
"channel from {} to {} not ready to send {} msat within 180s",
734+
source_node.node_id(),
735+
counterparty,
736+
min_amount_msat,
737+
);
738+
}
739+
702740
pub(crate) async fn exponential_backoff_poll<T, F>(mut poll: F) -> T
703741
where
704742
F: FnMut() -> Option<T>,
@@ -824,12 +862,18 @@ pub async fn open_channel(
824862
node_a: &TestNode, node_b: &TestNode, funding_amount_sat: u64, should_announce: bool,
825863
electrsd: &ElectrsD,
826864
) -> OutPoint {
827-
open_channel_push_amt(node_a, node_b, funding_amount_sat, None, should_announce, electrsd).await
865+
let funding_txo =
866+
open_channel_no_wait(node_a, node_b, funding_amount_sat, None, should_announce).await;
867+
wait_for_tx(&electrsd.client, funding_txo.txid).await;
868+
funding_txo
828869
}
829870

830-
pub async fn open_channel_push_amt(
871+
/// Like [`open_channel`] but skips the `wait_for_tx` electrum check so that
872+
/// multiple channels can be opened back-to-back before any blocks are mined.
873+
/// The caller is responsible for mining blocks and confirming the funding txs.
874+
pub async fn open_channel_no_wait(
831875
node_a: &TestNode, node_b: &TestNode, funding_amount_sat: u64, push_amount_msat: Option<u64>,
832-
should_announce: bool, electrsd: &ElectrsD,
876+
should_announce: bool,
833877
) -> OutPoint {
834878
if should_announce {
835879
node_a
@@ -857,11 +901,20 @@ pub async fn open_channel_push_amt(
857901
let funding_txo_a = expect_channel_pending_event!(node_a, node_b.node_id());
858902
let funding_txo_b = expect_channel_pending_event!(node_b, node_a.node_id());
859903
assert_eq!(funding_txo_a, funding_txo_b);
860-
wait_for_tx(&electrsd.client, funding_txo_a.txid).await;
861-
862904
funding_txo_a
863905
}
864906

907+
pub async fn open_channel_push_amt(
908+
node_a: &TestNode, node_b: &TestNode, funding_amount_sat: u64, push_amount_msat: Option<u64>,
909+
should_announce: bool, electrsd: &ElectrsD,
910+
) -> OutPoint {
911+
let funding_txo =
912+
open_channel_no_wait(node_a, node_b, funding_amount_sat, push_amount_msat, should_announce)
913+
.await;
914+
wait_for_tx(&electrsd.client, funding_txo.txid).await;
915+
funding_txo
916+
}
917+
865918
pub async fn open_channel_with_all(
866919
node_a: &TestNode, node_b: &TestNode, should_announce: bool, electrsd: &ElectrsD,
867920
) -> OutPoint {

0 commit comments

Comments
 (0)