docs(contract-interface): bound the delta to an up-to-date peer - #90
Conversation
Please do not merge as written — a live contract violates this ruleI requested this change, and I specified the rule wrongly. Flagging before it merges, because the trait-level sentence attaches "may result in the contract being deprioritized or removed" to it. Atlas violates the rule as written, and is behaving correctly.
into_writer(&st.delta(&summ), &mut out)?;
Ok(StateDelta::from(out)) // unconditional — no None arm, no empty collapseand IndexDelta { key_auth, records }Against its own summary, River passes only because The corrected rule, in three tiers
The discriminator has to be size-relative: Atlas is ~4 bytes against a 500 KB state; the offender is state-sized. Five orders of magnitude apart, and no encoding choice moves a contract across that gap. What needs changing
The Apologies for the churn — the error is mine, not the author's. They implemented the brief I gave them, and the brief predated my checking Atlas. [AI-assisted - Claude] |
Document what `get_state_delta` owes a peer that is already up to date, in three tiers, because a flat "must be zero bytes" rule flags contracts that are behaving correctly: - MUST NOT return a delta containing the state, or approaching its size. This is the actual defect and the only tier that carries the deprioritized-or-removed consequence. - SHOULD return a literally empty delta. Zero bytes is the unambiguous "converged" signal that lets peers skip the broadcast. - Acceptable: a small fixed amount of encoding framing from serializing an all-empty delta struct, about a byte per field with bincode and tens of bytes with CBOR since ciborium writes field names. The discriminator is delta size relative to state size. A contract that hand-rolls `get_state_delta` around a plain (non-`Option`) delta struct returns ~20 bytes to an up-to-date peer, which is fine; freenet/freenet-core#5056 returns 25,403 bytes against a 24,832-byte state, which is not. Those are five orders of magnitude apart and no encoding choice moves a contract across the gap. Also notes the concrete difference this makes to an author: `freenet-scaffold`'s `#[composable]` derive collapses an all-`None` delta to `None` and gets zero bytes for free, while a hand-rolled implementation has to add that collapse itself. `summarize_state` gains the companion rule: the summary must be much smaller than the state, and a summary that is a copy of the state is always a bug. Doc comments only, no code or behavior change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SeAw3G4bFhTT242xoEeMxN
0929d98 to
bc7eb26
Compare
|
Force-pushed a correction (0929d98 -> bc7eb26). The first revision of this PR stated the rule as a flat "MUST return zero bytes" and attached the existing "may result in the contract being deprioritized or removed" consequence to it. That was wrong, and it would have flagged a correct app. Atlas's index contract hand-rolls // atlas/contracts/index-contract/src/lib.rs:139
into_writer(&st.delta(&summ), &mut out)?;
Ok(StateDelta::from(out)) // unconditional, no empty collapseAgainst its own summary that produces The rule is now stated in three tiers, and only the MUST NOT tier (a delta containing the state, or approaching its size) carries the deprioritize/remove consequence. The discriminator is delta size relative to state size: ~20 bytes against a 500 KB state versus the 25,403-byte delta against a 24,832-byte state in freenet/freenet-core#5056. [AI-assisted - Claude] |
1.10.1 stated it as a flat "get_state_delta MUST return zero bytes to an
up-to-date peer". That is wrong as a hard rule and marks a correct live app
noncompliant.
Atlas's index contract hand-rolls get_state_delta around a plain, non-`Option`
delta struct and serializes it unconditionally, so against its own summary it
returns `IndexDelta { key_auth: None, records: [] }`, about 20 bytes of CBOR
rather than zero, since ciborium serializes structs as maps and writes the field
names. Atlas is behaving correctly; it has no all-`None` collapse because it does
not use freenet-scaffold, and River clears the zero-byte bar only because the
`#[composable]` macro gives it one.
The rule is now three tiers: MUST NOT return a delta containing the state or
approaching its size (the actual defect, and the only tier with a consequence);
SHOULD return a literally empty StateDelta, the only result that passes core's
converged check; acceptable to return a few tens of bytes of encoding framing
from an all-empty struct. The discriminator is delta size relative to state size.
Roughly 20 bytes against a 500 KB state versus freenet/freenet-core#5056's 25,403
bytes against a 24,832-byte state is five orders of magnitude, and no encoding
choice moves a contract across that gap.
- `references/contract-patterns.md`: retitled and restated in tiers. The
all-`None` block is now "why you may see a few tens of bytes and why that is
tolerable" rather than a defect, with the byte figure corrected to 28 measured
for the three-field CBOR example. Adds the actionable difference for an author,
`#[composable]` gives zero bytes for free while a hand-rolled implementation
must add the collapse. Describes core's converged test as it actually works,
byte-identical summaries first and the get_state_delta probe only when summary
bytes differ. The test asserts the size bound rather than `== 0`.
- `SKILL.md`: same reframing in the sync section and Phase 1 step 4.
The companion stdlib change carries the same correction, scoping the
deprioritize-or-remove consequence to the MUST NOT tier (freenet/freenet-stdlib#90).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SeAw3G4bFhTT242xoEeMxN
|
All three items are applied in
Two corrections to the numbers in your comment, neither of which changes the argument:
[AI-assisted - Claude] |
|
Block resolved — I re-read the diff rather than taking the summary on trust, and all three items are addressed in the text.
Since I filed the block, two more data points landed that support this wording. Delta's The Merging. [AI-assisted - Claude] |
Problem
ContractInterface's rustdoc says nothing about whatget_state_deltaowes a peer that is already up to date, and there is a real defect in the wild that it should rule out: one live contract ignores thesummaryargument entirely and returns its whole state as the delta, 25,403 bytes against a 24,832-byte state. Every reconciliation re-ships data the peer already holds, it never converges with anyone, and it is currently 55.6% of all broadcast sends on the network (freenet/freenet-core#5056).Core decides whether a neighbour is up to date by running
get_state_deltaagainst that neighbour's summary and reading an empty result as "converged" (broadcast_queue.rs::fanout_send_neededviapeer_summary_has_pending_state), so this is load-bearing rather than a matter of efficiency.Approach
Doc comments only, no code or behavior change.
The obvious phrasing, "the delta to an up-to-date peer must be zero bytes", is wrong as a hard rule, and an earlier revision of this PR made that mistake. A contract that hand-rolls
get_state_deltaaround a plain (non-Option) delta struct serializes an all-empty struct and returns roughly 20 bytes. Atlas does exactly this and is behaving correctly. A flat zero-byte rule would mark it noncompliant, which is the same false-positive shape as freenet/freenet-core#4295.So the rustdoc states it in three tiers:
StateDelta::from(vec![]), since zero bytes is the unambiguous converged signal.ciborium-0.2.2/src/ser/mod.rs:288).The discriminator is delta size relative to state size, not an absolute byte count: ~20 bytes against a 500 KB state versus a state-sized delta is five orders of magnitude, and no encoding choice moves a contract across that gap.
Also documented, since it is the actionable difference between two live apps:
freenet-scaffold's#[composable]derive collapses an all-Nonedelta struct toNoneand so gets zero bytes for free (River), while a hand-rolledget_state_deltahas to add that collapse itself (Atlas).summarize_stategains the companion rule: the summary must be much smaller than the state, and a summary that is a copy of the state is always a bug. The offending contract fails this too, with summary == state == 24 KB.Testing
cargo fmt --checkclean.cargo doc --no-deps --features contractproduces no warnings fromtrait_def.rsand the new intra-doc links resolve; the 5 remaining warnings are pre-existing and in other files.[AI-assisted - Claude]