Skip to content

Commit 80a1b27

Browse files
jkczyzclaude
andcommitted
Test splice failure surfacing and recovery
A disconnect during the interactive negotiation fails the splice with PeerDisconnected. The first test asserts that exactly one SpliceNegotiationFailed reaches the user — carrying the reason and the originating request's parameters — and that a new splice initiated afterwards completes with a single funding payment. The window only exists mid-negotiation: a contribution still queued at disconnect is resumed by LDK itself on reconnect, and one awaiting signatures survives re-establishment. The test therefore synchronizes on the counterparty's splice_ack — logged by LDK's peer handler — and stretches the negotiation by funding the splice from many small UTXOs, each of which adds an interactive-tx round trip. A splice dropped by a restart is recovered silently: startup reconciliation releases what the wallet reserved and drops the record without fabricating a failure event. What does reach the user is the failure LDK persisted at shutdown and replays at startup — once, with parameters only when it still matches a kept record. The restart tests cover both cases: a dropped splice-out surfaces without parameters and a further restart stays silent, while a dropped fee bump — whose record reconciliation keeps, since LDK still holds the negotiated splice — surfaces with the bump's parameters. In both, the application re-initiates and the splice completes. A splice confirmed while its node was offline keeps exactly one payment record under its splice-time id regardless of whether wallet sync or classification sees the confirmation first. Developed with assistance from Claude Code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 05f5b7e commit 80a1b27

2 files changed

Lines changed: 514 additions & 14 deletions

File tree

tests/common/logging.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,21 @@ impl CollectingLogWriter {
192192
self.logs.lock().unwrap().iter().filter(|message| message.contains(text)).count()
193193
}
194194

195-
/// Waits up to ten seconds for a logged message containing `text`, returning whether one
196-
/// arrived. Polling beats a fixed sleep: it returns as soon as the line lands and only pays
197-
/// the full timeout when the line never comes.
195+
/// Waits up to [`INTEROP_TIMEOUT_SECS`] for a logged message containing `text`, returning
196+
/// whether one arrived. Polling beats a fixed sleep: it returns as soon as the line lands and
197+
/// only pays the full timeout when the line never comes.
198+
///
199+
/// [`INTEROP_TIMEOUT_SECS`]: super::INTEROP_TIMEOUT_SECS
198200
pub(crate) async fn wait_for(&self, text: &str) -> bool {
199201
self.wait_for_count(text, 1).await
200202
}
201203

202-
/// Waits up to ten seconds for `occurrences` logged messages containing `text`, returning
203-
/// whether they arrived.
204+
/// Waits up to [`INTEROP_TIMEOUT_SECS`] for `occurrences` logged messages containing `text`,
205+
/// returning whether they arrived.
206+
///
207+
/// [`INTEROP_TIMEOUT_SECS`]: super::INTEROP_TIMEOUT_SECS
204208
pub(crate) async fn wait_for_count(&self, text: &str, occurrences: usize) -> bool {
205-
for _ in 0..100 {
209+
for _ in 0..(super::INTEROP_TIMEOUT_SECS * 10) {
206210
if self.count(text) >= occurrences {
207211
return true;
208212
}
@@ -217,3 +221,30 @@ impl LogWriter for CollectingLogWriter {
217221
self.logs.lock().unwrap().push(record.args.to_string());
218222
}
219223
}
224+
225+
/// Forwards every record to an inner [`CollectingLogWriter`] and signals `seen` when a record
226+
/// contains `marker`. The signal fires from inside the logging call, so a test can react within
227+
/// the emitting code path's timing — where the collector's polling `wait_for` (100ms granularity)
228+
/// is too coarse.
229+
pub(crate) struct MarkerLogWriter {
230+
inner: Arc<CollectingLogWriter>,
231+
marker: &'static str,
232+
seen: Arc<tokio::sync::Notify>,
233+
}
234+
235+
impl MarkerLogWriter {
236+
pub(crate) fn new(
237+
inner: Arc<CollectingLogWriter>, marker: &'static str, seen: Arc<tokio::sync::Notify>,
238+
) -> Self {
239+
Self { inner, marker, seen }
240+
}
241+
}
242+
243+
impl LogWriter for MarkerLogWriter {
244+
fn log(&self, record: LogRecord) {
245+
if record.args.to_string().contains(self.marker) {
246+
self.seen.notify_one();
247+
}
248+
LogWriter::log(&*self.inner, record);
249+
}
250+
}

0 commit comments

Comments
 (0)