From 3d916a685e71305dc3af97f6446a693d83baba21 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 00:20:44 +0100 Subject: [PATCH] mesh-grants: the same list said again moves neither the sealed key nor the shadow list (issue 1126) Live since the take-over the shadow list was written every 3 s (revision 99270 to 99357 in 90 s, nothing differing): the sealed key read back says [] for the nil left-out a pass composes, so every pass wrote the key again; each write moved the bus node's report of what it loaded, whose revision and time were inputs of the shadow list, and woke the next pass. The bus itself reloads only when the list's bytes change (its log: two configurations alternating with the placement, f8567865 and 473f9ad3), so this loop was load, not the reload. --- modules/mesh-grants/grants_test.go | 15 +++++++++++++++ modules/mesh-grants/internal/grants/part.go | 7 +++++++ modules/mesh-grants/internal/grants/sealed.go | 9 +++++++++ 3 files changed, 31 insertions(+) diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 2ea008b..db1ba96 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -1302,6 +1302,21 @@ func TestGrants(t *testing.T) { if e := effect(); !strings.HasPrefix(e, "switched") { t.Errorf("effect once the bus node loaded the written list = %s", e) } + // Steady (issue 1126, 2026-10-28 00:16: the shadow list moved every 3 s, 99270 to 99357, with nothing + // differing): the bus node saying again that it loaded the same list, at a later time and the key's + // later revision, moves neither the shadow list nor the sealed key. + _, sealedRev, _ := p.Sealed() + p.Compose() + for i := 0; i < 3; i++ { + withKey(delivery.UserListWritten{Source: delivery.GrantsUserListSource, Revision: sealedRev + uint64(i+1), Wanted: g.Digest, + Digest: g.Digest, Users: g.Users, Follows: delivery.FollowGrants, At: time.Date(2026, 10, 28, 0, 16, i, 0, time.UTC)}) + if res := p.Compose(); res.Written { + t.Errorf("pass %d: the shadow list is written again for the bus node saying the same list again (revision %d)", i, res.Revision) + } + if _, rev, _ := p.Sealed(); rev != sealedRev { + t.Errorf("pass %d: the sealed key is written again with nothing changed: revision %d, was %d", i, rev, sealedRev) + } + } withKey(loadedBefore) // After take-over a list composed past a failed read, or one dropping a user of the list in force for // want of a hash, is not sealed: the list in force stays, and the pass says why. diff --git a/modules/mesh-grants/internal/grants/part.go b/modules/mesh-grants/internal/grants/part.go index 16d177f..9692077 100644 --- a/modules/mesh-grants/internal/grants/part.go +++ b/modules/mesh-grants/internal/grants/part.go @@ -77,6 +77,13 @@ type UserListState struct { func (s UserListState) inputs() string { s.At, s.Inputs = time.Time{}, "" + // When the bus node said it loaded a list is no input: the same list said again, at a later time and the key's later + // revision, composes nothing new (issue 1126: written again every 3 s, 2026-10-28). + if s.Loaded != nil { + l := *s.Loaded + l.Revision, l.At = 0, time.Time{} + s.Loaded = &l + } raw, _ := json.Marshal(s) sum := sha256.Sum256(raw) return "sha256:" + hex.EncodeToString(sum[:]) diff --git a/modules/mesh-grants/internal/grants/sealed.go b/modules/mesh-grants/internal/grants/sealed.go index c8d8eb8..eb76a44 100644 --- a/modules/mesh-grants/internal/grants/sealed.go +++ b/modules/mesh-grants/internal/grants/sealed.go @@ -177,7 +177,16 @@ func (p *Part) sayHeld(why string) { const HeldCondition = "mesh-grants.user-list-held" func sameSealed(a, b delivery.GrantsUserList) bool { + // Its time is no change, and no user left out is the same written as [] or null: the key read back says [] for the + // nil a pass composes, which read as a change wrote the key again at every pass, and each write moved the bus node's + // report and woke the next pass (issue 1126: the shadow list's revision 99270 to 99357 in 90 s, 2026-10-28). a.At, b.At = b.At, a.At + if len(a.LeftOut) == 0 { + a.LeftOut = nil + } + if len(b.LeftOut) == 0 { + b.LeftOut = nil + } x, _ := json.Marshal(a) y, _ := json.Marshal(b) return string(x) == string(y)