chanevents+db: add latest-event-before lookup

Add GetLatestChannelUpdateBefore, which fetches the most recent
EventTypeUpdate strictly before a given instant. Forwarding-ability
analyses that summarise behaviour over a window must seed their state
from the channel's balance at the window's lower bound; without the
ability to look back past that bound, the first events in the window
have no baseline to compare against. The lookup tolerates missing
predecessors by returning (nil, nil), letting callers distinguish "no
prior update" from a genuine error. Coverage exercises both the present
and absent cases against the existing TestStore fixture.
This commit is contained in:
bitromortac 2026-05-06 07:04:27 +02:00
parent a568dfedf0
commit 7431bb5d67
5 changed files with 83 additions and 0 deletions

View file

@ -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]

View file

@ -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.

View file

@ -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
`

View file

@ -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

View file

@ -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;