Add bounded topology snapshots and validate TRACE route evidence

This commit is contained in:
n30nex committed 2026-10-13 04:21:34 -04:00
1 parent bc84be577c
commit 1d3f7ce621
43 files changed
+1746 -144

No files matched your search

+31 -1
View File
@@ -3,7 +3,7 @@
//
// Generated by this command:
//
// mockgen -source=db/sqlc/querier.go -destination=db/sqlc/mock/querier.go -package=mockdb
// mockgen -source db/sqlc/querier.go -destination db/sqlc/mock/querier.go -package mockdb
//
// Package mockdb is a generated GoMock package.
@@ -1166,6 +1166,21 @@ func (mr *MockQuerierMockRecorder) GetTopNodes(ctx, arg any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTopNodes", reflect.TypeOf((*MockQuerier)(nil).GetTopNodes), ctx, arg)
}
// GetTopologyLinks mocks base method.
func (m *MockQuerier) GetTopologyLinks(ctx context.Context, arg db.GetTopologyLinksParams) ([]db.GetTopologyLinksRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTopologyLinks", ctx, arg)
ret0, _ := ret[0].([]db.GetTopologyLinksRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetTopologyLinks indicates an expected call of GetTopologyLinks.
func (mr *MockQuerierMockRecorder) GetTopologyLinks(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTopologyLinks", reflect.TypeOf((*MockQuerier)(nil).GetTopologyLinks), ctx, arg)
}
// GetTransportScopeByName mocks base method.
func (m *MockQuerier) GetTransportScopeByName(ctx context.Context, name string) (int32, error) {
m.ctrl.T.Helper()
@@ -2029,6 +2044,21 @@ func (mr *MockQuerierMockRecorder) ResolveRelayHashPairsP4(ctx, arg any) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveRelayHashPairsP4", reflect.TypeOf((*MockQuerier)(nil).ResolveRelayHashPairsP4), ctx, arg)
}
// ResolveTraceHashes mocks base method.
func (m *MockQuerier) ResolveTraceHashes(ctx context.Context, arg db.ResolveTraceHashesParams) ([]db.ResolveTraceHashesRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ResolveTraceHashes", ctx, arg)
ret0, _ := ret[0].([]db.ResolveTraceHashesRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ResolveTraceHashes indicates an expected call of ResolveTraceHashes.
func (mr *MockQuerierMockRecorder) ResolveTraceHashes(ctx, arg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResolveTraceHashes", reflect.TypeOf((*MockQuerier)(nil).ResolveTraceHashes), ctx, arg)
}
// RollAdvertHearings mocks base method.
func (m *MockQuerier) RollAdvertHearings(ctx context.Context, hour pgtype.Timestamptz) error {
m.ctrl.T.Helper()
+5
View File
@@ -162,6 +162,8 @@ type Querier interface {
// Nodes by ADVERT hearings since the given hour. Live names win over the rolled snapshot;
// node_id is NULL once the node row is gone. iata is a representative one.
GetTopNodes(ctx context.Context, arg GetTopNodesParams) ([]GetTopNodesRow, error)
// Reduce route history to unique adjacent pairs before transferring it to a browser.
GetTopologyLinks(ctx context.Context, arg GetTopologyLinksParams) ([]GetTopologyLinksRow, error)
GetTransportScopeByName(ctx context.Context, name string) (int32, error)
GetTransportScopes(ctx context.Context) ([]GetTransportScopesRow, error)
// ============================================================
@@ -335,6 +337,9 @@ type Querier interface {
ResolveRelayHashPairsP3(ctx context.Context, arg ResolveRelayHashPairsP3Params) ([]ResolveRelayHashPairsP3Row, error)
// Batch form of ResolvePathHashesP4 across IATAs; callers check uniqueness per IATA.
ResolveRelayHashPairsP4(ctx context.Context, arg ResolveRelayHashPairsP4Params) ([]ResolveRelayHashPairsP4Row, error)
// TRACE can terminate at companions. Preserve the full prefix (including eight
// bytes), and return every candidate across heard regions instead of picking one.
ResolveTraceHashes(ctx context.Context, arg ResolveTraceHashesParams) ([]ResolveTraceHashesRow, error)
RollAdvertHearings(ctx context.Context, hour pgtype.Timestamptz) error
// Each distinct ADVERT packet counts once, under the exact set of IATAs that heard it this hour.
RollAdvertSets(ctx context.Context, hour pgtype.Timestamptz) error
+146 -16
View File
@@ -1422,6 +1422,8 @@ func (q *Queries) GetPacketByHash(ctx context.Context, packetHash []byte) (GetPa
const getPacketsByTraceTag = `-- name: GetPacketsByTraceTag :many
SELECT encode(p.packet_hash, 'hex') AS packet_hash_hex,
p.raw_payload, p.payload_version,
EXISTS(SELECT 1 FROM packet_observations po WHERE po.packet_hash=p.packet_hash AND po.path_length_byte>=64) AS invalid_snr_path,
p.route_type,
p.first_heard_at,
p.last_heard_at,
@@ -1442,14 +1444,17 @@ ORDER BY p.first_heard_at ASC
`
type GetPacketsByTraceTagRow struct {
PacketHashHex string `json:"packet_hash_hex"`
RouteType int16 `json:"route_type"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ParsedPayload []byte `json:"parsed_payload"`
ScopeID *int32 `json:"scope_id"`
ScopeName *string `json:"scope_name"`
Iatas []string `json:"iatas"`
PacketHashHex string `json:"packet_hash_hex"`
RawPayload []byte `json:"raw_payload"`
PayloadVersion int16 `json:"payload_version"`
InvalidSnrPath bool `json:"invalid_snr_path"`
RouteType int16 `json:"route_type"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
ParsedPayload []byte `json:"parsed_payload"`
ScopeID *int32 `json:"scope_id"`
ScopeName *string `json:"scope_name"`
Iatas []string `json:"iatas"`
}
// Return distinct observation IATAs in first-heard order for path resolution,
@@ -1465,6 +1470,9 @@ func (q *Queries) GetPacketsByTraceTag(ctx context.Context, decode string) ([]Ge
var i GetPacketsByTraceTagRow
if err := rows.Scan(
&i.PacketHashHex,
&i.RawPayload,
&i.PayloadVersion,
&i.InvalidSnrPath,
&i.RouteType,
&i.FirstHeardAt,
&i.LastHeardAt,
@@ -2254,6 +2262,59 @@ func (q *Queries) GetTopNodes(ctx context.Context, arg GetTopNodesParams) ([]Get
return items, nil
}
const getTopologyLinks = `-- name: GetTopologyLinks :many
WITH pairs AS (
SELECT h.node_id AS a, r.node_ids[(h.ord+1)::int] AS b
FROM known_routes r CROSS JOIN LATERAL unnest(r.node_ids) WITH ORDINALITY h(node_id,ord)
WHERE r.last_seen >= $2::timestamptz AND r.last_seen < $3::timestamptz
AND (COALESCE(cardinality($4::bpchar[]),0)=0 OR r.iata=ANY($4::bpchar[]))
AND h.ord < cardinality(r.node_ids)
)
SELECT LEAST(a,b)::uuid AS from_id, GREATEST(a,b)::uuid AS to_id
FROM pairs WHERE a IS NOT NULL AND b IS NOT NULL AND a<>b
GROUP BY LEAST(a,b), GREATEST(a,b)
ORDER BY LEAST(a,b), GREATEST(a,b)
LIMIT $1::int
`
type GetTopologyLinksParams struct {
LinkLimit int32 `json:"link_limit"`
Since pgtype.Timestamptz `json:"since"`
Until pgtype.Timestamptz `json:"until"`
Iatas []string `json:"iatas"`
}
type GetTopologyLinksRow struct {
FromID uuid.UUID `json:"from_id"`
ToID uuid.UUID `json:"to_id"`
}
// Reduce route history to unique adjacent pairs before transferring it to a browser.
func (q *Queries) GetTopologyLinks(ctx context.Context, arg GetTopologyLinksParams) ([]GetTopologyLinksRow, error) {
rows, err := q.db.Query(ctx, getTopologyLinks,
arg.LinkLimit,
arg.Since,
arg.Until,
arg.Iatas,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetTopologyLinksRow{}
for rows.Next() {
var i GetTopologyLinksRow
if err := rows.Scan(&i.FromID, &i.ToID); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getTransportScopeByName = `-- name: GetTransportScopeByName :one
SELECT id FROM transport_scopes WHERE name = $1
`
@@ -4194,7 +4255,11 @@ SELECT
WHERE ti.trace_tag = t.trace_tag
AND (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR ti.iata = ANY($1::bpchar[]))) AS iata_count,
COALESCE(t.trace_type, '')::text AS trace_type,
t.best_payload
t.best_payload,
ARRAY(SELECT ti.iata FROM trace_iatas ti WHERE ti.trace_tag=t.trace_tag)::bpchar[] AS iatas,
EXISTS(SELECT 1 FROM packets p WHERE p.trace_tag=t.trace_tag AND p.payload_version<>0) AS unsupported_version,
EXISTS(SELECT 1 FROM packets p WHERE p.trace_tag=t.trace_tag AND p.route_type NOT IN (2,3)) AS non_direct,
EXISTS(SELECT 1 FROM packets p JOIN packet_observations po ON po.packet_hash=p.packet_hash WHERE p.trace_tag=t.trace_tag AND po.path_length_byte>=64) AS invalid_snr_path
FROM trace_tags t
WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR EXISTS (
SELECT 1 FROM trace_iatas ti WHERE ti.trace_tag = t.trace_tag AND ti.iata = ANY($1::bpchar[])))
@@ -4222,13 +4287,17 @@ type ListTraceTagsParams struct {
}
type ListTraceTagsRow struct {
TraceTag string `json:"trace_tag"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
PacketCount int64 `json:"packet_count"`
IataCount int64 `json:"iata_count"`
TraceType string `json:"trace_type"`
BestPayload []byte `json:"best_payload"`
TraceTag string `json:"trace_tag"`
FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"`
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
PacketCount int64 `json:"packet_count"`
IataCount int64 `json:"iata_count"`
TraceType string `json:"trace_type"`
BestPayload []byte `json:"best_payload"`
Iatas []string `json:"iatas"`
UnsupportedVersion bool `json:"unsupported_version"`
NonDirect bool `json:"non_direct"`
InvalidSnrPath bool `json:"invalid_snr_path"`
}
// ============================================================
@@ -4261,6 +4330,10 @@ func (q *Queries) ListTraceTags(ctx context.Context, arg ListTraceTagsParams) ([
&i.IataCount,
&i.TraceType,
&i.BestPayload,
&i.Iatas,
&i.UnsupportedVersion,
&i.NonDirect,
&i.InvalidSnrPath,
); err != nil {
return nil, err
}
@@ -5103,6 +5176,63 @@ func (q *Queries) ResolveRelayHashPairsP4(ctx context.Context, arg ResolveRelayH
return items, nil
}
const resolveTraceHashes = `-- name: ResolveTraceHashes :many
SELECT h.hash::bytea AS hash, si.iata, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key
FROM unnest($1::bytea[]) AS h(hash)
JOIN node_short_ids si ON si.iata = ANY($2::bpchar[]) AND
((octet_length(h.hash)=1 AND si.prefix_1=h.hash) OR
(octet_length(h.hash)=2 AND si.prefix_2=h.hash) OR
(octet_length(h.hash) IN (4,8) AND si.prefix_4=substring(h.hash FROM 1 FOR 4)))
JOIN nodes n ON n.id=si.node_id
WHERE substring(n.public_key FROM 1 FOR octet_length(h.hash))=h.hash
ORDER BY si.iata, h.hash, n.id
`
type ResolveTraceHashesParams struct {
Hashes [][]byte `json:"hashes"`
Iatas []string `json:"iatas"`
}
type ResolveTraceHashesRow struct {
Hash []byte `json:"hash"`
Iata string `json:"iata"`
NodeID uuid.UUID `json:"node_id"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
PublicKey []byte `json:"public_key"`
}
// TRACE can terminate at companions. Preserve the full prefix (including eight
// bytes), and return every candidate across heard regions instead of picking one.
func (q *Queries) ResolveTraceHashes(ctx context.Context, arg ResolveTraceHashesParams) ([]ResolveTraceHashesRow, error) {
rows, err := q.db.Query(ctx, resolveTraceHashes, arg.Hashes, arg.Iatas)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ResolveTraceHashesRow{}
for rows.Next() {
var i ResolveTraceHashesRow
if err := rows.Scan(
&i.Hash,
&i.Iata,
&i.NodeID,
&i.Name,
&i.Latitude,
&i.Longitude,
&i.PublicKey,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const saveChannelCatalogue = `-- name: SaveChannelCatalogue :exec
INSERT INTO meshmapper_channel_catalogues (iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error)
VALUES ($1, $2, $3::jsonb, $4::text,