Skip to content
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

[3/5]sweep: make pending inputs stateful #8423

Merged

Conversation

yyforyongyu
Copy link
Collaborator

@yyforyongyu yyforyongyu commented Jan 25, 2024

Depends on

This PR prepares for #8424 by introducing states on the pending inputs so they are easier to be managed, in specific, SweepState is introduced and each pendingInput now has a state. Previously the field publishAttempts sort of tracks the pending input's state implicitly. We now replace it with the explicit state, and update it at different stages,

  • when it's first received, it's StateInit, which can be included in a new sweeping tx.
  • when it's included in a tx, it's StatePendingPublish, which cannot be included in another sweeping tx.
  • when it's published, it's StatePublished, which needs to be RBF-aware.
  • when it's successfully swept, it's StateSwept, which is the terminal state.
  • when it's excluded, it's StateExcluded, which is the terminal state.
  • otherwise, it's StateFailed, which is the terminal state, and we should analyze the failure.

With this change, it also means we won't give up sweeping a given input after 10 attempts anymore.

Summary by CodeRabbit

  • New Features

    • Introduced stateful pending inputs in the sweeper for improved lifecycle management.
    • Added functionality for aggregating inputs into clusters based on locktime and fee rates for sweeping transactions.
    • New mock implementations for testing purposes, enhancing the development and testing workflow.
  • Enhancements

    • Updated fee estimation logic across various components to require explicit specification of SatPerVbyte or TargetConf.
    • Revised the handling of anchor resolution and exclusivity, improving error messaging and outcomes.
    • Enhanced fee preference handling with the introduction of FeeEstimateInfo type, replacing FeePreference in multiple areas.
    • Improved transaction record management in the database with new serialization, deserialization, and migration functions.
  • Bug Fixes

    • Fixed conflicting nLockTime values in sweep transactions with the introduction of ErrLocktimeConflict.
    • Addressed issues related to fee rate estimation and preference conflicts, enhancing error handling and user feedback.
  • Tests

    • Extended test coverage with new test cases for transaction record management, fee estimation, and input aggregation.

Copy link

coderabbitai bot commented Jan 25, 2024

Important

Auto Review Skipped

Auto reviews are disabled on base/target branches other than the default branch. Please add the base/target branch pattern to the list of additional branches to be reviewed in the settings.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository.

To trigger a single review, invoke the @coderabbitai review command.

Walkthrough

The recent updates focus on refining fee estimation and transaction handling mechanisms across various components. Notably, the transition from FeePreference to FeeEstimateInfo across multiple files indicates a more nuanced approach to fee management. Additionally, the introduction of TargetConf fields, improvements in transaction record management, and enhancements in sweep transaction logic underscore efforts to optimize transaction execution and lifecycle management. The inclusion of mock implementations and test adjustments further supports robustness and reliability in testing environments.

Changes

