Skip to content

Commit 4281894

Browse files
committed
sweep: retry sweeping inputs upon TxUnknownSpend
We now start handling `TxUnknownSpend` in our sweeper to make sure the failed inputs are retried when possible.
1 parent 2f1205a commit 4281894

File tree

2 files changed

+387
-0
lines changed

2 files changed

+387
-0
lines changed

sweep/sweeper.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,12 @@ func (s *UtxoSweeper) handleBumpEvent(r *bumpResp) error {
18471847
case TxReplaced:
18481848
return s.handleBumpEventTxReplaced(r)
18491849

1850+
// There are inputs being spent in a tx which the fee bumper doesn't
1851+
// understand. We will remove the tx from the sweeper db and mark the
1852+
// inputs as swept.
1853+
case TxUnknownSpend:
1854+
s.handleBumpEventTxUnknownSpend(r)
1855+
18501856
// There's a fatal error in creating the tx, we will remove the tx from
18511857
// the sweeper db and mark the inputs as failed.
18521858
case TxFatal:
@@ -1878,3 +1884,141 @@ func (s *UtxoSweeper) IsSweeperOutpoint(op wire.OutPoint) bool {
18781884

18791885
return found
18801886
}
1887+
1888+
// markInputSwept marks the given input as swept by the tx. It will also notify
1889+
// all the subscribers of this input.
1890+
func (s *UtxoSweeper) markInputSwept(inp *SweeperInput, tx *wire.MsgTx) {
1891+
log.Debugf("Marking input as swept: %v from state=%v", inp.OutPoint(),
1892+
inp.state)
1893+
1894+
inp.state = Swept
1895+
1896+
// Signal result channels.
1897+
s.signalResult(inp, Result{
1898+
Tx: tx,
1899+
})
1900+
1901+
// Remove all other inputs in this exclusive group.
1902+
if inp.params.ExclusiveGroup != nil {
1903+
s.removeExclusiveGroup(*inp.params.ExclusiveGroup)
1904+
}
1905+
}
1906+
1907+
// handleUnknownSpendTx takes an input and its spending tx. If the spending tx
1908+
// cannot be found in the sweeper store, the input will be marked as fatal,
1909+
// otherwise it will be marked as swept.
1910+
func (s *UtxoSweeper) handleUnknownSpendTx(inp *SweeperInput, tx *wire.MsgTx) {
1911+
op := inp.OutPoint()
1912+
txid := tx.TxHash()
1913+
1914+
isOurTx, err := s.cfg.Store.IsOurTx(txid)
1915+
if err != nil {
1916+
log.Errorf("Cannot determine if tx %v is ours: %v", txid, err)
1917+
return
1918+
}
1919+
1920+
// If this is our tx, it means it's a previous sweeping tx that got
1921+
// confirmed, which could happen when a restart happens during the
1922+
// sweeping process.
1923+
if isOurTx {
1924+
log.Debugf("Found our sweeping tx %v, marking input %v as "+
1925+
"swept", txid, op)
1926+
1927+
// We now use the spending tx to update the state of the inputs.
1928+
s.markInputSwept(inp, tx)
1929+
1930+
return
1931+
}
1932+
1933+
// Since the input is spent by others, we now mark it as fatal and won't
1934+
// be retried.
1935+
s.markInputFatal(inp, ErrRemoteSpend)
1936+
1937+
log.Debugf("Removing descendant txns invalidated by (txid=%v): %v",
1938+
txid, lnutils.SpewLogClosure(tx))
1939+
1940+
// Construct a map of the inputs this transaction spends.
1941+
spentInputs := make(map[wire.OutPoint]struct{}, len(tx.TxIn))
1942+
for _, txIn := range tx.TxIn {
1943+
spentInputs[txIn.PreviousOutPoint] = struct{}{}
1944+
}
1945+
1946+
err = s.removeConflictSweepDescendants(spentInputs)
1947+
if err != nil {
1948+
log.Warnf("unable to remove descendant transactions "+
1949+
"due to tx %v: ", txid)
1950+
}
1951+
}
1952+
1953+
// handleBumpEventTxUnknownSpend handles the case where the confirmed tx is
1954+
// unknown to the fee bumper. In the case when the sweeping tx has been replaced
1955+
// by another party with their tx being confirmed. It will retry sweeping the
1956+
// "good" inputs once the "bad" ones are kicked out.
1957+
func (s *UtxoSweeper) handleBumpEventTxUnknownSpend(r *bumpResp) {
1958+
// Mark the inputs as publish failed, which means they will be retried
1959+
// later.
1960+
s.markInputsPublishFailed(r.set)
1961+
1962+
// Get all the inputs that are not spent in the current sweeping tx.
1963+
spentInputs := r.result.SpentInputs
1964+
1965+
// Create a slice to track inputs to be retried.
1966+
inputsToRetry := make([]input.Input, 0, len(r.set.Inputs()))
1967+
1968+
// Iterate all the inputs found in this bump and mark the ones spent by
1969+
// the third party as failed. The rest of inputs will then be updated
1970+
// with a new fee rate and be retried immediately.
1971+
for _, inp := range r.set.Inputs() {
1972+
op := inp.OutPoint()
1973+
input, ok := s.inputs[op]
1974+
1975+
// Wallet inputs are not tracked so we will not find them from
1976+
// the inputs map.
1977+
if !ok {
1978+
log.Debugf("Skipped marking input: %v not found in "+
1979+
"pending inputs", op)
1980+
1981+
continue
1982+
}
1983+
1984+
// Check whether this input has been spent, if so we mark it as
1985+
// fatal or swept based on whether this is one of our previous
1986+
// sweeping txns, then move to the next.
1987+
tx, spent := spentInputs[op]
1988+
if spent {
1989+
s.handleUnknownSpendTx(input, tx)
1990+
1991+
continue
1992+
}
1993+
1994+
log.Debugf("Input(%v): updating params: starting fee rate "+
1995+
"[%v -> %v], immediate [%v -> true]", op,
1996+
input.params.StartingFeeRate, r.result.FeeRate,
1997+
input.params.Immediate)
1998+
1999+
// Update the input using the fee rate specified from the
2000+
// BumpResult, which should be the starting fee rate to use for
2001+
// the next sweeping attempt.
2002+
input.params.StartingFeeRate = fn.Some(r.result.FeeRate)
2003+
input.params.Immediate = true
2004+
inputsToRetry = append(inputsToRetry, input)
2005+
}
2006+
2007+
// Exit early if there are no inputs to be retried.
2008+
if len(inputsToRetry) == 0 {
2009+
return
2010+
}
2011+
2012+
log.Debugf("Retry sweeping inputs with updated params: %v",
2013+
inputTypeSummary(inputsToRetry))
2014+
2015+
// Get the latest inputs, which should put the PublishFailed inputs back
2016+
// to the sweeping queue.
2017+
inputs := s.updateSweeperInputs()
2018+
2019+
// Immediately sweep the remaining inputs - the previous inputs should
2020+
// now be swept with the updated StartingFeeRate immediately. We may
2021+
// also include more inputs in the new sweeping tx if new ones with the
2022+
// same deadline are offered.
2023+
s.sweepPendingInputs(inputs)
2024+
}

0 commit comments

Comments
 (0)