fix(header-chain): preserve auxiliary capacity and diversity - #845
Conversation
a63cb99 to
a09990b
Compare
|
|
||
| **LC-AUX-04 [ZW] — Cryptographic metadata authentication.** Auxiliary roots MAY be marked authenticated only after the existing integrated verifier reconstructs the relevant ZIP 221 history-tree inputs and checks them against the appropriate checkpoint/header commitment, including the one-header-later authentication boundary. Proven bad metadata MAY score its supplier but MUST NOT invalidate the header. | ||
|
|
||
| **LC-AUX-06 [LS] — Semantic auxiliary capacity.** The engine MUST retain no more than 32 distinct auxiliary payloads for one header. The semantic payload identity MUST include the header hash and every tree-aux field. It MUST exclude the delivery ID, supplier, session, request, work owner, and body-size hint. A duplicate semantic payload MUST NOT consume another slot or replace a distinct retained payload. One supplier MUST NOT consume more than one rooted semantic-payload slot for one header. Independent plan verification MUST enforce the same rules. |
There was a problem hiding this comment.
I assume we have a later plan to make this a precise constant and if you send the incorrect number you get banned?
|
Ai finding which I imagine is fixed in follow-on's
The new invariant only checks semantic uniqueness for newly inserted delivery IDs (auxiliary.rs L121-L126). It never checks all retained rows pairwise. That matters because pre-PR databases may legitimately contain multiple delivery IDs for an identical payload—the exact transport-retry behavior this PR addresses. Startup recovery
It does not enforce semantic uniqueness or the one-rooted-payload-per-supplier rule (authoritative_rows.rs L62-L87). I confirmed diagnostically that hydration accepts two different delivery IDs with the same semantic fingerprint. Consequences:
Recommendation: add an explicit durable migration or recovery repair that deterministically compacts existing semantic duplicates, then verify semantic/source uniqueness over the
The verifier calculates: old count - all deleted IDs + only previously absent Put IDs (auxiliary.rs L50-L70) It does not reject the same delivery ID appearing in both a Delete and a Put. For an existing ID, such a plan subtracts the deletion but does not add the replacement. Because auxiliary changes are applied in order, this sequence can finish above the aggregate Delete(existing A) At a full aggregate limit, the verifier counts this as unchanged, while the committed state gains B. Repeating the pattern can hide multiple new rows. PlanCandidate is sealed inside the crate, so there is no direct peer-controlled construction path today. Nevertheless, this defeats the independent verifier’s safety purpose if the Recommendation: reject duplicate deletes and any delivery ID present in both Put and Delete, or calculate the final keyed delivery set by replaying changes in order. Test both Put/ |
ValarDragon
left a comment
There was a problem hiding this comment.
I did review this before, should've merged. I think this addresses the bug, and we just have a few more that are likely already fixed in follow-on's!
Nice job, and hope to tighten this with message expectation work!
Motivation
The dual-stack continuous-sync canary repeatedly received
AuxiliaryLimitExceededwhile VCT repair was pending. Header-chain admissionevaluated new deliveries against the pre-retention engine state. It could reject
a transition whose retention pass would evict an old delivery and keep the
settled state within the aggregate limit.
The retained run does not expose auxiliary-delivery or retained-node counts. It
therefore cannot show whether the failed transition would have evicted a
delivery. The shared error also does not identify whether the event-local
per-header limit, the event-local aggregate limit, the retained per-header
limit, or the retained aggregate limit caused the refusal.
Version 1.3 allows 16 retained auxiliary deliveries per header and 65,536
retained auxiliary deliveries across the graph. Transport retries can store the
same payload under new delivery IDs. An attacker can also send several distinct
payloads from one peer. Both cases can consume capacity without adding an
independent repair candidate.
This change raises the per-header limit to 32 distinct semantic payloads. It
keeps the 65,536-row aggregate limit. It also removes the pre-retention
false-rejection case. It does not bound the VCT repair episode or claim to fix
every cause of that livelock.
Solution
retention produce the settled projection.
the semantic identity.
The event-local per-header bound prevents one aggregate-sized event from forcing
quadratic vector insertion work for one header. The post-retention bound allows
retention to replace old deliveries without a false capacity rejection.
The first retained row preserves provenance for a semantic payload. Header sync
continues to track which peers announced the header. A repeated delivery from a
new peer does not need another durable payload row.
The planner may represent a transition above the aggregate limit while it
calculates retention. The engine never commits that intermediate projection.
The final atomic transition must remain within both retained limits.
Alternatives
lets transport retries and supplier-controlled duplicates consume the new
capacity.
Sybil identities consume the per-header limit with identical payloads.
one supplier consume every slot with distinct payloads.
attacker displace useful retained input. The engine instead keeps the first
retained row for each semantic payload.
durable transition and creates a race between retention and later admission.
retry coordination and can repeat the same failure.
and can disagree with the settlement calculation.
and a larger protocol change.
stronger containment but belongs with the state-owned repair protocol.
The selected policy combines semantic deduplication, one rooted slot per
supplier, and a 32-payload per-header limit. The policy preserves useful
payload diversity without adding a new durable supplier-set schema.
Testing
cargo test -p zakura-header-chain --libcargo test -p zakura-state --libcargo clippy -p zakura-header-chain --all-targets -- -D warningscargo fmt --all -- --check./scripts/changelog.py check-pr --base origin/main --head HEAD --pr 845The replacement regression starts at a one-row aggregate auxiliary cap. The
transition adds one delivery while retention evicts the losing branch delivery.
The test verifies that the planner admits the transition and that the final
delta replaces the old delivery without exceeding the cap.
The saturation regression starts at the same cap without an eviction. It
verifies that the planner still returns
AuxiliaryLimitExceeded. Theevent-local test covers the per-header work bound. The invariant tests corrupt
candidates above the aggregate and per-header retained limits. Production and
exhaustive verification reject those candidates.
The semantic-diversity regression admits payload A and ignores the same payload
from another supplier. It then admits payload B and ignores another rooted
payload from B's supplier. The fingerprint regression proves that transport
metadata cannot make a duplicate payload distinct. Independent verification
rejects duplicate semantic payloads and multiple rooted payloads from one
supplier.
Follow-up Work
completed local failure can still repeat until that protocol lands.
production run to attribute a refusal to a specific saturation mode.