contractcourt+sweep: offer direct-preimage spend via SweepInput

This commit removes the method `CreateSweepTx` and makes sure when
sweeping the htlc output via the direct-preimage spend, it's offered via
the `SweepInput` interface.
This commit is contained in:
yyforyongyu 2024-03-19 12:32:01 +08:00
parent 9c1e6941c3
commit dc7d90c16f
No known key found for this signature in database
GPG key ID: 9BCD95C4FF296868
6 changed files with 56 additions and 155 deletions

View file

@ -331,7 +331,6 @@ func TestContractInsertionRetrieval(t *testing.T) {
htlc: channeldb.HTLC{ htlc: channeldb.HTLC{
RHash: testPreimage, RHash: testPreimage,
}, },
sweepTx: nil,
} }
resolvers := []ContractResolver{ resolvers := []ContractResolver{
&timeoutResolver, &timeoutResolver,

View file

@ -156,14 +156,6 @@ func (s *mockSweeper) SweepInput(input input.Input, params sweep.Params) (
return result, nil return result, nil
} }
func (s *mockSweeper) CreateSweepTx(inputs []input.Input,
feePref sweep.FeeEstimateInfo) (*wire.MsgTx, error) {
// We will wait for the test to supply the sweep tx to return.
sweepTx := <-s.createSweepTxChan
return sweepTx, nil
}
func (s *mockSweeper) RelayFeePerKW() chainfee.SatPerKWeight { func (s *mockSweeper) RelayFeePerKW() chainfee.SatPerKWeight {
return 253 return 253
} }

View file

@ -51,13 +51,6 @@ type htlcSuccessResolver struct {
// historical queries to the chain for spends/confirmations. // historical queries to the chain for spends/confirmations.
broadcastHeight uint32 broadcastHeight uint32
// sweepTx will be non-nil if we've already crafted a transaction to
// sweep a direct HTLC output. This is only a concern if we're sweeping
// from the commitment transaction of the remote party.
//
// TODO(roasbeef): send off to utxobundler
sweepTx *wire.MsgTx
// htlc contains information on the htlc that we are resolving on-chain. // htlc contains information on the htlc that we are resolving on-chain.
htlc channeldb.HTLC htlc channeldb.HTLC
@ -427,108 +420,75 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() ( func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
ContractResolver, error) { ContractResolver, error) {
// If we don't already have the sweep transaction constructed, we'll do isTaproot := txscript.IsPayToTaproot(
// so and broadcast it. h.htlcResolution.SweepSignDesc.Output.PkScript,
if h.sweepTx == nil { )
log.Infof("%T(%x): crafting sweep tx for incoming+remote "+
"htlc confirmed", h, h.htlc.RHash[:])
isTaproot := txscript.IsPayToTaproot( // Before we can craft out sweeping transaction, we need to
h.htlcResolution.SweepSignDesc.Output.PkScript, // create an input which contains all the items required to add
) // this input to a sweeping transaction, and generate a
// witness.
// Before we can craft out sweeping transaction, we need to var inp input.Input
// create an input which contains all the items required to add if isTaproot {
// this input to a sweeping transaction, and generate a inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
// witness. &h.htlcResolution.ClaimOutpoint,
var inp input.Input &h.htlcResolution.SweepSignDesc,
if isTaproot { h.htlcResolution.Preimage[:],
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput( h.broadcastHeight,
&h.htlcResolution.ClaimOutpoint, h.htlcResolution.CsvDelay,
&h.htlcResolution.SweepSignDesc, ))
h.htlcResolution.Preimage[:], } else {
h.broadcastHeight, inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
h.htlcResolution.CsvDelay, &h.htlcResolution.ClaimOutpoint,
)) &h.htlcResolution.SweepSignDesc,
} else { h.htlcResolution.Preimage[:],
inp = lnutils.Ptr(input.MakeHtlcSucceedInput( h.broadcastHeight,
&h.htlcResolution.ClaimOutpoint, h.htlcResolution.CsvDelay,
&h.htlcResolution.SweepSignDesc, ))
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
))
}
// With the input created, we can now generate the full sweep
// transaction, that we'll use to move these coins back into
// the backing wallet.
//
// TODO: Use time-based sweeper and result chan.
var err error
h.sweepTx, err = h.Sweeper.CreateSweepTx(
[]input.Input{inp},
sweep.FeeEstimateInfo{
ConfTarget: sweepConfTarget,
},
)
if err != nil {
return nil, err
}
log.Infof("%T(%x): crafted sweep tx=%v", h,
h.htlc.RHash[:], spew.Sdump(h.sweepTx))
// TODO(halseth): should checkpoint sweep tx to DB? Since after
// a restart we might create a different tx, that will conflict
// with the published one.
} }
// Register the confirmation notification before broadcasting the sweep // Calculate the budget for this sweep.
// transaction. budget := calculateBudget(
sweepTXID := h.sweepTx.TxHash() btcutil.Amount(inp.SignDesc().Output.Value),
sweepScript := h.sweepTx.TxOut[0].PkScript h.Budget.DeadlineHTLCRatio,
confNtfn, err := h.Notifier.RegisterConfirmationsNtfn( h.Budget.DeadlineHTLC,
&sweepTXID, sweepScript, 1, h.broadcastHeight, )
deadline := fn.Some(int32(h.htlc.RefundTimeout))
log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
"with deadline=%v, budget=%v", h, h.htlc.RHash[:],
h.htlc.RefundTimeout, budget)
// We'll now offer the direct preimage HTLC to the sweeper.
_, err := h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
DeadlineHeight: deadline,
},
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Regardless of whether an existing transaction was found or newly // Wait for the direct-preimage HTLC sweep tx to confirm.
// constructed, we'll broadcast the sweep transaction to the network. sweepTxDetails, err := waitForSpend(
label := labels.MakeLabel( &h.htlcResolution.ClaimOutpoint,
labels.LabelTypeChannelClose, &h.ShortChanID, h.htlcResolution.SweepSignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
) )
err = h.PublishTx(h.sweepTx, label)
if err != nil { if err != nil {
log.Infof("%T(%x): unable to publish tx: %v",
h, h.htlc.RHash[:], err)
confNtfn.Cancel()
return nil, err return nil, err
} }
log.Infof("%T(%x): waiting for sweep tx (txid=%v) to be confirmed", h,
h.htlc.RHash[:], sweepTXID)
select {
case _, ok := <-confNtfn.Confirmed:
if !ok {
return nil, errResolverShuttingDown
}
case <-h.quit:
return nil, errResolverShuttingDown
}
// Once the transaction has received a sufficient number of // Once the transaction has received a sufficient number of
// confirmations, we'll mark ourselves as fully resolved and exit. // confirmations, we'll mark ourselves as fully resolved and exit.
h.resolved = true h.resolved = true
// Checkpoint the resolver, and write the outcome to disk. // Checkpoint the resolver, and write the outcome to disk.
return nil, h.checkpointClaim( return nil, h.checkpointClaim(
&sweepTXID, sweepTxDetails.SpenderTxHash,
channeldb.ResolverOutcomeClaimed, channeldb.ResolverOutcomeClaimed,
) )
} }

