Skip to content

Commit bfe1edf

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 92a5d35 commit bfe1edf

File tree

8 files changed

+1089
-815
lines changed

8 files changed

+1089
-815
lines changed

channeldb/forwarding_log.go

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,16 @@ type ForwardingEventQuery struct {
256256

257257
// NumMaxEvents is the max number of events to return.
258258
NumMaxEvents uint32
259+
260+
// IncomingChanIds is the list of channels to filter HTLCs being
261+
// received from a particular channel.
262+
// If the list is empty, then it is ignored.
263+
IncomingChanIDs fn.Set[uint64]
264+
265+
// OutgoingChanIds is the list of channels to filter HTLCs being
266+
// forwarded to a particular channel.
267+
// If the list is empty, then it is ignored.
268+
OutgoingChanIDs fn.Set[uint64]
259269
}
260270

261271
// ForwardingLogTimeSlice is the response to a forwarding query. It includes
@@ -323,9 +333,13 @@ func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice,
323333
return nil
324334
}
325335

326-
// If we're not yet past the user defined offset, then
336+
// If no incoming or outgoing channel IDs were provided
337+
// and we're not yet past the user defined offset, then
327338
// we'll continue to seek forward.
328-
if recordsToSkip > 0 {
339+
if recordsToSkip > 0 &&
340+
q.IncomingChanIDs.IsEmpty() &&
341+
q.OutgoingChanIDs.IsEmpty() {
342+
329343
recordsToSkip--
330344
continue
331345
}
@@ -349,11 +363,41 @@ func (f *ForwardingLog) Query(q ForwardingEventQuery) (ForwardingLogTimeSlice,
349363
return err
350364
}
351365

366+
// Check if the incoming channel ID matches the
367+
// filter criteria. Either no filtering is
368+
// applied (IsEmpty), or the ID is explicitly
369+
// included.
370+
incomingMatch := q.IncomingChanIDs.IsEmpty() ||
371+
q.IncomingChanIDs.Contains(
372+
event.IncomingChanID.ToUint64(),
373+
)
374+
375+
// Check if the outgoing channel ID matches the
376+
// filter criteria. Either no filtering is
377+
// applied (IsEmpty), or the ID is explicitly
378+
// included.
379+
outgoingMatch := q.OutgoingChanIDs.IsEmpty() ||
380+
q.OutgoingChanIDs.Contains(
381+
event.OutgoingChanID.ToUint64(),
382+
)
383+
384+
// Skip this event if it doesn't match the
385+
// filters.
386+
if !incomingMatch || !outgoingMatch {
387+
continue
388+
}
389+
// If we're not yet past the user defined offset
390+
// then we'll continue to seek forward.
391+
if recordsToSkip > 0 {
392+
recordsToSkip--
393+
continue
394+
}
395+
352396
event.Timestamp = currentTime
353397
resp.ForwardingEvents = append(
354-
resp.ForwardingEvents, event,
398+
resp.ForwardingEvents,
399+
event,
355400
)
356-
357401
recordOffset++
358402
}
359403

