From 222f49c5936ad18ed94910bfd47a3690e3098111 Mon Sep 17 00:00:00 2001 From: Nishant Bansal Date: Thu, 11 Sep 2025 18:26:16 +0530 Subject: [PATCH] channeldb: add MarkConfirmationHeight to OpenChannel Add the MarkConfirmationHeight method to the OpenChannel struct to record the block height at which the funding transaction was first confirmed. Also, introduce the ConfirmationHeight field to persist this information in the database. Signed-off-by: Nishant Bansal --- channeldb/channel.go | 47 ++++++++++++++++++++++++++ channeldb/channel_test.go | 69 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index e16537f1c..d57647594 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -261,6 +261,10 @@ type openChannelTlvData struct { // customBlob is an optional TLV encoded blob of data representing // custom channel funding information. customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob] + + // confirmationHeight records the block height at which the funding + // transaction was first confirmed. + confirmationHeight tlv.RecordT[tlv.TlvType8, uint32] } // encode serializes the openChannelTlvData to the given io.Writer. @@ -270,6 +274,7 @@ func (c *openChannelTlvData) encode(w io.Writer) error { c.initialLocalBalance.Record(), c.initialRemoteBalance.Record(), c.realScid.Record(), + c.confirmationHeight.Record(), } c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) { tlvRecords = append(tlvRecords, memo.Record()) @@ -283,6 +288,8 @@ func (c *openChannelTlvData) encode(w io.Writer) error { tlvRecords = append(tlvRecords, blob.Record()) }) + tlv.SortRecords(tlvRecords) + // Create the tlv stream. tlvStream, err := tlv.NewStream(tlvRecords...) if err != nil { @@ -307,6 +314,7 @@ func (c *openChannelTlvData) decode(r io.Reader) error { memo.Record(), tapscriptRoot.Record(), blob.Record(), + c.confirmationHeight.Record(), ) if err != nil { return err @@ -906,6 +914,10 @@ type OpenChannel struct { // been confirmed before a certain height. FundingBroadcastHeight uint32 + // ConfirmationHeight records the block height at which the funding + // transaction was first confirmed. + ConfirmationHeight uint32 + // NumConfsRequired is the number of confirmations a channel's funding // transaction must have received in order to be considered available // for normal transactional use. @@ -1207,6 +1219,7 @@ func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) { auxData.initialRemoteBalance.Val, ) c.confirmedScid = auxData.realScid.Val + c.ConfirmationHeight = auxData.confirmationHeight.Val auxData.memo.WhenSomeV(func(memo []byte) { c.Memo = memo @@ -1234,6 +1247,9 @@ func (c *OpenChannel) extractTlvData() openChannelTlvData { realScid: tlv.NewRecordT[tlv.TlvType4]( c.confirmedScid, ), + confirmationHeight: tlv.NewPrimitiveRecord[tlv.TlvType8]( + c.ConfirmationHeight, + ), } if len(c.Memo) != 0 { @@ -1501,6 +1517,37 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { return putOpenChannel(chanBucket, c) } +// MarkConfirmationHeight updates the channel's confirmation height once the +// channel opening transaction receives one confirmation. +func (c *OpenChannel) MarkConfirmationHeight(height uint32) error { + c.Lock() + defer c.Unlock() + + if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { + chanBucket, err := fetchChanBucketRw( + tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, + ) + if err != nil { + return err + } + + channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint) + if err != nil { + return err + } + + channel.ConfirmationHeight = height + + return putOpenChannel(chanBucket, channel) + }, func() {}); err != nil { + return err + } + + c.ConfirmationHeight = height + + return nil +} + // MarkAsOpen marks a channel as fully open given a locator that uniquely // describes its location within the chain. func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error { diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index cd7d8db27..475040677 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -978,6 +978,73 @@ func TestChannelStateTransition(t *testing.T) { require.Empty(t, fwdPkgs, "no forwarding packages should exist") } +// TestOpeningChannelTxConfirmation verifies that calling MarkConfirmationHeight +// correctly updates the confirmed state. It also ensures that calling Refresh +// on a different OpenChannel updates its in-memory state to reflect the prior +// MarkConfirmationHeight call. +func TestOpeningChannelTxConfirmation(t *testing.T) { + t.Parallel() + + fullDB, err := MakeTestDB(t) + require.NoError(t, err) + + cdb := fullDB.ChannelStateDB() + + // Create a pending channel that was broadcast at height 99. + const broadcastHeight = uint32(99) + channelState := createTestChannel( + t, cdb, pendingHeightOption(broadcastHeight), + ) + + // Fetch pending channels from the database. + pendingChannels, err := cdb.FetchPendingChannels() + require.NoError(t, err) + require.Len(t, pendingChannels, 1) + + // Verify the broadcast height of the pending channel. + require.Equal( + t, broadcastHeight, pendingChannels[0].FundingBroadcastHeight, + ) + + confirmationHeight := broadcastHeight + 1 + + // Mark the channel's confirmation height. + err = pendingChannels[0].MarkConfirmationHeight(confirmationHeight) + require.NoError(t, err) + + // Verify the ConfirmationHeight is updated correctly. + require.Equal( + t, confirmationHeight, pendingChannels[0].ConfirmationHeight, + ) + + // Re-fetch the pending channels to confirm persistence. + pendingChannels, err = cdb.FetchPendingChannels() + require.NoError(t, err) + require.Len(t, pendingChannels, 1) + + // Validate the confirmation and broadcast height. + require.Equal( + t, confirmationHeight, pendingChannels[0].ConfirmationHeight, + ) + require.Equal( + t, broadcastHeight, pendingChannels[0].FundingBroadcastHeight, + ) + + // Ensure the original channel state's confirmation height is not + // updated before refresh. + require.EqualValues(t, channelState.ConfirmationHeight, 0) + + // Refresh the original channel state. + err = channelState.Refresh() + require.NoError(t, err) + + // Verify that both channel states now have the same ConfirmationHeight. + require.Equal( + t, channelState.ConfirmationHeight, + pendingChannels[0].ConfirmationHeight, + ) +} + func TestFetchPendingChannels(t *testing.T) { t.Parallel() @@ -1007,7 +1074,7 @@ func TestFetchPendingChannels(t *testing.T) { } chanOpenLoc := lnwire.ShortChannelID{ - BlockHeight: 5, + BlockHeight: broadcastHeight + 1, TxIndex: 10, TxPosition: 15, }