Skip to content

Commit 9ae99da

Browse files
committed
lnrpc: add filters to forwardhistoryrequest
This commit adds incoming and outgoing channel ids filter to forwarding history request to filter events received/forwarded from/to a particular channel
1 parent f0ea5bf commit 9ae99da

File tree

8 files changed

+1160
-802
lines changed

8 files changed

+1160
-802
lines changed

channeldb/forwarding_log.go

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/btcsuite/btcwallet/walletdb"
10+
"github.com/lightningnetwork/lnd/fn/v2"
1011
"github.com/lightningnetwork/lnd/kvdb"
1112
"github.com/lightningnetwork/lnd/lnwire"
1213
)
@@ -200,6 +201,16 @@ type ForwardingEventQuery struct {
200201

201202
// NumMaxEvents is the max number of events to return.
202203
NumMaxEvents uint32
204+
205+
// IncomingChanIds is the list of channels to filter HTLCs being
206+
// received from a particular channel.
207+
// If the list is empty, then it is ignored.
208+
IncomingChanIDs fn.Set[uint64]
209+
210+
// OutgoingChanIds is the list of channels to filter HTLCs being
211+
// forwarded to a particular channel.
212+
// If the list is empty, then it is ignored.
213+
OutgoingChanIDs fn.Set[uint64]
203214
}
204215

205216
// ForwardingLogTimeSlice is the response to a forwarding query. It includes
@@ -264,9 +275,13 @@ func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice, e
264275
return nil
265276
}
266277

267-
// If we're not yet past the user defined offset, then
278+
// If no incoming or outgoing channel IDs were provided
279+
// and we're not yet past the user defined offset, then
268280
// we'll continue to seek forward.
269-
if recordsToSkip > 0 {
281+
if recordsToSkip > 0 &&
282+
q.IncomingChanIDs.IsEmpty() &&
283+
q.OutgoingChanIDs.IsEmpty() {
284+
270285
recordsToSkip--
271286
continue
272287
}
@@ -287,10 +302,42 @@ func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice, e
287302
return err
288303
}
289304

290-
event.Timestamp = currentTime
291-
resp.ForwardingEvents = append(resp.ForwardingEvents, event)
292-
293-
recordOffset++
305+
// Check if the incoming channel ID matches the
306+
// filter criteria.
307+
// Either no filtering is applied (IsEmpty), or
308+
// the ID is explicitly included.
309+
incomingMatch := q.IncomingChanIDs.IsEmpty() ||
310+
q.IncomingChanIDs.Contains(
311+
event.IncomingChanID.ToUint64(),
312+
)
313+
314+
// Check if the outgoing channel ID matches the
315+
// filter criteria.
316+
// Either no filtering is applied (IsEmpty), or
317+
// the ID is explicitly included.
318+
outgoingMatch := q.OutgoingChanIDs.IsEmpty() ||
319+
q.OutgoingChanIDs.Contains(
320+
event.OutgoingChanID.ToUint64(),
321+
)
322+
323+
// If both conditions are met, then we'll add
324+
// the event to our return payload.
325+
if incomingMatch && outgoingMatch {
326+
// If we're not yet past the user
327+
// defined offset , then we'll continue
328+
// to seek forward.
329+
if recordsToSkip > 0 {
330+
recordsToSkip--
331+
continue
332+
}
333+
334+
event.Timestamp = currentTime
335+
resp.ForwardingEvents = append(
336+
resp.ForwardingEvents,
337+
event,
338+
)
339+
recordOffset++
340+
}
294341
}
295342
}
296343

