Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,11 @@ const (
// level tapscript commitment. This MUST be set along with the
// SimpleTaprootFeatureBit.
TapscriptRootBit ChannelType = 1 << 11

// TaprootFinalBit indicates that this is a MuSig2 channel using the
// final/production taproot scripts and feature bits 80/81. This MUST
// be set along with the SimpleTaprootFeatureBit.
TaprootFinalBit ChannelType = 1 << 12
)

// IsSingleFunder returns true if the channel type if one of the known single
Expand Down Expand Up @@ -488,6 +493,12 @@ func (c ChannelType) HasTapscriptRoot() bool {
return c&TapscriptRootBit == TapscriptRootBit
}

// IsTaprootFinal returns true if the channel is using final/production taproot
// scripts and feature bits.
func (c ChannelType) IsTaprootFinal() bool {
return c&TaprootFinalBit == TaprootFinalBit
}

// ChannelStateBounds are the parameters from OpenChannel and AcceptChannel
// that are responsible for providing bounds on the state space of the abstract
// channel state. These values must be remembered for normal channel operation
Expand Down
27 changes: 23 additions & 4 deletions contractcourt/channel_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2466,8 +2466,13 @@ func (c *ChannelArbitrator) prepContractResolutions(
continue
}

var chanType channeldb.ChannelType
if chanState != nil {
chanType = chanState.ChanType
}

