CIP-003: cross-node shard placement - #23
Merged
Merged
Conversation
Implements CIP-003. Until now every shard landed on one disk, which meant the
erasure coding was pure overhead with no durability behind it. Blocks are now
spread across n peers chosen for reputation and failure-domain diversity, and
read back from whichever k answer first.
New crate `c0mpute-placement`:
peer — PeerInfo, PeerCatalog, and FailureDomain (ASN, falling back to
IP prefix, then Unknown).
select — choosing n peers under CIP-001's rules. Pure; no network I/O,
because a slow peer lookup must not become a slow write.
transport — ShardTransport trait, with HTTP and in-memory implementations.
distributed— DistributedStorage, composing the three.
Two decisions worth calling out.
**HTTP first, libp2p later.** CIP-003 assumed the libp2p protocol had to be
rewritten before placement could work. It didn't: CIP-002 already ships shard
PUT/GET/HEAD endpoints that verify what they are given, so placement was built
against a transport trait with an HTTP implementation over those. Cross-node
placement works today on a real testnet, and the streaming libp2p protocol
becomes a second implementation of an existing trait rather than a blocker.
**Placement fails loudly.** CIP-001's durability figures assume shard hosts
fail independently; fourteen shards behind one ISP are one sample wearing
fourteen hats, and nothing downstream can detect it. So a write that cannot
satisfy the diversity policy is an error naming the constraint, not a warning:
failure-domain diversity unsatisfiable: 14 shards at most 2 per domain
needs 7 distinct domains, but only 1 are available
Greedy selection under a per-domain cap is optimal rather than heuristic — the
cap is a partition matroid, so a refusal means no assignment would have worked.
Also in this change:
- Write acknowledges at k + ceil(parity/2) (12 of 14 for standard), so two
slow peers do not fail a write; reads request all n and reconstruct from
the first k.
- `c0mpute storage peer add|ls|rm|ping`, a peers.json registry, and put/get/
info using the network when peers are configured.
- CIP-003 sketched the peer score with a `1/(1+rtt/100)` latency term. That
lets a fast flaky peer outrank a slow reliable one, which is the opposite
of what CIP-001 says matters. Narrowed to a band that breaks ties without
overturning a reputation gap; a test pins it.
Three bugs found by running it rather than by tests:
- `c0mpute storage get` still used the local read path, so an object placed
across the network was unreadable — placement worked and retrieval did not.
- anyhow's Display drops the cause chain, so the HTTP layer turned "not
enough eligible peers: need 14, found 3" into "placing block 0". The whole
point of CIP-003 is failing loudly; six sites now format with `{:#}`.
- The CLI panicked on SIGPIPE, so `c0mpute storage ls | head` crashed.
43 new tests (27 unit, 16 integration including a real multi-node HTTP test).
Verified on a 16-node testnet driven through the CLI: 14 shards on 14 distinct
peers, byte-identical read back, still readable with 4 holders killed, refused
with 5, and refused outright on a single-domain network.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsQAuvXkmyHTgnvquLHrRx
ThreatCrush Security Scan8 finding(s) HIGH/CRITICAL: 5 | MEDIUM: 3
Snippets are redacted; ThreatCrush never prints matched credential material. |
This was referenced Aug 29, 2026
Contributor
Author
|
Note for anyone following this: this PR merged into #20 squash-merged Recovery: #24 re-lands CIP-002 on master, #25 is this same commit replanted on top of it. Both verified green. Once those merge, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements CIP-003. Stacked on #21, so this PR's diff is placement only.
Until now every shard landed on one disk, which meant the erasure coding was pure overhead with no durability behind it. Blocks are now spread across
npeers chosen for reputation and failure-domain diversity, and read back from whicheverkanswer first.Verified on a real 16-node testnet, driven through the CLI
New crate:
c0mpute-placementpeerPeerInfo,PeerCatalog,FailureDomain(ASN → IP prefix → Unknown)selectnpeers under CIP-001's rules. Pure — no network I/O, because a slow peer lookup must not become a slow writetransportShardTransporttrait, with HTTP and in-memory implementationsdistributedDistributedStorage, composing the threeTwo decisions worth your attention
HTTP first, libp2p later. CIP-003 assumed the libp2p protocol had to be rewritten before placement could work. It didn't. CIP-002 already ships shard
PUT/GET/HEADendpoints that verify what they're given, so placement was built against a transport trait with an HTTP implementation over those. Cross-node placement works today; the streaming libp2p protocol becomes a second implementation of an existing trait rather than a blocker. It's still worth doing (removes an HTTP hop, gives CIP-005 a batchedHaveprobe) — it's just independently schedulable now. CIP-003 is updated to say so.Placement fails loudly. CIP-001's durability figures assume shard hosts fail independently. Fourteen shards behind one ISP are one sample wearing fourteen hats, and nothing downstream can detect that the assumption was broken. So a write that can't satisfy the diversity policy is an error naming the constraint, not a warning.
--insecure-ignore-diversityexists for testnets and says what it costs.Greedy selection under a per-domain cap is optimal, not heuristic — the cap is a partition matroid, so greedy is optimal over it. A
DiversityUnsatisfiableresult means no other assignment would have worked, so there's no backtracking and no better answer being missed.Also here
k + ceil(parity/2)(12 of 14 forstandard), so two slow peers don't fail a write. Reads request allnand reconstruct from the firstk— costs 1.4x read bandwidth, buys the k-th fastest peer instead of the slowest of a chosen k.c0mpute storage peer add|ls|rm|pingplus apeers.jsonregistry;put/get/infouse the network when peers are configured.peer lsreports readiness per tier against the domain count, which is the number that actually decides durability.1/(1+rtt/100)latency term makes a 400 ms peer score 20% below a 1 ms one — enough for a fast flaky node to outrank a slow reliable one, the opposite of what CIP-001 says matters. Narrowed to a band that breaks ties without overturning a reputation gap; a test pins it.Three bugs found by running it, not by tests
c0mpute storage getstill used the local read path, so an object placed across the network was unreadable — placement worked and retrieval didn't. Caught by the testnet script, not the suite.Displaydrops the cause chain, so the HTTP layer turnednot enough eligible peers: need 14, found 3intoplacing block 0. The entire point of CIP-003 is failing loudly; six sites now format with{:#}.c0mpute storage ls | headcrashed withfailed printing to stdout: Broken pipe. Restored the default disposition.Testing
43 new tests (27 unit, 16 integration) including a real multi-node HTTP test that stands up five gateway servers and places across them. Whole workspace green (28 suites); clippy-clean on the new crate.
Not in scope
Repair (CIP-005) — nothing yet regenerates a lost shard, so a degraded block stays degraded;
inforeports it. Stalehost_hintresolution via Kad provider records, and the batchedHaveprobe, land with the libp2p transport. Payouts are CIP-006.🤖 Generated with Claude Code
https://claude.ai/code/session_01LsQAuvXkmyHTgnvquLHrRx