Skip to content

Sweeper async change destination source fetching #3734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 14, 2025

Conversation

joostjager
Copy link
Contributor

@joostjager joostjager commented Apr 14, 2025

This PR converts OutputSweeper to take an async ChangeDestinationSource implementation. This allows a (remote) address fetch call to run without blocking chain notifications.

Furthermore the changes demonstrates how LDK could be written in a natively async way, and still allow usage from a sync context using wrappers.

Part of #3540

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Apr 14, 2025

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

}

Ok(())
self.persist_state(&*state_lock).map_err(|e| {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more sweeping in this method, all moved to a timer.

if let Some(spending_tx) = spending_tx_opt {
self.broadcaster.broadcast_transactions(&[&spending_tx]);
}
let _ = self.persist_state(&*state_lock).map_err(|e| {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more sweeping in this event handler.

if respend_descriptors.is_empty() {
// Nothing to do.
return None;
let change_destination_script = change_destination_script_result?;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo: address risk of getting a tx with a new address every block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo: investigate what BDK does here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traced through BDK a bit. It seems that there is only inflation if we actually use the address in a tx. It won't blindly regenerate addresses when called. But will double check this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that there is only inflation if we actually use the address in a tx. It won't blindly regenerate addresses when called. But will double check this.

This mostly depends on the API used.

@joostjager joostjager force-pushed the async-sweep branch 2 times, most recently from c3420ab to d6051d9 Compare April 16, 2025 09:28
@joostjager joostjager force-pushed the async-sweep branch 2 times, most recently from 90d7104 to 370d677 Compare April 16, 2025 16:32
sweeper: OutputSweeper<B, D, E, F, K, L, O>,
}

impl<B: Deref, D: Deref, E: Deref, F: Deref, K: Deref, L: Deref, O: Deref>
Copy link
Contributor Author

@joostjager joostjager Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What might be good about this wrapper is that it eliminates the possibility of someone implementing async logic in combination with future poll/ready checking. This wrapper only accepts a sync trait.

@joostjager joostjager force-pushed the async-sweep branch 4 times, most recently from a9812ea to df47e8d Compare April 18, 2025 10:23
Copy link

codecov bot commented Apr 21, 2025

Codecov Report

Attention: Patch coverage is 81.65939% with 42 lines in your changes missing coverage. Please review.

Project coverage is 90.61%. Comparing base (f507778) to head (1a98158).
Report is 68 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/util/sweep.rs 77.16% 25 Missing and 12 partials ⚠️
lightning/src/util/async_poll.rs 57.14% 3 Missing ⚠️
lightning-background-processor/src/lib.rs 96.22% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3734      +/-   ##
==========================================
+ Coverage   89.13%   90.61%   +1.47%     
==========================================
  Files         157      157              
  Lines      123851   134826   +10975     
  Branches   123851   134826   +10975     
==========================================
+ Hits       110395   122170   +11775     
+ Misses      10779    10016     -763     
+ Partials     2677     2640      -37     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@joostjager joostjager force-pushed the async-sweep branch 6 times, most recently from f3e911e to 6cd735c Compare April 21, 2025 11:35
@joostjager joostjager requested a review from tnull May 1, 2025 12:32
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excuse the delay here!

Mostly looks good from my side I think, just a few minor comments/nits. Will to another final thorough pass after.


use crate::sync::Arc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Let's move this to the other crate imports

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cleaner, but also I have to say I never look at these imports. But may be different for others. Moved.

@@ -567,16 +623,15 @@ where
}

fn spend_outputs(
&self, sweeper_state: &SweeperState, descriptors: Vec<&SpendableOutputDescriptor>,
&self, sweeper_state: &SweeperState, descriptors: &Vec<&SpendableOutputDescriptor>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Mhh, &Vec<&..> looks funny. I guess the signature from spend_spendable_outputs would be preferable: &[&SpendableOutputDescriptor].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what is funny about it? Or how is &[&..] less funny? Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we try to create the least-strict API if there is no reason to. In this case, the prior API would enforce a reference to an allocated Vec, while, if we just require the slice in the API, we might (easier) be able to drop the allocation if it's not necessary.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, mod one question and a few final nits.

},
Err(e) => {
log_error!(self.logger, "Error spending outputs: {:?}", e);
return Ok(());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we ignoring the error here instead of propagating it up? (If we'd want to ignore it for some reason, can we just have spend_outputs not return a Result and move this match / logging into it?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was pre-existing to not do anything with that error other than log:

I am not sure if the caller can do something more with the error. I do see that there non-happy flow error cases in spend_spendable_outputs, so might be better to at least give the users the choice. Added map_err.

@joostjager joostjager requested a review from tnull May 13, 2025 11:00
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, feel free to squash.

One outstanding nit, non-blocking though. If you want, feel free to address while squashing also.

tnull
tnull previously approved these changes May 13, 2025
@joostjager joostjager requested a review from TheBlueMatt May 13, 2025 11:43
TheBlueMatt
TheBlueMatt previously approved these changes May 13, 2025
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new comments but they can all happen in followups too.

@@ -677,14 +703,18 @@ use futures_util::{dummy_waker, OptionalSelector, Selector, SelectorOutput};
/// # persister: Arc<Store>,
/// # logger: Arc<Logger>,
/// # scorer: Arc<Scorer>,
/// # sweeper: Arc<OutputSweeper<Arc<B>, Arc<D>, Arc<FE>, Arc<F>, Arc<K>, Arc<Logger>, Arc<O>>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and above at least on the D line, you have inconsistent spaces/tabs with the existing code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunate that github doesn't show it. Convert all tabs in this block to spaces, so that should have caught them all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ironically I can see it in github's diff snippet that they included in the emails about these comments!

},
};

if state_lock.outputs.iter().find(|o| o.descriptor == output_info.descriptor).is_some()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For move-only/indentation-change changes, please do the move or indentation change and then rustfmt in a separate commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't great indeed. Also seems an unfortunate diff by github, because in vscode it detects a lot more whitespace only line changes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-b does somewhat better, which I imagine is what vscode is doing. But git show --color-moved would be much happier if the formatting were a separate commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suppressing rustfmt in a commit for diff optimization. Wasn't prepared for that 😅

let cur_height = sweeper_state.best_block.height;
let cur_hash = sweeper_state.best_block.block_hash;
let filter_fn = |o: &TrackedSpendableOutput| {
/// Regenerates and broadcasts the spending transaction for any outputs that are pending. This method will be a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to explain the return value (specifically that it will return an Err only due to failures calling user code - failures persisting or failures signing a spending transaction).

{
let mut sweeper_state = self.sweeper_state.lock().unwrap();

let change_destination_script = change_destination_script_result?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I buy that we need to do ? in the state lock :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left over from when I didn't have the atomic bool, good catch.

To prepare for asynchronous processing of the sweep, we need to decouple
the spending from the chain notifications. These notifications run in a
sync context and wouldn't allow calls into an async trait.

Instead we now periodically call into the sweeper, to open up the
possibility to do so from an async context if desired.
synchronous wrappers for usage in a sync context.
@joostjager joostjager dismissed stale reviews from TheBlueMatt and tnull via 1a98158 May 13, 2025 19:48
@joostjager joostjager requested review from TheBlueMatt and tnull May 13, 2025 19:49
@joostjager
Copy link
Contributor Author

@TheBlueMatt addressed comments. Didn't do fixup commits, the github compare button shows the changes.

@tnull tnull merged commit 361ff01 into lightningdevkit:main May 14, 2025
27 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
weekly goal Someone wants to land this this week
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants