Skip to content

Commit e1c9825

Browse files
authored
Merge pull request #9813 from Abdulkbk/htlcidx-to-fwdingevent
lnrpc: add HtlcIndex to ForwardingEvents
2 parents 4a7cd00 + ccf3a28 commit e1c9825

File tree

9 files changed

+1038
-774
lines changed

9 files changed

+1038
-774
lines changed

channeldb/forwarding_log.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package channeldb
22

33
import (
44
"bytes"
5+
"errors"
56
"io"
67
"sort"
78
"time"
89

910
"github.com/btcsuite/btcwallet/walletdb"
11+
"github.com/lightningnetwork/lnd/fn/v2"
1012
"github.com/lightningnetwork/lnd/kvdb"
1113
"github.com/lightningnetwork/lnd/lnwire"
1214
)
@@ -25,11 +27,12 @@ const (
2527
// is as follows:
2628
//
2729
// * 8 byte incoming chan ID || 8 byte outgoing chan ID || 8 byte value in
28-
// || 8 byte value out
30+
// || 8 byte value out || 8 byte incoming htlc id || 8 byte
31+
// outgoing htlc id
2932
//
3033
// From the value in and value out, callers can easily compute the
3134
// total fee extract from a forwarding event.
32-
forwardingEventSize = 32
35+
forwardingEventSize = 48
3336

3437
// MaxResponseEvents is the max number of forwarding events that will
3538
// be returned by a single query response. This size was selected to
@@ -78,14 +81,44 @@ type ForwardingEvent struct {
7881
// AmtOut is the amount of the outgoing HTLC. Subtracting the incoming
7982
// amount from this gives the total fees for this payment circuit.
8083
AmtOut lnwire.MilliSatoshi
84+
85+
// IncomingHtlcID is the ID of the incoming HTLC in the payment circuit.
86+
// If this is not set, the value will be nil. This field is added in
87+
// v0.20 and is made optional to make it backward compatible with
88+
// existing forwarding events created before it's introduction.
89+
IncomingHtlcID fn.Option[uint64]
90+
91+
// OutgoingHtlcID is the ID of the outgoing HTLC in the payment circuit.
92+
// If this is not set, the value will be nil. This field is added in
93+
// v0.20 and is made optional to make it backward compatible with
94+
// existing forwarding events created before it's introduction.
95+
OutgoingHtlcID fn.Option[uint64]
8196
}
8297

8398
// encodeForwardingEvent writes out the target forwarding event to the passed
8499
// io.Writer, using the expected DB format. Note that the timestamp isn't
85100
// serialized as this will be the key value within the bucket.
86101
func encodeForwardingEvent(w io.Writer, f *ForwardingEvent) error {
102+
// We check for the HTLC IDs if they are set. If they are not,
103+
// from v0.20 upward, we return an error to make it clear they are
104+
// required.
105+
incomingID, err := f.IncomingHtlcID.UnwrapOrErr(
106+
errors.New("incoming HTLC ID must be set"),
107+
)
108+
if err != nil {
109+
return err
110+
}
111+
112+
outgoingID, err := f.OutgoingHtlcID.UnwrapOrErr(
113+
errors.New("outgoing HTLC ID must be set"),
114+
)
115+
if err != nil {
116+
return err
117+
}
118+
87119
return WriteElements(
88120
w, f.IncomingChanID, f.OutgoingChanID, f.AmtIn, f.AmtOut,
121+
incomingID, outgoingID,
89122
)
90123
}
91124

@@ -94,9 +127,32 @@ func encodeForwardingEvent(w io.Writer, f *ForwardingEvent) error {
94127
// won't be decoded, as the caller is expected to set this due to the bucket
95128
// structure of the forwarding log.
96129
func decodeForwardingEvent(r io.Reader, f *ForwardingEvent) error {
97-
return ReadElements(
130+
// Decode the original fields of the forwarding event.
131+
err := ReadElements(
98132
r, &f.IncomingChanID, &f.OutgoingChanID, &f.AmtIn, &f.AmtOut,
99133
)
134+
if err != nil {
135+
return err
136+
}
137+
138+
// Decode the incoming and outgoing htlc IDs. For backward compatibility
139+
// with older records that don't have these fields, we handle EOF by
140+
// setting the ID to nil. Any other error is treated as a read failure.
141+
var incomingHtlcID, outgoingHtlcID uint64
142+
err = ReadElements(r, &incomingHtlcID, &outgoingHtlcID)
143+
switch {
144+
case err == nil:
145+
f.IncomingHtlcID = fn.Some(incomingHtlcID)
146+
f.OutgoingHtlcID = fn.Some(outgoingHtlcID)
147+
148+
return nil
149+
150+
case errors.Is(err, io.EOF):
151+
return nil
152+
153+
default:
154+
return err
155+
}
100156
}
101157

102158
// AddForwardingEvents adds a series of forwarding events to the database.

channeldb/forwarding_log_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package channeldb
22

33
import (
4+
"bytes"
45
"math/rand"
56
"reflect"
67
"testing"
78
"time"
89

910
"github.com/davecgh/go-spew/spew"
11+
"github.com/lightningnetwork/lnd/fn/v2"
12+
"github.com/lightningnetwork/lnd/kvdb"
1013
"github.com/lightningnetwork/lnd/lnwire"
1114
"github.com/stretchr/testify/assert"
1215
"github.com/stretchr/testify/require"
@@ -41,6 +44,8 @@ func TestForwardingLogBasicStorageAndQuery(t *testing.T) {
4144
OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())),
4245
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
4346
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
47+
IncomingHtlcID: fn.Some(uint64(i)),
48+
OutgoingHtlcID: fn.Some(uint64(i)),
4449
}
4550

4651
timestamp = timestamp.Add(time.Minute * 10)
@@ -109,6 +114,8 @@ func TestForwardingLogQueryOptions(t *testing.T) {
109114
OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())),
110115
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
111116
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
117+
IncomingHtlcID: fn.Some(uint64(i)),
118+
OutgoingHtlcID: fn.Some(uint64(i)),
112119
}
113120