resolver := newSuccessResolver(
resolution, height, htlc, resolverCfg,
resolution, height, htlc, chanType, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
Expand All @@ -2494,8 +2499,13 @@ func (c *ChannelArbitrator) prepContractResolutions(
continue
}

var chanType channeldb.ChannelType
if chanState != nil {
chanType = chanState.ChanType
}

resolver := newTimeoutResolver(
resolution, height, htlc, resolverCfg,
resolution, height, htlc, chanType, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
Expand Down Expand Up @@ -2534,8 +2544,13 @@ func (c *ChannelArbitrator) prepContractResolutions(
continue
}

var chanType channeldb.ChannelType
if chanState != nil {
chanType = chanState.ChanType
}

resolver := newIncomingContestResolver(
resolution, height, htlc,
resolution, height, htlc, chanType,
resolverCfg,
)
if chanState != nil {
Expand Down Expand Up @@ -2566,8 +2581,12 @@ func (c *ChannelArbitrator) prepContractResolutions(
continue
}

var chanType channeldb.ChannelType
if chanState != nil {
chanType = chanState.ChanType
}
resolver := newOutgoingContestResolver(
resolution, height, htlc, resolverCfg,
resolution, height, htlc, chanType, resolverCfg,
)
if chanState != nil {
resolver.SupplementState(chanState)
Expand Down
12 changes: 10 additions & 2 deletions contractcourt/commit_sweep_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,11 +506,19 @@ func (c *commitSweepResolver) decideWitnessType() (input.WitnessType, error) {
// commitment tweak to discern which type of commitment this is.
var witnessType input.WitnessType
switch {
// The local delayed output for a taproot channel.
// The local delayed output for a final taproot channel.
case isLocalCommitTx && c.chanType.IsTaprootFinal():
witnessType = input.TaprootLocalCommitSpendFinal

// The local delayed output for a staging taproot channel.
case isLocalCommitTx && c.chanType.IsTaproot():
witnessType = input.TaprootLocalCommitSpend

// The CSV 1 delayed output for a taproot channel.
// The CSV 1 delayed output for a final taproot channel.
case !isLocalCommitTx && c.chanType.IsTaprootFinal():
witnessType = input.TaprootRemoteCommitSpendFinal

// The CSV 1 delayed output for a staging taproot channel.
case !isLocalCommitTx && c.chanType.IsTaproot():
witnessType = input.TaprootRemoteCommitSpend

Expand Down
4 changes: 2 additions & 2 deletions contractcourt/htlc_incoming_contest_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ type htlcIncomingContestResolver struct {
// newIncomingContestResolver instantiates a new incoming htlc contest resolver.
func newIncomingContestResolver(
res lnwallet.IncomingHtlcResolution, broadcastHeight uint32,
htlc channeldb.HTLC, resCfg ResolverConfig) *htlcIncomingContestResolver {
htlc channeldb.HTLC, chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcIncomingContestResolver {

success := newSuccessResolver(
res, broadcastHeight, htlc, resCfg,
res, broadcastHeight, htlc, chanType, resCfg,
)

return &htlcIncomingContestResolver{
Expand Down
4 changes: 2 additions & 2 deletions contractcourt/htlc_outgoing_contest_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ type htlcOutgoingContestResolver struct {
// resolver.
func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcOutgoingContestResolver {
chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcOutgoingContestResolver {

timeout := newTimeoutResolver(
res, broadcastHeight, htlc, resCfg,
res, broadcastHeight, htlc, chanType, resCfg,
)

return &htlcOutgoingContestResolver{
Expand Down
28 changes: 25 additions & 3 deletions contractcourt/htlc_success_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
// htlc contains information on the htlc that we are resolving on-chain.
htlc channeldb.HTLC

// chanType denotes the type of channel the HTLC belongs to.
chanType channeldb.ChannelType

// currentReport stores the current state of the resolver for reporting
// over the rpc interface. This should only be reported in case we have
// a non-nil SignDetails on the htlcResolution, otherwise the nursery
Expand All @@ -67,13 +70,14 @@
// newSuccessResolver instanties a new htlc success resolver.
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcSuccessResolver {
chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcSuccessResolver {

h := &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(resCfg),
htlcResolution: res,
broadcastHeight: broadcastHeight,
htlc: htlc,
chanType: chanType,
}

h.initReport()
Expand Down Expand Up @@ -409,6 +413,11 @@
)
}

// isTaprootFinal returns true if the htlc output is from a final taproot channel.
func (h *htlcSuccessResolver) isTaprootFinal() bool {
return h.chanType.IsTaprootFinal()

Check failure on line 418 in contractcourt/htlc_success_resolver.go

View workflow job for this annotation

GitHub Actions / check commits

h.chanType.IsTaprootFinal undefined (type channeldb.ChannelType has no field or method IsTaprootFinal)

Check failure on line 418 in contractcourt/htlc_success_resolver.go

View workflow job for this annotation

GitHub Actions / check commits

h.chanType.IsTaprootFinal undefined (type channeldb.ChannelType has no field or method IsTaprootFinal)
}

// sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on
// the remote commitment via the direct preimage-spend.
func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error {
Expand All @@ -417,7 +426,18 @@
// sweeping transaction, and generate a witness.
var inp input.Input

if h.isTaproot() {
if h.isTaprootFinal() {
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInputFinal(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
input.WithResolutionBlob(
h.htlcResolution.ResolutionBlob,
),
))
} else if h.isTaproot() {
inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
Expand Down Expand Up @@ -562,7 +582,9 @@
// Let the sweeper sweep the second-level output now that the
// CSV/CLTV locks have expired.
var witType input.StandardWitnessType
if h.isTaproot() {
if h.isTaprootFinal() {
witType = input.TaprootHtlcAcceptedSuccessSecondLevelFinal
} else if h.isTaproot() {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else {
witType = input.HtlcAcceptedSuccessSecondLevel
Expand Down
19 changes: 16 additions & 3 deletions contractcourt/htlc_timeout_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
// htlc contains information on the htlc that we are resolving on-chain.
htlc channeldb.HTLC

// chanType denotes the type of channel the HTLC belongs to.
chanType channeldb.ChannelType

// currentReport stores the current state of the resolver for reporting
// over the rpc interface. This should only be reported in case we have
// a non-nil SignDetails on the htlcResolution, otherwise the nursery
Expand All @@ -70,13 +73,14 @@
// newTimeoutResolver instantiates a new timeout htlc resolver.
func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution,
broadcastHeight uint32, htlc channeldb.HTLC,
resCfg ResolverConfig) *htlcTimeoutResolver {
chanType channeldb.ChannelType, resCfg ResolverConfig) *htlcTimeoutResolver {

h := &htlcTimeoutResolver{
contractResolverKit: *newContractResolverKit(resCfg),
htlcResolution: res,
broadcastHeight: broadcastHeight,
htlc: htlc,
chanType: chanType,
}

h.initReport()
Expand All @@ -92,6 +96,11 @@
)
}

// isTaprootFinal returns true if the htlc output is from a final taproot channel.
func (h *htlcTimeoutResolver) isTaprootFinal() bool {
return h.chanType.IsTaprootFinal()

Check failure on line 101 in contractcourt/htlc_timeout_resolver.go

View workflow job for this annotation

GitHub Actions / check commits

h.chanType.IsTaprootFinal undefined (type channeldb.ChannelType has no field or method IsTaprootFinal)

Check failure on line 101 in contractcourt/htlc_timeout_resolver.go

View workflow job for this annotation

GitHub Actions / check commits

h.chanType.IsTaprootFinal undefined (type channeldb.ChannelType has no field or method IsTaprootFinal)
}

// outpoint returns the outpoint of the HTLC output we're attempting to sweep.
func (h *htlcTimeoutResolver) outpoint() wire.OutPoint {
// The primary key for this resolver will be the outpoint of the HTLC
Expand Down Expand Up @@ -515,7 +524,9 @@
// are resolved via this path.
func (h *htlcTimeoutResolver) sweepDirectHtlcOutput() error {
var htlcWitnessType input.StandardWitnessType
if h.isTaproot() {
if h.isTaprootFinal() {
htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeoutFinal
} else if h.isTaproot() {
htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout
} else {
htlcWitnessType = input.HtlcOfferedRemoteTimeout
Expand Down Expand Up @@ -1030,7 +1041,9 @@
}

var witType input.StandardWitnessType
if h.isTaproot() {
if h.isTaprootFinal() {
witType = input.TaprootHtlcOfferedTimeoutSecondLevelFinal
} else if h.isTaproot() {
witType = input.TaprootHtlcOfferedTimeoutSecondLevel
} else {
witType = input.HtlcOfferedTimeoutSecondLevel
Expand Down
39 changes: 34 additions & 5 deletions contractcourt/utxonursery.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,10 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
)

var witType input.StandardWitnessType
if isTaproot {
isProdTaproot := isTaproot && u.isProdTaprootResolution(htlcRes.ResolutionBlob)
if isProdTaproot {
witType = input.TaprootHtlcAcceptedSuccessSecondLevelFinal
} else if isTaproot {
witType = input.TaprootHtlcAcceptedSuccessSecondLevel
} else {
witType = input.HtlcAcceptedSuccessSecondLevel
Expand All @@ -409,7 +412,7 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
// a baby output as we need to go to the second level to sweep
// it.
if htlcRes.SignedTimeoutTx != nil {
htlcOutput := makeBabyOutput(
htlcOutput := u.makeBabyOutput(
&chanPoint, &htlcRes, deadlineHeight,
)

Expand All @@ -428,7 +431,10 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
)

var witType input.StandardWitnessType
if isTaproot {
isProdTaproot := isTaproot && u.isProdTaprootResolution(htlcRes.ResolutionBlob)
if isProdTaproot {
witType = input.TaprootHtlcOfferedRemoteTimeoutFinal
} else if isTaproot {
witType = input.TaprootHtlcOfferedRemoteTimeout
} else {
witType = input.HtlcOfferedRemoteTimeout
Expand Down Expand Up @@ -504,6 +510,16 @@ func (u *UtxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
return nil
}

// isProdTaprootResolution determines if a resolution blob indicates production taproot.
// For now, we use a simple heuristic: if there's a resolution blob, it's likely production.
// This can be refined later to parse the actual blob structure.
func (u *UtxoNursery) isProdTaprootResolution(resolutionBlob fn.Option[tlv.Blob]) bool {
// For production taproot channels, there should be a resolution blob
// containing auxiliary channel information. If no blob is present,
// this is likely a staging taproot channel.
return resolutionBlob.IsSome()
}

// NurseryReport attempts to return a nursery report stored for the target
// outpoint. A nursery report details the maturity/sweeping progress for a
// contract that was previously force closed. If a report entry for the target
Expand Down Expand Up @@ -556,6 +572,8 @@ func (u *UtxoNursery) NurseryReport(
switch kid.WitnessType() {

//nolint:ll
case input.TaprootHtlcAcceptedSuccessSecondLevelFinal:
fallthrough
case input.TaprootHtlcAcceptedSuccessSecondLevel:
fallthrough
case input.HtlcAcceptedSuccessSecondLevel:
Expand All @@ -566,6 +584,7 @@ func (u *UtxoNursery) NurseryReport(
report.AddLimboStage1SuccessHtlc(&kid)

case input.HtlcOfferedRemoteTimeout,
input.TaprootHtlcOfferedRemoteTimeoutFinal,
input.TaprootHtlcOfferedRemoteTimeout:
// This is an HTLC output on the
// commitment transaction of the remote
Expand All @@ -582,6 +601,7 @@ func (u *UtxoNursery) NurseryReport(
switch kid.WitnessType() {

case input.HtlcOfferedRemoteTimeout,
input.TaprootHtlcOfferedRemoteTimeoutFinal,
input.TaprootHtlcOfferedRemoteTimeout:
// This is an HTLC output on the
// commitment transaction of the remote
Expand Down Expand Up @@ -612,14 +632,20 @@ func (u *UtxoNursery) NurseryReport(
switch kid.WitnessType() {

//nolint:ll
case input.TaprootHtlcAcceptedSuccessSecondLevelFinal:
fallthrough
case input.TaprootHtlcAcceptedSuccessSecondLevel:
fallthrough
case input.TaprootHtlcOfferedTimeoutSecondLevelFinal:
fallthrough
case input.TaprootHtlcOfferedTimeoutSecondLevel:
fallthrough
case input.HtlcAcceptedSuccessSecondLevel:
fallthrough
case input.HtlcOfferedTimeoutSecondLevel:
fallthrough
case input.TaprootHtlcOfferedRemoteTimeoutFinal:
fallthrough
case input.TaprootHtlcOfferedRemoteTimeout:
fallthrough
case input.HtlcOfferedRemoteTimeout:
Expand Down Expand Up @@ -1303,7 +1329,7 @@ type babyOutput struct {
// makeBabyOutput constructs a baby output that wraps a future kidOutput. The
// provided sign descriptors and witness types will be used once the output
// reaches the delay and claim stage.
func makeBabyOutput(chanPoint *wire.OutPoint,
func (u *UtxoNursery) makeBabyOutput(chanPoint *wire.OutPoint,
htlcResolution *lnwallet.OutgoingHtlcResolution,
deadlineHeight fn.Option[int32]) babyOutput {

Expand All @@ -1315,7 +1341,10 @@ func makeBabyOutput(chanPoint *wire.OutPoint,
)

var witnessType input.StandardWitnessType
if isTaproot {
isProdTaproot := isTaproot && u.isProdTaprootResolution(htlcResolution.ResolutionBlob)
if isProdTaproot {
witnessType = input.TaprootHtlcOfferedTimeoutSecondLevelFinal
} else if isTaproot {
witnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
} else {
witnessType = input.HtlcOfferedTimeoutSecondLevel
Expand Down
Loading
Loading