Skip to content
25 changes: 25 additions & 0 deletions channeldb/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@ type openChannelTlvData struct {
// Note: if not set, it means either the channel has not been
// closed yet, or it was closed before this field was introduced.
closeConfirmationHeight tlv.OptionalRecordT[tlv.TlvType9, uint32]

// revocationAuxSigs is an optional blob carrying the revocation aux
// signatures attached to the most recently sent RevokeAndAck. It is
// only ever set for aux/custom (taproot asset) channels, so the same
// signatures can be re-attached if that RevokeAndAck has to be
// retransmitted on channel reestablish.
revocationAuxSigs tlv.OptionalRecordT[tlv.TlvType10, tlv.Blob]
}

// encode serializes the openChannelTlvData to the given io.Writer.
Expand Down Expand Up @@ -313,6 +320,11 @@ func (c *openChannelTlvData) encode(w io.Writer) error {
tlvRecords = append(tlvRecords, h.Record())
},
)
c.revocationAuxSigs.WhenSome(
func(sigs tlv.RecordT[tlv.TlvType10, tlv.Blob]) {
tlvRecords = append(tlvRecords, sigs.Record())
},
)

tlv.SortRecords(tlvRecords)

Expand All @@ -331,6 +343,7 @@ func (c *openChannelTlvData) decode(r io.Reader) error {
tapscriptRoot := c.tapscriptRoot.Zero()
blob := c.customBlob.Zero()
closeConfHeight := c.closeConfirmationHeight.Zero()
revocationAuxSigs := c.revocationAuxSigs.Zero()

// Create the tlv stream.
tlvStream, err := tlv.NewStream(
Expand All @@ -343,6 +356,7 @@ func (c *openChannelTlvData) decode(r io.Reader) error {
blob.Record(),
c.confirmationHeight.Record(),
closeConfHeight.Record(),
revocationAuxSigs.Record(),
)
if err != nil {
return err
Expand All @@ -365,6 +379,9 @@ func (c *openChannelTlvData) decode(r io.Reader) error {
if _, ok := tlvs[closeConfHeight.TlvType()]; ok {
c.closeConfirmationHeight = tlv.SomeRecordT(closeConfHeight)
}
if _, ok := tlvs[revocationAuxSigs.TlvType()]; ok {
c.revocationAuxSigs = tlv.SomeRecordT(revocationAuxSigs)
}

return nil
}
Expand Down Expand Up @@ -630,6 +647,9 @@ func amendOpenChannelTlvData(channel *OpenChannel, auxData openChannelTlvData) {
auxData.closeConfirmationHeight.WhenSomeV(func(h uint32) {
channel.CloseConfirmationHeight = fn.Some(h)
})
auxData.revocationAuxSigs.WhenSomeV(func(sigs tlv.Blob) {
channel.RevocationAuxSigs = fn.Some(sigs)
})
}

// extractOpenChannelTlvData creates a new openChannelTlvData from the given
Expand Down Expand Up @@ -673,6 +693,11 @@ func extractOpenChannelTlvData(channel *OpenChannel) openChannelTlvData {
tlv.NewPrimitiveRecord[tlv.TlvType9](h),
)
})
channel.RevocationAuxSigs.WhenSome(func(sigs tlv.Blob) {
auxData.revocationAuxSigs = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType10](sigs),
)
})

return auxData
}
Expand Down
4 changes: 3 additions & 1 deletion channeldb/channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,9 @@ func TestChannelStateTransition(t *testing.T) {
},
}

_, err = channel.UpdateCommitment(&commitment, unsignedAckedUpdates)
_, err = channel.UpdateCommitment(
&commitment, unsignedAckedUpdates, fn.None[tlv.Blob](),
)
require.NoError(t, err, "unable to update commitment")

