contractcourt: add spend path helpers in timeout/success resolver

This commit adds a few helper methods to decide how the htlc output
should be spent.
This commit is contained in:
yyforyongyu
2024-11-14 02:28:56 +08:00
parent 1f2cfc6a60
commit 10e5a43e46
2 changed files with 65 additions and 18 deletions

View File

@@ -127,7 +127,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
// If we don't have a success transaction, then this means that this is // If we don't have a success transaction, then this means that this is
// an output on the remote party's commitment transaction. // an output on the remote party's commitment transaction.
if h.htlcResolution.SignedSuccessTx == nil { if h.isRemoteCommitOutput() {
return h.resolveRemoteCommitOutput() return h.resolveRemoteCommitOutput()
} }
@@ -176,7 +176,7 @@ func (h *htlcSuccessResolver) broadcastSuccessTx() (
// and attach fees at will. We let the sweeper handle this job. We use // and attach fees at will. We let the sweeper handle this job. We use
// the checkpointed outputIncubating field to determine if we already // the checkpointed outputIncubating field to determine if we already
// swept the HTLC output into the second level transaction. // swept the HTLC output into the second level transaction.
if h.htlcResolution.SignDetails != nil { if h.isZeroFeeOutput() {
return h.broadcastReSignedSuccessTx() return h.broadcastReSignedSuccessTx()
} }
@@ -236,12 +236,9 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
// We will have to let the sweeper re-sign the success tx and wait for // We will have to let the sweeper re-sign the success tx and wait for
// it to confirm, if we haven't already. // it to confirm, if we haven't already.
isTaproot := txscript.IsPayToTaproot(
h.htlcResolution.SweepSignDesc.Output.PkScript,
)
if !h.outputIncubating { if !h.outputIncubating {
var secondLevelInput input.HtlcSecondLevelAnchorInput var secondLevelInput input.HtlcSecondLevelAnchorInput
if isTaproot { if h.isTaproot() {
//nolint:ll //nolint:ll
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput( secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
h.htlcResolution.SignedSuccessTx, h.htlcResolution.SignedSuccessTx,
@@ -371,7 +368,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
// Let the sweeper sweep the second-level output now that the // Let the sweeper sweep the second-level output now that the
// CSV/CLTV locks have expired. // CSV/CLTV locks have expired.
var witType input.StandardWitnessType var witType input.StandardWitnessType
if isTaproot { if h.isTaproot() {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else { } else {
witType = input.HtlcAcceptedSuccessSecondLevel witType = input.HtlcAcceptedSuccessSecondLevel
@@ -421,16 +418,12 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() ( func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
ContractResolver, error) { ContractResolver, error) {
isTaproot := txscript.IsPayToTaproot(
h.htlcResolution.SweepSignDesc.Output.PkScript,
)
// Before we can craft out sweeping transaction, we need to // Before we can craft out sweeping transaction, we need to
// create an input which contains all the items required to add // create an input which contains all the items required to add
// this input to a sweeping transaction, and generate a // this input to a sweeping transaction, and generate a
// witness. // witness.
var inp input.Input var inp input.Input
if isTaproot { if h.isTaproot() {
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput( inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint, &h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc, &h.htlcResolution.SweepSignDesc,
@@ -712,3 +705,29 @@ func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
// A compile time assertion to ensure htlcSuccessResolver meets the // A compile time assertion to ensure htlcSuccessResolver meets the
// ContractResolver interface. // ContractResolver interface.
var _ htlcContractResolver = (*htlcSuccessResolver)(nil) var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
// on the remote commitment.
func (h *htlcSuccessResolver) isRemoteCommitOutput() bool {
// If we don't have a success transaction, then this means that this is
// an output on the remote party's commitment transaction.
return h.htlcResolution.SignedSuccessTx == nil
}
// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
func (h *htlcSuccessResolver) isZeroFeeOutput() bool {
// If we have non-nil SignDetails, this means it has a 2nd level HTLC
// transaction that is signed using sighash SINGLE|ANYONECANPAY (the
// case for anchor type channels). In this case we can re-sign it and
// attach fees at will.
return h.htlcResolution.SignedSuccessTx != nil &&
h.htlcResolution.SignDetails != nil
}
// isTaproot returns true if the resolver is for a taproot output.
func (h *htlcSuccessResolver) isTaproot() bool {
return txscript.IsPayToTaproot(
h.htlcResolution.SweepSignDesc.Output.PkScript,
)
}

View File

@@ -634,7 +634,7 @@ func (h *htlcTimeoutResolver) spendHtlcOutput() (
// HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY
// (the case for anchor type channels). In this case we can re-sign it // (the case for anchor type channels). In this case we can re-sign it
// and attach fees at will. We let the sweeper handle this job. // and attach fees at will. We let the sweeper handle this job.
case h.htlcResolution.SignDetails != nil && !h.outputIncubating: case h.isZeroFeeOutput() && !h.outputIncubating:
if err := h.sweepSecondLevelTx(); err != nil { if err := h.sweepSecondLevelTx(); err != nil {
log.Errorf("Sending timeout tx to sweeper: %v", err) log.Errorf("Sending timeout tx to sweeper: %v", err)
@@ -643,7 +643,7 @@ func (h *htlcTimeoutResolver) spendHtlcOutput() (
// If this is a remote commitment there's no second level timeout txn, // If this is a remote commitment there's no second level timeout txn,
// and we can just send this directly to the sweeper. // and we can just send this directly to the sweeper.
case h.htlcResolution.SignedTimeoutTx == nil && !h.outputIncubating: case h.isRemoteCommitOutput() && !h.outputIncubating:
if err := h.sweepDirectHtlcOutput(); err != nil { if err := h.sweepDirectHtlcOutput(); err != nil {
log.Errorf("Sending direct spend to sweeper: %v", err) log.Errorf("Sending direct spend to sweeper: %v", err)
@@ -653,7 +653,7 @@ func (h *htlcTimeoutResolver) spendHtlcOutput() (
// If we have a SignedTimeoutTx but no SignDetails, this is a local // If we have a SignedTimeoutTx but no SignDetails, this is a local
// commitment for a non-anchor channel, so we'll send it to the utxo // commitment for a non-anchor channel, so we'll send it to the utxo
// nursery. // nursery.
case h.htlcResolution.SignDetails == nil && !h.outputIncubating: case h.isLegacyOutput() && !h.outputIncubating:
if err := h.sendSecondLevelTxLegacy(); err != nil { if err := h.sendSecondLevelTxLegacy(); err != nil {
log.Errorf("Sending timeout tx to nursery: %v", err) log.Errorf("Sending timeout tx to nursery: %v", err)
@@ -769,7 +769,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
// If the sweeper is handling the second level transaction, wait for // If the sweeper is handling the second level transaction, wait for
// the CSV and possible CLTV lock to expire, before sweeping the output // the CSV and possible CLTV lock to expire, before sweeping the output
// on the second-level. // on the second-level.
case h.htlcResolution.SignDetails != nil: case h.isZeroFeeOutput():
waitHeight := h.deriveWaitHeight( waitHeight := h.deriveWaitHeight(
h.htlcResolution.CsvDelay, commitSpend, h.htlcResolution.CsvDelay, commitSpend,
) )
@@ -851,7 +851,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
// Finally, if this was an output on our commitment transaction, we'll // Finally, if this was an output on our commitment transaction, we'll
// wait for the second-level HTLC output to be spent, and for that // wait for the second-level HTLC output to be spent, and for that
// transaction itself to confirm. // transaction itself to confirm.
case h.htlcResolution.SignedTimeoutTx != nil: case !h.isRemoteCommitOutput():
log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+ log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
"delayed output", h, claimOutpoint) "delayed output", h, claimOutpoint)
@@ -1232,7 +1232,7 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
// continue the loop. // continue the loop.
hasPreimage := isPreimageSpend( hasPreimage := isPreimageSpend(
h.isTaproot(), spendDetail, h.isTaproot(), spendDetail,
h.htlcResolution.SignedTimeoutTx != nil, !h.isRemoteCommitOutput(),
) )
if !hasPreimage { if !hasPreimage {
log.Debugf("HTLC output %s spent doesn't "+ log.Debugf("HTLC output %s spent doesn't "+
@@ -1260,3 +1260,31 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
} }
} }
} }
// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
// on the remote commitment.
func (h *htlcTimeoutResolver) isRemoteCommitOutput() bool {
// If we don't have a timeout transaction, then this means that this is
// an output on the remote party's commitment transaction.
return h.htlcResolution.SignedTimeoutTx == nil
}
// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
func (h *htlcTimeoutResolver) isZeroFeeOutput() bool {
// If we have non-nil SignDetails, this means it has a 2nd level HTLC
// transaction that is signed using sighash SINGLE|ANYONECANPAY (the
// case for anchor type channels). In this case we can re-sign it and
// attach fees at will.
return h.htlcResolution.SignedTimeoutTx != nil &&
h.htlcResolution.SignDetails != nil
}
// isLegacyOutput returns a boolean indicating whether the htlc output is from
// a non-anchor-enabled channel.
func (h *htlcTimeoutResolver) isLegacyOutput() bool {
// If we have a SignedTimeoutTx but no SignDetails, this is a local
// commitment for a non-anchor channel.
return h.htlcResolution.SignedTimeoutTx != nil &&
h.htlcResolution.SignDetails == nil
}