Add missing `Listen`/`Readable`/methods for `OutputSweeperSync`
What changed, and why it matters
This commit fills in missing plumbing for a synchronous wrapper around an existing async component (OutputSweeperSync). It adds the ability to receive block updates, handle chain reorganizations, and restore state from disk—features that the async version already had but the sync wrapper lacked. The change is best described as a bug fix / API completeness patch rather than a direct security vulnerability. However, missing these interfaces could have caused a node to miss on-chain events, which in a Lightning context can eventually lead to loss of funds if outputs aren't swept in time.
Treat as a routine correctness/bug-fix patch. Users running sync OutputSweeperSync builds should upgrade to ensure the sweeper receives all chain events and can be properly restored from persistence. No emergency response is warranted based on the diff alone.
Security signals we found
Missing trait implementations restored on a chain-event listener wrapper
State deserialization (ReadableArgs) added for a component that sweeps on-chain outputs
No direct memory-safety or cryptographic bug visible in the diff
Potential operational risk: without these methods, sync users could fail to learn about block disconnections/reorgs and miss sweeps
Evidence from the diff
The patch adds Listen, filtered_block_connected, blocks_disconnected, current_best_block, and ReadableArgs implementations for OutputSweeperSync in lightning/src/util/sweep.rs. Previously the sync wrapper only exposed Confirm methods and a manual regenerate_and_broadcast_spend_if_necessary helper. The change reorders an existing method and adds trait forwarding to the inner async OutputSweeper plus deserialization support that wraps sync-compatible dependencies. There is no change to cryptographic logic, network parsing, or permission checks.
Changed components
lightning/src/util/sweep.rsOutputSweeperSyncListen trait implementationReadableArgs trait implementationInspect captured patch +69 / −15
diff --git a/lightning/src/util/sweep.rs b/lightning/src/util/sweep.rs
index b60d4d8..e193000 100644
--- a/lightning/src/util/sweep.rs
+++ b/lightning/src/util/sweep.rs
@@ -977,21 +977,6 @@ where
Self { sweeper }
}
- /// Regenerates and broadcasts the spending transaction for any outputs that are pending. Wraps
- /// [`OutputSweeper::regenerate_and_broadcast_spend_if_necessary`].
- pub fn regenerate_and_broadcast_spend_if_necessary(&self) -> Result<(), ()> {
- let mut fut = Box::pin(self.sweeper.regenerate_and_broadcast_spend_if_necessary());
- let mut waker = dummy_waker();
- let mut ctx = task::Context::from_waker(&mut waker);
- match fut.as_mut().poll(&mut ctx) {
- task::Poll::Ready(result) => result,
- task::Poll::Pending => {
- // In a sync context, we can't wait for the future to complete.
- unreachable!("OutputSweeper::regenerate_and_broadcast_spend_if_necessary should not be pending in a sync context");
- },
- }
- }
-
/// Wrapper around [`OutputSweeper::track_spendable_outputs`].
pub fn track_spendable_outputs(
&self, output_descriptors: Vec<SpendableOutputDescriptor>, channel_id: Option<ChannelId>,
@@ -1019,6 +1004,27 @@ where
self.sweeper.tracked_spendable_outputs()
}
+ /// Gets the latest best block which was connected either via [`Listen`] or [`Confirm`]
+ /// interfaces.
+ pub fn current_best_block(&self) -> BestBlock {
+ self.sweeper.current_best_block()
+ }
+
+ /// Regenerates and broadcasts the spending transaction for any outputs that are pending. Wraps
+ /// [`OutputSweeper::regenerate_and_broadcast_spend_if_necessary`].
+ pub fn regenerate_and_broadcast_spend_if_necessary(&self) -> Result<(), ()> {
+ let mut fut = Box::pin(self.sweeper.regenerate_and_broadcast_spend_if_necessary());
+ let mut waker = dummy_waker();
+ let mut ctx = task::Context::from_waker(&mut waker);
+ match fut.as_mut().poll(&mut ctx) {
+ task::Poll::Ready(result) => result,
+ task::Poll::Pending => {
+ // In a sync context, we can't wait for the future to complete.
+ unreachable!("OutputSweeper::regenerate_and_broadcast_spend_if_necessary should not be pending in a sync context");
+ },
+ }
+ }
+
/// Fetch the inner async sweeper.
///
/// In general you shouldn't have much reason to use this - you have a sync [`KVStore`] backing
@@ -1034,6 +1040,28 @@ where
}
}
+impl<B: Deref, D: Deref, E: Deref, F: Deref, K: Deref, L: Deref, O: Deref> Listen
+ for OutputSweeperSync<B, D, E, F, K, L, O>
+where
+ B::Target: BroadcasterInterface,
+ D::Target: ChangeDestinationSourceSync,
+ E::Target: FeeEstimator,
+ F::Target: Filter + Sync + Send,
+ K::Target: KVStoreSync,
+ L::Target: Logger,
+ O::Target: OutputSpender,
+{
+ fn filtered_block_connected(
+ &self, header: &Header, txdata: &chain::transaction::TransactionData, height: u32,
+ ) {
+ self.sweeper.filtered_block_connected(header, txdata, height);
+ }
+
+ fn blocks_disconnected(&self, fork_point: BestBlock) {
+ self.sweeper.blocks_disconnected(fork_point);
+ }
+}
+
impl<B: Deref, D: Deref, E: Deref, F: Deref, K: Deref, L: Deref, O: Deref> Confirm
for OutputSweeperSync<B, D, E, F, K, L, O>
where
@@ -1063,3 +1091,29 @@ where
self.sweeper.get_relevant_txids()
}
}
+
+impl<B: Deref, D: Deref, E: Deref, F: Deref, K: Deref, L: Deref, O: Deref>
+ ReadableArgs<(B, E, Option<F>, O, D, K, L)> for (BestBlock, OutputSweeperSync<B, D, E, F, K, L, O>)
+where
+ B::Target: BroadcasterInterface,
+ D::Target: ChangeDestinationSourceSync,
+ E::Target: FeeEstimator,
+ F::Target: Filter + Sync + Send,
+ K::Target: KVStoreSync,
+ L::Target: Logger,
+ O::Target: OutputSpender,
+{
+ #[inline]
+ fn read<R: io::Read>(
+ reader: &mut R, args: (B, E, Option<F>, O, D, K, L),
+ ) -> Result<Self, DecodeError> {
+ let (a, b, c, d, change_destination_source, kv_store, e) = args;
+ let change_destination_source =
+ ChangeDestinationSourceSyncWrapper::new(change_destination_source);
+ let kv_store = KVStoreSyncWrapper(kv_store);
+ let args = (a, b, c, d, change_destination_source, kv_store, e);
+ let (best_block, sweeper) =
+ <(BestBlock, OutputSweeper<_, _, _, _, _, _, _>)>::read(reader, args)?;
+ Ok((best_block, OutputSweeperSync { sweeper }))
+ }
+}
Why this scored 23/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.