The vault reads a machine's placement once per answer and says how long each answer took (issue 774) #576
@@ -11,8 +11,10 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
@@ -87,7 +89,7 @@ func (v *Vault) credentialRefusal(node string, c CredentialAsked, plan *planRead
|
||||
}
|
||||
// The consuming module stands on the machine: read from the placement and the assignments, never assumed (one
|
||||
// that cannot be read refuses). The assignments' mirror can be behind, so one it does not hold is confirmed directly.
|
||||
placed, err := v.placedOn(node, c.ConsumerModule)
|
||||
placed, err := plan.placedOn(c.ConsumerModule)
|
||||
if err != nil {
|
||||
return fmt.Sprintf(notYetPrefix+"where %s stands could not be read: %v", c.ConsumerModule, err)
|
||||
}
|
||||
@@ -122,13 +124,19 @@ func (v *Vault) credentialRefusal(node string, c CredentialAsked, plan *planRead
|
||||
}
|
||||
|
||||
// planReader reads the assignments of one machine's modules for one answer of `own`, each module's once (issue 1126,
|
||||
// 2026-10-28: one read per own secret and per credential, 203 on novox, held the answer past the node-engine's bound).
|
||||
// 2026-10-28: one read per own secret and per credential, 203 on novox, held the answer past the node-engine's bound),
|
||||
// and the machine's placement once (issue 774, 2026-10-31: read once per credential, 143 bus reads in one answer to ace).
|
||||
type planReader struct {
|
||||
node string
|
||||
reader AssignmentReader
|
||||
confirm AssignmentReader
|
||||
why string
|
||||
got map[string]planRead
|
||||
// placement reads the machine's placed modules; placed and placedErr keep the one read for the answer.
|
||||
placement func() ([]string, error)
|
||||
placedRead bool
|
||||
placed []string
|
||||
placedErr error
|
||||
}
|
||||
|
||||
type planRead struct {
|
||||
@@ -142,7 +150,17 @@ func (v *Vault) plansOf(node string) *planReader {
|
||||
if d, ok := v.Askers.(Declared); ok && d.Confirm != nil {
|
||||
confirm = standing(d.Confirm)
|
||||
}
|
||||
return &planReader{node: node, confirm: confirm, reader: read, why: why, got: map[string]planRead{}}
|
||||
return &planReader{node: node, confirm: confirm, reader: read, why: why, got: map[string]planRead{},
|
||||
placement: func() ([]string, error) { return v.placedModules(node) }}
|
||||
}
|
||||
|
||||
// placedOn says module is placed on the machine by the catalogue, from the machine's placement read once for the answer.
|
||||
func (p *planReader) placedOn(module string) (bool, error) {
|
||||
if !p.placedRead {
|
||||
p.placed, p.placedErr = p.placement()
|
||||
p.placedRead = true
|
||||
}
|
||||
return slices.Contains(p.placed, module), p.placedErr
|
||||
}
|
||||
|
||||
// read is module's assignment on the machine, nil when there is none, or why the vault reads none.
|
||||
@@ -222,7 +240,22 @@ func composeOrigin(origin string) string {
|
||||
}
|
||||
|
||||
// OwnOfNode answers the node-engine of a machine the own secrets of its modules, each sealed to the machine's stated key.
|
||||
// Every answer says how long it took, and a refusal is said too (issue 774, 2026-10-31: ace's asks went unanswered past
|
||||
// the node-engine's 15 s and the vault's journal named none of them, so whether they arrived could not be told).
|
||||
func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
|
||||
began := v.Now()
|
||||
a, err := v.ownOfNode(caller, asked...)
|
||||
took := v.Now().Sub(began).Round(time.Millisecond)
|
||||
if err != nil {
|
||||
v.log("own for %s, %d credentials asked, refused after %s: %v", caller, len(asked), took, err)
|
||||
return a, err
|
||||
}
|
||||
v.log("handed %s's node-engine the %d own secrets of its modules and %d credentials (%d not held), sealed to its key, in %s",
|
||||
a.Node, len(a.Own), len(a.Credentials), len(a.Missing), took)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (v *Vault) ownOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, error) {
|
||||
node, ok := nodeEngineOf(caller)
|
||||
if !ok {
|
||||
if caller == "" || caller == stdio.Unproven {
|
||||
@@ -328,8 +361,6 @@ func (v *Vault) OwnOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, e
|
||||
return OwnAnswer{}, fmt.Errorf("nothing is handed out to %s: who was handed its %d values could not be recorded: %w",
|
||||
caller, len(handings), err)
|
||||
}
|
||||
v.log("handed %s's node-engine the %d own secrets of its modules and %d credentials (%d not held), sealed to its key",
|
||||
node, len(out.Own), len(out.Credentials), len(out.Missing))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -143,6 +144,11 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Issue 774, 2026-10-31 (ace's 143 credentials went unanswered past the node-engine's 15 s while novox's asked every
|
||||
// 8 s): the machine's placement, one key, is read once per answer too, never once per credential.
|
||||
placed := placementsOf(t, map[string][]string{"anchor": {"web"}})
|
||||
placementReads := 0
|
||||
v.Placement = PlacementReader{Keys: placed.Keys, Read: func(key string) ([]byte, error) { placementReads++; return placed.Read(key) }}
|
||||
for k := range reads {
|
||||
delete(reads, k)
|
||||
}
|
||||
@@ -161,6 +167,10 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
|
||||
if len(reads) != 2 {
|
||||
t.Errorf("the answer read %v, want each of anchor's two modules once", reads)
|
||||
}
|
||||
if placementReads != 1 {
|
||||
t.Errorf("one answer of two credentials read anchor's placement %d times, want once", placementReads)
|
||||
}
|
||||
v.Placement = PlacementReader{}
|
||||
if n := v.Store.syncs - syncs; n != 1 {
|
||||
t.Errorf("one answer synced the record %d times", n)
|
||||
}
|
||||
@@ -182,9 +192,15 @@ func TestOwnReadsEachAssignmentOnceAndSyncsItsRecordOnce(t *testing.T) {
|
||||
if err := os.Mkdir(filepath.Join(v.dir, "store", "handed.jsonl"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// And the refusal is said in the vault's journal with how long it took, as every answer is.
|
||||
var said []string
|
||||
v.Log = func(format string, args ...any) { said = append(said, fmt.Sprintf(format, args...)) }
|
||||
if a, err := v.OwnOfNode("node.anchor"); err == nil {
|
||||
t.Errorf("handed with no record of it: %+v", a)
|
||||
}
|
||||
if last := strings.Join(said, "\n"); !strings.Contains(last, "own for node.anchor, 0 credentials asked, refused after ") {
|
||||
t.Errorf("a refused own was said as %q", last)
|
||||
}
|
||||
}
|
||||
|
||||
func mustHanded(t *testing.T, v testVault) []Handing {
|
||||
|
||||
@@ -58,22 +58,29 @@ func (v *Vault) placements() (map[string]delivery.Placement, error) {
|
||||
|
||||
// placedOn says module is placed on node by the catalogue.
|
||||
func (v *Vault) placedOn(node, module string) (bool, error) {
|
||||
placed, err := v.placedModules(node)
|
||||
return slices.Contains(placed, module), err
|
||||
}
|
||||
|
||||
// placedModules is every module the catalogue places on node, read from the node's one key of the placement; none when
|
||||
// the vault reads no placement or the node has no key.
|
||||
func (v *Vault) placedModules(node string) ([]string, error) {
|
||||
if v.Placement.Read == nil {
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
key, err := delivery.PlacementKey(node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
raw, err := v.Placement.Read(key)
|
||||
if err != nil || raw == nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
p, err := delivery.DecodePlacement(raw)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||
return nil, fmt.Errorf("the placement of %s is not readable: %w", node, err)
|
||||
}
|
||||
return slices.Contains(p.Assigned(), module), nil
|
||||
return p.Assigned(), nil
|
||||
}
|
||||
|
||||
// standsOn says module stands on node: placed there, or assigned there while the assignments stand. A read that fails is
|
||||
|
||||
Reference in New Issue
Block a user