From 769500dfb7fd44c50e9d335b73047732703262c1 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Tue, 26 May 2026 18:17:28 +0000 Subject: [PATCH] lnwallet: thread CommitHeight through ResolutionReq for aux resolution Add ResolutionReq.CommitHeight (fn.Option[uint64]) so aux resolvers can detect resolution of a height-0 commitment, i.e. an immediate post-funding force close, before the channel_ready commit-point rotation. At height 0, KeyRing is itself derived from the initial commitment point, so resolvers can use KeyRing directly when CommitHeight is Some(0). CommitHeight is populated at every ResolutionReq construction site: NewLocalForceCloseSummary, NewUnilateralCloseSummary, NewBreachRetribution (local and remote breach output), and the four HTLC resolution sites (extractHtlcResolutions / newOutgoingHtlcResolution / newIncomingHtlcResolution now take a stateNum parameter). Co-Authored-By: Claude Opus 4.7 (1M context) --- lnwallet/aux_resolutions.go | 7 +++++++ lnwallet/channel.go | 35 +++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/lnwallet/aux_resolutions.go b/lnwallet/aux_resolutions.go index 14802c57c7b..35d0bb37d14 100644 --- a/lnwallet/aux_resolutions.go +++ b/lnwallet/aux_resolutions.go @@ -94,6 +94,13 @@ type ResolutionReq struct { // KeyRing is the key ring for the channel. KeyRing *CommitmentKeyRing + // CommitHeight is the commitment height of the commitment being + // resolved, when known. Downstream resolvers can use this to detect a + // height-0 commitment (i.e. an immediate post-funding force close, + // before the channel_ready commit point rotation), where KeyRing is + // derived from the initial commitment point. + CommitHeight fn.Option[uint64] + // CsvDelay is the CSV delay for the local output for this commitment. CsvDelay uint32 diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 78ca895655a..f87615582b8 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2282,6 +2282,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, CommitTxBlockHeight: breachHeight, SignDesc: *br.LocalOutputSignDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), CsvDelay: ourDelay, BreachCsvDelay: fn.Some(theirDelay), CommitFee: chanState.RemoteCommitment.CommitFee, @@ -2365,6 +2366,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, CommitTxBlockHeight: breachHeight, SignDesc: *br.RemoteOutputSignDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), CsvDelay: theirDelay, BreachCsvDelay: fn.Some(theirDelay), CommitFee: chanState.RemoteCommitment.CommitFee, @@ -7119,7 +7121,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen chainfee.SatPerKWeight(remoteCommit.FeePerKw), commitType, signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, commitSpend.SpendingTx, - commitTxHeight, chanState.ChanType, + commitTxHeight, remoteCommit.CommitHeight, chanState.ChanType, isRemoteInitiator, leaseExpiry, chanState, auxResult.AuxLeaves, auxResolver, ) @@ -7225,6 +7227,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, //nolint:funlen ContractPoint: *selfPoint, SignDesc: commitResolution.SelfOutputSignDesc, KeyRing: keyRing, + CommitHeight: fn.Some(remoteCommit.CommitHeight), CsvDelay: maturityDelay, CommitFee: chanState.RemoteCommitment.CommitFee, } @@ -7412,7 +7415,8 @@ type HtlcResolutions struct { // the remote party's commitment transaction. func newOutgoingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx, - commitTxHeight uint32, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, + commitTxHeight uint32, stateNum uint64, htlc *channeldb.HTLC, + keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, chanType channeldb.ChannelType, chanState *channeldb.OpenChannel, @@ -7503,6 +7507,7 @@ func newOutgoingHtlcResolution(signer input.Signer, ContractPoint: op, SignDesc: signDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), CsvDelay: htlcCsvDelay, CltvDelay: fn.Some(htlc.RefundTimeout), CommitFee: chanState.RemoteCommitment.CommitFee, @@ -7739,6 +7744,7 @@ func newOutgoingHtlcResolution(signer input.Signer, ContractPoint: op, SignDesc: sweepSignDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), CsvDelay: htlcCsvDelay, HtlcAmt: btcutil.Amount(txOut.Value), CommitCsvDelay: csvDelay, @@ -7786,7 +7792,8 @@ func newOutgoingHtlcResolution(signer input.Signer, // TODO(roasbeef) consolidate code with above func func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx, - commitTxHeight uint32, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing, + commitTxHeight uint32, stateNum uint64, htlc *channeldb.HTLC, + keyRing *CommitmentKeyRing, feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32, whoseCommit lntypes.ChannelParty, isCommitFromInitiator bool, chanType channeldb.ChannelType, chanState *channeldb.OpenChannel, @@ -7876,6 +7883,7 @@ func newIncomingHtlcResolution(signer input.Signer, ContractPoint: op, SignDesc: signDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), HtlcID: fn.Some(htlc.HtlcIndex), CsvDelay: htlcCsvDelay, CltvDelay: fn.Some(htlc.RefundTimeout), @@ -8103,6 +8111,7 @@ func newIncomingHtlcResolution(signer input.Signer, ContractPoint: op, SignDesc: sweepSignDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), HtlcID: fn.Some(htlc.HtlcIndex), CsvDelay: htlcCsvDelay, CommitFee: chanState.LocalCommitment.CommitFee, @@ -8172,7 +8181,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, whoseCommit lntypes.ChannelParty, signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing, localChanCfg, remoteChanCfg *channeldb.ChannelConfig, - commitTx *wire.MsgTx, commitTxHeight uint32, + commitTx *wire.MsgTx, commitTxHeight uint32, stateNum uint64, chanType channeldb.ChannelType, isCommitFromInitiator bool, leaseExpiry uint32, chanState *channeldb.OpenChannel, auxLeaves fn.Option[CommitAuxLeaves], @@ -8209,9 +8218,10 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, // as we can satisfy the contract. ihr, err := newIncomingHtlcResolution( signer, localChanCfg, commitTx, commitTxHeight, - &htlc, keyRing, feePerKw, uint32(csvDelay), - leaseExpiry, whoseCommit, isCommitFromInitiator, - chanType, chanState, auxLeaves, auxResolver, + stateNum, &htlc, keyRing, feePerKw, + uint32(csvDelay), leaseExpiry, whoseCommit, + isCommitFromInitiator, chanType, chanState, + auxLeaves, auxResolver, ) if err != nil { return nil, fmt.Errorf("incoming resolution "+ @@ -8223,10 +8233,10 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, } ohr, err := newOutgoingHtlcResolution( - signer, localChanCfg, commitTx, commitTxHeight, &htlc, - keyRing, feePerKw, uint32(csvDelay), leaseExpiry, - whoseCommit, isCommitFromInitiator, chanType, chanState, - auxLeaves, auxResolver, + signer, localChanCfg, commitTx, commitTxHeight, + stateNum, &htlc, keyRing, feePerKw, uint32(csvDelay), + leaseExpiry, whoseCommit, isCommitFromInitiator, + chanType, chanState, auxLeaves, auxResolver, ) if err != nil { return nil, fmt.Errorf("outgoing resolution "+ @@ -8539,6 +8549,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, ContractPoint: commitResolution.SelfOutPoint, SignDesc: commitResolution.SelfOutputSignDesc, KeyRing: keyRing, + CommitHeight: fn.Some(stateNum), CsvDelay: csvTimeout, CommitFee: chanState.LocalCommitment.CommitFee, }) @@ -8560,7 +8571,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, htlcResolutions, err := extractHtlcResolutions( chainfee.SatPerKWeight(localCommit.FeePerKw), lntypes.Local, signer, localCommit.Htlcs, keyRing, &chanState.LocalChanCfg, - &chanState.RemoteChanCfg, commitTx, commitTxHeight, + &chanState.RemoteChanCfg, commitTx, commitTxHeight, stateNum, chanState.ChanType, chanState.IsInitiator, leaseExpiry, chanState, auxResult.AuxLeaves, auxResolver, )