114121
endTime = endTime.Add(time.Minute * 10)
@@ -208,6 +215,8 @@ func TestForwardingLogQueryLimit(t *testing.T) {
208215
OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())),
209216
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
210217
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
218+
IncomingHtlcID: fn.Some(uint64(i)),
219+
OutgoingHtlcID: fn.Some(uint64(i)),
211220
}
212221

213222
endTime = endTime.Add(time.Minute * 10)
@@ -317,6 +326,8 @@ func TestForwardingLogStoreEvent(t *testing.T) {
317326
OutgoingChanID: lnwire.NewShortChanIDFromInt(uint64(rand.Int63())),
318327
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
319328
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
329+
IncomingHtlcID: fn.Some(uint64(i)),
330+
OutgoingHtlcID: fn.Some(uint64(i)),
320331
}
321332
}
322333

@@ -360,3 +371,107 @@ func TestForwardingLogStoreEvent(t *testing.T) {
360371
}
361372
}
362373
}
374+
375+
// TestForwardingLogDecodeForwardingEvent tests that we're able to decode
376+
// forwarding events that don't have the new incoming and outgoing htlc
377+
// indices.
378+
func TestForwardingLogDecodeForwardingEvent(t *testing.T) {
379+
t.Parallel()
380+
381+
// First, we'll set up a test database, and use that to instantiate the
382+
// forwarding event log that we'll be using for the duration of the
383+
// test.
384+
db, err := MakeTestDB(t)
385+
require.NoError(t, err)
386+
387+
log := ForwardingLog{
388+
db: db,
389+
}
390+
391+
initialTime := time.Unix(1234, 0)
392+
endTime := time.Unix(1234, 0)
393+
394+
// We'll create forwarding events that don't have the incoming and
395+
// outgoing htlc indices.
396+
numEvents := 10
397+
events := make([]ForwardingEvent, numEvents)
398+
for i := range numEvents {
399+
events[i] = ForwardingEvent{
400+
Timestamp: endTime,
401+
IncomingChanID: lnwire.NewShortChanIDFromInt(
402+
uint64(rand.Int63()),
403+
),
404+
OutgoingChanID: lnwire.NewShortChanIDFromInt(
405+
uint64(rand.Int63()),
406+
),
407+
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
408+
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
409+
}
410+
411+
endTime = endTime.Add(time.Minute * 10)
412+
}
413+
414+
// Now that all of our events are constructed, we'll add them to the
415+
// database.
416+
err = writeOldFormatEvents(db, events)
417+
require.NoError(t, err)
418+
419+
// With all of our events added, we'll now query for them and ensure
420+
// that the incoming and outgoing htlc indices are set to 0 (default
421+
// value) for all events.
422+
eventQuery := ForwardingEventQuery{
423+
StartTime: initialTime,
424+
EndTime: endTime,
425+
IndexOffset: 0,
426+
NumMaxEvents: uint32(numEvents * 3),
427+
}
428+
timeSlice, err := log.Query(eventQuery)
429+
require.NoError(t, err)
430+
require.Equal(t, numEvents, len(timeSlice.ForwardingEvents))
431+
432+
for _, event := range timeSlice.ForwardingEvents {
433+
require.Equal(t, fn.None[uint64](), event.IncomingHtlcID)
434+
require.Equal(t, fn.None[uint64](), event.OutgoingHtlcID)
435+
}
436+
}
437+
438+
// writeOldFormatEvents writes forwarding events to the database in the old
439+
// format (without incoming and outgoing htlc indices). This is used to test
440+
// backward compatibility.
441+
func writeOldFormatEvents(db *DB, events []ForwardingEvent) error {
442+
return kvdb.Batch(db.Backend, func(tx kvdb.RwTx) error {
443+
bucket, err := tx.CreateTopLevelBucket(forwardingLogBucket)
444+
if err != nil {
445+
return err
446+
}
447+
448+
for _, event := range events {
449+
var timestamp [8]byte
450+
byteOrder.PutUint64(timestamp[:], uint64(
451+
event.Timestamp.UnixNano(),
452+
))
453+
454+
// Use the old event size (32 bytes) for writing old
455+
// format events.
456+
var eventBytes [32]byte
457+
eventBuf := bytes.NewBuffer(eventBytes[0:0:32])
458+
459+
// Write only the original fields without incoming and
460+
// outgoing htlc indices.
461+
if err := WriteElements(
462+
eventBuf, event.IncomingChanID,
463+
event.OutgoingChanID, event.AmtIn, event.AmtOut,
464+
); err != nil {
465+
return err
466+
}
467+
468+
if err := bucket.Put(
469+
timestamp[:], eventBuf.Bytes(),
470+
); err != nil {
471+
return err
472+
}
473+
}
474+
475+
return nil
476+
})
477+
}

