diff --git a/channeldb/channel.go b/channeldb/channel.go index 67a32379e..dbd796c87 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -849,6 +849,30 @@ type OpenChannel struct { sync.RWMutex } +// String returns a string representation of the channel. +func (c *OpenChannel) String() string { + indexStr := "height=%v, local_htlc_index=%v, local_log_index=%v, " + + "remote_htlc_index=%v, remote_log_index=%v" + + commit := c.LocalCommitment + local := fmt.Sprintf(indexStr, commit.CommitHeight, + commit.LocalHtlcIndex, commit.LocalLogIndex, + commit.RemoteHtlcIndex, commit.RemoteLogIndex, + ) + + commit = c.RemoteCommitment + remote := fmt.Sprintf(indexStr, commit.CommitHeight, + commit.LocalHtlcIndex, commit.LocalLogIndex, + commit.RemoteHtlcIndex, commit.RemoteLogIndex, + ) + + return fmt.Sprintf("SCID=%v, status=%v, initiator=%v, pending=%v, "+ + "local commitment has %s, remote commitment has %s", + c.ShortChannelID, c.chanStatus, c.IsInitiator, c.IsPending, + local, remote, + ) +} + // ShortChanID returns the current ShortChannelID of this channel. func (c *OpenChannel) ShortChanID() lnwire.ShortChannelID { c.RLock() @@ -2100,6 +2124,10 @@ func (c *OpenChannel) ActiveHtlcs() []HTLC { // which ones are present on their commitment. remoteHtlcs := make(map[[32]byte]struct{}) for _, htlc := range c.RemoteCommitment.Htlcs { + log.Tracef("RemoteCommitment has htlc: id=%v, update=%v "+ + "incoming=%v", htlc.HtlcIndex, htlc.LogIndex, + htlc.Incoming) + onionHash := sha256.Sum256(htlc.OnionBlob[:]) remoteHtlcs[onionHash] = struct{}{} } @@ -2108,8 +2136,16 @@ func (c *OpenChannel) ActiveHtlcs() []HTLC { // as active if *we* know them as well. activeHtlcs := make([]HTLC, 0, len(remoteHtlcs)) for _, htlc := range c.LocalCommitment.Htlcs { + log.Tracef("LocalCommitment has htlc: id=%v, update=%v "+ + "incoming=%v", htlc.HtlcIndex, htlc.LogIndex, + htlc.Incoming) + onionHash := sha256.Sum256(htlc.OnionBlob[:]) if _, ok := remoteHtlcs[onionHash]; !ok { + log.Tracef("Skipped htlc due to onion mismatched: "+ + "id=%v, update=%v incoming=%v", + htlc.HtlcIndex, htlc.LogIndex, htlc.Incoming) + continue } diff --git a/docs/release-notes/release-notes-0.17.3.md b/docs/release-notes/release-notes-0.17.3.md index b00a5d6f7..1cbfe8cf8 100644 --- a/docs/release-notes/release-notes-0.17.3.md +++ b/docs/release-notes/release-notes-0.17.3.md @@ -23,6 +23,9 @@ `musig2Sessions` with a `SyncMap` used in `input` package to avoid concurrent write to this map. +* [Fixed](https://github.com/lightningnetwork/lnd/pull/8220) a loop variable + issue which may affect programs built using go `v1.20` and below. + # New Features ## Functional Enhancements ## RPC Additions diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 2a1e49923..e302cf3d8 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -649,12 +649,13 @@ func (l *channelLink) createFailureWithUpdate(incoming bool, // flow. We'll compare out commitment chains with the remote party, and re-send // either a danging commit signature, a revocation, or both. func (l *channelLink) syncChanStates() error { - l.log.Info("attempting to re-synchronize") + chanState := l.channel.State() + + l.log.Infof("Attempting to re-synchronize channel: %v", chanState) // First, we'll generate our ChanSync message to send to the other // side. Based on this message, the remote party will decide if they // need to retransmit any data or not. - chanState := l.channel.State() localChanSyncMsg, err := chanState.ChanSyncMsg() if err != nil { return fmt.Errorf("unable to generate chan sync message for "+ diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 98fb3abdc..7c383d8d2 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -904,6 +904,8 @@ func (lc *LightningChannel) extractPayDescs(commitHeight uint64, // persist state w.r.t to if forwarded or not, or can // inadvertently trigger replays + htlc := htlc + payDesc, err := lc.diskHtlcToPayDesc( feeRate, commitHeight, &htlc, localCommitKeys, remoteCommitKeys, diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 1ea726c63..b52cd9f66 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -10112,3 +10112,101 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) { ) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) } + +// TestExtractPayDescs asserts that `extractPayDescs` can correctly turn a +// slice of htlcs into two slices of PaymentDescriptors. +func TestExtractPayDescs(t *testing.T) { + t.Parallel() + + // Create a testing LightningChannel. + lnChan, _, err := CreateTestChannels( + t, channeldb.SingleFunderTweaklessBit, + ) + require.NoError(t, err) + + // Create two incoming HTLCs. + incomings := []channeldb.HTLC{ + createRandomHTLC(t, true), + createRandomHTLC(t, true), + } + + // Create two outgoing HTLCs. + outgoings := []channeldb.HTLC{ + createRandomHTLC(t, false), + createRandomHTLC(t, false), + } + + // Concatenate incomings and outgoings into a single slice. + htlcs := []channeldb.HTLC{} + htlcs = append(htlcs, incomings...) + htlcs = append(htlcs, outgoings...) + + // Run the method under test. + // + // NOTE: we use nil commitment key rings to avoid checking the htlc + // scripts(`genHtlcScript`) as it should be tested independently. + incomingPDs, outgoingPDs, err := lnChan.extractPayDescs( + 0, 0, htlcs, nil, nil, true, + ) + require.NoError(t, err) + + // Assert the incoming PaymentDescriptors are matched. + for i, pd := range incomingPDs { + htlc := incomings[i] + assertPayDescMatchHTLC(t, pd, htlc) + } + + // Assert the outgoing PaymentDescriptors are matched. + for i, pd := range outgoingPDs { + htlc := outgoings[i] + assertPayDescMatchHTLC(t, pd, htlc) + } +} + +// assertPayDescMatchHTLC compares a PaymentDescriptor to a channeldb.HTLC and +// asserts that the fields are matched. +func assertPayDescMatchHTLC(t *testing.T, pd PaymentDescriptor, + htlc channeldb.HTLC) { + + require := require.New(t) + + require.EqualValues(htlc.RHash, pd.RHash, "RHash") + require.Equal(htlc.RefundTimeout, pd.Timeout, "Timeout") + require.Equal(htlc.Amt, pd.Amount, "Amount") + require.Equal(htlc.HtlcIndex, pd.HtlcIndex, "HtlcIndex") + require.Equal(htlc.LogIndex, pd.LogIndex, "LogIndex") + require.EqualValues(htlc.OnionBlob[:], pd.OnionBlob, "OnionBlob") +} + +// createRandomHTLC creates an HTLC that has random value in every field except +// the `Incoming`. +func createRandomHTLC(t *testing.T, incoming bool) channeldb.HTLC { + var onionBlob [lnwire.OnionPacketSize]byte + _, err := rand.Read(onionBlob[:]) + require.NoError(t, err) + + var rHash [lntypes.HashSize]byte + _, err = rand.Read(rHash[:]) + require.NoError(t, err) + + sig := make([]byte, 64) + _, err = rand.Read(sig) + require.NoError(t, err) + + extra := make([]byte, 1000) + _, err = rand.Read(extra) + require.NoError(t, err) + + return channeldb.HTLC{ + Signature: sig, + RHash: rHash, + Amt: lnwire.MilliSatoshi(rand.Uint64()), + RefundTimeout: rand.Uint32(), + OutputIndex: rand.Int31n(1000), + Incoming: incoming, + OnionBlob: onionBlob, + HtlcIndex: rand.Uint64(), + LogIndex: rand.Uint64(), + ExtraData: extra, + } +} diff --git a/peer/brontide.go b/peer/brontide.go index 46954e9cd..ecb3d4d59 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -1941,7 +1941,8 @@ func messageSummary(msg lnwire.Message) string { msg.ChanID, int64(msg.FeePerKw)) case *lnwire.ChannelReestablish: - return fmt.Sprintf("next_local_height=%v, remote_tail_height=%v", + return fmt.Sprintf("chan_id=%v, next_local_height=%v, "+ + "remote_tail_height=%v", msg.ChanID, msg.NextLocalCommitHeight, msg.RemoteCommitTailHeight) case *lnwire.ReplyShortChanIDsEnd: