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:
2 files changed
+65
-18
No files matched your search
@@ -127,7 +127,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
|
||||
|
||||
// If we don't have a success transaction, then this means that this is
|
||||
// an output on the remote party's commitment transaction.
|
||||
if h.htlcResolution.SignedSuccessTx == nil {
|
||||
if h.isRemoteCommitOutput() {
|
||||
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
|
||||
// the checkpointed outputIncubating field to determine if we already
|
||||
// swept the HTLC output into the second level transaction.
|
||||
if h.htlcResolution.SignDetails != nil {
|
||||
if h.isZeroFeeOutput() {
|
||||
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
|
||||
// it to confirm, if we haven't already.
|
||||
isTaproot := txscript.IsPayToTaproot(
|
||||
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
||||
)
|
||||
if !h.outputIncubating {
|
||||
var secondLevelInput input.HtlcSecondLevelAnchorInput
|
||||
if isTaproot {
|
||||
if h.isTaproot() {
|
||||
//nolint:ll
|
||||
secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
|
||||
h.htlcResolution.SignedSuccessTx,
|
||||
@@ -371,7 +368,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
|
||||
// Let the sweeper sweep the second-level output now that the
|
||||
// CSV/CLTV locks have expired.
|
||||
var witType input.StandardWitnessType
|
||||
if isTaproot {
|
||||
if h.isTaproot() {
|
||||
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
|
||||
} else {
|
||||
witType = input.HtlcAcceptedSuccessSecondLevel
|
||||
@@ -421,16 +418,12 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (*wire.OutPoint,
|
||||
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
|
||||
ContractResolver, error) {
|
||||
|
||||
isTaproot := txscript.IsPayToTaproot(
|
||||
h.htlcResolution.SweepSignDesc.Output.PkScript,
|
||||
)
|
||||
|
||||
// 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 isTaproot {
|
||||
if h.isTaproot() {
|
||||
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
|
||||
&h.htlcResolution.ClaimOutpoint,
|
||||
&h.htlcResolution.SweepSignDesc,
|
||||
@@ -712,3 +705,29 @@ func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
|
||||
// A compile time assertion to ensure htlcSuccessResolver meets the
|
||||
// ContractResolver interface.
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -634,7 +634,7 @@ func (h *htlcTimeoutResolver) spendHtlcOutput() (
|
||||
// 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. 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 {
|
||||
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,
|
||||
// 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 {
|
||||
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
|
||||
// commitment for a non-anchor channel, so we'll send it to the utxo
|
||||
// nursery.
|
||||
case h.htlcResolution.SignDetails == nil && !h.outputIncubating:
|
||||
case h.isLegacyOutput() && !h.outputIncubating:
|
||||
if err := h.sendSecondLevelTxLegacy(); err != nil {
|
||||
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
|
||||
// the CSV and possible CLTV lock to expire, before sweeping the output
|
||||
// on the second-level.
|
||||
case h.htlcResolution.SignDetails != nil:
|
||||
case h.isZeroFeeOutput():
|
||||
waitHeight := h.deriveWaitHeight(
|
||||
h.htlcResolution.CsvDelay, commitSpend,
|
||||
)
|
||||
@@ -851,7 +851,7 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
|
||||
// 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
|
||||
// transaction itself to confirm.
|
||||
case h.htlcResolution.SignedTimeoutTx != nil:
|
||||
case !h.isRemoteCommitOutput():
|
||||
log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
|
||||
"delayed output", h, claimOutpoint)
|
||||
|
||||
@@ -1232,7 +1232,7 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
|
||||
// continue the loop.
|
||||
hasPreimage := isPreimageSpend(
|
||||
h.isTaproot(), spendDetail,
|
||||
h.htlcResolution.SignedTimeoutTx != nil,
|
||||
!h.isRemoteCommitOutput(),
|
||||
)
|
||||
if !hasPreimage {
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user