contractcourt: add sweep senders in htlcSuccessResolver

This commit is a pure refactor in which moves the sweep handling logic
into the new methods.
This commit is contained in:
yyforyongyu 2024-11-14 21:59:16 +08:00
parent 10e5a43e46
commit fb499bc4cc
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868

@ -237,55 +237,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
// We will have to let the sweeper re-sign the success tx and wait for
// it to confirm, if we haven't already.
if !h.outputIncubating {
var secondLevelInput input.HtlcSecondLevelAnchorInput
if h.isTaproot() {
//nolint:ll
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
)
} else {
//nolint:ll
secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight,
)
}
// Calculate the budget for this sweep.
value := btcutil.Amount(
secondLevelInput.SignDesc().Output.Value,
)
budget := calculateBudget(
value, h.Budget.DeadlineHTLCRatio,
h.Budget.DeadlineHTLC,
)
// The deadline would be the CLTV in this HTLC output. If we
// are the initiator of this force close, with the default
// `IncomingBroadcastDelta`, it means we have 10 blocks left
// when going onchain. Given we need to mine one block to
// confirm the force close tx, and one more block to trigger
// the sweep, we have 8 blocks left to sweep the HTLC.
deadline := fn.Some(int32(h.htlc.RefundTimeout))
log.Infof("%T(%x): offering second-level HTLC success tx to "+
"sweeper with deadline=%v, budget=%v", h,
h.htlc.RHash[:], h.htlc.RefundTimeout, budget)
// We'll now offer the second-level transaction to the sweeper.
_, err := h.Sweeper.SweepInput(
&secondLevelInput,
sweep.Params{
Budget: budget,
DeadlineHeight: deadline,
},
)
err := h.sweepSuccessTx()
if err != nil {
return nil, err
}
@ -316,99 +268,18 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
"confirmed!", h, h.htlc.RHash[:])
}
// If we ended up here after a restart, we must again get the
// spend notification.
if commitSpend == nil {
var err error
commitSpend, err = waitForSpend(
&h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return nil, err
}
}
// The HTLC success tx has a CSV lock that we must wait for, and if
// this is a lease enforced channel and we're the imitator, we may need
// to wait for longer.
waitHeight := h.deriveWaitHeight(
h.htlcResolution.CsvDelay, commitSpend,
)
// Now that the sweeper has broadcasted the second-level transaction,
// it has confirmed, and we have checkpointed our state, we'll sweep
// the second level output. We report the resolver has moved the next
// stage.
h.reportLock.Lock()
h.currentReport.Stage = 2
h.currentReport.MaturityHeight = waitHeight
h.reportLock.Unlock()
if h.hasCLTV() {
log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
"expire at height %v", h, h.htlc.RHash[:],
waitHeight)
} else {
log.Infof("%T(%x): waiting for CSV lock to expire at "+
"height %v", h, h.htlc.RHash[:], waitHeight)
}
// We'll use this input index to determine the second-level output
// index on the transaction, as the signatures requires the indexes to
// be the same. We don't look for the second-level output script
// directly, as there might be more than one HTLC output to the same
// pkScript.
op := &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: commitSpend.SpenderInputIndex,
}
// Let the sweeper sweep the second-level output now that the
// CSV/CLTV locks have expired.
var witType input.StandardWitnessType
if h.isTaproot() {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else {
witType = input.HtlcAcceptedSuccessSecondLevel
}
inp := h.makeSweepInput(
op, witType,
input.LeaseHtlcAcceptedSuccessSecondLevel,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
h.htlc.RHash, h.htlcResolution.ResolutionBlob,
)
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.NoDeadlineHTLCRatio,
h.Budget.NoDeadlineHTLC,
)
log.Infof("%T(%x): offering second-level success tx output to sweeper "+
"with no deadline and budget=%v at height=%v", h,
h.htlc.RHash[:], budget, waitHeight)
// TODO(roasbeef): need to update above for leased types
_, err := h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
// For second level success tx, there's no rush to get
// it confirmed, so we use a nil deadline.
DeadlineHeight: fn.None[int32](),
},
)
err := h.sweepSuccessTxOutput()
if err != nil {
return nil, err
}
// Will return this outpoint, when this is spent the resolver is fully
// resolved.
op := &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: commitSpend.SpenderInputIndex,
}
return op, nil
}
@ -418,53 +289,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
ContractResolver, error) {
// Before we can craft out sweeping transaction, we need to
// create an input which contains all the items required to add
// this input to a sweeping transaction, and generate a
// witness.
var inp input.Input
if h.isTaproot() {
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else {
inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
))
}
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.DeadlineHTLCRatio,
h.Budget.DeadlineHTLC,
)
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,
},
)
err := h.sweepRemoteCommitOutput()
if err != nil {
return nil, err
}
@ -731,3 +556,195 @@ func (h *htlcSuccessResolver) isTaproot() bool {
h.htlcResolution.SweepSignDesc.Output.PkScript,
)
}
// sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on
// the remote commitment via the direct preimage-spend.
func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error {
// Before we can craft out sweeping transaction, we need to create an
// input which contains all the items required to add this input to a
// sweeping transaction, and generate a witness.
var inp input.Input
if h.isTaproot() {
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else {
inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
))
}
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.DeadlineHTLCRatio,
h.Budget.DeadlineHTLC,
)
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,
},
)
return err
}
// sweepSuccessTx attempts to sweep the second level success tx.
func (h *htlcSuccessResolver) sweepSuccessTx() error {
var secondLevelInput input.HtlcSecondLevelAnchorInput
if h.isTaproot() {
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight, input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
)
} else {
secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
h.htlcResolution.SignedSuccessTx,
h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
h.broadcastHeight,
)
}
// Calculate the budget for this sweep.
value := btcutil.Amount(secondLevelInput.SignDesc().Output.Value)
budget := calculateBudget(
value, h.Budget.DeadlineHTLCRatio, h.Budget.DeadlineHTLC,
)
// The deadline would be the CLTV in this HTLC output. If we are the
// initiator of this force close, with the default
// `IncomingBroadcastDelta`, it means we have 10 blocks left when going
// onchain.
deadline := fn.Some(int32(h.htlc.RefundTimeout))
h.log.Infof("offering second-level HTLC success tx to sweeper with "+
"deadline=%v, budget=%v", h.htlc.RefundTimeout, budget)
// We'll now offer the second-level transaction to the sweeper.
_, err := h.Sweeper.SweepInput(
&secondLevelInput,
sweep.Params{
Budget: budget,
DeadlineHeight: deadline,
},
)
return err
}
// sweepSuccessTxOutput attempts to sweep the output of the second level
// success tx.
func (h *htlcSuccessResolver) sweepSuccessTxOutput() error {
h.log.Debugf("sweeping output %v from 2nd-level HTLC success tx",
h.htlcResolution.ClaimOutpoint)
// This should be non-blocking as we will only attempt to sweep the
// output when the second level tx has already been confirmed. In other
// words, waitForSpend will return immediately.
commitSpend, err := waitForSpend(
&h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
h.broadcastHeight, h.Notifier, h.quit,
)
if err != nil {
return err
}
// The HTLC success tx has a CSV lock that we must wait for, and if
// this is a lease enforced channel and we're the imitator, we may need
// to wait for longer.
waitHeight := h.deriveWaitHeight(h.htlcResolution.CsvDelay, commitSpend)
// Now that the sweeper has broadcasted the second-level transaction,
// it has confirmed, and we have checkpointed our state, we'll sweep
// the second level output. We report the resolver has moved the next
// stage.
h.reportLock.Lock()
h.currentReport.Stage = 2
h.currentReport.MaturityHeight = waitHeight
h.reportLock.Unlock()
if h.hasCLTV() {
log.Infof("%T(%x): waiting for CSV and CLTV lock to expire at "+
"height %v", h, h.htlc.RHash[:], waitHeight)
} else {
log.Infof("%T(%x): waiting for CSV lock to expire at height %v",
h, h.htlc.RHash[:], waitHeight)
}
// We'll use this input index to determine the second-level output
// index on the transaction, as the signatures requires the indexes to
// be the same. We don't look for the second-level output script
// directly, as there might be more than one HTLC output to the same
// pkScript.
op := &wire.OutPoint{
Hash: *commitSpend.SpenderTxHash,
Index: commitSpend.SpenderInputIndex,
}
// Let the sweeper sweep the second-level output now that the
// CSV/CLTV locks have expired.
var witType input.StandardWitnessType
if h.isTaproot() {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else {
witType = input.HtlcAcceptedSuccessSecondLevel
}
inp := h.makeSweepInput(
op, witType,
input.LeaseHtlcAcceptedSuccessSecondLevel,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
h.htlc.RHash, h.htlcResolution.ResolutionBlob,
)
// Calculate the budget for this sweep.
budget := calculateBudget(
btcutil.Amount(inp.SignDesc().Output.Value),
h.Budget.NoDeadlineHTLCRatio,
h.Budget.NoDeadlineHTLC,
)
log.Infof("%T(%x): offering second-level success tx output to sweeper "+
"with no deadline and budget=%v at height=%v", h,
h.htlc.RHash[:], budget, waitHeight)
// TODO(yy): use the result chan returned from SweepInput.
_, err = h.Sweeper.SweepInput(
inp,
sweep.Params{
Budget: budget,
// For second level success tx, there's no rush to get
// it confirmed, so we use a nil deadline.
DeadlineHeight: fn.None[int32](),
},
)
return err
}