-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
[3/5]sweep: make pending inputs stateful #8423
Conversation
Important Auto Review SkippedAuto 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 To trigger a single review, invoke the WalkthroughThe recent updates focus on refining fee estimation and transaction handling mechanisms across various components. Notably, the transition from Changes
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? TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Additionally, you can add CodeRabbit Configration File (
|
6058edc
to
dc65cd1
Compare
dc65cd1
to
26a2ed6
Compare
There was a problem hiding this 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
Outdated
|
||
// Do a non-blocking read on the spent event channel. | ||
select { | ||
case details := <-mempoolSpent.Spend: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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,
SubscribeMempoolSpent
callsNotifySpend
here:
lnd/chainntnfs/bitcoindnotify/bitcoind.go
Line 1060 in d41b86b
return event, b.chainConn.NotifySpent(ops) NotifySpend
then callsLookupInputSpend
hereLookupInputSpend
will return the tx if it's already in mempool, as validated by this test.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
- implement
gettxspendingprevout
inbtcd
, Addgettxspendingprevout
forbtcd
and fix version check btcsuite/btcd#2125 - add a mempool lookup method in
btcwallet
, chain: add mempool lookup for outpoints btcsuite/btcwallet#910 - make use of it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now looks super clean 👍
if pendingInput.publishAttempts > 0 { | ||
pendingInput.minPublishHeight = s.currentHeight | ||
} | ||
// TODO(yy): a dedicated state? |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
26a2ed6
to
957a019
Compare
957a019
to
6277f67
Compare
sweep/sweeper.go
Outdated
|
||
// Do a non-blocking read on the spent event channel. | ||
select { | ||
case details := <-mempoolSpent.Spend: |
There was a problem hiding this comment.
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
contractcourt/anchor_resolver.go
Outdated
// 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: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
6277f67
to
31d0b58
Compare
There was a problem hiding this 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.
if pendingInput.publishAttempts > 0 { | ||
pendingInput.minPublishHeight = s.currentHeight | ||
} | ||
// TODO(yy): a dedicated state? |
There was a problem hiding this comment.
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 ?
598119e
to
21b9cbd
Compare
98d691e
to
a605174
Compare
5a14b90
to
3e70f2f
Compare
There was a problem hiding this 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.
// 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 | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) ( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
A rebase would also be nice -- it seems there's some already merged changes in the diff. |
3e70f2f
to
ae42569
Compare
a605174
to
8e20164
Compare
ae42569
to
dba41a2
Compare
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). |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks to be fine: https://go.dev/play/p/W06zPXRwDFS
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
// 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 | ||
} |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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++ | |||
|
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switched!
There was a problem hiding this comment.
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.
There was a problem hiding this 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: |
There was a problem hiding this comment.
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_test.go
Outdated
// | ||
// 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? |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
sweep/sweeper_test.go
Outdated
{Index: 3}: input3, | ||
} | ||
|
||
// We expect only the inputs with `StateInit` and `StatePendingPublish` |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed!
sweep/sweeper.go
Outdated
pi.state = StatePublished | ||
} | ||
|
||
s.pendingInputs[outpoint] = pi | ||
log.Tracef("input %v added to pendingInputs", outpoint) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
8e20164
to
893799a
Compare
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.
dba41a2
to
fa2c3cf
Compare
Depends on
testmempoolaccept
when the backend version is too low #8505This PR prepares for #8424 by introducing states on the pending inputs so they are easier to be managed, in specific,
SweepState
is introduced and eachpendingInput
now has a state. Previously the fieldpublishAttempts
sort of tracks the pending input's state implicitly. We now replace it with the explicit state, and update it at different stages,StateInit
, which can be included in a new sweeping tx.StatePendingPublish
, which cannot be included in another sweeping tx.StatePublished
, which needs to be RBF-aware.StateSwept
, which is the terminal state.StateExcluded
, which is the terminal state.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
Enhancements
SatPerVbyte
orTargetConf
.FeeEstimateInfo
type, replacingFeePreference
in multiple areas.Bug Fixes
ErrLocktimeConflict
.Tests