-
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
sweep: remove possible RBF when sweeping new inputs #8091
sweep: remove possible RBF when sweeping new inputs #8091
Conversation
3106609
to
1e14ae2
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.
Solid set of refactoring and additional tests! I need to sit a bit more with the RBF change. IIUC, this shouldn't affect our sweeping unless the new sweep actually violates a rule of RBF. One example would be using the same transaction, but adding an a new unconfirmed input.
6e27721
to
48273b2
Compare
48273b2
to
a34c370
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 only realized after reviewing the first part, that it's another PR 😅, so left nits I did not seeing when I reviewed the base PR for it.
In terms of the new PR, I really like the tests and the refactoring. What I am not so sure about is the fix of the RBF case, because I think it deteriorates the sweeper situation mainly because most node runners have those min-relay-anchors sitting around and marking almost all sweeps invalid as soon as a third party bundled a big tx with a lot of anchors (increasing the RBF fee condition). So in the old case, we would at least publish the newSet as well, having a change for the new inputs to get broadcasted. LMKWYT ?
There are two RBF happening at the moment,
This PR aims to fix case 1 so we don't have this RBF condition - we can try broadcast |
a34c370
to
f4bf4f0
Compare
|
||
// Create a new pendingInput and initialize the listeners slice with | ||
// the passed in result channel. If this input is offered for sweep | ||
// again, the result channel will be appended to this slice. |
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.
the reason you append to this slice is to notify all of the requesters?
"due to tx %v: ", spendHash) | ||
} | ||
|
||
log.Debugf("Detected third party spend related to in flight "+ |
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.
should this be raised to a more visible log level, e.g Warnf
?
// lists of inputs. | ||
inputLists, err := s.getInputLists(cluster, currentHeight) | ||
// Examine pending inputs and try to construct lists of inputs. | ||
allSets, newSets, err := s.getInputLists(cluster, currentHeight) |
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.
seems like allSets
only returns sets of previously-published sweeps. Why not reflect that on the name, e.g existingSets
?
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.
allSet
uses all the inputs to make the sweeps tho.
2bcdf68
to
7cd9fce
Compare
The method `waitForSpend` makes it sounding like it's blocking while it's not, thus it's renamed to avoid confusion.
This commit attempts to make the polling logic in sweeper more linear. Previously, the sweep's timer is reset/restarted in multiple places, such as when a new input comes in, or a new block comes in, or a previous input being spent, making it difficult to follow. We now remove the old timer and replaces it with a simple polling logic - we will schedule sweeps every 5s(default), and if there's no input to be swept, we'd skip, just like the previous `scheduleSweep` does. It's also worthy noting that, although `scheduleSweep` triggers the timer to tick, by the time we do the actual sweep in `sweepCluster`, conditions may have changed. This is now also fixed because we only have one place to create the clusters and sweeps.
This commit changes how we create the input sets which are used to construct the sweeping transactions. Assume the sweeper has two inputs, one is new and one is retried, we'd end up having two transactions, - tx1: which spends both the new and old inputs. - tx2: which spends the new inputs only. When publishing these txes, depending on which one gets into the mempool first, the other one will be viewed as an RBF for the first one since they both spending the same input(the new input). This is now fixed by only attempt to publish the second tx when there isn't a first tx - when there is a tx1, it means the new inputs are already used in this tx along with retried inputs, hence there's no need to publish tx2 which spends the new inputs only.
7cd9fce
to
5d3c72b
Compare
5d3c72b
to
dba4c8e
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.
LGTM, one thing I want to highlight, we should definitely add a follow-up PR where we trigger the sweeper when the sweep has the force flag set. Because we have the variable batchwindow-duration people can mess-up there config and end up not sweeping time sensitive inputs.
Moreover it is worth noting that this PR is just one part of the fix in regards of the RBF problem (see yy's comment).
} | ||
|
||
// If we have successfully swept all inputs, there's no need to | ||
// sweep the new inputs as it'd create an RBF 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.
Nit: ... there's no need to sweep the new inputs separately ...
return nil | ||
} | ||
|
||
// We'd end up there if there's no retried inputs. In this |
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: Comment needs an update as well now
Ultimately, the RBF rules are pretty finicky, and the only way we can ensure we're about to publish a replaceable transaction, is by using |
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 = s.cfg.Wallet.PublishTransaction( | ||
tx, labels.MakeLabel(labels.LabelTypeSweepTransaction, nil), | ||
) | ||
|
||
// In case of an unexpected error, don't try to recover. | ||
if err != nil && err != lnwallet.ErrDoubleSpend { |
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.
Why doesn't this try to explicitly catch the double spend error any longer?
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 the rationale that since we reschedule everything above, we don't need to specially handle this control flow?
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 we want to make sure, that even unexpected errors
increase their attempt counter
allowing them to be removed after the default of 10 attempts (default value). Moreover we use the error to signal, that we try to publish all the new inputs in a separate transaction when the combined one (combination of retried and new inputs) failed.
Depends on some of the test framework from,
Fixes #7972 and possible #7181
This PR removes a possible case that when sweeping new and old inputs, the new inputs may end up in a second transaction which accidentally attempts an RBF(and fails). It also refactors the sweeper and simplified the ticker logic, preparing for incoming refactors.