diff --git a/contractcourt/utxonursery.go b/contractcourt/utxonursery.go index a763679ed..f288fedb6 100644 --- a/contractcourt/utxonursery.go +++ b/contractcourt/utxonursery.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" @@ -368,10 +369,23 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, // second-layer HTLC output. We effectively skip the baby stage (as the // timelock is zero), and enter the kid stage. for _, htlcRes := range incomingHtlcs { + // Based on the input pk script of the sign descriptor, we can + // determine if this is a taproot output or not. This'll + // determine the witness type we try to set below. + isTaproot := txscript.IsPayToTaproot( + htlcRes.SweepSignDesc.Output.PkScript, + ) + + var witType input.StandardWitnessType + if isTaproot { + witType = input.TaprootHtlcAcceptedSuccessSecondLevel + } else { + witType = input.HtlcAcceptedSuccessSecondLevel + } + htlcOutput := makeKidOutput( &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay, - input.HtlcAcceptedSuccessSecondLevel, - &htlcRes.SweepSignDesc, 0, + witType, &htlcRes.SweepSignDesc, 0, ) if htlcOutput.Amount() > 0 { @@ -396,6 +410,20 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, continue } + // Based on the input pk script of the sign descriptor, we can + // determine if this is a taproot output or not. This'll + // determine the witness type we try to set below. + isTaproot := txscript.IsPayToTaproot( + htlcRes.SweepSignDesc.Output.PkScript, + ) + + var witType input.StandardWitnessType + if isTaproot { + witType = input.TaprootHtlcOfferedRemoteTimeout + } else { + witType = input.HtlcOfferedRemoteTimeout + } + // Otherwise, this is actually a kid output as we can sweep it // once the commitment transaction confirms, and the absolute // CLTV lock has expired. We set the CSV delay what the @@ -403,8 +431,7 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint, // accordingly. htlcOutput := makeKidOutput( &htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay, - input.HtlcOfferedRemoteTimeout, - &htlcRes.SweepSignDesc, htlcRes.Expiry, + witType, &htlcRes.SweepSignDesc, htlcRes.Expiry, ) kidOutputs = append(kidOutputs, htlcOutput) } @@ -516,13 +543,15 @@ func (u *UtxoNursery) NurseryReport( // confirmation of the commitment transaction. switch kid.WitnessType() { - case input.HtlcAcceptedSuccessSecondLevel: + case input.HtlcAcceptedSuccessSecondLevel, + input.TaprootHtlcAcceptedSuccessSecondLevel: // An HTLC output on our commitment transaction // where the second-layer transaction hasn't // yet confirmed. report.AddLimboStage1SuccessHtlc(&kid) - case input.HtlcOfferedRemoteTimeout: + case input.HtlcOfferedRemoteTimeout, + input.TaprootHtlcOfferedRemoteTimeout: // This is an HTLC output on the // commitment transaction of the remote // party. We are waiting for the CLTV @@ -537,7 +566,8 @@ func (u *UtxoNursery) NurseryReport( // types. switch kid.WitnessType() { - case input.HtlcOfferedRemoteTimeout: + case input.HtlcOfferedRemoteTimeout, + input.TaprootHtlcOfferedRemoteTimeout: // This is an HTLC output on the // commitment transaction of the remote // party. The CLTV timelock has @@ -545,6 +575,10 @@ func (u *UtxoNursery) NurseryReport( // it. report.AddLimboDirectHtlc(&kid) + case input.TaprootHtlcAcceptedSuccessSecondLevel: + fallthrough + case input.TaprootHtlcOfferedTimeoutSecondLevel: + fallthrough case input.HtlcAcceptedSuccessSecondLevel: fallthrough case input.HtlcOfferedTimeoutSecondLevel: @@ -561,10 +595,16 @@ func (u *UtxoNursery) NurseryReport( // balance. switch kid.WitnessType() { + case input.TaprootHtlcAcceptedSuccessSecondLevel: + fallthrough + case input.TaprootHtlcOfferedTimeoutSecondLevel: + fallthrough case input.HtlcAcceptedSuccessSecondLevel: fallthrough case input.HtlcOfferedTimeoutSecondLevel: fallthrough + case input.TaprootHtlcOfferedRemoteTimeout: + fallthrough case input.HtlcOfferedRemoteTimeout: // This htlc output successfully // resides in a p2wkh output belonging @@ -731,7 +771,8 @@ func (u *UtxoNursery) graduateClass(classHeight uint32) error { // Fetch all information about the crib and kindergarten outputs at // this height. kgtnOutputs, cribOutputs, err := u.cfg.Store.FetchClass( - classHeight) + classHeight, + ) if err != nil { return err } @@ -1225,7 +1266,17 @@ func makeBabyOutput(chanPoint *wire.OutPoint, htlcOutpoint := htlcResolution.ClaimOutpoint blocksToMaturity := htlcResolution.CsvDelay - witnessType := input.HtlcOfferedTimeoutSecondLevel + + isTaproot := txscript.IsPayToTaproot( + htlcResolution.SweepSignDesc.Output.PkScript, + ) + + var witnessType input.StandardWitnessType + if isTaproot { + witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel + } else { + witnessType = input.HtlcOfferedTimeoutSecondLevel + } kid := makeKidOutput( &htlcOutpoint, chanPoint, blocksToMaturity, witnessType, @@ -1313,6 +1364,8 @@ func makeKidOutput(outpoint, originChanPoint *wire.OutPoint, // transaction, or is an outgoing HTLC on the commitment transaction of // the remote peer. isHtlc := (witnessType == input.HtlcAcceptedSuccessSecondLevel || + witnessType == input.TaprootHtlcAcceptedSuccessSecondLevel || + witnessType == input.TaprootHtlcOfferedRemoteTimeout || witnessType == input.HtlcOfferedRemoteTimeout) // heightHint can be safely set to zero here, because after this @@ -1389,7 +1442,17 @@ func (k *kidOutput) Encode(w io.Writer) error { return err } - return input.WriteSignDescriptor(w, k.SignDesc()) + if err := input.WriteSignDescriptor(w, k.SignDesc()); err != nil { + return err + } + + if k.SignDesc().ControlBlock == nil { + return nil + } + + // If this is a taproot output, then it'll also have a control block, + // so we'll go ahead and write that now. + return wire.WriteVarBytes(w, 1000, k.SignDesc().ControlBlock) } // Decode takes a byte array representation of a kidOutput and converts it to an @@ -1436,7 +1499,27 @@ func (k *kidOutput) Decode(r io.Reader) error { } k.witnessType = input.StandardWitnessType(byteOrder.Uint16(scratch[:2])) - return input.ReadSignDescriptor(r, &k.signDesc) + if err := input.ReadSignDescriptor(r, &k.signDesc); err != nil { + return err + } + + // If there's anything left in the reader, then this is a taproot + // output that also wrote a control block. + ctrlBlock, err := wire.ReadVarBytes(r, 0, 1000, "control block") + switch { + // If there're no bytes remaining, then we'll return early. + case errors.Is(err, io.EOF): + fallthrough + case errors.Is(err, io.ErrUnexpectedEOF): + return nil + + case err != nil: + return err + } + + k.signDesc.ControlBlock = ctrlBlock + + return nil } // TODO(bvu): copied from channeldb, remove repetition diff --git a/input/signdescriptor.go b/input/signdescriptor.go index 3ed18cf11..a01c939ae 100644 --- a/input/signdescriptor.go +++ b/input/signdescriptor.go @@ -225,8 +225,6 @@ func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error { return err } - // TODO(roasbeef): also write ctrl block? - return nil }