channeldb/forwarding_log_test.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/davecgh/go-spew/spew"
10+
"github.com/lightningnetwork/lnd/fn/v2"
1011
"github.com/lightningnetwork/lnd/lnwire"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
@@ -360,3 +361,213 @@ func TestForwardingLogStoreEvent(t *testing.T) {
360361
}
361362
}
362363
}
364+
365+
// TestForwardingLogQueryIncomingChanIDs tests that querying the forwarding log
366+
// using only incoming channel IDs returns the correct subset of events.
367+
func TestForwardingLogQueryIncomingChanIDs(t *testing.T) {
368+
t.Parallel()
369+
370+
// Set up a test database.
371+
db, err := MakeTestDB(t)
372+
require.NoError(t, err, "unable to make test db")
373+
374+
log := ForwardingLog{
375+
db: db,
376+
}
377+
378+
initialTime := time.Unix(1234, 0)
379+
endTime := time.Unix(1234, 0)
380+
381+
// Create 10 random events with varying incoming ChanIDs.
382+
numEvents := 10
383+
events := make([]ForwardingEvent, numEvents)
384+
incomingChanIDs := []lnwire.ShortChannelID{
385+
lnwire.NewShortChanIDFromInt(2001),
386+
lnwire.NewShortChanIDFromInt(2002),
387+
lnwire.NewShortChanIDFromInt(2003),
388+
}
389+
390+
for i := 0; i < numEvents; i++ {
391+
events[i] = ForwardingEvent{
392+
Timestamp: endTime,
393+
IncomingChanID: incomingChanIDs[i%len(incomingChanIDs)],
394+
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
395+
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
396+
}
397+
endTime = endTime.Add(time.Minute * 10)
398+
}
399+
400+
// Add events to the database.
401+
require.NoError(t, log.AddForwardingEvents(events),
402+
"unable to add events")
403+
404+
// Query with multiple incoming channel IDs.
405+
eventQuery := ForwardingEventQuery{
406+
StartTime: initialTime,
407+
EndTime: endTime,
408+
IncomingChanIDs: fn.NewSet(incomingChanIDs[0].ToUint64(),
409+
incomingChanIDs[1].ToUint64()),
410+
IndexOffset: 0,
411+
NumMaxEvents: 10,
412+
}
413+
timeSlice, err := log.Query(eventQuery)
414+
require.NoError(t, err, "unable to query for events")
415+
416+
// Verify that only events with the specified incomingChanIDs are
417+
// returned.
418+
expectedEvents := []ForwardingEvent{}
419+
for _, e := range events {
420+
if e.IncomingChanID == incomingChanIDs[0] ||
421+
e.IncomingChanID == incomingChanIDs[1] {
422+
423+
expectedEvents = append(expectedEvents, e)
424+
}
425+
}
426+
427+
require.Equal(t, expectedEvents, timeSlice.ForwardingEvents,
428+
"unexpected events returned")
429+
}
430+
431+
// TestForwardingLogQueryOutgoingChanIDs tests that querying the forwarding log
432+
// using only outgoing channel IDs returns the correct subset of events.
433+
func TestForwardingLogQueryOutgoingChanIDs(t *testing.T) {
434+
t.Parallel()
435+
436+
// Set up a test database.
437+
db, err := MakeTestDB(t)
438+
require.NoError(t, err, "unable to make test db")
439+
440+
log := ForwardingLog{
441+
db: db,
442+
}
443+
444+
initialTime := time.Unix(1234, 0)
445+
endTime := time.Unix(1234, 0)
446+
447+
// Create 10 random events with varying outgoing ChanIDs.
448+
numEvents := 10
449+
events := make([]ForwardingEvent, numEvents)
450+
outgoingChanIDs := []lnwire.ShortChannelID{
451+
lnwire.NewShortChanIDFromInt(2001),
452+
lnwire.NewShortChanIDFromInt(2002),
453+
lnwire.NewShortChanIDFromInt(2003),
454+
}
455+
456+
for i := 0; i < numEvents; i++ {
457+
events[i] = ForwardingEvent{
458+
Timestamp: endTime,
459+
OutgoingChanID: outgoingChanIDs[i%len(outgoingChanIDs)],
460+
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
461+
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
462+
}
463+
endTime = endTime.Add(time.Minute * 10)
464+
}
465+
466+
// Add events to the database.
467+
require.NoError(t, log.AddForwardingEvents(events),
468+
"unable to add events")
469+
470+
// Query with multiple outgoing channel IDs.
471+
eventQuery := ForwardingEventQuery{
472+
StartTime: initialTime,
473+
EndTime: endTime,
474+
OutgoingChanIDs: fn.NewSet(outgoingChanIDs[0].ToUint64(),
475+
outgoingChanIDs[1].ToUint64()),
476+
IndexOffset: 0,
477+
NumMaxEvents: 10,
478+
}
479+
timeSlice, err := log.Query(eventQuery)
480+
require.NoError(t, err, "unable to query for events")
481+
482+
// Verify that only events with the specified outgoingChanIDs are
483+
// returned.
484+
expectedEvents := []ForwardingEvent{}
485+
for _, e := range events {
486+
if e.OutgoingChanID == outgoingChanIDs[0] ||
487+
e.OutgoingChanID == outgoingChanIDs[1] {
488+
489+
expectedEvents = append(expectedEvents, e)
490+
}
491+
}
492+
493+
require.Equal(t, expectedEvents, timeSlice.ForwardingEvents,
494+
"unexpected events returned")
495+
}
496+
497+
// TestForwardingLogQueryIncomingAndOutgoingChanIDs tests that querying the
498+
// forwarding log using both incoming and outgoing channel IDs returns events
499+
// that match either of the specified criteria.
500+
func TestForwardingLogQueryIncomingAndOutgoingChanIDs(t *testing.T) {
501+
t.Parallel()
502+
503+
// Set up a test database.
504+
db, err := MakeTestDB(t)
505+
require.NoError(t, err, "unable to make test db")
506+
507+
log := ForwardingLog{
508+
db: db,
509+
}
510+
511+
initialTime := time.Unix(1234, 0)
512+
endTime := time.Unix(1234, 0)
513+
514+
// Create 10 random events with varying incoming and outgoing ChanIDs.
515+
numEvents := 10
516+
events := make([]ForwardingEvent, numEvents)
517+
outgoingChanIDs := []lnwire.ShortChannelID{
518+
lnwire.NewShortChanIDFromInt(2001),
519+
lnwire.NewShortChanIDFromInt(2002),
520+
lnwire.NewShortChanIDFromInt(2003),
521+
}
522+
523+
incomingChanIDs := []lnwire.ShortChannelID{
524+
lnwire.NewShortChanIDFromInt(3001),
525+
lnwire.NewShortChanIDFromInt(3002),
526+
lnwire.NewShortChanIDFromInt(3003),
527+
}
528+
529+
for i := 0; i < numEvents; i++ {
530+
events[i] = ForwardingEvent{
531+
Timestamp: endTime,
532+
OutgoingChanID: outgoingChanIDs[i%len(outgoingChanIDs)],
533+
IncomingChanID: incomingChanIDs[i%len(incomingChanIDs)],
534+
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
535+
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
536+
}
537+
endTime = endTime.Add(time.Minute * 10)
538+
}
539+
540+
// Add events to the database.
541+
require.NoError(t, log.AddForwardingEvents(events),
542+
"unable to add events")
543+
544+
// Query with multiple outgoing channel IDs.
545+
eventQuery := ForwardingEventQuery{
546+
StartTime: initialTime,
547+
EndTime: endTime,
548+
OutgoingChanIDs: fn.NewSet(outgoingChanIDs[0].ToUint64(),
549+
outgoingChanIDs[1].ToUint64()),
550+
IncomingChanIDs: fn.NewSet(incomingChanIDs[0].ToUint64(),
551+
incomingChanIDs[1].ToUint64()),
552+
IndexOffset: 0,
553+
NumMaxEvents: 10,
554+
}
555+
timeSlice, err := log.Query(eventQuery)
556+
require.NoError(t, err, "unable to query for events")
557+
558+
// Verify that only events with the specified outgoingChanIDs are
559+
// returned.
560+
expectedEvents := []ForwardingEvent{}
561+
for _, e := range events {
562+
if e.OutgoingChanID == outgoingChanIDs[0] ||
563+
e.OutgoingChanID == outgoingChanIDs[1] ||
564+
e.IncomingChanID == incomingChanIDs[0] ||
565+
e.IncomingChanID == incomingChanIDs[1] {
566+
567+
expectedEvents = append(expectedEvents, e)
568+
}
569+
}
570+
571+
require.Equal(t, expectedEvents, timeSlice.ForwardingEvents,
572+
"unexpected events returned")
573+
}

