diff --git a/lnwallet/channel.go b/lnwallet/channel.go index a083b89c3..d03c5df1a 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 000000000..0256bbd60 --- /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) +}