Judgements and refused setuid searches, which any caller may ask, roll in one bounded key of the node's and add no key to the bucket every node shares; a key per act is kept for hand-overs, access acts and searches started (issue 774, ADR 0563 §2; rev-l3 round 2)

This commit is contained in:
2026-10-28 20:35:33 +01:00
parent 2a0c428ef9
commit 6cbdb3712e
2 changed files with 91 additions and 2 deletions
+62 -2
View File
@@ -242,6 +242,54 @@ func (h *HandActs) readOne(key string) (*HandAct, uint64, error) {
return &a, e.Revision, nil
}
// judgedKey keeps the node's judgements and refused searches, the last keptJudged of them: any caller may ask them, so
// they roll in one key and never add one (ADR 0563 §2: a wrong caller gains nothing, and fills no bucket).
const (
judgedKey = "judged"
keptJudged = 50
)
// roll adds a to the rolling key by compare-and-set, the oldest falling out.
func (h *HandActs) roll(a HandAct) error {
for range 5 {
e, err := h.State.Get(judgedKey)
if err != nil {
return err
}
var kept []HandAct
if e != nil {
if err := json.Unmarshal(e.Value, &kept); err != nil {
kept = nil // an unreadable roll is started again, never a reason to refuse
}
}
kept = append(kept, a)
if len(kept) > keptJudged {
kept = kept[len(kept)-keptJudged:]
}
if e == nil {
_, err = h.State.Create(judgedKey, kept)
} else {
_, err = h.State.Update(judgedKey, kept, e.Revision)
}
if !errors.Is(err, stdio.ErrChanged) {
return err
}
}
return errors.New("the engine's judgements kept changing under its write")
}
func (h *HandActs) rolled() ([]HandAct, error) {
e, err := h.State.Get(judgedKey)
if err != nil || e == nil {
return nil, err
}
var kept []HandAct
if err := json.Unmarshal(e.Value, &kept); err != nil {
return nil, fmt.Errorf("the engine's judgements cannot be read: %w", err)
}
return kept, nil
}
func (h *HandActs) record(a HandAct) error {
_, err := h.State.Create(actKey(a.ID), a)
return err
@@ -286,6 +334,12 @@ func (h *HandActs) Acts(limit int) (any, error) {
if err != nil {
return nil, err
}
judged, err := h.rolled()
if err != nil {
return nil, err
}
acts = append(acts, judged...)
sort.SliceStable(acts, func(i, j int) bool { return acts[i].At.Before(acts[j].At) })
out := make([]HandAct, 0, len(acts))
for i := len(acts) - 1; i >= 0; i-- {
out = append(out, acts[i])
@@ -308,7 +362,7 @@ func (h *HandActs) Judge(caller string) (any, error) {
if err != nil {
a.State, a.Said = HandRefused, err.Error()
}
if rerr := h.record(a); rerr != nil && err == nil {
if rerr := h.roll(a); rerr != nil && err == nil {
return nil, rerr
}
if err != nil {
@@ -339,7 +393,13 @@ func (h *HandActs) Search(caller, why string) (any, error) {
if err != nil {
a.State, a.Said = HandRefused, err.Error()
}
if rerr := h.record(a); rerr != nil {
// A search started is bounded to one an hour and kept as an act of its own; a refused one, which any caller may
// cause, rolls with the judgements.
record := h.record
if err != nil {
record = h.roll
}
if rerr := record(a); rerr != nil {
return nil, rerr
}
if err != nil {
+29
View File
@@ -105,5 +105,34 @@ func TestASetuidSearchAskIsBelievedOnlySignedForThisNodeAndOnce(t *testing.T) {
if _, err := h.Call(EngineCall{Verb: ActSetuidSearch, Caller: "ace.node-tools"}); err == nil || !strings.Contains(err.Error(), "the next may start") || started != 2 {
t.Errorf("a search with the acts gone: %v, %d started", err, started)
}
// rev-l3 round 2 (ADR 0563 §2, a wrong caller gains nothing): judgements and refused searches, which any caller
// may ask, keep one rolling key of the node's, so ten thousand of them write a bounded number of bytes and add no
// key to the bucket every node shares.
keysNow := func() (int, int) {
all, _ := h.State.Keys("")
size := 0
for _, k := range all {
if e, _ := h.State.Get(k); e != nil {
size += len(e.Value)
}
}
return len(all), size
}
_, _ = h.Call(EngineCall{Verb: ActJudgeDirectories, Caller: "unproven"})
keysBefore, _ := keysNow()
for i := range 10000 {
verb := ActJudgeDirectories
if i%10 == 0 {
verb = ActSetuidSearch
}
_, _ = h.Call(EngineCall{Verb: verb, Caller: "unproven"})
}
if n, size := keysNow(); n != keysBefore || size > 64<<10 {
t.Errorf("ten thousand judgements and refused searches left %d keys (was %d) of %d bytes", n, keysBefore, size)
}
listed, _ := h.Acts(0)
if got := listed.(map[string]any)["acts"].([]HandAct); len(got) == 0 || got[0].Caller != "unproven" {
t.Errorf("acts does not show the latest judgement first: %d", len(got))
}
})
}