From 23631733258e0e319d18e2d32a67288380dee86a Mon Sep 17 00:00:00 2001 From: bitromortac Date: Wed, 6 May 2026 07:05:20 +0200 Subject: [PATCH] chanevents+db: add scid-to-peer index Add ScidToPeerMap, which materialises a snapshot of every short channel id paired with the pubkey of the channel's remote peer. Forwarding-data sources index events by short channel id, but downstream analyses need to attribute behaviour to the peer, not the channel. The map skips channels whose short channel id is still zero (unconfirmed), so callers see only fully advertised channels. Coverage extends TestStore with a two-channel fixture pinning the join. --- chanevents/store.go | 25 ++++++++++++++++++++++++ chanevents/store_test.go | 7 +++++++ db/sqlc/chanevents.sql.go | 35 ++++++++++++++++++++++++++++++++++ db/sqlc/querier.go | 1 + db/sqlc/queries/chanevents.sql | 5 +++++ 5 files changed, 73 insertions(+) diff --git a/chanevents/store.go b/chanevents/store.go index 78a7a7e..0cbe442 100644 --- a/chanevents/store.go +++ b/chanevents/store.go @@ -49,6 +49,8 @@ type Queries interface { sqlc.ChannelEvent, error, ) + + GetChannels(ctx context.Context) ([]sqlc.GetChannelsRow, error) } // Store provides access to the db for channel events. @@ -263,6 +265,29 @@ func (s *Store) GetChannelEvents(ctx context.Context, channelID, afterID int64, return events, nil } +// ScidToPeerMap returns the historic scid→peer index, including channels that +// have since closed. Unconfirmed channels (scid still zero) are not part of +// the contract. +func (s *Store) ScidToPeerMap(ctx context.Context) (map[uint64]string, error) { + dbChannels, err := s.db.GetChannels(ctx) + if err != nil { + return nil, err + } + + scidToPeer := make(map[uint64]string, len(dbChannels)) + for _, dbChannel := range dbChannels { + // The short channel ID can be zero if it's not known yet. We + // should just ignore those. + if dbChannel.ShortChannelID == 0 { + continue + } + + scidToPeer[uint64(dbChannel.ShortChannelID)] = dbChannel.Pubkey + } + + return scidToPeer, 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, diff --git a/chanevents/store_test.go b/chanevents/store_test.go index 7cb91c1..b2cbae6 100644 --- a/chanevents/store_test.go +++ b/chanevents/store_test.go @@ -100,6 +100,13 @@ func TestStore(t *testing.T) { require.NoError(t, err) require.NotZero(t, channel2ID) + // Get the historic channel to peer map. + chanToPeer, err := store.ScidToPeerMap(ctx) + require.NoError(t, err) + require.Len(t, chanToPeer, 2) + require.Equal(t, testPubKey, chanToPeer[testShortChanID1]) + require.Equal(t, testPubKey, chanToPeer[testShortChanID2]) + // Add an online event for the channel. onlineEvent := &ChannelEvent{ ChannelID: channelID, diff --git a/db/sqlc/chanevents.sql.go b/db/sqlc/chanevents.sql.go index 82441f6..ba1dee2 100644 --- a/db/sqlc/chanevents.sql.go +++ b/db/sqlc/chanevents.sql.go @@ -98,6 +98,41 @@ func (q *Queries) GetChannelEvents(ctx context.Context, arg GetChannelEventsPara return items, nil } +const getChannels = `-- name: GetChannels :many +SELECT c.id, c.short_channel_id, p.pubkey +FROM channels c +JOIN peers p ON c.peer_id = p.id +` + +type GetChannelsRow struct { + ID int64 + ShortChannelID int64 + Pubkey string +} + +func (q *Queries) GetChannels(ctx context.Context) ([]GetChannelsRow, error) { + rows, err := q.db.QueryContext(ctx, getChannels) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetChannelsRow + for rows.Next() { + var i GetChannelsRow + if err := rows.Scan(&i.ID, &i.ShortChannelID, &i.Pubkey); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + 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 diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index 661f75c..b62ec68 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) + GetChannels(ctx context.Context) ([]GetChannelsRow, 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) diff --git a/db/sqlc/queries/chanevents.sql b/db/sqlc/queries/chanevents.sql index 9f567f3..d9d1629 100644 --- a/db/sqlc/queries/chanevents.sql +++ b/db/sqlc/queries/chanevents.sql @@ -33,3 +33,8 @@ SELECT * FROM channel_events WHERE channel_id = $1 AND event_type = $2 AND timestamp < $3 ORDER BY timestamp DESC, id DESC LIMIT 1; + +-- name: GetChannels :many +SELECT c.id, c.short_channel_id, p.pubkey +FROM channels c +JOIN peers p ON c.peer_id = p.id;