channeldb/forwarding_log_test.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,134 @@ func writeOldFormatEvents(db *DB, events []ForwardingEvent) error {
475475
return nil
476476
})
477477
}
478+
479+
// TestForwardingLogQueryChanIDs tests that querying the forwarding log with
480+
// various combinations of incoming and/or outgoing channel IDs returns the
481+
// correct subset of forwarding events.
482+
func TestForwardingLogQueryChanIDs(t *testing.T) {
483+
t.Parallel()
484+
485+
db, err := MakeTestDB(t)
486+
require.NoError(t, err, "unable to make test db")
487+
488+
log := ForwardingLog{db: db}
489+
490+
initialTime := time.Unix(1234, 0)
491+
endTime := initialTime
492+
493+
numEvents := 10
494+
incomingChanIDs := []lnwire.ShortChannelID{
495+
lnwire.NewShortChanIDFromInt(2001),
496+
lnwire.NewShortChanIDFromInt(2002),
497+
lnwire.NewShortChanIDFromInt(2003),
498+
}
499+
outgoingChanIDs := []lnwire.ShortChannelID{
500+
lnwire.NewShortChanIDFromInt(3001),
501+
lnwire.NewShortChanIDFromInt(3002),
502+
lnwire.NewShortChanIDFromInt(3003),
503+
}
504+
505+
events := make([]ForwardingEvent, numEvents)
506+
for i := 0; i < numEvents; i++ {
507+
events[i] = ForwardingEvent{
508+
Timestamp: endTime,
509+
IncomingChanID: incomingChanIDs[i%len(incomingChanIDs)],
510+
OutgoingChanID: outgoingChanIDs[i%len(outgoingChanIDs)],
511+
AmtIn: lnwire.MilliSatoshi(rand.Int63()),
512+
AmtOut: lnwire.MilliSatoshi(rand.Int63()),
513+
IncomingHtlcID: fn.Some(uint64(i)),
514+
OutgoingHtlcID: fn.Some(uint64(i)),
515+
}
516+
endTime = endTime.Add(10 * time.Minute)
517+
}
518+
519+
require.NoError(
520+
t,
521+
log.AddForwardingEvents(events),
522+
"unable to add events",
523+
)
524+
525+
tests := []struct {
526+
name string
527+
query ForwardingEventQuery
528+
expected func(e ForwardingEvent) bool
529+
}{
530+
{
531+
name: "only incomingChanIDs filter",
532+
query: ForwardingEventQuery{
533+
StartTime: initialTime,
534+
EndTime: endTime,
535+
IncomingChanIDs: fn.NewSet(
536+
incomingChanIDs[0].ToUint64(),
537+
incomingChanIDs[1].ToUint64(),
538+
),
539+
IndexOffset: 0,
540+
NumMaxEvents: 10,
541+
},
542+
expected: func(e ForwardingEvent) bool {
543+
return e.IncomingChanID == incomingChanIDs[0] ||
544+
e.IncomingChanID == incomingChanIDs[1]
545+
},
546+
},
547+
{
548+
name: "only outgoingChanIDs filter",
549+
query: ForwardingEventQuery{
550+
StartTime: initialTime,
551+
EndTime: endTime,
552+
OutgoingChanIDs: fn.NewSet(
553+
outgoingChanIDs[0].ToUint64(),
554+
outgoingChanIDs[1].ToUint64(),
555+
),
556+
IndexOffset: 0,
557+
NumMaxEvents: 10,
558+
},
559+
expected: func(e ForwardingEvent) bool {
560+
return e.OutgoingChanID == outgoingChanIDs[0] ||
561+
e.OutgoingChanID == outgoingChanIDs[1]
562+
},
563+
},
564+
{
565+
name: "incoming and outgoingChanIDs filter",
566+
query: ForwardingEventQuery{
567+
StartTime: initialTime,
568+
EndTime: endTime,
569+
IncomingChanIDs: fn.NewSet(
570+
incomingChanIDs[0].ToUint64(),
571+
incomingChanIDs[1].ToUint64(),
572+
),
573+
OutgoingChanIDs: fn.NewSet(
574+
outgoingChanIDs[0].ToUint64(),
575+
outgoingChanIDs[1].ToUint64(),
576+
),
577+
IndexOffset: 0,
578+
NumMaxEvents: 10,
579+
},
580+
expected: func(e ForwardingEvent) bool {
581+
return e.IncomingChanID ==
582+
incomingChanIDs[0] ||
583+
e.IncomingChanID ==
584+
incomingChanIDs[1] ||
585+
e.OutgoingChanID ==
586+
outgoingChanIDs[0] ||
587+
e.OutgoingChanID ==
588+
outgoingChanIDs[1]
589+
},
590+
},
591+
}
592+
593+
for _, tc := range tests {
594+
t.Run(tc.name, func(t *testing.T) {
595+
result, err := log.Query(tc.query)
596+
require.NoError(t, err, "query failed")
597+
598+
expected := make([]ForwardingEvent, 0)
599+
for _, e := range events {
600+
if tc.expected(e) {
601+
expected = append(expected, e)
602+
}
603+
}
604+
605+
require.Equal(t, expected, result.ForwardingEvents)
606+
})
607+
}
608+
}

cmd/commands/cmd_payments.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,10 +1521,11 @@ func listPayments(ctx *cli.Context) error {
15211521
}
15221522

