lnwallet: add new modified HTLC index to the updateLog

In this commit, we add a new index to the HTLC log. This new index is
meant to ensure that we don't attempt to modify and HTLC twice. An HTLC
modification is either a fail or a settle. This is the first in a series
of commits to fix an existing bug in the state machine that can cause a
panic if a remote node attempts to settle an HTLC twice.
This commit is contained in:
Olaoluwa Osuntokun 2018-05-25 18:37:45 -07:00
parent 19e826cada
commit 56ebd6de8d
No known key found for this signature in database
GPG key ID: 964EA263DD637C21

View file

@ -825,7 +825,7 @@ func (lc *LightningChannel) extractPayDescs(commitHeight uint64,
return incomingHtlcs, outgoingHtlcs, nil return incomingHtlcs, outgoingHtlcs, nil
} }
// diskCommitToMemCommit converts tthe on-disk commitment format to our // diskCommitToMemCommit converts the on-disk commitment format to our
// in-memory commitment format which is needed in order to properly resume // in-memory commitment format which is needed in order to properly resume
// channel operations after a restart. // channel operations after a restart.
func (lc *LightningChannel) diskCommitToMemCommit(isLocal, isPendingCommit bool, func (lc *LightningChannel) diskCommitToMemCommit(isLocal, isPendingCommit bool,
@ -1090,16 +1090,22 @@ type updateLog struct {
// offerIndex is an index that maps the counter for offered HTLC's to // offerIndex is an index that maps the counter for offered HTLC's to
// their list element within the main list.List. // their list element within the main list.List.
htlcIndex map[uint64]*list.Element htlcIndex map[uint64]*list.Element
// modifiedHtlcs is a set that keeps track of all the current modified
// htlcs. 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. // newUpdateLog creates a new updateLog instance.
func newUpdateLog(logIndex, htlcCounter uint64) *updateLog { func newUpdateLog(logIndex, htlcCounter uint64) *updateLog {
return &updateLog{ return &updateLog{
List: list.New(), List: list.New(),
updateIndex: make(map[uint64]*list.Element), updateIndex: make(map[uint64]*list.Element),
htlcIndex: make(map[uint64]*list.Element), htlcIndex: make(map[uint64]*list.Element),
logIndex: logIndex, logIndex: logIndex,
htlcCounter: htlcCounter, htlcCounter: htlcCounter,
modifiedHtlcs: make(map[uint64]struct{}),
} }
} }
@ -1158,6 +1164,19 @@ func (u *updateLog) removeHtlc(i uint64) {
entry := u.htlcIndex[i] entry := u.htlcIndex[i]
u.Remove(entry) u.Remove(entry)
delete(u.htlcIndex, i) delete(u.htlcIndex, 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 // compactLogs performs garbage collection within the log removing HTLCs which
@ -1509,10 +1528,12 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
if !isDustRemote { if !isDustRemote {
theirP2WSH, theirWitnessScript, err := genHtlcScript( theirP2WSH, theirWitnessScript, err := genHtlcScript(
false, false, wireMsg.Expiry, wireMsg.PaymentHash, false, false, wireMsg.Expiry, wireMsg.PaymentHash,
remoteCommitKeys) remoteCommitKeys,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pd.theirPkScript = theirP2WSH pd.theirPkScript = theirP2WSH
pd.theirWitnessScript = theirWitnessScript pd.theirWitnessScript = theirWitnessScript
} }
@ -1571,7 +1592,7 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
} }
// restoreCommitState will restore the local commitment chain and updateLog // restoreCommitState will restore the local commitment chain and updateLog
// state to a consistent in-memory representation of the passed dis commitment. // state to a consistent in-memory representation of the passed disk commitment.
// This method is to be used upon reconnection to our channel counter party. // This method is to be used upon reconnection to our channel counter party.
// Once the connection has been established, we'll prepare our in memory state // Once the connection has been established, we'll prepare our in memory state
// to re-sync states with the remote party, and also verify/extend new proposed // to re-sync states with the remote party, and also verify/extend new proposed