docs/release-notes/release-notes-0.20.0.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
## Functional Enhancements
2626

2727
## RPC Additions
28+
* When querying [`ForwardingEvents`](https://github.com/lightningnetwork/lnd/pull/9813)
29+
logs, the response now include the incoming and outgoing htlc indices of the payment
30+
circuit. The indices are only available for forwarding events saved after v0.20.
31+
2832

2933
* The `lncli addinvoice --blind` command now has the option to include a
3034
[chained channels](https://github.com/lightningnetwork/lnd/pull/9127)
@@ -94,4 +98,5 @@
9498

9599
# Contributors (Alphabetical Order)
96100

101+
* Abdulkbk
97102
Pins

htlcswitch/switch.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3074,6 +3074,12 @@ func (s *Switch) handlePacketSettle(packet *htlcPacket) error {
30743074
OutgoingChanID: circuit.Outgoing.ChanID,
30753075
AmtIn: circuit.IncomingAmount,
30763076
AmtOut: circuit.OutgoingAmount,
3077+
IncomingHtlcID: fn.Some(
3078+
circuit.Incoming.HtlcID,
3079+
),
3080+
OutgoingHtlcID: fn.Some(
3081+
circuit.Outgoing.HtlcID,
3082+
),
30773083
},
30783084
)
30793085
s.fwdEventMtx.Unlock()

itest/lnd_multi-hop-payments_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,31 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
213213
require.Equal(ht, aliceAlias, event.PeerAliasOut)
214214
}
215215

216+
// Verify HTLC IDs are not nil and unique across all forwarding events.
217+
seenIDs := make(map[uint64]bool)
218+
for _, event := range fwdingHistory.ForwardingEvents {
219+
// We check that the incoming and outgoing htlc indices are not
220+
// set to nil. The indices are required for any forwarding event
221+
// recorded after v0.20.
222+
require.NotNil(ht, event.IncomingHtlcId)
223+
require.NotNil(ht, event.OutgoingHtlcId)
224+
225+
require.False(ht, seenIDs[*event.IncomingHtlcId])
226+
require.False(ht, seenIDs[*event.OutgoingHtlcId])
227+
seenIDs[*event.IncomingHtlcId] = true
228+
seenIDs[*event.OutgoingHtlcId] = true
229+
}
230+
231+
// The HTLC IDs should be exactly 0, 1, 2, 3, 4.
232+
expectedIDs := map[uint64]bool{
233+
0: true,
234+
1: true,
235+
2: true,
236+
3: true,
237+
4: true,
238+
}
239+
require.Equal(ht, expectedIDs, seenIDs)
240+
216241
// We expect Carol to have successful forwards and settles for
217242
// her sends.
218243
ht.AssertHtlcEvents(

0 commit comments

Comments
 (0)