View file

@ -181,17 +181,14 @@ func TestHtlcSuccessSingleStage(t *testing.T) {
// that our sweep succeeded. // that our sweep succeeded.
preCheckpoint: func(ctx *htlcResolverTestContext, preCheckpoint: func(ctx *htlcResolverTestContext,
_ bool) error { _ bool) error {
// The resolver will create and publish a sweep
// tx.
resolver := ctx.resolver.(*htlcSuccessResolver)
resolver.Sweeper.(*mockSweeper).
createSweepTxChan <- sweepTx
// Confirm the sweep, which should resolve it. // The resolver will offer the input to the
ctx.notifier.ConfChan <- &chainntnfs.TxConfirmation{ // sweeper.
Tx: sweepTx, details := &chainntnfs.SpendDetail{
BlockHeight: testInitialBlockHeight - 1, SpendingTx: sweepTx,
SpenderTxHash: &sweepTxid,
} }
ctx.notifier.SpendChan <- details
return nil return nil
}, },

View file

@ -50,12 +50,6 @@ type UtxoSweeper interface {
SweepInput(input input.Input, params sweep.Params) (chan sweep.Result, SweepInput(input input.Input, params sweep.Params) (chan sweep.Result,
error) error)
// CreateSweepTx accepts a list of inputs and signs and generates a txn
// that spends from them. This method also makes an accurate fee
// estimate before generating the required witnesses.
CreateSweepTx(inputs []input.Input,
feePref sweep.FeeEstimateInfo) (*wire.MsgTx, error)
// RelayFeePerKW returns the minimum fee rate required for transactions // RelayFeePerKW returns the minimum fee rate required for transactions
// to be relayed. // to be relayed.
RelayFeePerKW() chainfee.SatPerKWeight RelayFeePerKW() chainfee.SatPerKWeight

View file

@ -1159,47 +1159,6 @@ func (s *UtxoSweeper) handleUpdateReq(req *updateReq) (
return resultChan, nil return resultChan, nil
} }
// CreateSweepTx accepts a list of inputs and signs and generates a txn that
// spends from them. This method also makes an accurate fee estimate before
// generating the required witnesses.
//
// The created transaction has a single output sending all the funds back to
// the source wallet, after accounting for the fee estimate.
//
// The value of currentBlockHeight argument will be set as the tx locktime.
// This function assumes that all CLTV inputs will be unlocked after
// currentBlockHeight. Reasons not to use the maximum of all actual CLTV expiry
// values of the inputs:
//
// - Make handling re-orgs easier.
// - Thwart future possible fee sniping attempts.
// - Make us blend in with the bitcoind wallet.
//
// TODO(yy): remove this method and only allow sweeping via requests.
func (s *UtxoSweeper) CreateSweepTx(inputs []input.Input,
feePref FeeEstimateInfo) (*wire.MsgTx, error) {
feePerKw, err := feePref.Estimate(
s.cfg.FeeEstimator, s.cfg.MaxFeeRate.FeePerKWeight(),
)
if err != nil {
return nil, err
}
// Generate the receiving script to which the funds will be swept.
pkScript, err := s.cfg.GenSweepScript()
if err != nil {
return nil, err
}
tx, _, err := createSweepTx(
inputs, nil, pkScript, uint32(s.currentHeight), feePerKw,
s.cfg.MaxFeeRate.FeePerKWeight(), s.cfg.Signer,
)
return tx, err
}
// ListSweeps returns a list of the sweeps recorded by the sweep store. // ListSweeps returns a list of the sweeps recorded by the sweep store.
func (s *UtxoSweeper) ListSweeps() ([]chainhash.Hash, error) { func (s *UtxoSweeper) ListSweeps() ([]chainhash.Hash, error) {
return s.cfg.Store.ListSweeps() return s.cfg.Store.ListSweeps()