Skip to content

fix(dig-node-core): bind admit root to ChunkPool content (#2246 #2240) - #197

Merged
MichaelTaylor3d merged 5 commits into
mainfrom
fix/anchor-admit-merkle-recompute
Aug 8, 2026
Merged

fix(dig-node-core): bind admit root to ChunkPool content (#2246 #2240)#197
MichaelTaylor3d merged 5 commits into
mainfrom
fix/anchor-admit-merkle-recompute

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Closes #2246, Closes #2240 (super-repo DIG-Network/dig_ecosystem#2246 / #2240 — one deduped family; both are the same ChainAnchoredModuleVerifier code path).

Problem: the capsule-admit trust gate (ChainAnchoredModuleVerifier::rejection_reason, module_anchor.rs) today only byte-compares the module's committed CurrentRoot section against the chain-anchored root — it never proves that committed root is the merkle root of the DATA actually present. A header-matching-but-incomplete/tampered .dig is therefore admitted → cached → served → DHT-announced, turning an honest node into an authoritative source of corrupt content. Same gate backs both reshare-admit (#2246) and cache.pushCapsule land (#2240).

Fix: add a fail-closed rule to the shared verifier — recompute MerkleTree::from_leaves(decode_merkle_leaves(MerkleNodes)).root() from the parsed DataView and reject (NotAnchored) if it ≠ the committed CurrentRoot (or MerkleNodes is absent/undecodable). digstore recompute primitives are already available (no cross-repo cascade).

RED-first regression test (small in-memory fixture — committed root == chain root but MerkleNodes recompute to a different/absent root → must flip Anchored→NotAnchored) + a push-path twin. §5.1 backwards-compat: verified against golden .dig fixtures (the canonical read path already requires MerkleNodes). Version fix: patch 0.100.1→0.100.2.

Draft = loop salvage anchor; implementation in progress.


Generated by Claude Code

The capsule-admit trust gate (ChainAnchoredModuleVerifier::rejection_reason)
recomputed the merkle root from the capsule's own MerkleNodes section and
compared it to the committed CurrentRoot. That is hollow: rule 4 already forces
CurrentRoot == chain_root, a single untagged leaf's root IS that leaf, and
decode_merkle_leaves accepts arbitrary bytes -- so MerkleNodes=[chain_root] with
an empty/garbage ChunkPool passed for free, admitting a contentless capsule that
was then cached and DHT-announced (phantom-holder / holder-set poisoning).
Bind the SERVED content instead: recompute each resource leaf as
resource_leaf(concat(ChunkPool ciphertexts)) per KeyTable entry, sort by
static_key (the producer's order), fold, and require the root == committed root
(== chain_root). Also require the recomputed leaves equal decode_merkle_leaves
(MerkleNodes) so served inclusion proofs stay consistent with content. MerkleNodes
is no longer trusted for the admit decision. Fail-closed on absent/undecodable
KeyTable/ChunkPool or any out-of-range chunk index; an empty store folds to
sha256([]). Covers both reshare-admit and cache.pushCapsule land (shared gate;
full ChunkPool is materialized before the gate on both paths). No digstore change.
0.100.2->0.100.3.
Closes #2246, Closes #2240.
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3dforce-pushed the fix/anchor-admit-merkle-recompute branch from aa55493 to 130e242CompareAugust 7, 2026 20:32
@MichaelTaylor3dMichaelTaylor3d changed the title fix(dig-node): recompute the merkle root in the shared anchor-admit gate so admitted/pushed capsules are data-complete (#2246 #2240)fix(dig-node-core): bind admit root to ChunkPool content (#2246 #2240)Aug 7, 2026
claudeand others added 2 commits August 7, 2026 20:54
… scan
The #2246 admit gate resolved each KeyTable chunk reference via read_chunk,
which is O(global_index) (re-scans the ChunkPool from offset 0 per call), so
N references over an M-chunk pool were Theta(N*M). Zero-length chunks add 0
bytes, slipping past the MAX_STORE_BYTES cap, so a pool of M zero-length chunks
+ one current-gen entry referencing index M-1 N times pinned a core for
~Theta(module^2) per unauthenticated reshare request.
Pre-index the pool once into per-chunk byte ranges (O(1) lookup, byte-identical
to read_chunk) so content_leaves is O(pool + refs); additionally cap cumulative
references at MAX_STORE_BYTES/4 as defense-in-depth for the zero-length case.
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d marked this pull request as ready for review August 8, 2026 05:09
@MichaelTaylor3d
MichaelTaylor3d merged commit b0aaaac into mainAug 8, 2026
15 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the fix/anchor-admit-merkle-recompute branch August 8, 2026 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@MichaelTaylor3d@claude