15231523
var forwardingHistoryCommand = cli.Command{
1524-
Name: "fwdinghistory",
1525-
Category: "Payments",
1526-
Usage: "Query the history of all forwarded HTLCs.",
1527-
ArgsUsage: "start_time [end_time] [index_offset] [max_events]",
1524+
Name: "fwdinghistory",
1525+
Category: "Payments",
1526+
Usage: "Query the history of all forwarded HTLCs.",
1527+
ArgsUsage: "start_time [end_time] [index_offset] [max_events]" +
1528+
"[--incoming_channel_ids] [--outgoing_channel_ids]",
15281529
Description: `
15291530
Query the HTLC switch's internal forwarding log for all completed
15301531
payment circuits (HTLCs) over a particular time range (--start_time and
@@ -1539,6 +1540,9 @@ var forwardingHistoryCommand = cli.Command{
15391540
The max number of events returned is 50k. The default number is 100,
15401541
callers can use the --max_events param to modify this value.
15411542
1543+
Incoming and outgoing channel IDs can be provided to further filter
1544+
the events. If not provided, all events will be returned.
1545+
15421546
Finally, callers can skip a series of events using the --index_offset
15431547
parameter. Each response will contain the offset index of the last
15441548
entry. Using this callers can manually paginate within a time slice.
@@ -1567,6 +1571,18 @@ var forwardingHistoryCommand = cli.Command{
15671571
Usage: "skip the peer alias lookup per forwarding " +
15681572
"event in order to improve performance",
15691573
},
1574+
cli.Int64SliceFlag{
1575+
Name: "incoming_chan_ids",
1576+
Usage: "the short channel id of the incoming " +
1577+
"channel to filter events by; can be " +
1578+
"specified multiple times in the same command",
1579+
},
1580+
cli.Int64SliceFlag{
1581+
Name: "outgoing_chan_ids",
1582+
Usage: "the short channel id of the outgoing " +
1583+
"channel to filter events by; can be " +
1584+
"specified multiple times in the same command",
1585+
},
15701586
},
15711587
Action: actionDecorator(forwardingHistory),
15721588
}
@@ -1647,6 +1663,21 @@ func forwardingHistory(ctx *cli.Context) error {
16471663
NumMaxEvents: maxEvents,
16481664
PeerAliasLookup: lookupPeerAlias,
16491665
}
1666+
outgoingChannelIDs := ctx.Int64Slice("outgoing_chan_ids")
1667+
if len(outgoingChannelIDs) != 0 {
1668+
req.OutgoingChanIds = make([]uint64, len(outgoingChannelIDs))
1669+
for i, c := range outgoingChannelIDs {
1670+
req.OutgoingChanIds[i] = uint64(c)
1671+
}
1672+
}
1673+
1674+
incomingChannelIDs := ctx.Int64Slice("incoming_chan_ids")
1675+
if len(incomingChannelIDs) != 0 {
1676+
req.IncomingChanIds = make([]uint64, len(incomingChannelIDs))
1677+
for i, c := range incomingChannelIDs {
1678+
req.IncomingChanIds[i] = uint64(c)
1679+
}
1680+
}
16501681
resp, err := client.ForwardingHistory(ctxc, req)
16511682
if err != nil {
16521683
return err

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,22 @@ circuit. The indices are only available for forwarding events saved after v0.20.
3737
the option to specify multiple channels this control can be extended to
3838
multiple hops leading to the node.
3939

40+
41+
* The `lnrpc.ForwardingHistory` RPC method now supports filtering by
42+
[`incoming_chan_ids` and `outgoing_chan_ids`](https://github.com/lightningnetwork/lnd/pull/9356).
43+
This allows to retrieve forwarding events for specific channels.
44+
4045
## lncli Additions
4146

4247
* [`lncli sendpayment` and `lncli queryroutes` now support the
4348
`--route_hints` flag](https://github.com/lightningnetwork/lnd/pull/9721) to
4449
support routing through private channels.
4550

51+
52+
* The `lncli fwdinghistory` command now supports two new flags:
53+
[`--incoming_chan_ids` and `--outgoing_chan_ids`](https://github.com/lightningnetwork/lnd/pull/9356).
54+
These filters allows to query forwarding events for specific channels.
55+
4656
# Improvements
4757
## Functional Updates
4858

@@ -105,4 +115,5 @@ circuit. The indices are only available for forwarding events saved after v0.20.
105115

106116
* Abdulkbk
107117
* Elle Mouton
118+
* Funyug
108119
* Pins

0 commit comments

Comments
 (0)