From 39db0221f0208673bb0203679349982edaa55156 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 30 Apr 2024 16:54:27 -0700 Subject: [PATCH 1/5] lnwallet: move PaymentDescriptor definition to its own file --- lnwallet/channel.go | 214 ------------------------------- lnwallet/payment_descriptor.go | 224 +++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 214 deletions(-) create mode 100644 lnwallet/payment_descriptor.go diff --git a/lnwallet/channel.go b/lnwallet/channel.go index b4ea2fee01..a083b89c32 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -167,220 +167,6 @@ func (e *ErrCommitSyncLocalDataLoss) Error() string { // payments requested by the wallet/daemon. type PaymentHash [32]byte -// updateType is the exact type of an entry within the shared HTLC log. -type updateType uint8 - -const ( - // Add is an update type that adds a new HTLC entry into the log. - // Either side can add a new pending HTLC by adding a new Add entry - // into their update log. - Add updateType = iota - - // Fail is an update type which removes a prior HTLC entry from the - // log. Adding a Fail entry to one's log will modify the _remote_ - // party's update log once a new commitment view has been evaluated - // which contains the Fail entry. - Fail - - // MalformedFail is an update type which removes a prior HTLC entry - // from the log. Adding a MalformedFail entry to one's log will modify - // the _remote_ party's update log once a new commitment view has been - // evaluated which contains the MalformedFail entry. The difference - // from Fail type lie in the different data we have to store. - MalformedFail - - // Settle is an update type which settles a prior HTLC crediting the - // balance of the receiving node. Adding a Settle entry to a log will - // result in the settle entry being removed on the log as well as the - // original add entry from the remote party's log after the next state - // transition. - Settle - - // FeeUpdate is an update type sent by the channel initiator that - // updates the fee rate used when signing the commitment transaction. - FeeUpdate -) - -// String returns a human readable string that uniquely identifies the target -// update type. -func (u updateType) String() string { - switch u { - case Add: - return "Add" - case Fail: - return "Fail" - case MalformedFail: - return "MalformedFail" - case Settle: - return "Settle" - case FeeUpdate: - return "FeeUpdate" - default: - return "" - } -} - -// PaymentDescriptor represents a commitment state update which either adds, -// settles, or removes an HTLC. PaymentDescriptors encapsulate all necessary -// metadata w.r.t to an HTLC, and additional data pairing a settle message to -// the original added HTLC. -// -// TODO(roasbeef): LogEntry interface?? -// - need to separate attrs for cancel/add/settle/feeupdate -type PaymentDescriptor struct { - // RHash is the payment hash for this HTLC. The HTLC can be settled iff - // the preimage to this hash is presented. - RHash PaymentHash - - // RPreimage is the preimage that settles the HTLC pointed to within the - // log by the ParentIndex. - RPreimage PaymentHash - - // Timeout is the absolute timeout in blocks, after which this HTLC - // expires. - Timeout uint32 - - // Amount is the HTLC amount in milli-satoshis. - Amount lnwire.MilliSatoshi - - // LogIndex is the log entry number that his HTLC update has within the - // log. Depending on if IsIncoming is true, this is either an entry the - // remote party added, or one that we added locally. - LogIndex uint64 - - // HtlcIndex is the index within the main update log for this HTLC. - // Entries within the log of type Add will have this field populated, - // as other entries will point to the entry via this counter. - // - // NOTE: This field will only be populate if EntryType is Add. - HtlcIndex uint64 - - // ParentIndex is the HTLC index of the entry that this update settles or - // times out. - // - // NOTE: This field will only be populate if EntryType is Fail or - // Settle. - ParentIndex uint64 - - // SourceRef points to an Add update in a forwarding package owned by - // this channel. - // - // NOTE: This field will only be populated if EntryType is Fail or - // Settle. - SourceRef *channeldb.AddRef - - // DestRef points to a Fail/Settle update in another link's forwarding - // package. - // - // NOTE: This field will only be populated if EntryType is Fail or - // Settle, and the forwarded Add successfully included in an outgoing - // link's commitment txn. - DestRef *channeldb.SettleFailRef - - // OpenCircuitKey references the incoming Chan/HTLC ID of an Add HTLC - // packet delivered by the switch. - // - // NOTE: This field is only populated for payment descriptors in the - // *local* update log, and if the Add packet was delivered by the - // switch. - OpenCircuitKey *models.CircuitKey - - // ClosedCircuitKey references the incoming Chan/HTLC ID of the Add HTLC - // that opened the circuit. - // - // NOTE: This field is only populated for payment descriptors in the - // *local* update log, and if settle/fails have a committed circuit in - // the circuit map. - ClosedCircuitKey *models.CircuitKey - - // localOutputIndex is the output index of this HTLc output in the - // commitment transaction of the local node. - // - // NOTE: If the output is dust from the PoV of the local commitment - // chain, then this value will be -1. - localOutputIndex int32 - - // remoteOutputIndex is the output index of this HTLC output in the - // commitment transaction of the remote node. - // - // NOTE: If the output is dust from the PoV of the remote commitment - // chain, then this value will be -1. - remoteOutputIndex int32 - - // sig is the signature for the second-level HTLC transaction that - // spends the version of this HTLC on the commitment transaction of the - // local node. This signature is generated by the remote node and - // stored by the local node in the case that local node needs to - // broadcast their commitment transaction. - sig input.Signature - - // addCommitHeight[Remote|Local] encodes the height of the commitment - // which included this HTLC on either the remote or local commitment - // chain. This value is used to determine when an HTLC is fully - // "locked-in". - addCommitHeightRemote uint64 - addCommitHeightLocal uint64 - - // removeCommitHeight[Remote|Local] encodes the height of the - // commitment which removed the parent pointer of this - // PaymentDescriptor either due to a timeout or a settle. Once both - // these heights are below the tail of both chains, the log entries can - // safely be removed. - removeCommitHeightRemote uint64 - removeCommitHeightLocal uint64 - - // OnionBlob is an opaque blob which is used to complete multi-hop - // routing. - // - // NOTE: Populated only on add payment descriptor entry types. - OnionBlob []byte - - // ShaOnionBlob is a sha of the onion blob. - // - // NOTE: Populated only in payment descriptor with MalformedFail type. - ShaOnionBlob [sha256.Size]byte - - // FailReason stores the reason why a particular payment was canceled. - // - // NOTE: Populate only in fail payment descriptor entry types. - FailReason []byte - - // FailCode stores the code why a particular payment was canceled. - // - // NOTE: Populated only in payment descriptor with MalformedFail type. - FailCode lnwire.FailCode - - // [our|their|]PkScript are the raw public key scripts that encodes the - // redemption rules for this particular HTLC. These fields will only be - // populated iff the EntryType of this PaymentDescriptor is Add. - // ourPkScript is the ourPkScript from the context of our local - // commitment chain. theirPkScript is the latest pkScript from the - // context of the remote commitment chain. - // - // NOTE: These values may change within the logs themselves, however, - // they'll stay consistent within the commitment chain entries - // themselves. - ourPkScript []byte - ourWitnessScript []byte - theirPkScript []byte - theirWitnessScript []byte - - // EntryType denotes the exact type of the PaymentDescriptor. In the - // case of a Timeout, or Settle type, then the Parent field will point - // into the log to the HTLC being modified. - EntryType updateType - - // isForwarded denotes if an incoming HTLC has been forwarded to any - // possible upstream peers in the route. - isForwarded bool - - // BlindingPoint is an optional ephemeral key used in route blinding. - // This value is set for nodes that are relaying payments inside of a - // blinded route (ie, not the introduction node) from update_add_htlc's - // TLVs. - BlindingPoint lnwire.BlindingPointRecord -} - // PayDescsFromRemoteLogUpdates converts a slice of LogUpdates received from the // remote peer into PaymentDescriptors to inform a link's forwarding decisions. // diff --git a/lnwallet/payment_descriptor.go b/lnwallet/payment_descriptor.go new file mode 100644 index 0000000000..f0ac8b9f7e --- /dev/null +++ b/lnwallet/payment_descriptor.go @@ -0,0 +1,224 @@ +package lnwallet + +import ( + "crypto/sha256" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" +) + +// updateType is the exact type of an entry within the shared HTLC log. +type updateType uint8 + +const ( + // Add is an update type that adds a new HTLC entry into the log. + // Either side can add a new pending HTLC by adding a new Add entry + // into their update log. + Add updateType = iota + + // Fail is an update type which removes a prior HTLC entry from the + // log. Adding a Fail entry to one's log will modify the _remote_ + // party's update log once a new commitment view has been evaluated + // which contains the Fail entry. + Fail + + // MalformedFail is an update type which removes a prior HTLC entry + // from the log. Adding a MalformedFail entry to one's log will modify + // the _remote_ party's update log once a new commitment view has been + // evaluated which contains the MalformedFail entry. The difference + // from Fail type lie in the different data we have to store. + MalformedFail + + // Settle is an update type which settles a prior HTLC crediting the + // balance of the receiving node. Adding a Settle entry to a log will + // result in the settle entry being removed on the log as well as the + // original add entry from the remote party's log after the next state + // transition. + Settle + + // FeeUpdate is an update type sent by the channel initiator that + // updates the fee rate used when signing the commitment transaction. + FeeUpdate +) + +// String returns a human readable string that uniquely identifies the target +// update type. +func (u updateType) String() string { + switch u { + case Add: + return "Add" + case Fail: + return "Fail" + case MalformedFail: + return "MalformedFail" + case Settle: + return "Settle" + case FeeUpdate: + return "FeeUpdate" + default: + return "" + } +} + +// PaymentDescriptor represents a commitment state update which either adds, +// settles, or removes an HTLC. PaymentDescriptors encapsulate all necessary +// metadata w.r.t to an HTLC, and additional data pairing a settle message to +// the original added HTLC. +// +// TODO(roasbeef): LogEntry interface?? +// - need to separate attrs for cancel/add/settle/feeupdate +type PaymentDescriptor struct { + // RHash is the payment hash for this HTLC. The HTLC can be settled iff + // the preimage to this hash is presented. + RHash PaymentHash + + // RPreimage is the preimage that settles the HTLC pointed to within the + // log by the ParentIndex. + RPreimage PaymentHash + + // Timeout is the absolute timeout in blocks, after which this HTLC + // expires. + Timeout uint32 + + // Amount is the HTLC amount in milli-satoshis. + Amount lnwire.MilliSatoshi + + // LogIndex is the log entry number that his HTLC update has within the + // log. Depending on if IsIncoming is true, this is either an entry the + // remote party added, or one that we added locally. + LogIndex uint64 + + // HtlcIndex is the index within the main update log for this HTLC. + // Entries within the log of type Add will have this field populated, + // as other entries will point to the entry via this counter. + // + // NOTE: This field will only be populate if EntryType is Add. + HtlcIndex uint64 + + // ParentIndex is the HTLC index of the entry that this update settles + // or times out. + // + // NOTE: This field will only be populate if EntryType is Fail or + // Settle. + ParentIndex uint64 + + // SourceRef points to an Add update in a forwarding package owned by + // this channel. + // + // NOTE: This field will only be populated if EntryType is Fail or + // Settle. + SourceRef *channeldb.AddRef + + // DestRef points to a Fail/Settle update in another link's forwarding + // package. + // + // NOTE: This field will only be populated if EntryType is Fail or + // Settle, and the forwarded Add successfully included in an outgoing + // link's commitment txn. + DestRef *channeldb.SettleFailRef + + // OpenCircuitKey references the incoming Chan/HTLC ID of an Add HTLC + // packet delivered by the switch. + // + // NOTE: This field is only populated for payment descriptors in the + // *local* update log, and if the Add packet was delivered by the + // switch. + OpenCircuitKey *models.CircuitKey + + // ClosedCircuitKey references the incoming Chan/HTLC ID of the Add HTLC + // that opened the circuit. + // + // NOTE: This field is only populated for payment descriptors in the + // *local* update log, and if settle/fails have a committed circuit in + // the circuit map. + ClosedCircuitKey *models.CircuitKey + + // localOutputIndex is the output index of this HTLc output in the + // commitment transaction of the local node. + // + // NOTE: If the output is dust from the PoV of the local commitment + // chain, then this value will be -1. + localOutputIndex int32 + + // remoteOutputIndex is the output index of this HTLC output in the + // commitment transaction of the remote node. + // + // NOTE: If the output is dust from the PoV of the remote commitment + // chain, then this value will be -1. + remoteOutputIndex int32 + + // sig is the signature for the second-level HTLC transaction that + // spends the version of this HTLC on the commitment transaction of the + // local node. This signature is generated by the remote node and + // stored by the local node in the case that local node needs to + // broadcast their commitment transaction. + sig input.Signature + + // addCommitHeight[Remote|Local] encodes the height of the commitment + // which included this HTLC on either the remote or local commitment + // chain. This value is used to determine when an HTLC is fully + // "locked-in". + addCommitHeightRemote uint64 + addCommitHeightLocal uint64 + + // removeCommitHeight[Remote|Local] encodes the height of the + // commitment which removed the parent pointer of this + // PaymentDescriptor either due to a timeout or a settle. Once both + // these heights are below the tail of both chains, the log entries can + // safely be removed. + removeCommitHeightRemote uint64 + removeCommitHeightLocal uint64 + + // OnionBlob is an opaque blob which is used to complete multi-hop + // routing. + // + // NOTE: Populated only on add payment descriptor entry types. + OnionBlob []byte + + // ShaOnionBlob is a sha of the onion blob. + // + // NOTE: Populated only in payment descriptor with MalformedFail type. + ShaOnionBlob [sha256.Size]byte + + // FailReason stores the reason why a particular payment was canceled. + // + // NOTE: Populate only in fail payment descriptor entry types. + FailReason []byte + + // FailCode stores the code why a particular payment was canceled. + // + // NOTE: Populated only in payment descriptor with MalformedFail type. + FailCode lnwire.FailCode + + // [our|their|]PkScript are the raw public key scripts that encodes the + // redemption rules for this particular HTLC. These fields will only be + // populated iff the EntryType of this PaymentDescriptor is Add. + // ourPkScript is the ourPkScript from the context of our local + // commitment chain. theirPkScript is the latest pkScript from the + // context of the remote commitment chain. + // + // NOTE: These values may change within the logs themselves, however, + // they'll stay consistent within the commitment chain entries + // themselves. + ourPkScript []byte + ourWitnessScript []byte + theirPkScript []byte + theirWitnessScript []byte + + // EntryType denotes the exact type of the PaymentDescriptor. In the + // case of a Timeout, or Settle type, then the Parent field will point + // into the log to the HTLC being modified. + EntryType updateType + + // isForwarded denotes if an incoming HTLC has been forwarded to any + // possible upstream peers in the route. + isForwarded bool + + // BlindingPoint is an optional ephemeral key used in route blinding. + // This value is set for nodes that are relaying payments inside of a + // blinded route (ie, not the introduction node) from update_add_htlc's + // TLVs. + BlindingPoint lnwire.BlindingPointRecord +} From 51060aed45abf9092bfb82fd64346d509645ee00 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Thu, 6 Jun 2024 14:08:53 -0700 Subject: [PATCH 2/5] lnwallet: move updateLog to its own file. --- lnwallet/channel.go | 192 ---------------------------------------- lnwallet/update_log.go | 196 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 192 deletions(-) create mode 100644 lnwallet/update_log.go diff --git a/lnwallet/channel.go b/lnwallet/channel.go index a083b89c32..d03c5df1ae 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -823,198 +823,6 @@ func (s *commitmentChain) hasUnackedCommitment() bool { return s.commitments.Front() != s.commitments.Back() } -// updateLog is an append-only log that stores updates to a node's commitment -// chain. This structure can be seen as the "mempool" within Lightning where -// changes are stored before they're committed to the chain. Once an entry has -// been committed in both the local and remote commitment chain, then it can be -// removed from this log. -// -// TODO(roasbeef): create lightning package, move commitment and update to -// package? -// - also move state machine, separate from lnwallet package -// - possible embed updateLog within commitmentChain. -type updateLog struct { - // logIndex is a monotonically increasing integer that tracks the total - // number of update entries ever applied to the log. When sending new - // commitment states, we include all updates up to this index. - logIndex uint64 - - // htlcCounter is a monotonically increasing integer that tracks the - // total number of offered HTLC's by the owner of this update log, - // hence the `Add` update type. We use a distinct index for this - // purpose, as update's that remove entries from the log will be - // indexed using this counter. - htlcCounter uint64 - - // List is the updatelog itself, we embed this value so updateLog has - // access to all the method of a list.List. - *list.List - - // updateIndex maps a `logIndex` to a particular update entry. It - // deals with the four update types: - // `Fail|MalformedFail|Settle|FeeUpdate` - updateIndex map[uint64]*list.Element - - // htlcIndex maps a `htlcCounter` to an offered HTLC entry, hence the - // `Add` update. - htlcIndex map[uint64]*list.Element - - // modifiedHtlcs is a set that keeps track of all the current modified - // htlcs, hence update types `Fail|MalformedFail|Settle`. A modified - // HTLC is one that's present in the log, and has as a pending fail or - // settle that's attempting to consume it. - modifiedHtlcs map[uint64]struct{} -} - -// newUpdateLog creates a new updateLog instance. -func newUpdateLog(logIndex, htlcCounter uint64) *updateLog { - return &updateLog{ - List: list.New(), - updateIndex: make(map[uint64]*list.Element), - htlcIndex: make(map[uint64]*list.Element), - logIndex: logIndex, - htlcCounter: htlcCounter, - modifiedHtlcs: make(map[uint64]struct{}), - } -} - -// restoreHtlc will "restore" a prior HTLC to the updateLog. We say restore as -// this method is intended to be used when re-covering a prior commitment -// state. This function differs from appendHtlc in that it won't increment -// either of log's counters. If the HTLC is already present, then it is -// ignored. -func (u *updateLog) restoreHtlc(pd *PaymentDescriptor) { - if _, ok := u.htlcIndex[pd.HtlcIndex]; ok { - return - } - - u.htlcIndex[pd.HtlcIndex] = u.PushBack(pd) -} - -// appendUpdate appends a new update to the tip of the updateLog. The entry is -// also added to index accordingly. -func (u *updateLog) appendUpdate(pd *PaymentDescriptor) { - u.updateIndex[u.logIndex] = u.PushBack(pd) - u.logIndex++ -} - -// restoreUpdate appends a new update to the tip of the updateLog. The entry is -// also added to index accordingly. This function differs from appendUpdate in -// that it won't increment the log index counter. -func (u *updateLog) restoreUpdate(pd *PaymentDescriptor) { - u.updateIndex[pd.LogIndex] = u.PushBack(pd) -} - -// appendHtlc appends a new HTLC offer to the tip of the update log. The entry -// is also added to the offer index accordingly. -func (u *updateLog) appendHtlc(pd *PaymentDescriptor) { - u.htlcIndex[u.htlcCounter] = u.PushBack(pd) - u.htlcCounter++ - - u.logIndex++ -} - -// lookupHtlc attempts to look up an offered HTLC according to its offer -// index. If the entry isn't found, then a nil pointer is returned. -func (u *updateLog) lookupHtlc(i uint64) *PaymentDescriptor { - htlc, ok := u.htlcIndex[i] - if !ok { - return nil - } - - return htlc.Value.(*PaymentDescriptor) -} - -// remove attempts to remove an entry from the update log. If the entry is -// found, then the entry will be removed from the update log and index. -func (u *updateLog) removeUpdate(i uint64) { - entry := u.updateIndex[i] - u.Remove(entry) - delete(u.updateIndex, i) -} - -// removeHtlc attempts to remove an HTLC offer form the update log. If the -// entry is found, then the entry will be removed from both the main log and -// the offer index. -func (u *updateLog) removeHtlc(i uint64) { - entry := u.htlcIndex[i] - u.Remove(entry) - delete(u.htlcIndex, i) - - delete(u.modifiedHtlcs, i) -} - -// htlcHasModification returns true if the HTLC identified by the passed index -// has a pending modification within the log. -func (u *updateLog) htlcHasModification(i uint64) bool { - _, o := u.modifiedHtlcs[i] - return o -} - -// markHtlcModified marks an HTLC as modified based on its HTLC index. After a -// call to this method, htlcHasModification will return true until the HTLC is -// removed. -func (u *updateLog) markHtlcModified(i uint64) { - u.modifiedHtlcs[i] = struct{}{} -} - -// compactLogs performs garbage collection within the log removing HTLCs which -// have been removed from the point-of-view of the tail of both chains. The -// entries which timeout/settle HTLCs are also removed. -func compactLogs(ourLog, theirLog *updateLog, - localChainTail, remoteChainTail uint64) { - - compactLog := func(logA, logB *updateLog) { - var nextA *list.Element - for e := logA.Front(); e != nil; e = nextA { - // Assign next iteration element at top of loop because - // we may remove the current element from the list, - // which can change the iterated sequence. - nextA = e.Next() - - htlc := e.Value.(*PaymentDescriptor) - - // We skip Adds, as they will be removed along with the - // fail/settles below. - if htlc.EntryType == Add { - continue - } - - // If the HTLC hasn't yet been removed from either - // chain, the skip it. - if htlc.removeCommitHeightRemote == 0 || - htlc.removeCommitHeightLocal == 0 { - continue - } - - // Otherwise if the height of the tail of both chains - // is at least the height in which the HTLC was - // removed, then evict the settle/timeout entry along - // with the original add entry. - if remoteChainTail >= htlc.removeCommitHeightRemote && - localChainTail >= htlc.removeCommitHeightLocal { - - // Fee updates have no parent htlcs, so we only - // remove the update itself. - if htlc.EntryType == FeeUpdate { - logA.removeUpdate(htlc.LogIndex) - continue - } - - // The other types (fail/settle) do have a - // parent HTLC, so we'll remove that HTLC from - // the other log. - logA.removeUpdate(htlc.LogIndex) - logB.removeHtlc(htlc.ParentIndex) - } - - } - } - - compactLog(ourLog, theirLog) - compactLog(theirLog, ourLog) -} - // LightningChannel implements the state machine which corresponds to the // current commitment protocol wire spec. The state machine implemented allows // for asynchronous fully desynchronized, batched+pipelined updates to diff --git a/lnwallet/update_log.go b/lnwallet/update_log.go new file mode 100644 index 0000000000..0256bbd602 --- /dev/null +++ b/lnwallet/update_log.go @@ -0,0 +1,196 @@ +package lnwallet + +import "container/list" + +// updateLog is an append-only log that stores updates to a node's commitment +// chain. This structure can be seen as the "mempool" within Lightning where +// changes are stored before they're committed to the chain. Once an entry has +// been committed in both the local and remote commitment chain, then it can be +// removed from this log. +// +// TODO(roasbeef): create lightning package, move commitment and update to +// package? +// - also move state machine, separate from lnwallet package +// - possible embed updateLog within commitmentChain. +type updateLog struct { + // logIndex is a monotonically increasing integer that tracks the total + // number of update entries ever applied to the log. When sending new + // commitment states, we include all updates up to this index. + logIndex uint64 + + // htlcCounter is a monotonically increasing integer that tracks the + // total number of offered HTLC's by the owner of this update log, + // hence the `Add` update type. We use a distinct index for this + // purpose, as update's that remove entries from the log will be + // indexed using this counter. + htlcCounter uint64 + + // List is the updatelog itself, we embed this value so updateLog has + // access to all the method of a list.List. + *list.List + + // updateIndex maps a `logIndex` to a particular update entry. It + // deals with the four update types: + // `Fail|MalformedFail|Settle|FeeUpdate` + updateIndex map[uint64]*list.Element + + // htlcIndex maps a `htlcCounter` to an offered HTLC entry, hence the + // `Add` update. + htlcIndex map[uint64]*list.Element + + // modifiedHtlcs is a set that keeps track of all the current modified + // htlcs, hence update types `Fail|MalformedFail|Settle`. A modified + // HTLC is one that's present in the log, and has as a pending fail or + // settle that's attempting to consume it. + modifiedHtlcs map[uint64]struct{} +} + +// newUpdateLog creates a new updateLog instance. +func newUpdateLog(logIndex, htlcCounter uint64) *updateLog { + return &updateLog{ + List: list.New(), + updateIndex: make(map[uint64]*list.Element), + htlcIndex: make(map[uint64]*list.Element), + logIndex: logIndex, + htlcCounter: htlcCounter, + modifiedHtlcs: make(map[uint64]struct{}), + } +} + +// restoreHtlc will "restore" a prior HTLC to the updateLog. We say restore as +// this method is intended to be used when re-covering a prior commitment +// state. This function differs from appendHtlc in that it won't increment +// either of log's counters. If the HTLC is already present, then it is +// ignored. +func (u *updateLog) restoreHtlc(pd *PaymentDescriptor) { + if _, ok := u.htlcIndex[pd.HtlcIndex]; ok { + return + } + + u.htlcIndex[pd.HtlcIndex] = u.PushBack(pd) +} + +// appendUpdate appends a new update to the tip of the updateLog. The entry is +// also added to index accordingly. +func (u *updateLog) appendUpdate(pd *PaymentDescriptor) { + u.updateIndex[u.logIndex] = u.PushBack(pd) + u.logIndex++ +} + +// restoreUpdate appends a new update to the tip of the updateLog. The entry is +// also added to index accordingly. This function differs from appendUpdate in +// that it won't increment the log index counter. +func (u *updateLog) restoreUpdate(pd *PaymentDescriptor) { + u.updateIndex[pd.LogIndex] = u.PushBack(pd) +} + +// appendHtlc appends a new HTLC offer to the tip of the update log. The entry +// is also added to the offer index accordingly. +func (u *updateLog) appendHtlc(pd *PaymentDescriptor) { + u.htlcIndex[u.htlcCounter] = u.PushBack(pd) + u.htlcCounter++ + + u.logIndex++ +} + +// lookupHtlc attempts to look up an offered HTLC according to its offer +// index. If the entry isn't found, then a nil pointer is returned. +func (u *updateLog) lookupHtlc(i uint64) *PaymentDescriptor { + htlc, ok := u.htlcIndex[i] + if !ok { + return nil + } + + return htlc.Value.(*PaymentDescriptor) +} + +// remove attempts to remove an entry from the update log. If the entry is +// found, then the entry will be removed from the update log and index. +func (u *updateLog) removeUpdate(i uint64) { + entry := u.updateIndex[i] + u.Remove(entry) + delete(u.updateIndex, i) +} + +// removeHtlc attempts to remove an HTLC offer form the update log. If the +// entry is found, then the entry will be removed from both the main log and +// the offer index. +func (u *updateLog) removeHtlc(i uint64) { + entry := u.htlcIndex[i] + u.Remove(entry) + delete(u.htlcIndex, i) + + delete(u.modifiedHtlcs, i) +} + +// htlcHasModification returns true if the HTLC identified by the passed index +// has a pending modification within the log. +func (u *updateLog) htlcHasModification(i uint64) bool { + _, o := u.modifiedHtlcs[i] + return o +} + +// markHtlcModified marks an HTLC as modified based on its HTLC index. After a +// call to this method, htlcHasModification will return true until the HTLC is +// removed. +func (u *updateLog) markHtlcModified(i uint64) { + u.modifiedHtlcs[i] = struct{}{} +} + +// compactLogs performs garbage collection within the log removing HTLCs which +// have been removed from the point-of-view of the tail of both chains. The +// entries which timeout/settle HTLCs are also removed. +func compactLogs(ourLog, theirLog *updateLog, + localChainTail, remoteChainTail uint64) { + + compactLog := func(logA, logB *updateLog) { + var nextA *list.Element + for e := logA.Front(); e != nil; e = nextA { + // Assign next iteration element at top of loop because + // we may remove the current element from the list, + // which can change the iterated sequence. + nextA = e.Next() + + //nolint:forcetypeassert + htlc := e.Value.(*PaymentDescriptor) + + // We skip Adds, as they will be removed along with the + // fail/settles below. + if htlc.EntryType == Add { + continue + } + + // If the HTLC hasn't yet been removed from either + // chain, the skip it. + if htlc.removeCommitHeightRemote == 0 || + htlc.removeCommitHeightLocal == 0 { + continue + } + + // Otherwise if the height of the tail of both chains + // is at least the height in which the HTLC was + // removed, then evict the settle/timeout entry along + // with the original add entry. + if remoteChainTail >= htlc.removeCommitHeightRemote && + localChainTail >= htlc.removeCommitHeightLocal { + + // Fee updates have no parent htlcs, so we only + // remove the update itself. + if htlc.EntryType == FeeUpdate { + logA.removeUpdate(htlc.LogIndex) + continue + } + + // The other types (fail/settle) do have a + // parent HTLC, so we'll remove that HTLC from + // the other log. + logA.removeUpdate(htlc.LogIndex) + logB.removeHtlc(htlc.ParentIndex) + } + + } + } + + compactLog(ourLog, theirLog) + compactLog(theirLog, ourLog) +} From 3e7866d85ce82e09b04f63e256acb34543f9b001 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 30 Jul 2024 10:17:47 -0700 Subject: [PATCH 3/5] lnwallet: fix linter errors introduced by updateLog move --- lnwallet/update_log.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lnwallet/update_log.go b/lnwallet/update_log.go index 0256bbd602..5cb39ef1ef 100644 --- a/lnwallet/update_log.go +++ b/lnwallet/update_log.go @@ -101,6 +101,7 @@ func (u *updateLog) lookupHtlc(i uint64) *PaymentDescriptor { return nil } + //nolint:forcetypeassert return htlc.Value.(*PaymentDescriptor) } @@ -164,6 +165,7 @@ func compactLogs(ourLog, theirLog *updateLog, // chain, the skip it. if htlc.removeCommitHeightRemote == 0 || htlc.removeCommitHeightLocal == 0 { + continue } @@ -187,7 +189,6 @@ func compactLogs(ourLog, theirLog *updateLog, logA.removeUpdate(htlc.LogIndex) logB.removeHtlc(htlc.ParentIndex) } - } } From 4cf7555922069d3cb7743476c857b97324332a9a Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Thu, 6 Jun 2024 14:12:07 -0700 Subject: [PATCH 4/5] lnwallet: move commitChain to its own file. --- lnwallet/channel.go | 56 ---------------------------------- lnwallet/commitment_chain.go | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 56 deletions(-) create mode 100644 lnwallet/commitment_chain.go diff --git a/lnwallet/channel.go b/lnwallet/channel.go index d03c5df1ae..96face44a2 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2,7 +2,6 @@ package lnwallet import ( "bytes" - "container/list" "crypto/sha256" "errors" "fmt" @@ -768,61 +767,6 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool, return commit, nil } -// commitmentChain represents a chain of unrevoked commitments. The tail of the -// chain is the latest fully signed, yet unrevoked commitment. Two chains are -// tracked, one for the local node, and another for the remote node. New -// commitments we create locally extend the remote node's chain, and vice -// versa. Commitment chains are allowed to grow to a bounded length, after -// which the tail needs to be "dropped" before new commitments can be received. -// The tail is "dropped" when the owner of the chain sends a revocation for the -// previous tail. -type commitmentChain struct { - // commitments is a linked list of commitments to new states. New - // commitments are added to the end of the chain with increase height. - // Once a commitment transaction is revoked, the tail is incremented, - // freeing up the revocation window for new commitments. - commitments *list.List -} - -// newCommitmentChain creates a new commitment chain. -func newCommitmentChain() *commitmentChain { - return &commitmentChain{ - commitments: list.New(), - } -} - -// addCommitment extends the commitment chain by a single commitment. This -// added commitment represents a state update proposed by either party. Once -// the commitment prior to this commitment is revoked, the commitment becomes -// the new defacto state within the channel. -func (s *commitmentChain) addCommitment(c *commitment) { - s.commitments.PushBack(c) -} - -// advanceTail reduces the length of the commitment chain by one. The tail of -// the chain should be advanced once a revocation for the lowest unrevoked -// commitment in the chain is received. -func (s *commitmentChain) advanceTail() { - s.commitments.Remove(s.commitments.Front()) -} - -// tip returns the latest commitment added to the chain. -func (s *commitmentChain) tip() *commitment { - return s.commitments.Back().Value.(*commitment) -} - -// tail returns the lowest unrevoked commitment transaction in the chain. -func (s *commitmentChain) tail() *commitment { - return s.commitments.Front().Value.(*commitment) -} - -// hasUnackedCommitment returns true if the commitment chain has more than one -// entry. The tail of the commitment chain has been ACKed by revoking all prior -// commitments, but any subsequent commitments have not yet been ACKed. -func (s *commitmentChain) hasUnackedCommitment() bool { - return s.commitments.Front() != s.commitments.Back() -} - // LightningChannel implements the state machine which corresponds to the // current commitment protocol wire spec. The state machine implemented allows // for asynchronous fully desynchronized, batched+pipelined updates to diff --git a/lnwallet/commitment_chain.go b/lnwallet/commitment_chain.go new file mode 100644 index 0000000000..23887815b1 --- /dev/null +++ b/lnwallet/commitment_chain.go @@ -0,0 +1,58 @@ +package lnwallet + +import "container/list" + +// commitmentChain represents a chain of unrevoked commitments. The tail of the +// chain is the latest fully signed, yet unrevoked commitment. Two chains are +// tracked, one for the local node, and another for the remote node. New +// commitments we create locally extend the remote node's chain, and vice +// versa. Commitment chains are allowed to grow to a bounded length, after +// which the tail needs to be "dropped" before new commitments can be received. +// The tail is "dropped" when the owner of the chain sends a revocation for the +// previous tail. +type commitmentChain struct { + // commitments is a linked list of commitments to new states. New + // commitments are added to the end of the chain with increase height. + // Once a commitment transaction is revoked, the tail is incremented, + // freeing up the revocation window for new commitments. + commitments *list.List +} + +// newCommitmentChain creates a new commitment chain. +func newCommitmentChain() *commitmentChain { + return &commitmentChain{ + commitments: list.New(), + } +} + +// addCommitment extends the commitment chain by a single commitment. This +// added commitment represents a state update proposed by either party. Once +// the commitment prior to this commitment is revoked, the commitment becomes +// the new defacto state within the channel. +func (s *commitmentChain) addCommitment(c *commitment) { + s.commitments.PushBack(c) +} + +// advanceTail reduces the length of the commitment chain by one. The tail of +// the chain should be advanced once a revocation for the lowest unrevoked +// commitment in the chain is received. +func (s *commitmentChain) advanceTail() { + s.commitments.Remove(s.commitments.Front()) +} + +// tip returns the latest commitment added to the chain. +func (s *commitmentChain) tip() *commitment { + return s.commitments.Back().Value.(*commitment) +} + +// tail returns the lowest unrevoked commitment transaction in the chain. +func (s *commitmentChain) tail() *commitment { + return s.commitments.Front().Value.(*commitment) +} + +// hasUnackedCommitment returns true if the commitment chain has more than one +// entry. The tail of the commitment chain has been ACKed by revoking all prior +// commitments, but any subsequent commitments have not yet been ACKed. +func (s *commitmentChain) hasUnackedCommitment() bool { + return s.commitments.Front() != s.commitments.Back() +} From 82ff360ad2e893533b782a0a233a98b94792d490 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Tue, 30 Jul 2024 10:22:23 -0700 Subject: [PATCH 5/5] lnwallet: fix linter errors resulting from commitmentChain move. --- lnwallet/commitment_chain.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnwallet/commitment_chain.go b/lnwallet/commitment_chain.go index 23887815b1..7894e9e3f4 100644 --- a/lnwallet/commitment_chain.go +++ b/lnwallet/commitment_chain.go @@ -42,11 +42,13 @@ func (s *commitmentChain) advanceTail() { // tip returns the latest commitment added to the chain. func (s *commitmentChain) tip() *commitment { + //nolint:forcetypeassert return s.commitments.Back().Value.(*commitment) } // tail returns the lowest unrevoked commitment transaction in the chain. func (s *commitmentChain) tail() *commitment { + //nolint:forcetypeassert return s.commitments.Front().Value.(*commitment) }