Skip to content

feat(spice): Sketch the datatypes to use in the data distribution#16053

Draft
ssavenko-near wants to merge 2 commits into
masterfrom
slavas/spice-data-distribution-draft
Draft

feat(spice): Sketch the datatypes to use in the data distribution#16053
ssavenko-near wants to merge 2 commits into
masterfrom
slavas/spice-data-distribution-draft

Conversation

@ssavenko-near

@ssavenko-near ssavenko-near commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

wip.
I am still only reasonably happy with mod.rs and item.rs. The rest has gaps. Working on those.

The core idea: one place that knows what data I need, what I have, and who I got it from — a single per-node state machine (SpiceDataManager) that is really a generic fetch engine.
Witness, receipts, and code are the same "go fetch this" operation with three plug-in configs (what its ID is, who has it, how it's verified).

How data moves (push-primary, pull-recovery):

  • Push is the normal path. Every producer holds the full data but, to save bandwidth, each pushes only its own erasure-coded slice to recipients. Collect ~60% of the slices → reassemble. In the happy case no requests are sent at all.
  • Pull is only for recovery (you missed pushes, restarted, or are catching up). Then you request only the missing slices from good producers, any single producer can serve the whole thing.

Availability is discovered, not announced.
We already know what we need (from the chain) and who produces it (structural).
The first slice that arrives is the signal it exists.
If you have none, you speculatively ask — and just wait for the push on recent blocks, since it's coming anyway.

Smarter retries.
The single 1-second timer becomes a per-item scheduler: don't request data that isn't produced yet (gate on execution progress), fetch fast when you know it exists, back off and fan out across a few high-score producers if stuck, and give up automatically once the block passes the final execution head.

Safety and fairness:

  • Size limits checked before allocating memory, with byte budgets.
  • Accountability: nodes that send garbage or withhold data lose reputation (which steers future requests away from them) (and that also feeds the network-layer ban system).
  • Authorization: the server checks you're actually entitled to the data before serving it.
  • RPC nodes get a lower-priority fetch lane in the same engine so they can't starve validators.

@ssavenko-near
ssavenko-near force-pushed the slavas/spice-data-distribution-draft branch from eae50ef to aa98b12 Compare July 10, 2026 16:03
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 116 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.27%. Comparing base (7797706) to head (dda90c7).
⚠️ Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
chain/client/src/spice/data_manager/item.rs 0.00% 28 Missing ⚠️
chain/client/src/spice/data_manager/scheduler.rs 0.00% 25 Missing ⚠️
chain/client/src/spice/data_manager/admission.rs 0.00% 22 Missing ⚠️
chain/client/src/spice/data_manager/reputation.rs 0.00% 19 Missing ⚠️
chain/client/src/spice/data_manager/mod.rs 0.00% 16 Missing ⚠️
chain/client/src/spice/data_manager/fetchable.rs 0.00% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #16053      +/-   ##
==========================================
+ Coverage   73.25%   73.27%   +0.01%     
==========================================
  Files         857      863       +6     
  Lines      188641   189349     +708     
  Branches   188641   189349     +708     
==========================================
+ Hits       138188   138740     +552     
- Misses      46038    46180     +142     
- Partials     4415     4429      +14     
Flag Coverage Δ
pytests-nightly 1.22% <0.00%> (-0.01%) ⬇️
unittests 69.92% <0.00%> (+0.02%) ⬆️
unittests-nightly 69.92% <0.00%> (+0.02%) ⬆️
unittests-spice 65.54% <0.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread chain/client/src/spice/data_manager/scheduler.rs Outdated
Comment thread chain/client/src/spice/data_manager/serve.rs Outdated
Comment thread chain/client/src/spice/data_manager/messages.rs Outdated
Comment thread chain/client/src/spice/data_manager/messages.rs
Comment thread chain/client/src/spice/data_manager/messages.rs Outdated
Comment on lines +262 to +266
/// Everyone who contributed any unit — the culprit set for a semantic `Failed`
/// on the delivered (winning-commitment) data.
pub(crate) fn all(&self) -> Vec<AccountId> {
Vec::new() // sketch
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood this to mean if there are competing commitments, we let the first internally consistent one "win", then if it turns out to be semantically incorrect, an upper layer will discredit the peers we got it from? Does it trigger a retry too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"First internally consistent one wins" - yes.
"Upper layer discredits the peers we got it from" - yes
"Does it trigger a retry too?" - yes, but only for locally recognized inconsistencies. If we deliver the endorsement for self-consistent but "wrong" witness, there won't be a retry.

/// at/below the final execution head instead of scanning every item (which would
/// rebuild the global sweep this design deletes). Heights are captured at seed
/// time (`FetchItem::height`) — expiry never does per-item store lookups.
items_by_height: BTreeMap<BlockHeight, Vec<DataId>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible draining based on this would evict a request for something that was needed for a higher height as well (eg, code which is by hash)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. There's also a FetchItem::height field. If that's higher than the block height here we know the entry is stale.

Comment on lines +30 to +31
/// Contract accesses inconsistent with the witness's `contract_accesses_hash`.
AccessesInconsistent,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the code access list get requested in this model? Specifically how can we resolve the data dependency on block, witness, and code accesses together in this design?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the code access list get requested in this model?

It is not. It is either pushed separately as an optimization, or included (always duplicated) as part of the witness.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which node(s) is supposed to do the pushing?

If we consider it an optimization, we need to think carefully whether malicious node(s) that are supposed to do the pushing can selectively delay some chunk endorsers from starting the work.

Comment on lines +156 to +157
/// per-`PeerId`/connection. This maps account→peer (Tier1 mapping) and pushes signals
/// into the network peer scorer, which owns actual banning. A producer with no

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this generally mean this model does not support requesting data from non-validators? For example, during initial block sync pull for incoming receipts.

Or a node that tracks only some shards pulls which was offline for some time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming that a catching-up node can get everything it needs (execution results, receipts, code) from block/state sync without the distribution engine. So the engine is for live data only.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that is the case, then why do we need to have the requestor priorities? Maybe I didn't understand what you consider "engine"?

Non-spice block-sync code does not need to separately distribute receipts, since they are distributed automatically with the chunks / blocks.

I think an implementation for requesting this data will also need retries, knowing which nodes have the data, and avoiding requests from nodes sending bad data/non-responsive nodes, DoS protection, so it seems similar enough to the live case to consider them here.

There is also the case of a validator performing catchup after state sync for a shard that it needs to start validating in the next epoch. So maybe this is even an intermediate level of priority to respond to.

Comment on lines +41 to +42
pub(crate) global_priority_bytes: u64,
pub(crate) global_background_bytes: u64,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if priorty and background could be two separate instances of an actor or a similar design? Otherwise we'd need some kind of priority queue to ensure too many background requests (even within a budget) do not delay the validator push or responding to a pull request from a validator.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to base on a single state machine. If we split into several actors we'll have to split the state. I also like the idea to let data distribution manager to define/alter the priority. So probably there's no way around having the priority queue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that we can/should hide inside the spawner.

@ssavenko-near
ssavenko-near force-pushed the slavas/spice-data-distribution-draft branch 7 times, most recently from c5a6ada to f9a686d Compare July 20, 2026 13:07
@ssavenko-near
ssavenko-near force-pushed the slavas/spice-data-distribution-draft branch from f9a686d to dda90c7 Compare July 20, 2026 16:14
Sign up for free to 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