diff --git a/chanevents/store.go b/chanevents/store.go index e652faa..78a7a7e 100644 --- a/chanevents/store.go +++ b/chanevents/store.go @@ -43,6 +43,12 @@ type Queries interface { GetChannelEvents(ctx context.Context, arg sqlc.GetChannelEventsParams) ([]sqlc.ChannelEvent, error) + + GetLatestChannelEventBefore(ctx context.Context, + arg sqlc.GetLatestChannelEventBeforeParams) ( + sqlc.ChannelEvent, + error, + ) } // Store provides access to the db for channel events. @@ -257,6 +263,31 @@ func (s *Store) GetChannelEvents(ctx context.Context, channelID, afterID int64, return events, nil } +// GetLatestChannelUpdateBefore returns the latest channel event before a given +// time (exclusive). If no event is found, it returns (nil, nil). +func (s *Store) GetLatestChannelUpdateBefore(ctx context.Context, + channelID int64, before time.Time) (*ChannelEvent, error) { + + dbEvent, err := s.db.GetLatestChannelEventBefore( + ctx, sqlc.GetLatestChannelEventBeforeParams{ + ChannelID: channelID, + Timestamp: before.UTC(), + EventType: int16(EventTypeUpdate), + }, + ) + if err != nil { + // If there are no events before the start time, we return (nil, + // nil). + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + + return nil, err + } + + return marshalChannelEvent(dbEvent), nil +} + // marshalChannelEvent converts a db channel event into our internal type. func marshalChannelEvent(dbEvent sqlc.ChannelEvent) *ChannelEvent { var localBalance fn.Option[btcutil.Amount] diff --git a/chanevents/store_test.go b/chanevents/store_test.go index 86f025a..7cb91c1 100644 --- a/chanevents/store_test.go +++ b/chanevents/store_test.go @@ -134,6 +134,23 @@ func TestStore(t *testing.T) { requireEqualEvent( t, updateEvent, testTime.Add(time.Second), events[1], ) + updateEvent = events[1] + + // If we query a time after the update event, we'll obtain the update + // event as the latest event. + initEvent, err := store.GetLatestChannelUpdateBefore( + ctx, channelID, updateEvent.Timestamp.Add(500*time.Millisecond), + ) + require.NoError(t, err) + requireEqualEvent(t, updateEvent, testTime.Add(time.Second), initEvent) + + // If we query at the update event's timestamp, the only event before + // that is left is the online event, which is not an update. + initEvent, err = store.GetLatestChannelUpdateBefore( + ctx, channelID, updateEvent.Timestamp, + ) + require.NoError(t, err) + require.Nil(t, initEvent) // Advance the clock and add a sync event to verify the IsSync flag // round-trips correctly. diff --git a/db/sqlc/chanevents.sql.go b/db/sqlc/chanevents.sql.go index 9a24296..82441f6 100644 --- a/db/sqlc/chanevents.sql.go +++ b/db/sqlc/chanevents.sql.go @@ -98,6 +98,34 @@ func (q *Queries) GetChannelEvents(ctx context.Context, arg GetChannelEventsPara return items, nil } +const getLatestChannelEventBefore = `-- name: GetLatestChannelEventBefore :one +SELECT id, channel_id, event_type, timestamp, local_balance_sat, remote_balance_sat, is_sync FROM channel_events +WHERE channel_id = $1 AND event_type = $2 AND timestamp < $3 +ORDER BY timestamp DESC, id DESC +LIMIT 1 +` + +type GetLatestChannelEventBeforeParams struct { + ChannelID int64 + EventType int16 + Timestamp time.Time +} + +func (q *Queries) GetLatestChannelEventBefore(ctx context.Context, arg GetLatestChannelEventBeforeParams) (ChannelEvent, error) { + row := q.db.QueryRowContext(ctx, getLatestChannelEventBefore, arg.ChannelID, arg.EventType, arg.Timestamp) + var i ChannelEvent + err := row.Scan( + &i.ID, + &i.ChannelID, + &i.EventType, + &i.Timestamp, + &i.LocalBalanceSat, + &i.RemoteBalanceSat, + &i.IsSync, + ) + return i, err +} + const getPeerByPubKey = `-- name: GetPeerByPubKey :one SELECT id, pubkey FROM peers WHERE pubkey = $1 ` diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index f56f962..661f75c 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -12,6 +12,7 @@ type Querier interface { GetChannelByChanPoint(ctx context.Context, channelPoint string) (Channel, error) GetChannelByShortChanID(ctx context.Context, shortChannelID int64) (Channel, error) GetChannelEvents(ctx context.Context, arg GetChannelEventsParams) ([]ChannelEvent, error) + GetLatestChannelEventBefore(ctx context.Context, arg GetLatestChannelEventBeforeParams) (ChannelEvent, error) GetPeerByPubKey(ctx context.Context, pubkey string) (Peer, error) InsertChannel(ctx context.Context, arg InsertChannelParams) (int64, error) InsertChannelEvent(ctx context.Context, arg InsertChannelEventParams) error diff --git a/db/sqlc/queries/chanevents.sql b/db/sqlc/queries/chanevents.sql index 186c8d7..9f567f3 100644 --- a/db/sqlc/queries/chanevents.sql +++ b/db/sqlc/queries/chanevents.sql @@ -27,3 +27,9 @@ WHERE channel_id = $1 AND timestamp < $4 ORDER BY id ASC LIMIT $5; + +-- name: GetLatestChannelEventBefore :one +SELECT * FROM channel_events +WHERE channel_id = $1 AND event_type = $2 AND timestamp < $3 +ORDER BY timestamp DESC, id DESC +LIMIT 1;