test(nodes): region fallback places never-zero-hop nodes by fewest hops (#1879)

Tests plus a stub GetNodeFallbackRegionSets; fails on assertions: the
fallback still puts a node in every region that heard it.
This commit is contained in:
Adam Nieto (MeshTexas) authored and Adam Nieto committed 2026-10-15 08:14:57 -05:00
1 parent 80bad40740
commit a66a7f36ee
3 files changed
+92

No files matched your search

+11
View File
@@ -27,6 +27,10 @@ import (
const nodeHomeRegionsRecomputerDefaultInterval = 5 * time.Minute
// regionFallbackMaxHops bounds the fallback for nodes never heard zero-hop: a
// node whose closest hearing took more hops than this is in no region.
const regionFallbackMaxHops = 3
var (
nodeHomeRegionsRecompStartedMu sync.Mutex
nodeHomeRegionsRecompStarted bool
@@ -129,3 +133,10 @@ func enrichNodeHomeRegions(node map[string]interface{}, regions []string) {
}
node["home_regions"] = regions
}
// GetNodeFallbackRegionSets returns the current fallback snapshot (pubkey →
// sorted regions, empty for a node in no region), or an empty map before the
// first compute. Read-only by convention.
func (s *PacketStore) GetNodeFallbackRegionSets() map[string][]string {
return map[string][]string{}
}
+78
View File
@@ -135,3 +135,81 @@ func TestHandleNodesHomeRegions(t *testing.T) {
t.Fatalf("expected both seeded nodes in the response, saw %d", seen)
}
}
// mkPathAdvert is a relayed flood advert heard by each observer over the given
// path (JSON array of hop hashes, "[]" for a direct hearing).
func mkPathAdvert(id int, pubkey string, hearings map[string]string) *StoreTx {
tx := mkHomeRegionAdvert(id, pubkey, homeRelayedFlood)
i := 0
for oid, path := range hearings {
tx.Observations = append(tx.Observations, &StoreObs{
ID: id*10 + i, TransmissionID: id, ObserverID: oid, PathJSON: path,
})
i++
}
return tx
}
// A node never heard zero-hop used to be in every region that heard any of its
// adverts. Adverts flood the whole mesh, so on a statewide deployment that put
// most nodes in every region. It now goes in the regions that heard it in the
// fewest hops, and in none when even that is over regionFallbackMaxHops.
func TestRegionFallbackUsesFewestHops(t *testing.T) {
ps := newRegionTestStore(t, map[string]string{"o-aus": "AUS", "o-sat": "SAT", "o-hou": "HOU"})
addToStore(ps,
// One hop from SAT, relayed four hops to AUS: SAT only.
mkPathAdvert(1, "pk_near", map[string]string{"o-sat": `["aa"]`, "o-aus": `["aa","bb","cc","dd"]`}),
// Tied fewest hops: both.
mkPathAdvert(2, "pk_tie", map[string]string{"o-sat": `["aa","bb"]`, "o-hou": `["cc","dd"]`, "o-aus": `["aa","bb","cc"]`}),
// Exactly regionFallbackMaxHops is still placed.
mkPathAdvert(3, "pk_cap", map[string]string{"o-aus": `["aa","bb","cc"]`}),
// Closest hearing beyond the cap: no region.
mkPathAdvert(4, "pk_gulf", map[string]string{"o-aus": `["aa","bb","cc","dd"]`, "o-hou": `["aa","bb","cc","dd","ee","ff"]`}),
// A later advert heard closer counts.
mkPathAdvert(5, "pk_gulf2", map[string]string{"o-hou": `["aa","bb","cc","dd","ee"]`}),
mkPathAdvert(6, "pk_gulf2", map[string]string{"o-hou": `["aa"]`}),
)
ps.recomputeNodeHomeRegionSets()
cases := []struct {
region string
want []string
}{
{"SAT", []string{"pk_near", "pk_tie"}},
{"AUS", []string{"pk_cap"}},
{"HOU", []string{"pk_gulf2", "pk_tie"}},
}
for _, c := range cases {
got, ok := ps.RegionNodePubkeys(c.region)
if !ok {
t.Fatalf("region %q: ok=false", c.region)
}
if g := sortedKeys(got); !reflect.DeepEqual(g, c.want) {
t.Errorf("region %q: got %v, want %v", c.region, g, c.want)
}
}
want := map[string][]string{
"pk_near": {"SAT"},
"pk_tie": {"HOU", "SAT"},
"pk_cap": {"AUS"},
"pk_gulf": {},
"pk_gulf2": {"HOU"},
}
if got := ps.GetNodeFallbackRegionSets(); !reflect.DeepEqual(got, want) {
t.Errorf("fallback region sets = %v, want %v", got, want)
}
}
// Zero-hop home regions still win: a node with any zero-hop hearing is not in
// the fallback snapshot at all.
func TestRegionFallbackSkipsZeroHopNodes(t *testing.T) {
ps := newHomeRegionStore(t)
fb := ps.GetNodeFallbackRegionSets()
if _, has := fb["pk_aus"]; has {
t.Errorf("pk_aus has zero-hop home regions but is in the fallback: %v", fb)
}
if !reflect.DeepEqual(fb["pk_far"], []string{"SAT"}) {
t.Errorf("pk_far fallback = %v, want [SAT]", fb["pk_far"])
}
}
+3
View File
@@ -448,6 +448,9 @@ type PacketStore struct {
// #1879: pubkey → regions whose observers heard it zero-hop, for the
// /api/nodes region filter. Written by the node-home-regions recomputer.
nodeHomeRegionSets atomic.Pointer[map[string][]string]
// #1879: pubkey → regions that heard it in the fewest hops, for nodes
// never heard zero-hop; empty when even that is over regionFallbackMaxHops.
nodeFallbackRegionSets atomic.Pointer[map[string][]string]
// Coverage + Redundancy axes (issue #672 axes 3 & 4 of 4): atomic
// snapshots of pubkey → 0..1 score over the current neighbor graph.