diff --git a/network/backend/backend.go b/network/backend/backend.go index 5e1c830..4848962 100644 --- a/network/backend/backend.go +++ b/network/backend/backend.go @@ -43,4 +43,6 @@ type Backend interface { SubscribeRoutingEvents(context.Context, chan *models.RoutingEvent) error SubscribeGraphEvents(context.Context, chan *models.ChannelEdgeUpdate) error + + GetForwardingHistory(context.Context, string, uint32) ([]*models.ForwardingEvent, error) } diff --git a/network/backend/lnd/lnd.go b/network/backend/lnd/lnd.go index 8ca7442..672d06f 100644 --- a/network/backend/lnd/lnd.go +++ b/network/backend/lnd/lnd.go @@ -4,6 +4,8 @@ import ( "context" "encoding/hex" "fmt" + "regexp" + "strconv" "time" "github.com/lightningnetwork/lnd/lnrpc" @@ -447,6 +449,93 @@ func (l Backend) GetNode(ctx context.Context, pubkey string, includeChannels boo return result, nil } +func (l Backend) GetForwardingHistory(ctx context.Context, startTime string, maxNumEvents uint32) ([]*models.ForwardingEvent, error) { + l.logger.Debug("GetForwardingHistory") + + clt, err := l.Client(ctx) + if err != nil { + return nil, err + } + defer clt.Close() + t, err := parseTime(startTime, time.Now()) + req := &lnrpc.ForwardingHistoryRequest{ + StartTime: t, + NumMaxEvents: maxNumEvents, + } + resp, err := clt.ForwardingHistory(ctx, req) + if err != nil { + return nil, errors.WithStack(err) + } + result := protoToForwardingHistory(resp) + + // Enrich peer alias names. + // This can be removed once the ForwardingHistory + // contains the peer aliases by default. + enrichPeerAliases := func(ctx context.Context, events []*models.ForwardingEvent) error { + + if len(events) == 0 { + return nil + } + + selfInfo, err := clt.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + if err != nil { + return errors.WithStack(err) + } + + getPeerAlias := func(chanId uint64) (string, error) { + chanInfo, err := clt.GetChanInfo(ctx, &lnrpc.ChanInfoRequest{ + ChanId: chanId, + }) + if err != nil { + return "", errors.WithStack(err) + } + pubKey := chanInfo.Node1Pub + if selfInfo.IdentityPubkey == chanInfo.Node1Pub { + pubKey = chanInfo.Node2Pub + } + nodeInfo, err := clt.GetNodeInfo(ctx, &lnrpc.NodeInfoRequest{ + PubKey: pubKey, + }) + if err != nil { + return "", errors.WithStack(err) + } + + return nodeInfo.Node.Alias, nil + } + + cache := make(map[uint64]string) + for i, event := range events { + + if val, ok := cache[event.ChanIdIn]; ok { + events[i].PeerAliasIn = val + } else { + events[i].PeerAliasIn, err = getPeerAlias(event.ChanIdIn) + if err != nil { + cache[event.ChanIdIn] = events[i].PeerAliasIn + } + } + + if val, ok := cache[event.ChanIdOut]; ok { + events[i].PeerAliasOut = val + } else { + events[i].PeerAliasOut, err = getPeerAlias(event.ChanIdOut) + if err != nil { + cache[event.ChanIdOut] = events[i].PeerAliasOut + } + } + } + + return nil + + } + err = enrichPeerAliases(ctx, result) + if err != nil { + return nil, errors.WithStack(err) + } + + return result, nil +} + func (l Backend) CreateInvoice(ctx context.Context, amount int64, desc string) (*models.Invoice, error) { l.logger.Debug("Create invoice...", logging.Int64("amount", amount), @@ -563,3 +652,36 @@ func New(c *config.Network, logger logging.Logger) (*Backend, error) { return backend, nil } + +// reTimeRange matches systemd.time-like short negative timeranges, e.g. "-200s". +var reTimeRange = regexp.MustCompile(`^-\d{1,18}[s|m|h|d|w|M|y]$`) + +// secondsPer allows translating s(seconds), m(minutes), h(ours), d(ays), +// w(eeks), M(onths) and y(ears) into corresponding seconds. +var secondsPer = map[string]int64{ + "s": 1, + "m": 60, + "h": 3600, + "d": 86400, + "w": 604800, + "M": 2630016, // 30.44 days + "y": 31557600, // 365.25 days +} + +// parseTime parses UNIX timestamps or short timeranges inspired by systemd +// (when starting with "-"), e.g. "-1M" for one month (30.44 days) ago. +func parseTime(s string, base time.Time) (uint64, error) { + if reTimeRange.MatchString(s) { + last := len(s) - 1 + + d, err := strconv.ParseInt(s[1:last], 10, 64) + if err != nil { + return uint64(0), err + } + + mul := secondsPer[string(s[last])] + return uint64(base.Unix() - d*mul), nil + } + + return strconv.ParseUint(s, 10, 64) +} diff --git a/network/backend/lnd/proto.go b/network/backend/lnd/proto.go index 228d3e0..cf68cc0 100644 --- a/network/backend/lnd/proto.go +++ b/network/backend/lnd/proto.go @@ -398,3 +398,30 @@ func protoToRoutingEvent(resp *routerrpc.HtlcEvent) *models.RoutingEvent { FailureDetail: detail, } } + +func protoToForwardingHistory(resp *lnrpc.ForwardingHistoryResponse) []*models.ForwardingEvent { + if resp == nil { + return nil + } + + forwardingEvents := make([]*models.ForwardingEvent, len(resp.ForwardingEvents)) + for i := range resp.ForwardingEvents { + forwardingEvents[i] = protoToForwardingEvent(resp.ForwardingEvents[i]) + } + return forwardingEvents +} + +func protoToForwardingEvent(resp *lnrpc.ForwardingEvent) *models.ForwardingEvent { + return &models.ForwardingEvent{ + + ChanIdIn: resp.ChanIdIn, + ChanIdOut: resp.ChanIdOut, + AmtIn: resp.AmtIn, + AmtOut: resp.AmtOut, + Fee: resp.Fee, + FeeMsat: resp.FeeMsat, + AmtInMsat: resp.AmtInMsat, + AmtOutMsat: resp.AmtOutMsat, + EventTime: time.Unix(0, int64(resp.TimestampNs)), + } +} diff --git a/network/backend/mock/mock.go b/network/backend/mock/mock.go index 4fdc076..40b9091 100644 --- a/network/backend/mock/mock.go +++ b/network/backend/mock/mock.go @@ -86,6 +86,10 @@ func (b *Backend) DecodePayReq(ctx context.Context, payreq string) (*models.PayR return &models.PayReq{}, nil } +func (b *Backend) GetForwardingHistory(ctx context.Context, startTime string, maxNumEvents uint32) ([]*models.ForwardingEvent, error) { + return []*models.ForwardingEvent{}, nil +} + func (b *Backend) CreateInvoice(ctx context.Context, amt int64, desc string) (*models.Invoice, error) { b.Lock() defer b.Unlock() diff --git a/network/models/fwdingevent.go b/network/models/fwdingevent.go new file mode 100644 index 0000000..b080272 --- /dev/null +++ b/network/models/fwdingevent.go @@ -0,0 +1,17 @@ +package models + +import "time" + +type ForwardingEvent struct { + PeerAliasIn string + PeerAliasOut string + ChanIdIn uint64 + ChanIdOut uint64 + AmtIn uint64 + AmtOut uint64 + Fee uint64 + FeeMsat uint64 + AmtInMsat uint64 + AmtOutMsat uint64 + EventTime time.Time +} diff --git a/ui/models/models.go b/ui/models/models.go index 8c046a3..08341fe 100644 --- a/ui/models/models.go +++ b/ui/models/models.go @@ -68,11 +68,7 @@ func (m *Models) RefreshInfo(ctx context.Context) error { } func (m *Models) RefreshForwardingHistory(ctx context.Context) error { -<<<<<<< Updated upstream - forwardingEvents, err := m.network.GetForwardingHistory(ctx) -======= forwardingEvents, err := m.network.GetForwardingHistory(ctx, m.FwdingHist.StartTime, m.FwdingHist.MaxNumEvents) ->>>>>>> Stashed changes if err != nil { return err }