// Assert that update is correctly written to the database.
Expand Down
4 changes: 3 additions & 1 deletion channeldb/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import (
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/btcsuite/btcd/chainhash/v2"
"github.com/btcsuite/btcd/wire/v2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -382,7 +384,7 @@ func TestRestoreChannelShells(t *testing.T) {
// Ensure that it isn't possible to modify the commitment state machine
// of this restored channel.
channel := nodeChans[0]
_, err = channel.UpdateCommitment(nil, nil)
_, err = channel.UpdateCommitment(nil, nil, fn.None[tlv.Blob]())
if err != ErrNoRestoredChannelMutation {
t.Fatalf("able to mutate restored channel")
}
Expand Down
19 changes: 18 additions & 1 deletion chanstate/open_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ type OpenChannel struct {
// immutable.
CustomBlob fn.Option[tlv.Blob]

// RevocationAuxSigs is an optional blob carrying the revocation aux
// signatures attached to the most recently sent RevokeAndAck. It is
// only ever set for aux/custom (taproot asset) channels, and is
// persisted so the exact same signatures can be re-attached if that
// RevokeAndAck has to be retransmitted on channel reestablish. Only
// the latest revocation can ever be owed to the peer, so a single
// slot suffices; it is overwritten on every revocation.
RevocationAuxSigs fn.Option[tlv.Blob]

// Db persists channel state through the Store contract. This field
// intentionally keeps the existing name while callers still construct
// channels through the channeldb compatibility alias. The store
Expand Down Expand Up @@ -791,7 +800,8 @@ func (c *OpenChannel) SyncPending(addr net.Addr, pendingHeight uint32) error {
// commitment. Keys correspond to htlc indices and values indicate whether the
// htlc was settled or failed.
func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment,
unsignedAckedUpdates []LogUpdate) (map[uint64]bool, error) {
unsignedAckedUpdates []LogUpdate,
revocationAuxSigs fn.Option[tlv.Blob]) (map[uint64]bool, error) {

c.Lock()
defer c.Unlock()
Expand All @@ -803,6 +813,13 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment,
return nil, ErrNoRestoredChannelMutation
}

// Record the aux sigs attached to the RevokeAndAck this commitment
// update corresponds to (custom channels only, None otherwise), so
// they persist alongside it and survive for retransmission. This
// overwrites the previous revocation's sigs, which can no longer be
// owed to the peer.
c.RevocationAuxSigs = revocationAuxSigs

finalHtlcs, err := c.Db.UpdateChannelCommitment(
c, newCommitment, unsignedAckedUpdates,
)
Expand Down
2 changes: 2 additions & 0 deletions contractcourt/breach_arbitrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,7 @@ func testBreachSpends(t *testing.T, test breachTest) {
fn.Some[lnwallet.AuxContractResolver](
&lnwallet.MockAuxContractResolver{},
),
fn.None[lnwallet.AuxSigner](),
)
require.NoError(t, err, "unable to create breach retribution")

Expand Down Expand Up @@ -1802,6 +1803,7 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) {
fn.Some[lnwallet.AuxContractResolver](
&lnwallet.MockAuxContractResolver{},
),
fn.None[lnwallet.AuxSigner](),
)
require.NoError(t, err, "unable to create breach retribution")

Expand Down
2 changes: 2 additions & 0 deletions contractcourt/chain_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,7 @@ func (c *ChainArbitrator) WatchNewChannel(
extractStateNumHint: lnwallet.GetStateNumHint,
auxLeafStore: c.cfg.AuxLeafStore,
auxResolver: c.cfg.AuxResolver,
auxSigner: c.cfg.AuxSigner,
auxCloser: c.cfg.AuxCloser,
chanCloseConfs: c.cfg.ChannelCloseConfs,
notifyEarlyCoopClose: c.cfg.NotifyEarlyClosedChannel,
Expand Down Expand Up @@ -1385,6 +1386,7 @@ func (c *ChainArbitrator) loadOpenChannels() error {
extractStateNumHint: lnwallet.GetStateNumHint,
auxLeafStore: c.cfg.AuxLeafStore,
auxResolver: c.cfg.AuxResolver,
auxSigner: c.cfg.AuxSigner,
auxCloser: c.cfg.AuxCloser,
chanCloseConfs: c.cfg.ChannelCloseConfs,
notifyEarlyCoopClose: notifyEarlyClose,
Expand Down
8 changes: 7 additions & 1 deletion contractcourt/chain_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ type chainWatcherConfig struct {
// auxResolver is used to supplement contract resolution.
auxResolver fn.Option[lnwallet.AuxContractResolver]

// auxSigner is an optional signer that can be used to determine
// channel-specific HTLC sighash types based on negotiated features.
auxSigner fn.Option[lnwallet.AuxSigner]

// auxCloser is used to finalize cooperative closes.
auxCloser fn.Option[AuxChanCloser]

Expand Down Expand Up @@ -1160,6 +1164,7 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
retribution, err := lnwallet.NewBreachRetribution(
c.cfg.chanState, broadcastStateNum, spendHeight,
commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
c.cfg.auxSigner,
)

switch {
Expand Down Expand Up @@ -1574,7 +1579,7 @@ func (c *chainWatcher) dispatchLocalForceClose(
forceClose, err := lnwallet.NewLocalForceCloseSummary(
c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx,
uint32(commitSpend.SpendingHeight), stateNum,
c.cfg.auxLeafStore, c.cfg.auxResolver,
c.cfg.auxLeafStore, c.cfg.auxResolver, c.cfg.auxSigner,
)
if err != nil {
return err
Expand Down Expand Up @@ -1681,6 +1686,7 @@ func (c *chainWatcher) dispatchRemoteForceClose(
uniClose, err := lnwallet.NewUnilateralCloseSummary(
c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
c.cfg.auxSigner,
)
if err != nil {
return err
Expand Down
3 changes: 3 additions & 0 deletions contractcourt/commit_sweep_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ type mockSweeper struct {
createSweepTxChan chan *wire.MsgTx

deadlines []int
budgets []btcutil.Amount
}

func newMockSweeper() *mockSweeper {
Expand All @@ -142,6 +143,8 @@ func (s *mockSweeper) SweepInput(input input.Input, params sweep.Params) (
s.deadlines = append(s.deadlines, int(d))
})

s.budgets = append(s.budgets, params.Budget)

result := make(chan sweep.Result, 1)
result <- sweep.Result{
Tx: s.sweepTx,
Expand Down
Loading
Loading