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

[7/5]contractcourt: make use of the new sweeper params #8514

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b23686d
lnd: start using `BudgetAggregator`
yyforyongyu Mar 17, 2024
9769f11
multi: add new config option `BudgetConfig` and `NoDeadlineConfTarget`
yyforyongyu Mar 18, 2024
e3c3481
contractcourt: init `BudgetConfig` in unit tests
yyforyongyu Mar 18, 2024
98d2b0a
contractcourt: specify deadline and budget for commit sweep
yyforyongyu Mar 2, 2024
0ba194a
contractcourt+lnwallet: specify deadline and budget for htlc success
yyforyongyu Mar 18, 2024
441ab44
contractcourt: specify deadline and budget for htlc timeout
yyforyongyu Mar 19, 2024
c85ae96
contractcourt: calculate value left when searching for commit deadline
yyforyongyu Mar 19, 2024
25729dc
contractcourt: specify deadline and budget for anchor output
yyforyongyu Mar 19, 2024
1de1ed5
contractcourt+lnd: make `IncubateOutputs` take fn.Option
yyforyongyu Mar 29, 2024
a5d7e85
contractcourt: specify deadline and budget for nursery
yyforyongyu Mar 29, 2024
996e0fb
contractcourt+sweep: offer direct-preimage spend via `SweepInput`
yyforyongyu Mar 19, 2024
4c18d8a
sweep: make sure max fee rate can be reached
yyforyongyu Apr 3, 2024
ceabae3
multi: query circuit map inside contractcourt
yyforyongyu Apr 4, 2024
48cd001
contractcourt: add locks in `SubscribeChannelEvents`
yyforyongyu Apr 8, 2024
082a460
contractcourt: offer second-level outputs at CSV-1
yyforyongyu Apr 8, 2024
91061a6
sweep: allow published input to be marked as `PublishFailed`
yyforyongyu Apr 8, 2024
583307f
multi: add itest `testSweepAnchorCPFPLocalForceClose`
yyforyongyu Apr 1, 2024
cc2b47e
itest+lntest: add itest `testSweepHTLCs` to check HTLC sweepings
yyforyongyu Apr 3, 2024
3bdcace
itest: remove old CPFP tests
yyforyongyu Mar 21, 2024
2e38d02
itest+lntest: fix channel force close itest
yyforyongyu Mar 26, 2024
cb2f5d4
itest: fix multi-hop itest
yyforyongyu Apr 6, 2024
265bbc6
itest: fix channel backup tests
yyforyongyu Apr 7, 2024
5f3b444
itest: fix revocation itest
yyforyongyu Apr 8, 2024
dd17504
itest+lntest: fix onchain tests
yyforyongyu Apr 8, 2024
cf983cf
itest: fix watchtower test
yyforyongyu Apr 8, 2024
4639a39
sweep: catch third party spent in fee bumper for neutrino
yyforyongyu Apr 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions sweep/fee_bumper.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ var (
// ErrTxNoOutput is returned when an output cannot be created during tx
// preparation, usually due to the output being dust.
ErrTxNoOutput = errors.New("tx has no output")

// ErrThirdPartySpent is returned when a third party has spent the
// input in the sweeping tx.
ErrThirdPartySpent = errors.New("third party spent the output")
)

// Bumper defines an interface that can be used by other subsystems for fee
Expand Down Expand Up @@ -290,6 +294,11 @@ func NewTxPublisher(cfg TxPublisherConfig) *TxPublisher {
}
}

// isNeutrinoBackend checks if the wallet backend is neutrino.
func (t *TxPublisher) isNeutrinoBackend() bool {
return t.cfg.Wallet.BackEnd() == "neutrino"
}

// Broadcast is used to publish the tx created from the given inputs. It will,
// 1. init a fee function based on the given strategy.
// 2. create an RBF-compliant tx and monitor it for confirmation.
Expand Down Expand Up @@ -724,6 +733,12 @@ func (t *TxPublisher) processRecords() {
// feeBumpRecords stores a map of the records which need to be bumped.
feeBumpRecords := make(map[uint64]*monitorRecord)

// failedRecords stores a map of the records which has inputs being
// spent by a third party.
//
// NOTE: this is only used for neutrino backend.
failedRecords := make(map[uint64]*monitorRecord)

// visitor is a helper closure that visits each record and divides them
// into two groups.
visitor := func(requestID uint64, r *monitorRecord) error {
Expand All @@ -738,6 +753,16 @@ func (t *TxPublisher) processRecords() {
return nil
}

// Check whether the inputs has been spent by a third party.
//
// NOTE: this check is only done for neutrino backend.
if t.isThirdPartySpent(r.tx.TxHash(), r.req.Inputs) {
failedRecords[requestID] = r

// Move to the next record.
return nil
}

feeBumpRecords[requestID] = r

// Return nil to move to the next record.
Expand Down Expand Up @@ -768,6 +793,17 @@ func (t *TxPublisher) processRecords() {
t.wg.Add(1)
go t.handleFeeBumpTx(requestID, rec, currentHeight)
}

// For records that are failed, we'll notify the caller about this
// result.
for requestID, r := range failedRecords {
rec := r

log.Debugf("Tx=%v has inputs been spent by a third party, "+
"failing it now", r.tx.TxHash())
t.wg.Add(1)
go t.handleThirdPartySpent(rec, requestID)
}
}

// handleTxConfirmed is called when a monitored tx is confirmed. It will
Expand Down Expand Up @@ -837,6 +873,33 @@ func (t *TxPublisher) handleFeeBumpTx(requestID uint64, r *monitorRecord,
})
}

