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,12 +420,6 @@ 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
// so and broadcast it.
if h.sweepTx == nil {
log.Infof("%T(%x): crafting sweep tx for incoming+remote "+
"htlc confirmed", h, h.htlc.RHash[:])
isTaproot := txscript.IsPayToTaproot( isTaproot := txscript.IsPayToTaproot(
h.htlcResolution.SweepSignDesc.Output.PkScript, h.htlcResolution.SweepSignDesc.Output.PkScript,
) )
@ -460,75 +447,48 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
)) ))
} }
// With the input created, we can now generate the full sweep // Calculate the budget for this sweep.
// transaction, that we'll use to move these coins back into budget := calculateBudget(
// the backing wallet. btcutil.Amount(inp.SignDesc().Output.Value),
// h.Budget.DeadlineHTLCRatio,
// TODO: Use time-based sweeper and result chan. h.Budget.DeadlineHTLC,
var err error )
h.sweepTx, err = h.Sweeper.CreateSweepTx(
[]input.Input{inp}, deadline := fn.Some(int32(h.htlc.RefundTimeout))
sweep.FeeEstimateInfo{
ConfTarget: sweepConfTarget, 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
} }
log.Infof("%T(%x): crafted sweep tx=%v", h, // Wait for the direct-preimage HTLC sweep tx to confirm.
h.htlc.RHash[:], spew.Sdump(h.sweepTx)) sweepTxDetails, err := waitForSpend(
&h.htlcResolution.ClaimOutpoint,
// TODO(halseth): should checkpoint sweep tx to DB? Since after h.htlcResolution.SweepSignDesc.Output.PkScript,
// a restart we might create a different tx, that will conflict h.broadcastHeight, h.Notifier, h.quit,
// with the published one.
}
// Register the confirmation notification before broadcasting the sweep
// transaction.
sweepTXID := h.sweepTx.TxHash()
sweepScript := h.sweepTx.TxOut[0].PkScript
confNtfn, err := h.Notifier.RegisterConfirmationsNtfn(
&sweepTXID, sweepScript, 1, h.broadcastHeight,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Regardless of whether an existing transaction was found or newly
// constructed, we'll broadcast the sweep transaction to the network.
label := labels.MakeLabel(
labels.LabelTypeChannelClose, &h.ShortChanID,
)
err = h.PublishTx(h.sweepTx, label)
if err != nil {
log.Infof("%T(%x): unable to publish tx: %v",
h, h.htlc.RHash[:], err)
confNtfn.Cancel()
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()