cmd/commands/cmd_payments.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,10 +1471,11 @@ func listPayments(ctx *cli.Context) error {
14711471
}
14721472

14731473
var forwardingHistoryCommand = cli.Command{
1474-
Name: "fwdinghistory",
1475-
Category: "Payments",
1476-
Usage: "Query the history of all forwarded HTLCs.",
1477-
ArgsUsage: "start_time [end_time] [index_offset] [max_events]",
1474+
Name: "fwdinghistory",
1475+
Category: "Payments",
1476+
Usage: "Query the history of all forwarded HTLCs.",
1477+
ArgsUsage: "start_time [end_time] [index_offset] [max_events] " +
1478+
"[incoming_channel_ids] [outgoing_channel_ids]",
14781479
Description: `
14791480
Query the HTLC switch's internal forwarding log for all completed
14801481
payment circuits (HTLCs) over a particular time range (--start_time and
@@ -1489,6 +1490,9 @@ var forwardingHistoryCommand = cli.Command{
14891490
The max number of events returned is 50k. The default number is 100,
14901491
callers can use the --max_events param to modify this value.
14911492
1493+
Incoming and outgoing channel IDs can be provided to further filter
1494+
the events. If not provided, all events will be returned.
1495+
14921496
Finally, callers can skip a series of events using the --index_offset
14931497
parameter. Each response will contain the offset index of the last
14941498
entry. Using this callers can manually paginate within a time slice.
@@ -1517,6 +1521,16 @@ var forwardingHistoryCommand = cli.Command{
15171521
Usage: "skip the peer alias lookup per forwarding " +
15181522
"event in order to improve performance",
15191523
},
1524+
cli.Int64SliceFlag{
1525+
Name: "incoming_chan_ids",
1526+
Usage: "the short channel ids of the incoming " +
1527+
"channels to filter events by",
1528+
},
1529+
cli.Int64SliceFlag{
1530+
Name: "outgoing_chan_ids",
1531+
Usage: "the short channel ids of the outgoing " +
1532+
"channels to filter events by",
1533+
},
15201534
},
15211535
Action: actionDecorator(forwardingHistory),
15221536
}
@@ -1597,6 +1611,21 @@ func forwardingHistory(ctx *cli.Context) error {
15971611
NumMaxEvents: maxEvents,
15981612
PeerAliasLookup: lookupPeerAlias,
15991613
}
1614+
outgoingChannelIDs := ctx.Int64Slice("outgoing_chan_ids")
1615+
if len(outgoingChannelIDs) != 0 {
1616+
req.OutgoingChanIds = make([]uint64, len(outgoingChannelIDs))
1617+
for i, c := range outgoingChannelIDs {
1618+
req.OutgoingChanIds[i] = uint64(c)
1619+
}
1620+
}
1621+
1622+
incomingChannelIDs := ctx.Int64Slice("incoming_chan_ids")
1623+
if len(incomingChannelIDs) != 0 {
1624+
req.IncomingChanIds = make([]uint64, len(incomingChannelIDs))
1625+
for i, c := range incomingChannelIDs {
1626+
req.IncomingChanIds[i] = uint64(c)
1627+
}
1628+
}
16001629
resp, err := client.ForwardingHistory(ctxc, req)
16011630
if err != nil {
16021631
return err

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

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

2727
## RPC Additions
28+
* The `lnrpc.ForwardingHistory` RPC method now supports filtering by
29+
[`incoming_chan_ids` and `outgoing_chan_ids`](https://github.com/lightningnetwork/lnd/pull/9356).
30+
This allows to retrieve forwarding events for specific channels.
2831

2932
## lncli Additions
33+
* The `lncli fwdinghistory` command now supports two new flags:
34+
[`--incoming_chan_ids` and `--outgoing_chan_ids`](https://github.com/lightningnetwork/lnd/pull/9356).
35+
These filters allows to query forwarding events for specific channels.
3036

3137
# Improvements
3238
## Functional Updates

0 commit comments

Comments
 (0)