mirror of
https://github.com/lightninglabs/faraday.git
synced 2026-08-13 12:33:35 +02:00
frdrpcserver: implement GetChannelEvents
Implements the handler against the chanevents store. A zero end_time defaults to the server's current wall clock so callers can omit it for "up to now" queries. max_events is clamped to a 10000-row hard cap that also serves as the implicit default when the caller leaves the field at zero. An unknown chan_point maps to NotFound; negative time bounds and start_time after end_time map to InvalidArgument. The response sets has_more whenever the page filled to the requested limit so the client knows to keep paginating. Also registers the new endpoint in the macaroon permissions table under the channels:read entitlement.
This commit is contained in:
parent
44bb23186a
commit
dc9b6388af
2 changed files with 152 additions and 0 deletions
148
frdrpcserver/getchanevents.go
Normal file
148
frdrpcserver/getchanevents.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
package frdrpcserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightninglabs/faraday/chanevents"
|
||||
"github.com/lightninglabs/faraday/frdrpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// maxChannelEventsLimit is the hard cap the server will return in a single
|
||||
// GetChannelEvents response, regardless of what the client asks for. It also
|
||||
// serves as the default when the request leaves max_events at zero.
|
||||
const maxChannelEventsLimit = 10000
|
||||
|
||||
// GetChannelEvents serves a paginated read of a channel's events. A zero
|
||||
// end_time defaults to the server's current time, max_events is clamped to
|
||||
// the server's hard cap, and an unknown channel point yields NotFound.
|
||||
func (s *RPCServer) GetChannelEvents(ctx context.Context,
|
||||
req *frdrpc.ChannelEventsRequest) (*frdrpc.ChannelEventsResponse,
|
||||
error) {
|
||||
|
||||
log.Debugf("[GetChannelEvents]: chan_point=%s, start_time=%d, "+
|
||||
"end_time=%d, max_events=%d, last_id=%d", req.ChanPoint,
|
||||
req.StartTime, req.EndTime, req.MaxEvents, req.LastId)
|
||||
|
||||
if req.ChanPoint == "" {
|
||||
return nil, status.Error(
|
||||
codes.InvalidArgument, "channel point required",
|
||||
)
|
||||
}
|
||||
|
||||
if req.StartTime < 0 || req.EndTime < 0 {
|
||||
return nil, status.Error(
|
||||
codes.InvalidArgument,
|
||||
"start_time and end_time must be >= 0",
|
||||
)
|
||||
}
|
||||
|
||||
startTime := time.Unix(req.StartTime, 0)
|
||||
endTime := time.Now()
|
||||
if req.EndTime != 0 {
|
||||
endTime = time.Unix(req.EndTime, 0)
|
||||
}
|
||||
|
||||
if startTime.After(endTime) {
|
||||
return nil, status.Error(
|
||||
codes.InvalidArgument, "start_time must be <= end_time",
|
||||
)
|
||||
}
|
||||
|
||||
if req.LastId < 0 {
|
||||
return nil, status.Error(
|
||||
codes.InvalidArgument, "last_id must be >= 0",
|
||||
)
|
||||
}
|
||||
|
||||
channel, err := s.cfg.ChanEvents.GetChannel(ctx, req.ChanPoint)
|
||||
if err != nil {
|
||||
if errors.Is(err, chanevents.ErrUnknownChannel) {
|
||||
return nil, status.Errorf(codes.NotFound, "channel %s "+
|
||||
"not found", req.ChanPoint)
|
||||
}
|
||||
log.Errorf("GetChannel(%s): %v", req.ChanPoint, err)
|
||||
|
||||
return nil, status.Error(
|
||||
codes.Internal, "failed to look up channel",
|
||||
)
|
||||
}
|
||||
|
||||
limit := int32(maxChannelEventsLimit)
|
||||
if req.MaxEvents != 0 && req.MaxEvents < maxChannelEventsLimit {
|
||||
limit = int32(req.MaxEvents)
|
||||
}
|
||||
|
||||
events, err := s.cfg.ChanEvents.GetChannelEvents(
|
||||
ctx, channel.ID, req.LastId, startTime, endTime, limit,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("GetChannelEvents(%s): %v", req.ChanPoint, err)
|
||||
|
||||
return nil, status.Error(
|
||||
codes.Internal, "failed to query channel events",
|
||||
)
|
||||
}
|
||||
|
||||
resp := &frdrpc.ChannelEventsResponse{
|
||||
Events: marshalRPCChannelEvents(events),
|
||||
HasMore: int32(len(events)) == limit,
|
||||
}
|
||||
if n := len(events); n > 0 {
|
||||
resp.LastId = events[n-1].ID
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// marshalRPCChannelEvents converts a slice of chanevents.ChannelEvent into a
|
||||
// slice of frdrpc.ChannelEvent.
|
||||
func marshalRPCChannelEvents(
|
||||
events []*chanevents.ChannelEvent) []*frdrpc.ChannelEvent {
|
||||
|
||||
rpcEvents := make([]*frdrpc.ChannelEvent, len(events))
|
||||
|
||||
for i, event := range events {
|
||||
rpcEvent := &frdrpc.ChannelEvent{
|
||||
Id: event.ID,
|
||||
Timestamp: event.Timestamp.Unix(),
|
||||
EventType: rpcEventType(event.EventType),
|
||||
}
|
||||
|
||||
event.LocalBalance.WhenSome(
|
||||
func(b btcutil.Amount) {
|
||||
rpcEvent.LocalBalance = uint64(b)
|
||||
},
|
||||
)
|
||||
event.RemoteBalance.WhenSome(
|
||||
func(b btcutil.Amount) {
|
||||
rpcEvent.RemoteBalance = uint64(b)
|
||||
},
|
||||
)
|
||||
|
||||
rpcEvents[i] = rpcEvent
|
||||
}
|
||||
|
||||
return rpcEvents
|
||||
}
|
||||
|
||||
// rpcEventType maps a stored chanevents.EventType to its proto counterpart.
|
||||
func rpcEventType(e chanevents.EventType) frdrpc.ChannelEventType {
|
||||
switch e {
|
||||
case chanevents.EventTypeOnline:
|
||||
return frdrpc.ChannelEventType_CHAN_EVENT_ONLINE
|
||||
|
||||
case chanevents.EventTypeOffline:
|
||||
return frdrpc.ChannelEventType_CHAN_EVENT_OFFLINE
|
||||
|
||||
case chanevents.EventTypeUpdate:
|
||||
return frdrpc.ChannelEventType_CHAN_EVENT_UPDATE
|
||||
|
||||
default:
|
||||
return frdrpc.ChannelEventType_CHAN_EVENT_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
|
@ -33,4 +33,8 @@ var RequiredPermissions = map[string][]bakery.Op{
|
|||
Entity: "report",
|
||||
Action: "read",
|
||||
}},
|
||||
"/frdrpc.FaradayServer/GetChannelEvents": {{
|
||||
Entity: "events",
|
||||
Action: "read",
|
||||
}},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue