From df2a2d83eaddfde06ae4a836e28162c1e33fde76 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 1 Mar 2023 22:16:42 -0800 Subject: [PATCH] contractcourt: update htlcSuccessResolver for taproot chans --- .../htlc_incoming_contest_resolver.go | 49 +++++++++---- contractcourt/htlc_success_resolver.go | 71 ++++++++++++++----- 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index f3df57b10..33f97c2cb 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -8,6 +8,7 @@ import ( "io" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/htlcswitch/hop" @@ -103,9 +104,9 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { // If we've locked in an htlc with an invalid payload on our // commitment tx, we don't need to resolve it. The other party - // will time it out and get their funds back. This situation can - // present itself when we crash before processRemoteAdds in the - // link has ran. + // will time it out and get their funds back. This situation + // can present itself when we crash before processRemoteAdds in + // the link has ran. h.resolved = true if err := h.processFinalHtlcFail(); err != nil { @@ -192,17 +193,41 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { log.Infof("%T(%v): applied preimage=%v", h, h.htlcResolution.ClaimOutpoint, preimage) + isSecondLevel := h.htlcResolution.SignedSuccessTx != nil + + // If we didn't have to go to the second level to claim (this + // is the remote commitment transaction), then we don't need to + // modify our canned witness. + if !isSecondLevel { + return nil + } + + isTaproot := txscript.IsPayToTaproot( + h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript, + ) + // If this is our commitment transaction, then we'll need to // populate the witness for the second-level HTLC transaction. - if h.htlcResolution.SignedSuccessTx != nil { - // Within the witness for the success transaction, the - // preimage is the 4th element as it looks like: - // - // * - // - // We'll populate it within the witness, as since this - // was a "contest" resolver, we didn't yet know of the - // preimage. + switch { + // For taproot channels, the witness for sweeping with sucess + // looks like: + // - + // + // + // So we'll insert it at the 3rd index of the witness. + case isTaproot: + //nolint:lll + h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:] + + // Within the witness for the success transaction, the + // preimage is the 4th element as it looks like: + // + // * <0> + // + // We'll populate it within the witness, as since this + // was a "contest" resolver, we didn't yet know of the + // preimage. + case !isTaproot: h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:] } diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 26a906ce4..5124ead19 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -7,6 +7,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" @@ -14,6 +15,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/labels" + "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/sweep" ) @@ -171,9 +173,9 @@ func (h *htlcSuccessResolver) broadcastSuccessTx() (*wire.OutPoint, error) { // If we have non-nil SignDetails, this means that have 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. 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. + // 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 { return h.broadcastReSignedSuccessTx() } @@ -233,16 +235,29 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() ( // 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 { log.Infof("%T(%x): offering second-layer transition tx to "+ "sweeper: %v", h, h.htlc.RHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx)) - secondLevelInput := input.MakeHtlcSecondLevelSuccessAnchorInput( - h.htlcResolution.SignedSuccessTx, - h.htlcResolution.SignDetails, h.htlcResolution.Preimage, - h.broadcastHeight, - ) + var secondLevelInput input.HtlcSecondLevelAnchorInput + if isTaproot { + secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput( + h.htlcResolution.SignedSuccessTx, + h.htlcResolution.SignDetails, h.htlcResolution.Preimage, + h.broadcastHeight, + ) + } else { + secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput( + h.htlcResolution.SignedSuccessTx, + h.htlcResolution.SignDetails, h.htlcResolution.Preimage, + h.broadcastHeight, + ) + } + _, err := h.Sweeper.SweepInput( &secondLevelInput, sweep.Params{ @@ -341,13 +356,20 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() ( // Let the sweeper sweep the second-level output now that the // CSV/CLTV locks have expired. + var witType input.StandardWitnessType + if isTaproot { + witType = input.TaprootHtlcAcceptedSuccessSecondLevel + } else { + witType = input.HtlcAcceptedSuccessSecondLevel + } inp := h.makeSweepInput( - op, input.HtlcAcceptedSuccessSecondLevel, + op, witType, input.LeaseHtlcAcceptedSuccessSecondLevel, &h.htlcResolution.SweepSignDesc, h.htlcResolution.CsvDelay, h.broadcastHeight, h.htlc.RHash, ) + // TODO(roasbeef): need to update above for leased types _, err = h.Sweeper.SweepInput( inp, sweep.Params{ @@ -377,17 +399,32 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput() ( log.Infof("%T(%x): crafting sweep tx for incoming+remote "+ "htlc confirmed", h, h.htlc.RHash[:]) + 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. - inp := input.MakeHtlcSucceedInput( - &h.htlcResolution.ClaimOutpoint, - &h.htlcResolution.SweepSignDesc, - h.htlcResolution.Preimage[:], - h.broadcastHeight, - h.htlcResolution.CsvDelay, - ) + var inp input.Input + if isTaproot { + inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput( + &h.htlcResolution.ClaimOutpoint, + &h.htlcResolution.SweepSignDesc, + h.htlcResolution.Preimage[:], + h.broadcastHeight, + h.htlcResolution.CsvDelay, + )) + } else { + inp = lnutils.Ptr(input.MakeHtlcSucceedInput( + &h.htlcResolution.ClaimOutpoint, + &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 @@ -400,7 +437,7 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput() ( // TODO: Use time-based sweeper and result chan. var err error h.sweepTx, err = h.Sweeper.CreateSweepTx( - []input.Input{&inp}, + []input.Input{inp}, sweep.FeePreference{ ConfTarget: sweepConfTarget, }, 0,