Files Change Summary
chainntnfs/mocks.go, htlcswitch/mock.go, lnwallet/chainfee/mocks.go, sweep/mocks.go, sweep/store_mock.go Introduced or updated mock implementations for various interfaces and testing purposes.
cmd/lncli/walletrpc_types.go Marked NextBroadcastHeight as deprecated in PendingSweep struct.
contractcourt/*.go, lnrpc/rpc_utils.go, lnrpc/walletrpc/walletkit_server.go, sweep/*.go (excluding mocks and tests) Replaced FeePreference with FeeEstimateInfo and made related adjustments in fee handling and transaction logic.
docs/release-notes/release-notes-0.18.0.md, itest/* Documented changes and updated integration tests to include TargetConf field and handle new fee estimation logic.
lntest/harness.go, server.go, sweep/aggregator.go, sweep/store.go, sweep/txgenerator.go, sweep/walletsweep.go Various enhancements including input aggregation, transaction record management, and error handling improvements.

🐇✨
In the land of code, where transactions flow,
A rabbit hopped, with changes in tow.
"FeeEstimateInfo," it cheerfully said,
Making transactions smoother, ahead.
With every hop, and every line,
It sought to make the blockchain shine.
🌟🐾

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

@yyforyongyu yyforyongyu force-pushed the sweeper-rbf-2 branch 6 times, most recently from 6058edc to dc65cd1 Compare January 27, 2024 02:14
@saubyk saubyk added this to the v0.18.0 milestone Jan 28, 2024
@yyforyongyu yyforyongyu self-assigned this Jan 30, 2024
@yyforyongyu yyforyongyu added utxo sweeping llm-review add to a PR to have an LLM bot review it labels Jan 30, 2024
@ProofOfKeags ProofOfKeags self-requested a review February 8, 2024 22:24
Copy link
Collaborator

@ProofOfKeags ProofOfKeags left a comment

Choose a reason for hiding this comment

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

I'm once again really impressed by the quality of these PRs. I have a couple comments to work through on this one but it's pretty much ready to ship.

sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Outdated

// Do a non-blocking read on the spent event channel.
select {
case details := <-mempoolSpent.Spend:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this guaranteed to be immediately available?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Only if this outpoint has already been spent in mempool, so it will block when a) the tx is not in mempool, or b) the tx has already been confirmed, which are both fine as we are doing a non-blocking read here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you point to the code where this happens? I don't think that the result is immediately available

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, so here are the lines,

  1. SubscribeMempoolSpent calls NotifySpend here:
    return event, b.chainConn.NotifySpent(ops)
  2. NotifySpend then calls LookupInputSpend here
  3. LookupInputSpend will return the tx if it's already in mempool, as validated by this test.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That doesn't look immediate because the call to onRelevantTx puts the tx into a concurrent queue

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Turns out you are right. This leads to a bigger change as there's no way to do mempool look ups for btcd, and for bitcoind it's easier as we already have the method in place. So we'll need to

  1. implement gettxspendingprevout in btcd, Add gettxspendingprevout for btcd and fix version check btcsuite/btcd#2125
  2. add a mempool lookup method in btcwallet, chain: add mempool lookup for outpoints btcsuite/btcwallet#910
  3. make use of it here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Now looks super clean 👍

sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Outdated Show resolved Hide resolved
sweep/sweeper.go Outdated Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
if pendingInput.publishAttempts > 0 {
pendingInput.minPublishHeight = s.currentHeight
}
// TODO(yy): a dedicated state?
Copy link
Collaborator

Choose a reason for hiding this comment

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

No I think resetting to an initial state is ideal if we can get away with it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm I am not really sure, imagine the user tries to decrease the fee, we should prob. report that we already published a higher fee rate tx and this operation makes no sense ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

imagine the user tries to decrease the fee

I don't think this is possible?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't they lower it through BumpFee?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm I am not really sure, imagine the user tries to decrease the fee, we should prob. report that we already published a higher fee rate tx and this operation makes no sense ?

Maybe? More states, more problems: you have to ensure all possible operations that yield state transitions can be interleaved in different ways and have predictable behavior.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is possible?

Yeah, as since we use testmempoolaccept now, we'd get an error that the fee increase wasn't high enough.

Copy link
Member

Choose a reason for hiding this comment

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

I think it is more correct to to use a new state though as: the input may have already been published with active RBF state by the time a user attempt to do a manual fee bump.

sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
@yyforyongyu yyforyongyu changed the base branch from master to elle-sweeper-rbf-1 February 20, 2024 20:46
@yyforyongyu yyforyongyu removed the request for review from ellemouton February 20, 2024 20:56
sweep/sweeper.go Outdated Show resolved Hide resolved
sweep/sweeper.go Outdated

// Do a non-blocking read on the spent event channel.
select {
case details := <-mempoolSpent.Spend:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you point to the code where this happens? I don't think that the result is immediately available

sweep/sweeper.go Outdated Show resolved Hide resolved
sweep/sweeper.go Outdated Show resolved Hide resolved
// This happens when the same anchor output is swept by both
// remote and local force close transactions. When one
// confirms, the other is excluded.
case sweep.ErrExclusiveGroupSpend:
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the anchor resolver exists, then the commitment transaction is confirmed so I don't think this case can be hit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call, think this should be handled at the sweepAnchors side in channel arbitrator, tho not sure if it's needed as we are not catching the result there anyway.

Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

I like the new statefulness of the sweeper inputs, in addition I think its very good that we removed themaxattempt number for failed published inputs, which had some side effects especially removing inputs hence also the notifications for inputs which did not confirm quickly.

Had questions mostly.

As commented in the prior PRs it makes sense to merge/approve those PRs as a package.

sweep/sweeper.go Outdated Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper_test.go Outdated Show resolved Hide resolved
sweep/sweeper_test.go Outdated Show resolved Hide resolved
sweep/sweeper_test.go Show resolved Hide resolved
if pendingInput.publishAttempts > 0 {
pendingInput.minPublishHeight = s.currentHeight
}
// TODO(yy): a dedicated state?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm I am not really sure, imagine the user tries to decrease the fee, we should prob. report that we already published a higher fee rate tx and this operation makes no sense ?

sweep/sweeper.go Show resolved Hide resolved
@yyforyongyu yyforyongyu force-pushed the elle-sweeper-rbf-1 branch 2 times, most recently from 598119e to 21b9cbd Compare February 27, 2024 15:48
Copy link
Collaborator

@morehouse morehouse left a comment

Choose a reason for hiding this comment

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

I like that this PR leverages the mempool to check for previous spends of each input. Maybe we should leverage the mempool even more to detect and outbid spends from other parties.

Comment on lines +1649 to +1653
// If this input has been included in a sweep tx that's not
// published yet, we'd skip this input and wait for the sweep
// tx to be published.
if input.state == StatePendingPublish {
continue
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should log an error in this case.

StatePendingPublish is a temporary state that should transition to StatePublished or StatePublishFailed before sweep returns. If we see it here, something is broken.

And if we do somehow get to this state, the continue makes us forever ignore that input... Would it be better to reattempt sweeping the input?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nice catch - this will be clearer in PR 5 tho, where we peel off the publishing logic from the sweeper and create a tx publisher - it will make more sense to use this state there. But I do find a potential place we may end up stuck in this state in the final PR, need to think about it more..

Copy link
Member

Choose a reason for hiding this comment

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

How can it get stuck in this state? IIUC, we have a single event loop in the sweeper as is. After something is marked as StatePendingPublish, then it can only go to published or failed as mentioned above.

The only case I can see plainly is if the call to StoreTx fails (something something postgres txn issue), so then the transaction is published, but all inputs in that set are still marked as StatePendingPublish. In that case, we should mark the publish attempt as failed.

txid := tx.TxHash()
tr, err := s.cfg.Store.GetTx(txid)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We look up the transaction in the mempool to get tx, and then we look up tx.TxHash() in the store to get the RBF info. Perhaps we should determine the RBF info directly from the tx instead.

This would be more accurate in situations where another party is also allowed to spend the input (e.g., HTLC spends).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hmmm not sure if I understand it correctly, are you saying the stored tx can be different from the tx fetched from the mempool?

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should determine the RBF info directly from the tx instead.

Deriving the fee information requires access to all the previous inputs to have access to the total input sum. So we'd need to iteratively fetch the transactions of all the referenced inputs, but some of those might already be confirmed, so we can't get it solely from the mempool.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Exactly imagine the case where and HTLC is swept by the preimage after we registered it with the sweeper. But this preimage monitoring is already handled in the resolver. Not sure if we wanna open this discussion here because we immediately fails the incoming back as soon as we see the preimage in mempool

// state Init. When spent, it will query the sweeper store to fetch the fee
// info of the spending transction, and construct an RBFInfo based on it.
// Suppose an error occurs, fn.None is returned.
func (s *UtxoSweeper) decideStateAndRBFInfo(op wire.OutPoint) (
Copy link
Collaborator

Choose a reason for hiding this comment

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

So the basic idea for this function is that it allows us to recover RBF info after a restart, right?

I wonder if we should also use this function to detect a competing spend from another party. For example, suppose we have an HTLC-Timeout / preimage spend bidding war going on. It would be good to detect the other party's fee/rate so we can replace their transaction in the mempool.

Or does the "fee bumper" appropriately handle this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So the basic idea for this function is that it allows us to recover RBF info after a restart, right?

Exactly. And the rest should be handled by the fee bumper - however, the whole recovering-from-restart is not implemented in these PR series in order to limit the scope. With the existing design, I think it should be straightforward - we just need to grab the inputs here, make them into a set, and re-offer it to the fee bumper.

As for the race case, it's more complicated. My general approach is to avoid it as much as possible, by extracting the preimage from the mempool, #7564 helps, tho it'd be nicer if we could also have #7615. As for the role of fee bumper, it just blindly does RBF when a new block comes in, and will use up all the budget configured by the user by the time the race happens.

Copy link
Member

Choose a reason for hiding this comment

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

For example, suppose we have an HTLC-Timeout / preimage spend bidding war going on. It would be good to detect the other party's fee/rate so we can replace their transaction in the mempool.

So for that, we created this PR (#7564) which allows resolvers to directly get notified if a spend of a transaction happens in the mempool.

@morehouse
Copy link
Collaborator

A rebase would also be nice -- it seems there's some already merged changes in the diff.

@Roasbeef
Copy link
Member

Maybe we should leverage the mempool even more to detect and outbid spends from other parties.

I don't think we should extend the scope of this PR further. We should also be careful to not let considerations of more adversarial edge cases (malicious pinning and other funny business) overshadow the more fundamental considerations of making sure everything can be confirmed in a timely manner in routine conditions (eg: we have no general solution for "pinning"). Lest we spend a lot of time and energy attempting to target more adversarial scenarios that may not be precisely defined, invariably adding more complexity to protect against edge cases that are rare with unclear payoffs for would be attackers.

I think if we want to add more elaborate strategies for fee bumping, time sensitive bidding battles, etc -- we should also consider design paths that allow more of this logic to live encapsulated within the resolver. The resolver's duty is to make sure that the contract is fully resolved on chain. Part of that duty can be extended to include broader mempool awareness and defensive strategies in the face of anomalous confirmation delays (pinning, etc, etc).

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🏄🏾

Err: ErrExclusiveGroupSpend,
})

// Update the input's state as it can no longer be swept.
input.state = StateExcluded
Copy link
Member

Choose a reason for hiding this comment

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

Will this actually modify the value in the map, or do we also need to do a re-assignment?

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah input is a pointer so we are fine here.

//
// TODO(yy): sweeper is made to communicate via go channels, so no
// locks are needed to access the map. However, it'd be safer if we
// turn this pendingInputs into a SyncMap in case we wanna add
Copy link
Member

Choose a reason for hiding this comment

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

We could alternatively just have pendingInputs be a variable that lives in the scope of the main event loop.

Non-blocking suggestion.

Comment on lines +1649 to +1653
// If this input has been included in a sweep tx that's not
// published yet, we'd skip this input and wait for the sweep
// tx to be published.
if input.state == StatePendingPublish {
continue
}
Copy link
Member

Choose a reason for hiding this comment

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

How can it get stuck in this state? IIUC, we have a single event loop in the sweeper as is. After something is marked as StatePendingPublish, then it can only go to published or failed as mentioned above.

The only case I can see plainly is if the call to StoreTx fails (something something postgres txn issue), so then the transaction is published, but all inputs in that set are still marked as StatePendingPublish. In that case, we should mark the publish attempt as failed.

@@ -157,6 +158,19 @@ func (s SweepState) String() string {
}
}

// RBFInfo stores the information required to perform a RBF bump on a pending
// sweeping tx.
type RBFInfo struct {
Copy link
Member

Choose a reason for hiding this comment

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

To be fully aware of all the RBF rules, don't we also need to track information related to the set of inputs in the publishing transaction? As an example: if the inputs are unconfirmed, if a new unconfirmed input was added in a recent iteration, etc.

Though part of me wonders if we can just get away more with the existing spray-and-pray approach...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah so the idea is, since we have txid, we can fetch the tx and get its inputs. meanwhile we have a set of inputs tracked in the sweeper, so can then use tx.TxIn to decide the starting state of the inputs in the sweeper.

Though part of me wonders if we can just get away more with the existing spray-and-pray approach...

Def, we just relentlessly call testmempoolaccept, not feasible for neutrino tho.

txid := tx.TxHash()
tr, err := s.cfg.Store.GetTx(txid)
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should determine the RBF info directly from the tx instead.

Deriving the fee information requires access to all the previous inputs to have access to the total input sum. So we'd need to iteratively fetch the transactions of all the referenced inputs, but some of those might already be confirmed, so we can't get it solely from the mempool.

@@ -1041,36 +1023,6 @@ func (s *UtxoSweeper) markInputsPendingPublish(tr *TxRecord,

// Record another publish attempt.
pi.publishAttempts++

Copy link
Member

Choose a reason for hiding this comment

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

Can we also get rid of publishAttempts here? Elsewhere, we used this to detect if an input was published or not, but now we have SweepState. We could even morph things slightly and store the amount of attempts directly into the state.

if pendingInput.publishAttempts > 0 {
pendingInput.minPublishHeight = s.currentHeight
}
// TODO(yy): a dedicated state?
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is possible?

Yeah, as since we use testmempoolaccept now, we'd get an error that the fee increase wasn't high enough.

if pendingInput.publishAttempts > 0 {
pendingInput.minPublishHeight = s.currentHeight
}
// TODO(yy): a dedicated state?
Copy link
Member

Choose a reason for hiding this comment

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

I think it is more correct to to use a new state though as: the input may have already been published with active RBF state by the time a user attempt to do a manual fee bump.

@@ -1421,7 +1421,7 @@ func (s *UtxoSweeper) handleNewInput(input *sweepInputMessage) {
)
if err != nil {
err := fmt.Errorf("wait for spend: %w", err)
s.signalResult(pi, Result{Err: err})
s.markInputFailed(pi, err)
Copy link
Member

Choose a reason for hiding this comment

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

As is, this can only error out if we fail to obtain the spend notification. At this point, shouldn't we just tear down the entire sweeper to crash and then force a full daemon restart? If we can't get spend notifications, then we don't know when anything is confirmed, which would impact the sweeper's ability to know when it should stop trying to increase the fee.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a very good point! Will address this in PR 6 to avoid rebase conflicts.


// Extract the spending tx from the option.
var tx *wire.MsgTx
txOption.WhenSome(func(t wire.MsgTx) {
Copy link
Member

Choose a reason for hiding this comment

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

Can alternatively write this as:

tx := txOption.UnwrapOr(nil) 

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

switched!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hmmm seems we need to update fn mod, gonna defer this change in later PRs.

Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

LGTM 👌, had only nits in the last pass through.

sweep/sweeper.go Outdated

// Do a non-blocking read on the spent event channel.
select {
case details := <-mempoolSpent.Spend:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now looks super clean 👍

//
// TODO(yy): this means the behavior of not having a mempool is the
// same as an erroneous mempool. The question is should we
// differentiate the two from their returned values?
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think as long as having no mempool returns an error, it seems to be fine and I don't think we need this TODO

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

cool removed the TODO.

sweep/sweeper.go Outdated

// StateExcluded is the state of a pending input that has been excluded
// and can no longer be swept. For instance, when one of the three
// anchor sweeping transactions confirmed, the rest two will be
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: :s/rest two/remaining two/g

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done!

{Index: 3}: input3,
}

// We expect only the inputs with `StateInit` and `StatePendingPublish`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: :s/StatePendingPublish/StatePublishFailed/g

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed!

sweep/sweeper.go Show resolved Hide resolved
sweep/sweeper.go Outdated
pi.state = StatePublished
}

s.pendingInputs[outpoint] = pi
log.Tracef("input %v added to pendingInputs", outpoint)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Trace comment start with Captial Letters.

sweep/sweeper.go Outdated

// Exit early if it's not found.
//
// NOTE: this is not accurate for backends don't support mempool
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: :%s/backends don't support/backends that don't support/g

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done!

sweep/sweeper.go Outdated
// NOTE: this is not accurate for backends don't support mempool
// lookup:
// - for neutrino we don't have a mempool.
// - for bitcoind below v24.0.0 we don't have `gettxspendingprevout`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this comment is not quite right, although we don't have the gettxspendingprevout we have a mempool cache for prior versions, so I think thats accurate as well for bitcoind < 24, not sure when we introduced the mempool cache though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch - i think for bitcoind we can assume it's always supported as the cache has been there for at least a year now, removed the comment.

txid := tx.TxHash()
tr, err := s.cfg.Store.GetTx(txid)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Exactly imagine the case where and HTLC is swept by the preimage after we registered it with the sweeper. But this preimage monitoring is already handled in the resolver. Not sure if we wanna open this discussion here because we immediately fails the incoming back as soon as we see the preimage in mempool

sweep/sweeper.go Outdated Show resolved Hide resolved
This commit starts tracking the state of a pending input so it's easier
to control the sweeping flow and provide RBF awareness in the future.
This commit changes how a new input sweep request is handled - now we
will query the mempool and see if it's already been spent. If so, we'll
update its state as we may need to RBF this input.
This commit uniforms and put the deletion of pending inputs in a single
point.
This commit attaches RBFInfo to an input before it's been published or
it's already been published.
This commit removes the logic where we remove an input when it's been
published more than 10 times. This is needed as in our future fee
bumper, we might start with a low fee and rebroadcast the same input for
hundred of blocks.
…ublish`

Now that the refactor is done, we start patching unit tests for these
two methods. Minor changes are also made based on the feedback from the
tests.
So we can use the methods implemented on the chain RPC client.
sweeper

This commit implements a new method, `LookupInputMempoolSpend` to do
lookups in the mempool. This method is useful in the case when we only
want to know whether an input is already been spent in the mempool by
the time we call.
Thus this method `decideStateAndRBFInfo` won't touch the state changes
of a given input.
@yyforyongyu yyforyongyu merged commit 987f524 into lightningnetwork:elle-new-sweeper Mar 27, 2024
25 of 27 checks passed
@yyforyongyu yyforyongyu deleted the sweeper-rbf-2 branch March 27, 2024 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llm-review add to a PR to have an LLM bot review it utxo sweeping
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

8 participants