What changed, and why it matters
This commit is a straightforward internal code reorganization. It moves two data structures, ChannelCommitment and HTLC, from one package (channeldb) to another (chanstate), and creates type aliases so existing code keeps working. The on-disk format and serialization logic are explicitly kept unchanged. There is no security fix or behavior change visible in the diff.
No security action required. Treat as normal refactoring review; verify that follow-up commits using the new chanstate package do not introduce serialization or state-handling changes.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors ChannelCommitment and HTLC definitions into a new chanstate package. channeldb now uses type aliases (ChannelCommitment = cstate.ChannelCommitment, HTLC = cstate.HTLC) and keeps all KV serialization, deserialization, and TLV helpers in place. Methods are converted to package-level helper functions where needed (e.g., extractCommitTlvData, amendCommitTlvData, serializeHtlcExtraData, deserializeHtlcExtraData). The Copy methods are moved to the new package with identical implementations. The commit message states the goal is to let upcoming store subinterfaces reference commitment state without importing channeldb, while preserving the current disk format.
Changed components
channeldb/channel.gochanstate/commitment.goInspect captured patch +243 / −225
diff --git a/channeldb/channel.go b/channeldb/channel.go
index bd810e7..40cb9f0 100644
--- a/channeldb/channel.go
+++ b/channeldb/channel.go
@@ -227,6 +227,15 @@ const (
indexStatusType tlv.Type = 0
)
+type (
+ // ChannelCommitment is a snapshot of the commitment state at a
+ // particular point in the commitment chain.
+ ChannelCommitment = cstate.ChannelCommitment
+
+ // HTLC is the on-disk representation of a hash time-locked contract.
+ HTLC = cstate.HTLC
+)
+
// openChannelTlvData houses the new data fields that are stored for each
// channel in a TLV stream within the root bucket. This is stored as a TLV
// stream appended to the existing hard-coded fields in the channel's root
@@ -518,97 +527,15 @@ func (c *commitTlvData) decode(r io.Reader) error {
return nil
}
-// ChannelCommitment is a snapshot of the commitment state at a particular
-// point in the commitment chain. With each state transition, a snapshot of the
-// current state along with all non-settled HTLCs are recorded. These snapshots
-// detail the state of the _remote_ party's commitment at a particular state
-// number. For ourselves (the local node) we ONLY store our most recent
-// (unrevoked) state for safety purposes.
-type ChannelCommitment struct {
- // CommitHeight is the update number that this ChannelDelta represents
- // the total number of commitment updates to this point. This can be
- // viewed as sort of a "commitment height" as this number is
- // monotonically increasing.
- CommitHeight uint64
-
- // LocalLogIndex is the cumulative log index index of the local node at
- // this point in the commitment chain. This value will be incremented
- // for each _update_ added to the local update log.
- LocalLogIndex uint64
-
- // LocalHtlcIndex is the current local running HTLC index. This value
- // will be incremented for each outgoing HTLC the local node offers.
- LocalHtlcIndex uint64
-
- // RemoteLogIndex is the cumulative log index index of the remote node
- // at this point in the commitment chain. This value will be
- // incremented for each _update_ added to the remote update log.
- RemoteLogIndex uint64
-
- // RemoteHtlcIndex is the current remote running HTLC index. This value
- // will be incremented for each outgoing HTLC the remote node offers.
- RemoteHtlcIndex uint64
-
- // LocalBalance is the current available settled balance within the
- // channel directly spendable by us.
- //
- // NOTE: This is the balance *after* subtracting any commitment fee,
- // AND anchor output values.
- LocalBalance lnwire.MilliSatoshi
-
- // RemoteBalance is the current available settled balance within the
- // channel directly spendable by the remote node.
- //
- // NOTE: This is the balance *after* subtracting any commitment fee,
- // AND anchor output values.
- RemoteBalance lnwire.MilliSatoshi
-
- // CommitFee is the amount calculated to be paid in fees for the
- // current set of commitment transactions. The fee amount is persisted
- // with the channel in order to allow the fee amount to be removed and
- // recalculated with each channel state update, including updates that
- // happen after a system restart.
- CommitFee btcutil.Amount
-
- // FeePerKw is the min satoshis/kilo-weight that should be paid within
- // the commitment transaction for the entire duration of the channel's
- // lifetime. This field may be updated during normal operation of the
- // channel as on-chain conditions change.
- //
- // TODO(halseth): make this SatPerKWeight. Cannot be done atm because
- // this will cause the import cycle lnwallet<->channeldb. Fee
- // estimation stuff should be in its own package.
- FeePerKw btcutil.Amount
-
- // CommitTx is the latest version of the commitment state, broadcast
- // able by us.
- CommitTx *wire.MsgTx
-
- // CustomBlob is an optional blob that can be used to store information
- // specific to a custom channel type. This may track some custom
- // specific state for this given commitment.
- CustomBlob fn.Option[tlv.Blob]
-
- // CommitSig is one half of the signature required to fully complete
- // the script for the commitment transaction above. This is the
- // signature signed by the remote party for our version of the
- // commitment transactions.
- CommitSig []byte
-
- // Htlcs is the set of HTLC's that are pending at this particular
- // commitment height.
- Htlcs []HTLC
-}
-
-// amendTlvData updates the channel with the given auxiliary TLV data.
-func (c *ChannelCommitment) amendTlvData(auxData commitTlvData) {
+// amendCommitTlvData updates the commitment with the given auxiliary TLV data.
+func amendCommitTlvData(c *ChannelCommitment, auxData commitTlvData) {
auxData.customBlob.WhenSomeV(func(blob tlv.Blob) {
c.CustomBlob = fn.Some(blob)
})
}
-// extractTlvData creates a new commitTlvData from the given commitment.
-func (c *ChannelCommitment) extractTlvData() commitTlvData {
+// extractCommitTlvData creates a new commitTlvData from the given commitment.
+func extractCommitTlvData(c *ChannelCommitment) commitTlvData {
var auxData commitTlvData
c.CustomBlob.WhenSome(func(blob tlv.Blob) {
@@ -620,33 +547,6 @@ 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 = cstate.ChannelStatus
@@ -2641,89 +2541,7 @@ func (c *OpenChannel) ActiveHtlcs() []HTLC {
return activeHtlcs
}
-// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are
-// contained within ChannelDeltas which encode the current state of the
-// commitment between state updates.
-//
-// TODO(roasbeef): save space by using smaller ints at tail end?
-type HTLC struct {
- // TODO(yy): can embed an HTLCEntry here.
-
- // Signature is the signature for the second level covenant transaction
- // for this HTLC. The second level transaction is a timeout tx in the
- // case that this is an outgoing HTLC, and a success tx in the case
- // that this is an incoming HTLC.
- //
- // TODO(roasbeef): make [64]byte instead?
- Signature []byte
-
- // RHash is the payment hash of the HTLC.
- RHash [32]byte
-
- // Amt is the amount of milli-satoshis this HTLC escrows.
- Amt lnwire.MilliSatoshi
-
- // RefundTimeout is the absolute timeout on the HTLC that the sender
- // must wait before reclaiming the funds in limbo.
- RefundTimeout uint32
-
- // OutputIndex is the output index for this particular HTLC output
- // within the commitment transaction.
- OutputIndex int32
-
- // Incoming denotes whether we're the receiver or the sender of this
- // HTLC.
- Incoming bool
-
- // OnionBlob is an opaque blob which is used to complete multi-hop
- // routing.
- OnionBlob [lnwire.OnionPacketSize]byte
-
- // HtlcIndex is the HTLC counter index of this active, outstanding
- // HTLC. This differs from the LogIndex, as the HtlcIndex is only
- // incremented for each offered HTLC, while they LogIndex is
- // incremented for each update (includes settle+fail).
- HtlcIndex uint64
-
- // LogIndex is the cumulative log index of this HTLC. This differs
- // from the HtlcIndex as this will be incremented for each new log
- // update added.
- LogIndex uint64
-
- // ExtraData contains any additional information that was transmitted
- // with the HTLC via TLVs. This data *must* already be encoded as a
- // TLV stream, and may be empty. The length of this data is naturally
- // limited by the space available to TLVs in update_add_htlc:
- // = 65535 bytes (bolt 8 maximum message size):
- // - 2 bytes (bolt 1 message_type)
- // - 32 bytes (channel_id)
- // - 8 bytes (id)
- // - 8 bytes (amount_msat)
- // - 32 bytes (payment_hash)
- // - 4 bytes (cltv_expiry)
- // - 1366 bytes (onion_routing_packet)
- // = 64083 bytes maximum possible TLV stream
- //
- // Note that this extra data is stored inline with the OnionBlob for
- // legacy reasons, see serialization/deserialization functions for
- // detail.
- ExtraData lnwire.ExtraOpaqueData
-
- // BlindingPoint is an optional blinding point included with the HTLC.
- //
- // Note: this field is not a part of on-disk representation of the
- // HTLC. It is stored in the ExtraData field, which is used to store
- // a TLV stream of additional information associated with the HTLC.
- BlindingPoint lnwire.BlindingPointRecord
-
- // CustomRecords is a set of custom TLV records that are associated with
- // this HTLC. These records are used to store additional information
- // about the HTLC that is not part of the standard HTLC fields. This
- // field is encoded within the ExtraData field.
- CustomRecords lnwire.CustomRecords
-}
-
-// serializeExtraData encodes a TLV stream of extra data to be stored with a
+// serializeHtlcExtraData encodes a TLV stream of extra data to be stored with a
// HTLC. It uses the update_add_htlc TLV types, because this is where extra
// data is passed with a HTLC. At present blinding points are the only extra
// data that we will store, and the function is a no-op if a nil blinding
@@ -2731,7 +2549,7 @@ type HTLC struct {
//
// This function MUST be called to persist all HTLC values when they are
// serialized.
-func (h *HTLC) serializeExtraData() error {
+func serializeHtlcExtraData(h *HTLC) error {
var records []tlv.RecordProducer
h.BlindingPoint.WhenSome(func(b tlv.RecordT[lnwire.BlindingPointTlvType,
*btcec.PublicKey]) {
@@ -2747,12 +2565,12 @@ func (h *HTLC) serializeExtraData() error {
return h.ExtraData.PackRecords(records...)
}
-// deserializeExtraData extracts TLVs from the extra data persisted for the
-// htlc and populates values in the struct accordingly.
+// deserializeHtlcExtraData extracts TLVs from the extra data persisted for the
+// HTLC and populates values in the struct accordingly.
//
// This function MUST be called to populate the struct properly when HTLCs
// are deserialized.
-func (h *HTLC) deserializeExtraData() error {
+func deserializeHtlcExtraData(h *HTLC) error {
if len(h.ExtraData) == 0 {
return nil
}
@@ -2804,7 +2622,7 @@ func SerializeHtlcs(b io.Writer, htlcs ...HTLC) error {
for _, htlc := range htlcs {
// Populate TLV stream for any additional fields contained
// in the TLV.
- if err := htlc.serializeExtraData(); err != nil {
+ if err := serializeHtlcExtraData(&htlc); err != nil {
return err
}
@@ -2896,7 +2714,7 @@ func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
// Finally, deserialize any TLVs contained in that extra data
// if they are present.
- if err := htlcs[i].deserializeExtraData(); err != nil {
+ if err := deserializeHtlcExtraData(&htlcs[i]); err != nil {
return nil, err
}
}
@@ -2904,23 +2722,6 @@ func DeserializeHtlcs(r io.Reader) ([]HTLC, error) {
return htlcs, nil
}
-// Copy returns a full copy of the target HTLC.
-func (h *HTLC) Copy() HTLC {
- clone := HTLC{
- Incoming: h.Incoming,
- Amt: h.Amt,
- RefundTimeout: h.RefundTimeout,
- OutputIndex: h.OutputIndex,
- }
- copy(clone.Signature[:], h.Signature)
- copy(clone.RHash[:], h.RHash[:])
- copy(clone.ExtraData, h.ExtraData)
- clone.BlindingPoint = h.BlindingPoint
- clone.CustomRecords = h.CustomRecords.Copy()
-
- return clone
-}
-
// LogUpdate represents a pending update to the remote commitment chain. The
// log update may be an add, fail, or settle entry. We maintain this data in
// order to be able to properly retransmit our proposed state if necessary.
@@ -3081,7 +2882,7 @@ func serializeCommitDiff(w io.Writer, diff *CommitDiff) error { // nolint: dupl
// We'll also encode the commit aux data stream here. We do this here
// rather than above (at the call to serializeChanCommit), to ensure
// backwards compat for reads to existing non-custom channels.
- auxData := diff.Commitment.extractTlvData()
+ auxData := extractCommitTlvData(&diff.Commitment)
if err := auxData.encode(w); err != nil {
return fmt.Errorf("unable to write aux data: %w", err)
}
@@ -3155,7 +2956,7 @@ func deserializeCommitDiff(r io.Reader) (*CommitDiff, error) {
return nil, fmt.Errorf("unable to decode aux data: %w", err)
}
- d.Commitment.amendTlvData(auxData)
+ amendCommitTlvData(&d.Commitment, auxData)
return &d, nil
}
@@ -4173,8 +3974,8 @@ func (c *OpenChannel) Copy() *OpenChannel {
InitialRemoteBalance: c.InitialRemoteBalance,
LocalChanCfg: c.LocalChanCfg,
RemoteChanCfg: c.RemoteChanCfg,
- LocalCommitment: c.LocalCommitment.copy(),
- RemoteCommitment: c.RemoteCommitment.copy(),
+ LocalCommitment: c.LocalCommitment.Copy(),
+ RemoteCommitment: c.RemoteCommitment.Copy(),
RemoteCurrentRevocation: c.RemoteCurrentRevocation,
RemoteNextRevocation: c.RemoteNextRevocation,
RevocationProducer: c.RevocationProducer,
@@ -4630,7 +4431,7 @@ func putChanCommitment(chanBucket kvdb.RwBucket, c *ChannelCommitment,
}
// Before we write to disk, we'll also write our aux data as well.
- auxData := c.extractTlvData()
+ auxData := extractCommitTlvData(c)
if err := auxData.encode(&b); err != nil {
return fmt.Errorf("unable to write aux data: %w", err)
}
@@ -4810,7 +4611,7 @@ func fetchChanCommitment(chanBucket kvdb.RBucket,
"chan aux data: %w", err)
}
- chanCommit.amendTlvData(auxData)
+ amendCommitTlvData(&chanCommit, auxData)
return chanCommit, nil
}
diff --git a/chanstate/commitment.go b/chanstate/commitment.go
new file mode 100644
index 0000000..3132ddb
--- /dev/null
+++ b/chanstate/commitment.go
@@ -0,0 +1,217 @@
+package chanstate
+
+import (
+ "github.com/btcsuite/btcd/btcutil/v2"
+ "github.com/btcsuite/btcd/wire/v2"
+ "github.com/lightningnetwork/lnd/fn/v2"
+ "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// ChannelCommitment is a snapshot of the commitment state at a particular
+// point in the commitment chain. With each state transition, a snapshot of the
+// current state along with all non-settled HTLCs are recorded. These snapshots
+// detail the state of the _remote_ party's commitment at a particular state
+// number. For ourselves (the local node) we ONLY store our most recent
+// (unrevoked) state for safety purposes.
+type ChannelCommitment struct {
+ // CommitHeight is the update number that this ChannelDelta represents
+ // the total number of commitment updates to this point. This can be
+ // viewed as sort of a "commitment height" as this number is
+ // monotonically increasing.
+ CommitHeight uint64
+
+ // LocalLogIndex is the cumulative log index of the local node at this
+ // point in the commitment chain. This value will be incremented for
+ // each _update_ added to the local update log.
+ LocalLogIndex uint64
+
+ // LocalHtlcIndex is the current local running HTLC index. This value
+ // will be incremented for each outgoing HTLC the local node offers.
+ LocalHtlcIndex uint64
+
+ // RemoteLogIndex is the cumulative log index of the remote node at
+ // this point in the commitment chain. This value will be incremented
+ // for each _update_ added to the remote update log.
+ RemoteLogIndex uint64
+
+ // RemoteHtlcIndex is the current remote running HTLC index. This value
+ // will be incremented for each outgoing HTLC the remote node offers.
+ RemoteHtlcIndex uint64
+
+ // LocalBalance is the current available settled balance within the
+ // channel directly spendable by us.
+ //
+ // NOTE: This is the balance *after* subtracting any commitment fee,
+ // AND anchor output values.
+ LocalBalance lnwire.MilliSatoshi
+
+ // RemoteBalance is the current available settled balance within the
+ // channel directly spendable by the remote node.
+ //
+ // NOTE: This is the balance *after* subtracting any commitment fee,
+ // AND anchor output values.
+ RemoteBalance lnwire.MilliSatoshi
+
+ // CommitFee is the amount calculated to be paid in fees for the
+ // current set of commitment transactions. The fee amount is persisted
+ // with the channel in order to allow the fee amount to be removed and
+ // recalculated with each channel state update, including updates that
+ // happen after a system restart.
+ CommitFee btcutil.Amount
+
+ // FeePerKw is the min satoshis/kilo-weight that should be paid within
+ // the commitment transaction for the entire duration of the channel's
+ // lifetime. This field may be updated during normal operation of the
+ // channel as on-chain conditions change.
+ //
+ // TODO(halseth): make this SatPerKWeight. Cannot be done atm because
+ // this will cause the import cycle lnwallet<->channeldb. Fee
+ // estimation stuff should be in its own package.
+ FeePerKw btcutil.Amount
+
+ // CommitTx is the latest version of the commitment state, broadcast
+ // able by us.
+ CommitTx *wire.MsgTx
+
+ // CustomBlob is an optional blob that can be used to store information
+ // specific to a custom channel type. This may track some custom
+ // specific state for this given commitment.
+ CustomBlob fn.Option[tlv.Blob]
+
+ // CommitSig is one half of the signature required to fully complete
+ // the script for the commitment transaction above. This is the
+ // signature signed by the remote party for our version of the
+ // commitment transactions.
+ CommitSig []byte
+
+ // Htlcs is the set of HTLC's that are pending at this particular
+ // commitment height.
+ Htlcs []HTLC
+}
+
+// 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
+}
+
+// HTLC is the on-disk representation of a hash time-locked contract. HTLCs are
+// contained within ChannelDeltas which encode the current state of the
+// commitment between state updates.
+//
+// TODO(roasbeef): save space by using smaller ints at tail end?
+type HTLC struct {
+ // TODO(yy): can embed an HTLCEntry here.
+
+ // Signature is the signature for the second level covenant transaction
+ // for this HTLC. The second level transaction is a timeout tx in the
+ // case that this is an outgoing HTLC, and a success tx in the case
+ // that this is an incoming HTLC.
+ //
+ // TODO(roasbeef): make [64]byte instead?
+ Signature []byte
+
+ // RHash is the payment hash of the HTLC.
+ RHash [32]byte
+
+ // Amt is the amount of milli-satoshis this HTLC escrows.
+ Amt lnwire.MilliSatoshi
+
+ // RefundTimeout is the absolute timeout on the HTLC that the sender
+ // must wait before reclaiming the funds in limbo.
+ RefundTimeout uint32
+
+ // OutputIndex is the output index for this particular HTLC output
+ // within the commitment transaction.
+ OutputIndex int32
+
+ // Incoming denotes whether we're the receiver or the sender of this
+ // HTLC.
+ Incoming bool
+
+ // OnionBlob is an opaque blob which is used to complete multi-hop
+ // routing.
+ OnionBlob [lnwire.OnionPacketSize]byte
+
+ // HtlcIndex is the HTLC counter index of this active, outstanding
+ // HTLC. This differs from the LogIndex, as the HtlcIndex is only
+ // incremented for each offered HTLC, while they LogIndex is
+ // incremented for each update (includes settle+fail).
+ HtlcIndex uint64
+
+ // LogIndex is the cumulative log index of this HTLC. This differs
+ // from the HtlcIndex as this will be incremented for each new log
+ // update added.
+ LogIndex uint64
+
+ // ExtraData contains any additional information that was transmitted
+ // with the HTLC via TLVs. This data *must* already be encoded as a
+ // TLV stream, and may be empty. The length of this data is naturally
+ // limited by the space available to TLVs in update_add_htlc:
+ // = 65535 bytes (bolt 8 maximum message size):
+ // - 2 bytes (bolt 1 message_type)
+ // - 32 bytes (channel_id)
+ // - 8 bytes (id)
+ // - 8 bytes (amount_msat)
+ // - 32 bytes (payment_hash)
+ // - 4 bytes (cltv_expiry)
+ // - 1366 bytes (onion_routing_packet)
+ // = 64083 bytes maximum possible TLV stream
+ //
+ // Note that this extra data is stored inline with the OnionBlob for
+ // legacy reasons, see serialization/deserialization functions for
+ // detail.
+ ExtraData lnwire.ExtraOpaqueData
+
+ // BlindingPoint is an optional blinding point included with the HTLC.
+ //
+ // Note: this field is not a part of on-disk representation of the
+ // HTLC. It is stored in the ExtraData field, which is used to store
+ // a TLV stream of additional information associated with the HTLC.
+ BlindingPoint lnwire.BlindingPointRecord
+
+ // CustomRecords is a set of custom TLV records that are associated with
+ // this HTLC. These records are used to store additional information
+ // about the HTLC that is not part of the standard HTLC fields. This
+ // field is encoded within the ExtraData field.
+ CustomRecords lnwire.CustomRecords
+}
+
+// Copy returns a full copy of the target HTLC.
+func (h *HTLC) Copy() HTLC {
+ clone := HTLC{
+ Incoming: h.Incoming,
+ Amt: h.Amt,
+ RefundTimeout: h.RefundTimeout,
+ OutputIndex: h.OutputIndex,
+ }
+ copy(clone.Signature, h.Signature)
+ copy(clone.RHash[:], h.RHash[:])
+ copy(clone.ExtraData, h.ExtraData)
+ clone.BlindingPoint = h.BlindingPoint
+ clone.CustomRecords = h.CustomRecords.Copy()
+
+ return clone
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.