// handleThirdPartySpent is called when the inputs in an unconfirmed tx is
// spent. It will notify the subscriber then remove the record from the maps
// and send a TxFailed event to the subscriber.
//
// NOTE: Must be run as a goroutine to avoid blocking on sending the result.
func (t *TxPublisher) handleThirdPartySpent(r *monitorRecord,
requestID uint64) {

defer t.wg.Done()

// Create a result that will be sent to the resultChan which is
// listened by the caller.
//
// TODO(yy): create a new state `TxThirdPartySpent` to notify the
// sweeper to remove the input, hence moving the monitoring of inputs
// spent inside the fee bumper.
result := &BumpResult{
Event: TxFailed,
Tx: r.tx,
requestID: requestID,
Err: ErrThirdPartySpent,
}

// Notify that this tx is confirmed and remove the record from the map.
t.handleResult(result)
}

// createAndPublishTx creates a new tx with a higher fee rate and publishes it
// to the network. It will update the record with the new tx and fee rate if
// successfully created, and return the result when published successfully.
Expand Down Expand Up @@ -953,6 +1016,66 @@ func (t *TxPublisher) isConfirmed(txid chainhash.Hash) bool {
return details.NumConfirmations > 0
}

// isThirdPartySpent checks whether the inputs of the tx has already been spent
// by a third party. When a tx is not confirmed, yet its inputs has been spent,
// then it must be spent by a different tx other than the sweeping tx here.
//
// NOTE: this check is only performed for neutrino backend as it has no
// reliable way to tell a tx has been replaced.
func (t *TxPublisher) isThirdPartySpent(txid chainhash.Hash,
inputs []input.Input) bool {

// Skip this check for if this is not neutrino backend.
if !t.isNeutrinoBackend() {
return false
}

// Iterate all the inputs and check if they have been spent already.
for _, inp := range inputs {
op := inp.OutPoint()

// If the input has already been spent after the height hint, a
// spend event is sent back immediately.
spendEvent, err := t.cfg.Notifier.RegisterSpendNtfn(
&op, inp.SignDesc().Output.PkScript, inp.HeightHint(),
)
if err != nil {
log.Criticalf("Failed to register spend ntfn: %v", err)
return false
}

// Remove the subscription when exit.
defer spendEvent.Cancel()

// Do a non-blocking read to see if the output has been spent.
select {
case spend, ok := <-spendEvent.Spend:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if the time lacking in the RegisterSpendNtfn rescan logic could cause problems here during restarts here?

if !ok {
log.Debugf("Spend ntfn for %v canceled", op)
return false
}

spendingTxID := spend.SpendingTx.TxHash()

// If the spending tx is the same as the sweeping tx
// then we are good.
if spendingTxID == txid {
continue
}

log.Warnf("Detected third party spent of output=%v "+
"in tx=%v", op, spend.SpendingTx.TxHash())

return true

// Move to the next input.
default:
}
}

return false
}

// calcCurrentConfTarget calculates the current confirmation target based on
// the deadline height. The conf target is capped at 0 if the deadline has
// already been past.
Expand Down
1 change: 1 addition & 0 deletions sweep/fee_bumper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,7 @@ func TestProcessRecords(t *testing.T) {
NumConfirmations: 0,
}, nil,
).Once()
m.wallet.On("BackEnd").Return("test-backend").Once()

// Setup the initial publisher state by adding the records to the maps.
subscriberConfirmed := make(chan *BumpResult, 1)
Expand Down
5 changes: 5 additions & 0 deletions sweep/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,9 @@ type Wallet interface {
// its transaction hash.
GetTransactionDetails(txHash *chainhash.Hash) (
*lnwallet.TransactionDetail, error)

// BackEnd returns a name for the wallet's backing chain service,
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
// service.
BackEnd() string
}
12 changes: 12 additions & 0 deletions sweep/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ func newMockBackend(t *testing.T, notifier *MockNotifier) *mockBackend {
}
}

func (b *mockBackend) BackEnd() string {
return "mockbackend"
}

func (b *mockBackend) CheckMempoolAcceptance(tx *wire.MsgTx) error {
return nil
}
Expand Down Expand Up @@ -357,6 +361,14 @@ type MockWallet struct {
// Compile-time constraint to ensure MockWallet implements Wallet.
var _ Wallet = (*MockWallet)(nil)

// BackEnd returns a name for the wallet's backing chain service, which could
// be e.g. btcd, bitcoind, neutrino, or another consensus service.
func (m *MockWallet) BackEnd() string {
args := m.Called()

return args.String(0)
}

// CheckMempoolAcceptance checks if the transaction can be accepted to the
// mempool.
func (m *MockWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
Expand Down
Loading