From 19860bb9f672d1519802f7c97eb3b90dc2a5865f Mon Sep 17 00:00:00 2001 From: bitromortac Date: Tue, 23 Sep 2025 10:32:58 +0200 Subject: [PATCH] lnwallet: expose channel state We create a deep copy of the channel state as we want to later expose the data structure to the rpc, which already has helper methods to marshal this representation to the rpc representation. --- channeldb/channel.go | 99 ++++++++++++++++++++++++++++++++++++++++++++ lnwallet/channel.go | 7 ++++ 2 files changed, 106 insertions(+) diff --git a/channeldb/channel.go b/channeldb/channel.go index d57647594..e6b2bc483 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -746,6 +746,33 @@ func (c *ChannelCommitment) extractTlvData() commitTlvData { return auxData } +// copy returns a deep copy of the channel commitment. +func (c *ChannelCommitment) copy() ChannelCommitment { + c2 := *c + if c.CommitTx != nil { + c2.CommitTx = c.CommitTx.Copy() + } + if len(c.CommitSig) > 0 { + c2.CommitSig = make([]byte, len(c.CommitSig)) + copy(c2.CommitSig, c.CommitSig) + } + + c.CustomBlob.WhenSome(func(blob tlv.Blob) { + blobCopy := make([]byte, len(blob)) + copy(blobCopy, blob) + c2.CustomBlob = fn.Some(blobCopy) + }) + + if len(c.Htlcs) > 0 { + c2.Htlcs = make([]HTLC, len(c.Htlcs)) + for i, h := range c.Htlcs { + c2.Htlcs[i] = h.Copy() + } + } + + return c2 +} + // ChannelStatus is a bit vector used to indicate whether an OpenChannel is in // the default usable state, or a state where it shouldn't be used. type ChannelStatus uint64 @@ -4066,6 +4093,78 @@ func (c *OpenChannel) Snapshot() *ChannelSnapshot { return snapshot } +// Copy returns a deep copy of the channel state. +func (c *OpenChannel) Copy() *OpenChannel { + c.RLock() + defer c.RUnlock() + + clone := &OpenChannel{ + ChanType: c.ChanType, + ChainHash: c.ChainHash, + FundingOutpoint: c.FundingOutpoint, + ShortChannelID: c.ShortChannelID, + IsPending: c.IsPending, + IsInitiator: c.IsInitiator, + chanStatus: c.chanStatus, + FundingBroadcastHeight: c.FundingBroadcastHeight, + ConfirmationHeight: c.ConfirmationHeight, + NumConfsRequired: c.NumConfsRequired, + ChannelFlags: c.ChannelFlags, + IdentityPub: c.IdentityPub, + Capacity: c.Capacity, + TotalMSatSent: c.TotalMSatSent, + TotalMSatReceived: c.TotalMSatReceived, + InitialLocalBalance: c.InitialLocalBalance, + InitialRemoteBalance: c.InitialRemoteBalance, + LocalChanCfg: c.LocalChanCfg, + RemoteChanCfg: c.RemoteChanCfg, + LocalCommitment: c.LocalCommitment.copy(), + RemoteCommitment: c.RemoteCommitment.copy(), + RemoteCurrentRevocation: c.RemoteCurrentRevocation, + RemoteNextRevocation: c.RemoteNextRevocation, + RevocationProducer: c.RevocationProducer, + RevocationStore: c.RevocationStore, + Packager: c.Packager, + ThawHeight: c.ThawHeight, + LastWasRevoke: c.LastWasRevoke, + RevocationKeyLocator: c.RevocationKeyLocator, + confirmedScid: c.confirmedScid, + TapscriptRoot: c.TapscriptRoot, + } + + if c.FundingTxn != nil { + clone.FundingTxn = c.FundingTxn.Copy() + } + + if len(c.LocalShutdownScript) > 0 { + clone.LocalShutdownScript = make( + lnwire.DeliveryAddress, + len(c.LocalShutdownScript), + ) + copy(clone.LocalShutdownScript, c.LocalShutdownScript) + } + if len(c.RemoteShutdownScript) > 0 { + clone.RemoteShutdownScript = make( + lnwire.DeliveryAddress, + len(c.RemoteShutdownScript), + ) + copy(clone.RemoteShutdownScript, c.RemoteShutdownScript) + } + + if len(c.Memo) > 0 { + clone.Memo = make([]byte, len(c.Memo)) + copy(clone.Memo, c.Memo) + } + + c.CustomBlob.WhenSome(func(blob tlv.Blob) { + blobCopy := make([]byte, len(blob)) + copy(blobCopy, blob) + clone.CustomBlob = fn.Some(blobCopy) + }) + + return clone +} + // LatestCommitments returns the two latest commitments for both the local and // remote party. These commitments are read from disk to ensure that only the // latest fully committed state is returned. The first commitment returned is diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 484a019da..fb1e3ac4b 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -6576,6 +6576,13 @@ func (lc *LightningChannel) ChannelPoint() wire.OutPoint { return lc.channelState.FundingOutpoint } +// ChannelState returns a copy of the internal channeldb.OpenChannel state +// struct. Modifications to the returned struct will not be reflected within +// the LightningChannel. +func (lc *LightningChannel) ChannelState() *channeldb.OpenChannel { + return lc.channelState.Copy() +} + // ChannelID returns the ChannelID of this LightningChannel. This is the same // ChannelID that is used in update messages for this channel. func (lc *LightningChannel) ChannelID() lnwire.ChannelID {