diff --git a/docs/design/dogwood-experiments.md b/docs/design/dogwood-experiments.md new file mode 100644 index 0000000000..3ce3020b9a --- /dev/null +++ b/docs/design/dogwood-experiments.md @@ -0,0 +1,973 @@ +# Dogwood experiment report + +The September 8–9, 2026 experiments test subscription allocation, proposer +seeding, connected relay graphs, bounded fallback, and TCP delivery feedback. +They do not establish production congestion control or sustained 50,000 TPS. +The [design](dogwood.md) explains the tradeoffs; the +[spec](../specs/dogwood.md#parameter-registry) owns parameter definitions. + +The experiment source and raw results remain on the local branch +`local/dogwood-experiments-20260908`. PR #901 contains documentation only. +The local worktree is `zakura.dogwood-experiments`, alongside the docs worktree. +Its `docs/experiments/dogwood` directory retains the September 5 experiments +and adds the scripts and result directories named below. + +## Submission timestamps, nonce echoes, and shared credit + +`tcp_timing_credit.py` tests sender submission timestamps with nonce-echo +calibration over four real TCP connections. It runs in a disposable network +namespace with a shared 100 Mbps loopback queue. The two matrices configure +5 ms or 25 ms delay, producing minimum echo RTTs near 10 ms or 50 ms. +Supplier application pacing offers 80/40/20/10 Mbps. Each run releases 160 +synthetic bodies at 32 ms intervals, with four required 64 KiB parts and five +available parts. This offers 65.536 Mbps of body data before redundancy. + +Two alternating proposer scopes maintain separate standing part masks and +receiver-measured route histories. The supplier delays upstream availability +by 2 ms for its nearby proposer and 35 ms for the other proposer. These are +synthetic entry-point delays, not geographic network measurements. Every +300 ms, the receiver can change future masks using delivery rate and local +per-proposer arrival history. Existing bodies retain their assignments. +Occasional extra part subscriptions probe idle suppliers. Both proposers share +each connection's payload credit, calibration state, and delivery-rate estimate. + +The receiver sends an unpredictable 64-bit nonce every 100 ms. The supplier +echoes the nonce with its submission timestamp on the same TCP connection as +parts. The receiver measures RTT on its own monotonic clock. For local challenge +send `t0`, local echo receipt `t1`, and remote echo submission `s`, an honest +clock offset lies in `[s-t1, s-t0]`. Calibration intersects recent intervals +with a 200 ppm drift allowance. It expires after one second without an echo. +This interval depends on honest timestamps; a consistent forged offset can +remain indistinguishable from an ordinary clock offset. + +The experiment compares three policies: + +- **Receiver RTT:** local nonce RTT and receiver-only delivery-rate samples. +- **Unchecked timestamps:** the same RTT measurement, sender/receiver rate + spans, and a submission-to-receipt delay estimate using the offset midpoint. +- **Echo-checked timestamps:** sender/receiver spans with timestamp validation + against the offset interval and monotonicity. Delay uses the conservative + offset bound. Invalid telemetry falls back to receiver-only rate sampling; + it does not invalidate a delivered part. + +Every sample needs at least four parts. Bytes divided by the larger of sender +and receiver spans bounds the sample by observed receiver delivery. A recent +maximum supplies the rate estimate. A fixed future timestamp offset cancels +in sender-span differences. Compression of sender spacing cannot push this +sample above the receiver rate, although compressed receiver arrivals remain +a source of estimation error when sender timing is dishonest. + +The candidate targets twice the estimated bandwidth-delay product. It limits +credit to 128 KiB–2 MiB per peer and starts at 256 KiB. It raises the limit by +at most 64 KiB per update, only after delivering at least half the current +credit while demand reached 75% of that credit. Rising local RTT or estimated +transit delay lowers the target. Payload arrival replenishes spent credit +immediately; the receiver does not revoke outstanding grants when lowering +the limit. These rules form an experimental controller, not BBR conformance. + +The attack matrix changes peer zero's part timestamps by 8 or 100 ms, compresses +timestamp spacing, or forges both echo and part timestamps. A separate pair +compares an honest 10 Mbps supplier with the same supplier delaying each echo +by 80 ms. Honest scenarios include upstream stalls and an 80-to-5 Mbps upload +drop from two to four seconds. The controller cannot use configured rates or +the scenario label. Each run reports all released bodies, completion within +800 ms and 1,200 ms, peak credit, route changes, and timestamp rejection counts. + +The final archive contains 81 low-RTT runs, 63 longer-RTT runs, and nine actual +network-drop runs. The network-drop case changes the shared qdisc from 100 to +50 Mbps at two seconds and restores 100 Mbps at four seconds. This differs +from the supplier-pacing drop, which occurs before submission. Each table cell +below gives **completion within 800 ms / completion within 1,200 ms**, out of +480 released bodies across three repetitions. + +| Configured queue delay | Scenario | Receiver RTT | Unchecked timestamps | Echo-checked timestamps | +| --- | --- | --- | --- | --- | +| 5 ms | Baseline | 480 / 480 | 480 / 480 | 480 / 480 | +| 5 ms | Upstream gap | 480 / 480 | 480 / 480 | 480 / 480 | +| 5 ms | Supplier pacing drop | 383 / 413 | 397 / 416 | 386 / 416 | +| 25 ms | Baseline | 480 / 480 | 476 / 480 | 480 / 480 | +| 25 ms | Upstream gap | 453 / 468 | 463 / 473 | 478 / 480 | +| 25 ms | Supplier pacing drop | 319 / 371 | 306 / 349 | 309 / 345 | +| 25 ms | Actual network drop | 297 / 359 | 271 / 335 | 276 / 345 | + +Sender timing helps the longer-RTT upstream-gap case in these repetitions. +It does not consistently improve delivery when capacity drops. The 50 Mbps +network-drop interval cannot sustain the 65.536 Mbps body load even without +redundancy. All policies lose timely completions. In the longer-RTT baseline, +mean run p95 is 292.4 ms with receiver-only sampling and 339.1 ms with checked +timestamps. The three repetitions and observed variation do not establish a +general performance advantage for either estimator. + +The low-RTT runs mostly hold credit at its 128 KiB minimum after startup. The +longer-RTT baseline exercises growth: peak peer-zero authorization reaches +896 KiB under receiver-only and checked timestamp policies. During the actual +network drop, the first checked run reduces peer-zero's configured credit from +about 808 KiB before the drop to 158 KiB during it, then grows again after +capacity returns. Local echo RTT and delivered load contribute to that response; +the experiment does not attribute it solely to sender timestamps. + +Echo calibration rejects impossible claims but accepts some forged timing. +The following counts cover peer-zero part timestamps under the checked policy: + +| Remote timestamp behavior | Rejected / received, 5 ms queue delay | Rejected / received, 25 ms queue delay | +| --- | --- | --- | +| Part stamp 8 ms ahead | 0 / 1,007 | 0 / 1,749 | +| Part stamp 100 ms ahead | 1,009 / 1,009 | 1,740 / 1,759 | +| Echo stamp 50 ms ahead; part stamp 58 ms ahead | 0 / 996 | Not run | +| Compressed timestamp spacing | 0 / 1,003 | Not run | + +The 8 ms shift fits the timing uncertainty. Some longer-RTT deliveries also +leave room for a 100 ms claim. A consistent forged echo offset can move the +calibration interval. The receiver still accepts valid payloads when it rejects +timing metadata. No other supplier's timestamps fail validation in these runs. + +Delayed echoes expose a separate weakness in application RTT. At 25 ms queue +delay, adding 80 ms of echo processing delay raises the slow supplier's minimum +RTT from about 50 to 130 ms. Its peak authorized credit rises from 256 to +320 KiB under all three policies. Under checked timestamps, mean configured +credit rises from about 142 to 311 KiB over the recorded control updates. +The nonce proves that the reply follows the challenge; it does not prove +prompt processing or physical propagation delay. Calibration therefore cannot +guarantee that an attacker receives no excess credit. + +All 153 runs finish without harness errors or uncredited payloads. No peer +exceeds the 2 MiB authorization cap or the 256-part supplier queue cap. The +local suite passes 91 tests, including nonce replay rejection, asymmetric-delay +offset bounds, receiver-bounded rate samples, forged offset acceptance, and +credit growth limits. Raw results, source snapshots, hashes, image IDs, and +commands remain in `2026-09-11-timing-credit-final`, +`2026-09-11-timing-credit-long-rtt`, and `2026-09-11-timing-credit-network-drop`. +`summarize_timing_credit.py` validates accounting and produces comparison tables. + +These runs use synthetic payload hashes, not Dogwood proofs or coding work. +They do not implement the specification's immutable grant protocol, preserve +failure coverage during route changes, or bound all pending assignments by the +payload credit limit. The report records pending assignment bytes separately. +The results support further testing of submission timing for upstream-limited +delivery. They do not justify treating echo calibration as timestamp +authentication or selecting this controller for production. + +## Feedback-driven standing routes + +`feedback_routes.py` changes standing mask subscriptions between concurrent +bodies. It compares static two-supplier routes, adaptation without probes, and +adaptation with probes. All policies start with the same routes and seed plan. +The receiver compares verified arrival times for the same part. It uses no +sender timestamps or knowledge of configured link rates. + +Every 250 ms, the candidate checks up to 16 recent paired observations per +receiver, mask bit, and supplier pair. Observations expire after one second. +Four observations, an 80% win fraction, and a 20% median advantage permit a +route change. The change takes effect after 20 ms and applies to future bodies. +The candidate removes the losing supplier and adds the winner if needed. +This can reduce two suppliers to one. Already released bodies retain their +assignments and consume the same shared queues during the transition. + +The probe policy adds one temporary mask subscription per receiver every eight +bodies. It prefers peers with no standing assignments, then considers other +unassigned peer/mask pairs. Probe transfers share upload, ingress, and CPU queues +with ordinary traffic. Each probe requests two or three 64 KiB parts in this +fixed-size workload, at most about 1.2% of body bytes over eight releases. +The experiment records probe authorization separately from actual transmitted +bytes. It counts control bytes but models control delivery as a fixed delay. + +The receiver observes each body after 200 ms. A missing contender supplies only +a censored comparison, bounded by the observation horizon or completion plus +cancellation delay. It does not supply a measured rate or a congestion failure. +Upstream availability and receiver CPU delay can affect the comparison. + +The capacity follow-up holds source upload at 1,250 Mbps and receiver ingress at +2,500 Mbps. It offers 819.2 Mbps of body data across 16 relays. Each run releases +320 bodies of 2 MiB with 25% parity. Three seeds per policy produce 15,360 +receiver/body targets for each row. All traffic, including repair and probes, +consumes the shared data queues. CPU costs remain the earlier model assumptions. + +| Relay upload | Policy | Complete within 400 ms | Mean run p95, completed bodies | Wire/body ratio | +| --- | --- | --- | --- | --- | +| 1,250 Mbps | Static two suppliers | 12.53% | 980.8 ms | 1.708 | +| 1,250 Mbps | Adaptive, no probes | 100% | 240.5 ms | 1.467 | +| 1,250 Mbps | Adaptive, probes | 100% | 246.4 ms | 1.465 | +| 1,750 Mbps | Static two suppliers | 100% | 192.1 ms | 2.061 | +| 1,750 Mbps | Adaptive, no probes | 100% | 112.4 ms | 1.671 | +| 1,750 Mbps | Adaptive, probes | 100% | 114.4 ms | 1.683 | + +At 1,250 Mbps, static routes also leave 620 targets unfinished after 1,200 ms. +Both adaptive policies complete all targets. At 1,750 Mbps, adaptation with +probes reduces traffic by 18.3% while preserving normal completion. The wire/body +ratio includes source and relay upload divided by released receiver/body bytes; +it does not normalize by successful delivery. Failed static deliveries therefore +make the 1,250 Mbps traffic comparison less useful than the completion result. +The probes reach peers with no standing assignments 751 times at 1,250 Mbps +and 715 times at 1,750 Mbps across the three seeds. These trials demonstrate +that inactive peers receive opportunities; they do not show a steady-load +advantage over adaptation without probes. + +The 54-run feedback matrix also tests 2,500 Mbps relay upload at full and half +body load. The upload-drop scenario reduces four relays to one eighth of their +upload rate from 2,000 to 4,000 ms, then restores service. Other scenarios use +steady or heterogeneous upload. Each combination runs three seeds. + +| Body load during upload-drop test | Policy | Complete within 400 ms | Complete within 1,200 ms | +| --- | --- | --- | --- | +| 819.2 Mbps | Static | 73.96% | 15,195 / 15,360 | +| 819.2 Mbps | Adaptive, no probes | 75.27% | 15,300 / 15,360 | +| 819.2 Mbps | Adaptive, probes | 76.64% | 15,223 / 15,360 | +| 409.6 Mbps | Static | 93.45% | 15,360 / 15,360 | +| 409.6 Mbps | Adaptive, no probes | 97.03% | 15,360 / 15,360 | +| 409.6 Mbps | Adaptive, probes | 96.93% | 15,360 / 15,360 | + +Probes improve the full-load normal-completion fraction slightly, but leave +more targets unfinished at 1,200 ms than adaptation without probes. Half-load +results show no consistent probe advantage. During the full-load drop, the +probe policy makes four supplier additions across three seeds; most route +changes still remove duplicate assignments. All policies complete every target +normally in steady and heterogeneous service. At full steady load, adaptation +with probes reduces the wire/body ratio from 2.360 to 1.790, about 24.2%. + +Together, the 72 runs support testing feedback-driven pruning further. They +demonstrate occasional probes of inactive peers, but do not establish a robust +probe policy or sustained recovery under changing capacity. The archives are +`2026-09-10-feedback-routes-final` and `2026-09-10-route-capacity` in the local +experiment tree. `summarize_routes.py` checks target counts and queue bounds and +produces the comparison tables. The local suite passes 82 tests, including +delayed route activation, unchanged assignments for existing bodies, stale +evidence rejection, idle-peer probing, and static-baseline equivalence. + +This candidate does not implement the complete specification controller. It +does not preserve single-supplier-loss coverage after pruning, fund probes from +validated completions, limit aggregate migrations per epoch, or adapt connection +credit. A serialization model supplies contention and queue limits; it does not +run TCP, Reed–Solomon, or consensus validation. FullBlock repair remains a +separate fallback after 400 ms. The experiment stops each body after 1,200 ms. +These limits prevent treating traffic savings as a production congestion-control +result. + +## Burst capacity calculation + +`capacity_budget.py` records the arithmetic behind the design's example burst +table in `results/2026-09-09-capacity-decision`. It uses 50,000 TPS, 2 KiB per +transaction, and 25% parity. It excludes the transaction-vector prefix, coding +rounding, proofs, transport, and CPU. This calculation is not an experiment. + +The exact W1 field bound permits 52,428 data parts, or 3,435,921,408 body bytes. +At the planning rate, that holds about 33.55392 seconds of transaction bytes. +A 75-second example exceeds the field bound. Sending its codeword within five +seconds requires at least 15.36 Gbps of source upload. The 1.344 Gbps average +capacity example needs at least 57.14 seconds for that transfer. Stripes cannot +remove this serialization time. The user must select the intended block +interval and propagation deadline before a burst result can meet the target. + +## Reference codec scaling + +The scaling follow-up runs the existing reference Reed–Solomon kernels on the +same **64 MiB of body data** with 25% parity. It compares 32 stripes of 2 MiB +through one codeword of 64 MiB. Each stripe runs encoding, its Merkle root, +verified on-arrival eager decoding, and re-encoding/root verification. The +benchmark compares systematic-first and parity-first arrivals with three data +seeds each: 30 successful processes. Every stripe reconstructs the original data +and matches its committed root. + +The benchmark uses one serial worker and caches the generator matrix across +equal-shape stripes. It processes one stripe at a time. These are measured +kernel durations, not a network replay. At 50,000 TPS and 2 KiB per transaction, +64 MiB arrives every **655.36 ms**. Compare each column's CPU time with that +cadence; source and receiver work belong to separate roles. + +| Stripe body | Stripes | Mean source encoding/root | Mean receiver, systematic first | Mean receiver, parity first | Maximum process RSS | +| --- | --- | --- | --- | --- | --- | +| 2 MiB | 32 | 179 ms | 231 ms | 382 ms | 21.9 MiB | +| 8 MiB | 8 | 589 ms | 640 ms | 1,191 ms | 44.1 MiB | +| 16 MiB | 4 | 1,146 ms | 1,211 ms | 2,318 ms | 80.3 MiB | +| 32 MiB | 2 | 2,268 ms | 2,304 ms | 4,558 ms | 153.2 MiB | +| 64 MiB | 1 | 4,462 ms | 4,614 ms | 9,132 ms | 301.0 MiB | + +Source means combine both arrival orders. Receiver time includes membership +proofs, elimination, and re-encoding/root verification. It excludes consensus +validation. The table excludes generator construction, which averages about +1.2 seconds for the 64 MiB codeword and less than 0.1 ms for 2 MiB stripes. +A new shape can require that construction even when prior shapes were cached. + +The 2 MiB cases fit the body cadence in this serial reference. Larger cases +exceed it under parity-first reception. This supports 2 MiB as the next stripe +size to test. It does not prove that larger stripes require these costs in an +optimized implementation. The benchmark uses the earlier experimental Merkle +tree, not W1 tags/padding or an authenticated outer stripe commitment. It also +excludes whole-body retention, concurrent workers, transport, and adversarial +work. Its RSS values do not size production assembly memory. + +`stripe_scaling.cpp`, `run_stripe_scaling.py`, and `summarize_stripe_scaling.py` +produce `results/2026-09-09-stripe-scaling`. The archive records source hashes, +compiler/host details, raw process output, and summaries. The measured work +also bounds interpretation of the earlier concurrent simulation: its fixed +8 ms reconstruction cost and 0.02 ms proof cost are assumptions, not these +measurements. The large-body profile still needs an outer commitment, bounded +pipeline and retention, and an experiment at the selected block burst. + +## W1 payload conformance + +The W1 follow-up fixes candidate payload bytes, tagged SHA-256 commitments, +balanced 16-bit selection, and the metadata signature transcript. It preserves +the whole-body codec. The profile separates seed permission from ordinary +demand and gives each class its own inheritance action. Default scope cannot +inherit. Both classes retain shared connection budgets and send-once records. + +`wire_profile.py` emits six frame fixtures and checks their round trips. Tests +cover all five message families, all selection classes, frame and field bounds, +range canonicalization, proof shape, body counts, byte credit, and malformed +input. The largest part frame is 66,102 bytes. The codec's two-data-part vector +has a fixed Merkle root in the spec. These checks do not implement the service +state machine or production allocation controls. + +`ed25519_profile.py` uses OpenSSL 3.6.3 to reproduce the published +[RFC 8032 test 2](https://www.rfc-editor.org/rfc/rfc8032.html#section-7.1) +signature. It checks canonical nonidentity prime-order points before signature +verification. Tests reject small-order points, mixed torsion, noncanonical +points/scalars, and malformed lengths. The probe signs the 171-byte W1 +transcript and rejects changes to the chain identifier, admitted block +identifier, and part root. This is not a cryptographic performance benchmark. + +The complete local Python suite passes **78 tests**. Reviewed results live in +`results/2026-09-09-wire-w1-reviewed` and +`results/2026-09-09-ed25519-w1-reviewed`, with source snapshots and hashes. +Earlier W1 directories preserve intermediate experiments. Header and coinbase +fields in these frame fixtures are structural placeholders. They are not +consensus-valid blocks or evidence of successful header/key-binding admission. +The production chain adapter, transport negotiation, and aggregate resource +bounds remain open. No result establishes sustained 50,000 TPS. + +## Concurrent bodies and key binding + +The concurrent follow-up ran **128 streams of 120 synthetic 2 MiB bodies**. +The 20.48 ms release interval supplies 819.2 Mbps of body bytes. It is a load +generator, not a selected consensus block interval. Each stream starts with +routes and seed placement learned from one separate reference body. + +`concurrent_push.py` shares each node's upload, ingress, and CPU service across +all bodies. It serves one part per body turn, then rotates peers within that +body. Each node can queue at most 256 parts for upload. The source has 1.25 Gbps +upload; relays have 2.5 Gbps upload and ingress. Encoding/root work costs an +assumed 6.38 ms per body, using the earlier local reference cost. Per-part proof +work costs 0.02 ms and reconstruction costs 8 ms. These CPU costs drive shared +queues; the simulation does not execute the codec. Metadata dissemination +remains idealized. Every body keeps the 400 ms fallback and 1,200 ms final +deadline, with at most `2k` extra requests per receiver. + +The first 80 streams compare two-supplier startup routes with retained reference +paths. They cover steady service, heterogeneous upload, a temporary relay-link +slowdown, a source slowdown, and a CPU slowdown. All steady and heterogeneous +cases complete before fallback. Four route seeds produce these steady means: + +| Relays | Routes | Mean per-run p95 completion | Network part bytes / receiver body bytes | Largest relay upload / body bytes | +| --- | --- | --- | --- | --- | +| 16 | Two suppliers | 93.0 ms | 2.37 | 3.00 | +| 16 | Retained reference paths | 74.7 ms | 1.15 | 1.89 | +| 64 | Two suppliers | 239.3 ms | 2.21 | 2.92 | +| 64 | Retained reference paths | 207.1 ms | 1.11 | 2.04 | + +The largest-relay column averages each run's maximum node upload. The network +ratio includes source and relay part upload, including the 384-byte proof/frame +allowance. It excludes transport and control overhead. Some receivers cancel +parts after obtaining enough distinct indices, so delivered traffic can fall +below a complete codeword per receiver. The source still seeds at most one +codeword per body. These short traces do not establish long-run queue stability +or end-to-end 50,000 TPS. + +Capacity planning must account for the largest relay, not only the network +average. A relay serving three body copies at this workload needs about +2.46 Gbps before additional overhead or utilization headroom. The retained-path +candidate lowers that cost in the steady cases, but does not protect a deadline +when service changes. + +The other 48 streams isolate upload changes. Four relays halve their upload or +reduce it eightfold from 1,000 to 2,000 ms; ingress remains 2.5 Gbps. A third +policy restores a receiver's startup suppliers for future bodies after a miss, +paying 20 ms control delay. It does not restore routes solely because enough +parts are waiting on local CPU work. It does not prune again during the run. + +| Relays | Upload change | Two suppliers: normal completions | Retained paths | Retained paths, then restore | +| --- | --- | --- | --- | --- | +| 16 | Half | 100% | 100% | 100% | +| 16 | One eighth | 78.0% | 68.6% | 71.5% | +| 64 | Half | 90.2% | 94.9% | 94.8% | +| 64 | One eighth | 59.1% | 52.5% | 51.5% | + +These fractions include every receiver/body observation. Restoration does not +consistently improve normal completion and sometimes adds traffic to a busy +network. For the 64-relay eightfold slowdown, mean direct fallback traffic is +225.7 MiB with two suppliers and 448.9 MiB with retained paths. Eventual +completion is 95.9% and 95.5%, respectively. Fallback neither hides the missed +normal deadline nor guarantees recovery within its caps. Do not select this +restoration rule as a complete congestion controller. + +The source and CPU slowdowns also cause misses. Some disturbances reduce a +node below the workload's required service rate. A routing policy cannot repair +that capacity deficit. The model enforces upload queue and repair caps, but +ingress and CPU queues are service-delay models without transport backpressure +or production memory admission. Retained paths remain a candidate under fixed +mapping and seed placement, not a deployed stripe profile. + +### Coinbase key commitment probe + +`coinbase_binding.py` constructs a canonical transparent-only V5 coinbase and +checks a proposer key in one zero-value output. Its input-script mutation keeps +the txid unchanged; its output-key mutation changes the txid and fails the old +Merkle proof. This matches the split between transaction effects and authorizing +data in [ZIP 244](https://zips.z.cash/zip-0244). + +The candidate script is `OP_RETURN`, a direct 40-byte push, `DOGWOOD`, byte +`01`, and a 32-byte key. The proof must establish coinbase position zero against +the admitted header. A txid proof cannot authenticate a key carried only in +the V5 input script. The probe rejects duplicate matching outputs, nonzero +commitment value, wrong position, stale roots, truncation, and noncanonical +counts. Its example coinbase is 119 bytes. It does not check proof of work, +metadata signatures, rewards, shielded coinbases, or post-Tachyon transaction +formats. The chain adapter remains an implementation gate. + +The current local suite passes **60 tests**. Results and source snapshots are +in `2026-09-09-concurrent-push`, `2026-09-09-concurrent-upload-change`, and +`2026-09-09-coinbase-binding` under the local experiment results directory. + +## Connected-network and transport follow-up + +The normal throughput target assumes that honest relays remain connected after +removing the proposer. `FullBlock`-triggered requests for missing parts are +bounded fallback. These experiments report completion before fallback separately +from eventual completion. The planning workload is **50,000 TPS at 2 KiB per +transaction after Tachyon**: 819.2 Mbps of body bytes, or 1.024 Gbps with 25% +parity before proof and transport overhead. + +### Connected push and route pruning + +`push_overlay.py` ran 656 single-block cases on rings with additional local +edges. It used 16/64 relays, degree 4/8, one/four source neighbors, and one/two +suppliers per mask bit. Every relay graph remains connected without the source +and one failed relay. The 2 MiB body has 32 data parts and eight parity parts. +The source seeds at most one codeword. The model compares spread seeds with +seeding a decodable subset to one neighbor first. + +Source upload is 1 Gbps. Relay upload and ingress are 1.6/2 Gbps, either equal +or divided by 1/2/4/8 across peers. The model serializes upload, ingress, and +verification queues. Proof verification costs 0.02 ms per part; reconstruction +costs 8 ms. These CPU values are assumptions. Metadata is preinstalled, parts +pay 5 ms propagation, and control messages pay 20 ms. Fallback starts at +400 ms, requests at most `2k` extra copies per receiver, and ends at 1,200 ms. +Only completion advertisements authorize pull attempts. Failed peers remain +silent; a separate case sends false completion advertisements. + +The following healthy, equal-rate cases use degree eight and four source +neighbors. Values average eight distinct route seeds. The 16-relay cases also +appear in the failure sweep; those repeated configurations add no independent +evidence. + +| Relays | Suppliers per bit | Seed policy | Receivers complete before fallback | Last completion, mean | +| --- | --- | --- | --- | --- | +| 16 | 1 | Spread | 0/16 | 542.6 ms, with fallback | +| 16 | 1 | Decodable first | 1/16 | 516.9 ms, with fallback | +| 16 | 2 | Spread | 16/16 | 65.5 ms | +| 64 | 1 | Spread | 0/64 | 889.4 ms, with fallback | +| 64 | 2 | Spread | 64/64 | 167.2 ms | + +Two suppliers establish useful startup delivery in these cases, but consume +bandwidth. Total source plus relay part upload divided by receiver count and +body size is 2.42 with 16 relays and 2.30 with 64 relays. These network averages +include the 384-byte per-part proof/framing allowance. They exclude transport +overhead and do not bound an individual relay's upload. A 5% allowance cannot +cover this duplicate traffic. + +`prune_routes.py` tests 72 sequences of 24 blocks. Local majority wins and a +one-supplier-loss coverage check do not preserve global delivery when several +nodes prune routes. `seed_offer_routes.py` adds 192 sequences. Its source sends +each seed only to an eligible neighbor with outgoing demand for that mask bit. +Every static two-supplier configuration then completes normally in the tested +six route seeds. Coverage-preserving pruning still causes fallback in three +of eight configuration groups. Physical connectivity, local coverage, and +past wins therefore do not establish a safe pruning rule. + +`witness_pruning.py` tests a separate stripe candidate. Each node retains every +incoming supplier that delivered a distinct part before its reconstruction of +one shared reference stripe. Subsequent equal-shape stripes retain the mask +mapping and source seed recipients. This preserves the reference's causal +delivery paths under honest peers, unchanged availability, adequate credit, +retention, and fair service. The argument establishes eventual delivery under +those assumptions; it does not establish a deadline or failure tolerance. + +All 48 paired configurations complete before fallback with and without this +pruning. Retained routes reduce mean relay upload by 45.8–52.5% across groups. +The 24 sequential stripes repeat fixed service and routes; they are not 24 +independent observations or a concurrent stream. This candidate requires a new +striped codec profile. The present whole-body codeword cannot apply its result +directly. Changing the mapping, seed placement, codeword shape, or availability +invalidates the reference argument. The earlier witness directory varied the +mapping and does not support that argument. + +These simulations omit transport backpressure and real coding work. Direct +pull responses have separate byte counters; later forwarding of repaired parts +still counts as relay forwarding. Normal success always requires completion +before the fallback timer. The models do not implement every grant and +controller rule. + +### Real TCP feedback + +`run_tcp_feedback.py` ran 45 isolated TCP experiments: five scenarios, three +allocation policies, and three repetitions. Four suppliers use application +pacing at 80/40/20/10 Mbps. They share a 100 Mbps loopback qdisc with 5 ms delay. +Each run releases 120 bodies at 25 ms intervals, with four required 64 KiB parts +and five available parts. The capacity-drop case reduces the fastest supplier +to 5 Mbps after 1.5 seconds. The application-limited case inserts 40 ms stalls. +The loss and ECN cases configure 0.2% netem loss; ECN marks eligible packets. + +The allocator compares equal assignment, receiver arrival-rate feedback, and +feedback using the larger of sender and receiver spans. It assigns exact parts +after release. This is a transport experiment, not Dogwood standing push or a +complete controller. It hashes synthetic payloads and does not run Reed–Solomon. + +| Scenario | Equal: complete within 800 ms | Receiver-rate feedback | Sender-span feedback | +| --- | --- | --- | --- | +| Baseline | 198/360 | 360/360 | 358/360 | +| Capacity drop | 148/360 | 272/360 | 270/360 | +| Application limited | 137/360 | 319/360 | 244/360 | +| Loss | 199/360 | 358/360 | 358/360 | +| ECN | 198/360 | 358/360 | 358/360 | + +Baseline mean per-run p95 completion falls from 1,011.8 ms with equal assignment +to 82.3 ms with receiver-rate feedback. That p95 includes completed bodies only; +the table includes every released body. Sender spans provide no consistent +advantage and perform worse during application stalls. Keep sender timestamps +out of the baseline wire profile. Continue testing receiver-local feedback. +TCP counters confirm ECN marks, but retransmission counts are small. These +short loopback runs do not establish WAN loss behavior, full controller +stability, or throughput at the post-Tachyon target. + +### Grants and remaining decisions + +`grant_model.py` exhausts 380 states and 1,270 transitions for a finite +two-index grant model. It checks queueing, cancellation, crossed `FullBlock`, +retirement, and send-once accounting. Separate tests cover exploration credit, +loaded cohorts, failure precedence, migration overlap, and final coverage. +That checkpoint passed 45 tests. This is bounded state exploration, not a proof +of the full protocol or multi-connection controller. + +Retain two-supplier startup coverage where budgets allow it. Do not select +majority-based pruning as a demonstrated path to one-copy throughput. Keep +`FullBlock` pull strictly as fallback. Keep 25% parity and one-part scheduling +portions in the current draft; the earlier small-block results remain candidate +profile evidence. A complete implementation still needs a joint controller, +codec, and transport test with concurrent bodies and changing routes. + +The remaining profile choices depend on the block interval, propagation +deadline, supported peak body size, and post-Tachyon chain binding. At this +workload, the current single-codeword limit is reached after about 33.55 seconds +of transactions. A stripe profile needs authenticated stripe identifiers, +commitments, completion semantics, resource limits, and failure tests before +adoption. These experiments do not complete that profile. + +The local result directories are `2026-09-09-connected-push`, +`2026-09-09-route-pruning-final`, `2026-09-09-seed-offer-routes`, +`2026-09-09-witness-pruning-final`, `2026-09-09-tcp-feedback-run`, and +`2026-09-09-grants` under `docs/experiments/dogwood/results`. +They retain source snapshots and provenance. The local README records commands. + +## Bounded-recovery follow-up + +The follow-up ran **2,448 deterministic single-block simulations** and eight +reference codec configurations. The simulations replace the earlier timeless, +all-index relay closure with sparse per-index subscriptions and serialized +upload. They advance the bootstrap and small-block TODOs; transport feedback +and controller convergence remain untested here. + +### Method + +The recovery sweep covers one receiver and three eight-receiver topologies: +a star, two branches with bridge peers, and a mesh with alternate paths. +The proposer seeds each encoded index once, spread evenly across its neighbors. +Each relay index selects one random non-source neighbor. Seeds 0–11 select +those subscriptions. The proposer has 1 Gbps upload; relays have either +200 Mbps each or repeated 800/400/200/100 Mbps upload rates. +These rates constrain aggregate node upload, not independently measured links. +The one-copy comparison does not implement the draft's startup coverage policy. + +We compare no repair, header-parent repair, and repair that tries alternatives +after the first attempt. A failed peer remains silent from the start; metadata +and the parent tree are preinstalled. Repairs start at 100/300/600 ms and pay +20 ms control delay. Each receiver can add at most `2k` requests. Source credit +allows either `n` parts or `n + nodes*k` parts, including initial seeds. +The experiment ends at 1,200 ms and counts unfinished receivers as failures. + +The model forwards a complete part after upload plus 5 ms propagation. +It assumes unlimited ingress, instant verification and regeneration, and one +valid block. It charges 384 bytes per part for proof/framing, includes sends +to failed peers, and allows a 20 ms cancellation tail after reconstruction. +It does not simulate full-block fallback, transport loss, competing blocks, +negotiated grants, or adaptive routes. Completion establishes reconstruction +in this model, not verified end-to-end production delivery. + +### Recovery result + +These rows use 2 MiB bodies, 25% parity, and equal relay rates. Completion time +is the mean time when the last healthy receiver finishes, over successful runs. +Source MiB includes seeds, repair, and cancellation tails. + +| Topology and failure | Source budget / repair | All healthy receivers finish | Completion ms | Source MiB | +| --- | --- | --- | --- | --- | +| Single receiver | One codeword / none | 12/12 | 21.9 | 2.515 | +| Eight-leaf star | One codeword / alternatives | 0/12 | — | 2.515 | +| Eight-leaf star | Reserve / alternatives | 12/12 | 238.9 | 16.094 | +| Mesh, healthy | One codeword / alternatives | 12/12 | 385.8 | 2.515 | +| Mesh, healthy | Reserve / parent | 12/12 | 205.6 | 5.270 | +| Mesh, failed parent | Reserve / parent | 0/12 | — | 5.501 | +| Mesh, failed parent | Reserve / alternatives | 12/12 | 355.8 | 5.501 | +| Bridge cut, failed bridge | Reserve / alternatives | 0/12 | — | 3.269 | + +The star requires eight body copies across eight separate source cuts. +Its 16.094 MiB result matches that payload lower bound plus framing. +The healthy mesh can trade repair delay for lower source upload. +Alternatives recover the connected mesh after parent failure. They cannot +recover the three honest receivers disconnected by the failed bridge. +These cases do not support an unconditional one-codeword bootstrap guarantee. + +### Small blocks, parity, and portions + +The mesh sweep varies `k=1/2/4/8/32`, 16/64 KiB parts, 25%/100% parity, +one/two/four-part service portions, and systematic-first/parity-first seeding. +All 1,440 runs finish with the repair reserve. At `k=1`, parity rounding makes +both ratios identical. The table uses 64 KiB parts, one-part portions, and +systematic-first seeding. +Times include repair; byte totals cover the whole network's source or relays. + +| Body | Parity | Completion ms | Source MiB | Relay MiB | +| --- | --- | --- | --- | --- | +| 256 KiB | 25% | 125.7 | 0.545 | 2.635 | +| 256 KiB | 100% | 66.6 | 0.545 | 3.641 | +| 512 KiB | 25% | 154.5 | 1.425 | 5.564 | +| 512 KiB | 100% | 71.3 | 1.058 | 6.470 | + +More parity can reduce total source bytes by avoiding repair copies, while +increasing relay bytes. Smaller parts also change that tradeoff: at 512 KiB +and 100% parity, 16 KiB parts finish in 56.9 ms with 7.024 MiB relay upload. +A threshold stated only as `k<=8` changes its body-size meaning when `S` changes. + +Across the equally weighted sweep, mean completion is 93.3/93.7/94.1 ms for +one/two/four-part portions with systematic-first seeding. Parity-first gives +92.3/92.5/93.0 ms. These small differences do not select a larger portion or +an ordering rule. Portions change local service fairness here; they add no +wire aggregation or measured CPU savings. + +The codec run uses the existing 64 KiB reference kernel, one warm-up, and three +retained repetitions per configuration on an unreserved host. At 512 KiB, +the sum of median encoding and root times rises from 0.71 ms at 25% parity to +1.77 ms at 100%. At 2 MiB it rises from 6.38 to 21.70 ms. These CPU measurements +are separate from the network simulation; we have not tested their queueing +interaction. They argue against extrapolating the small-block result to all +block sizes. + +### Decisions and next gates + +- **Source budget:** retain an initial seed budget plus a bounded repair reserve. + Decide which source-cut fanout and repair latency the supported topology must + accommodate. A global one-codeword cap cannot support the star case. +- **Recovery:** retain alternative suppliers after a parent stalls. The spec now + makes the shared deadline and non-resetting credit rules explicit. +- **Small-block profile:** keep 100% parity as a candidate, with a threshold in + body bytes and an explicit part size. Keep the draft's 25% rule until a joint + CPU/network run includes correlated failures and competing blocks. +- **Portions:** retain one part as the reference service quantum. No wire portion + message follows from this sweep. +- **Next implementation work:** build the complete grant/controller state model, + then test real transport feedback. Wire negotiation, PoW key binding, resource + caps, and the target block interval still require profile decisions. + +Run `python3 bounded_overlay.py results/my-bounded-overlay` and +`python3 summarize_bounded.py results/my-bounded-overlay` in the local experiment +directory. Final raw runs, summaries, source snapshots, and environment records +are in `results/2026-09-08-bounded-overlay-final`; codec CSVs and exact commands +are in `results/2026-09-08-small-codec`. All 25 Python tests pass, including +analytic serialization and source-cut checks, replay, credit, and deadline tests. + +## Congestion baseline + +The model releases 400 synthetic 2 MiB bodies at 20.48 ms intervals, equivalent +to 50,000 transactions/s at 2 KiB each. Each body has 32 data parts and eight +parity parts. Four suppliers offer 800/400/200/100 Mbps of usable upload; the +receiver has 1,600 Mbps ingress. These are scenario inputs, not peer measurements. +Balanced suppliers each offer 375 Mbps. The 1 Gbps case changes only ingress. +The capacity-drop case reduces the 800 Mbps supplier to 50 Mbps halfway through +the run, leaving only 750 Mbps aggregate upload. The source-delay case adds +150 ms to upstream part availability without changing link capacity. + +The half-load case uses 40.96 ms releases. The larger-body case uses 100 bodies +of 8 MiB at 81.92 ms intervals to preserve the offered byte rate. Every case +runs seeds 0–5 with all five policies. The model varies supplier availability +and part mapping with the seed. These finite traces are not a distribution of +real peer bandwidth or a reliable estimate of rare network tails. + +The policies are static equal allocation, paired races, the existing budgeted +race controller, a candidate delivery-rate allocator, and an informed reference. +The candidate starts each connection at a 100 Mbps estimate. Every 100 ms it +uses at least four delivered parts to estimate bytes divided by the larger of +sender and receiver time spans. It excludes the first part's bytes and weights +the new sample by 0.5. Different sender clocks have constant offsets, which +cancel in span differences. The experiment assumes honest timestamps at the +start of idealized link service. Application transport-submission timestamps +may provide a weaker signal. + +The candidate assigns each new distinct part to the smallest estimated +completion time: outstanding assigned bytes plus the new part, divided by the +estimated delivery rate. All blocks share that outstanding-byte count. Its +exact block assignments incur the modeled control delay. The informed policy +uses the same allocator with current supplier capacities, including the drop. +It knows information unavailable to the receiver; it is a comparison, not a +proven optimum. Neither allocator implements the complete spec controller. + +Each link serializes parts and serves block queues in round-robin order. +The model bounds queues at 256 parts and models receiver ingress separately. +It charges a 384-byte proof/framing allowance per part, including the candidate's +eight-byte timestamp. It models 20 ms control delay and cancellation tails. +At 400 ms, repair requests can add at most `2k` copies. At 1,200 ms, an unfinished +block counts as fallback, not reconstruction. Queue drops discard modeled work; +the model does not simulate reliable-transport retransmissions. + +The first comparison requests one copy of each encoded index outside challenges +and repair. It disables extra failure-coverage routes for every policy. Four +suppliers cannot satisfy the single-supplier-loss target with 25% parity and +no duplicates. This comparison isolates allocation and is not a conformant +coverage configuration. The parity comparison below restores that requirement. + +### Results + +The table reports means across six seeds. Body Mbps counts only reconstructed +bodies and divides by elapsed time through the last terminal block outcome. +Startup, drain time, and failed blocks therefore affect this metric. p95 is the +mean of each run's completed-block p95; misses and fallback include all blocks. +Wire/body includes delivered parity, duplicates, and late parts through drain, +relative to all offered bodies. It excludes separately recorded control bytes. + +| Scenario | Policy | Body Mbps | p95 ms | Miss % | Fallback % | Wire/body | +| --- | --- | ---: | ---: | ---: | ---: | ---: | +| heterogeneous | equal | 792.0 | 422.0 | 53.2 | 0.0 | 1.117 | +| heterogeneous | races | 793.4 | 396.2 | 41.9 | 0.0 | 1.098 | +| heterogeneous | budgeted | 791.9 | 422.0 | 49.9 | 0.0 | 1.113 | +| heterogeneous | delivery_rate | 816.2 | 52.9 | 0.0 | 0.0 | 1.255 | +| heterogeneous | informed | 816.2 | 51.7 | 0.0 | 0.0 | 1.257 | +| balanced | equal | 816.2 | 51.6 | 0.0 | 0.0 | 1.257 | +| balanced | races | 816.2 | 51.6 | 0.0 | 0.0 | 1.259 | +| balanced | budgeted | 816.2 | 51.6 | 0.0 | 0.0 | 1.258 | +| balanced | delivery_rate | 816.2 | 52.0 | 0.0 | 0.0 | 1.257 | +| balanced | informed | 816.2 | 52.0 | 0.0 | 0.0 | 1.257 | +| ingress_1gbps | equal | 791.9 | 424.1 | 53.8 | 0.0 | 1.119 | +| ingress_1gbps | races | 792.0 | 424.0 | 51.8 | 0.0 | 1.117 | +| ingress_1gbps | budgeted | 792.0 | 424.0 | 51.8 | 0.0 | 1.117 | +| ingress_1gbps | delivery_rate | 799.5 | 273.4 | 0.3 | 0.0 | 1.238 | +| ingress_1gbps | informed | 803.4 | 265.2 | 0.0 | 0.0 | 1.240 | +| capacity_drop | equal | 505.4 | 1057.9 | 55.2 | 31.9 | 1.027 | +| capacity_drop | races | 508.6 | 1058.0 | 52.4 | 31.4 | 1.028 | +| capacity_drop | budgeted | 506.9 | 1057.9 | 52.5 | 31.6 | 1.027 | +| capacity_drop | delivery_rate | 575.1 | 1046.0 | 42.2 | 21.1 | 1.150 | +| capacity_drop | informed | 570.0 | 1034.1 | 40.5 | 21.7 | 1.155 | +| upstream_stall | equal | 786.6 | 423.7 | 88.0 | 0.0 | 1.202 | +| upstream_stall | races | 786.6 | 423.7 | 88.1 | 0.0 | 1.202 | +| upstream_stall | budgeted | 786.6 | 423.7 | 88.1 | 0.0 | 1.202 | +| upstream_stall | delivery_rate | 801.3 | 239.0 | 0.0 | 0.0 | 1.237 | +| upstream_stall | informed | 801.6 | 201.5 | 0.0 | 0.0 | 1.257 | +| half_load | equal | 409.1 | 63.6 | 0.0 | 0.0 | 1.187 | +| half_load | races | 409.3 | 62.1 | 0.0 | 0.0 | 1.262 | +| half_load | budgeted | 409.3 | 62.1 | 0.0 | 0.0 | 1.262 | +| half_load | delivery_rate | 409.3 | 52.9 | 0.0 | 0.0 | 1.257 | +| half_load | informed | 409.4 | 51.8 | 0.0 | 0.0 | 1.257 | +| larger_blocks | equal | 796.6 | 423.3 | 81.8 | 0.0 | 1.165 | +| larger_blocks | races | 796.6 | 423.3 | 75.3 | 0.0 | 1.162 | +| larger_blocks | budgeted | 797.2 | 423.2 | 69.8 | 0.0 | 1.161 | +| larger_blocks | delivery_rate | 819.0 | 85.4 | 0.0 | 0.0 | 1.251 | +| larger_blocks | informed | 819.0 | 84.6 | 0.0 | 0.0 | 1.257 | + +The candidate improves allocation on unequal links in these traces. Equal +allocation already performs well on balanced links. The candidate also spends +more bytes by delivering more parity before cancellation. The 1 Gbps receiver +case accumulates queues and drops work even when reconstruction succeeds. +The capacity-drop case exceeds available service and produces substantial +fallback under every policy. A low completed-only latency cannot hide those +failures. These results do not select production parameters. + +### Do sender timestamps explain the improvement? + +A paired follow-up keeps the candidate allocator and all 42 case/seed inputs +fixed, but estimates rate from receiver arrival spans alone. It retains the +same per-part framing allowance to isolate the measurement change. + +| Scenario | Sender-span candidate p95 ms | Receiver-only p95 ms | +| --- | ---: | ---: | +| Balanced | 52.0 | 52.0 | +| Heterogeneous | 52.9 | 53.1 | +| 1 Gbps ingress | 273.4 | 271.6 | +| Capacity drop | 1,046.0 | 1,024.1 | +| Upstream delay | 239.0 | 239.9 | +| Half load | 52.9 | 53.1 | +| Larger bodies | 85.4 | 85.2 | + +Receiver-only fallback under the capacity drop is 21.4%, compared with 21.1% +for the sender-span candidate. Every other receiver-only case has zero fallback. +This test does not establish a timestamp benefit. The allocation rule explains +most of the observed improvement over equal assignment in this model. The +separate receive-compression test confirms that a sender span can guard against +an inflated sample, but the network traces do not establish its deployment value. + +## Parity versus duplicate subscriptions + +This comparison uses the same heterogeneous supplier rates and body workload. +It runs six seeds for each parity ratio and allocator. Every allocation must +retain at least `k` distinct parts after removing any one supplier's assignments. +The allocator adds duplicate routes where parity and assignment diversity do +not satisfy that test. Both policies use the same failure model and repair +deadline. The test does not model an actual supplier failure or independent +physical paths; it checks assignment coverage before measuring normal delivery. + +Proposer seed time below is an analytic lower bound for uploading the entire +encoded codeword once over a 1 Gbps link. It excludes encoding, proofs, framing, +and direct duplicate requests. The simulation starts with exogenous availability +at suppliers and does not model a proposer. These are separate measurements; +their times must not be added as if this experiment measured a complete path. + +| Parity/data | Policy | Proposer seed ms ≥ | p95 ms | Miss % | Wire/body | Duplicate/body | +| --- | --- | ---: | ---: | ---: | ---: | ---: | +| 12.5% | equal | 18.9 | 445.9 | 90.8 | 1.413 | 0.351 | +| 12.5% | delivery_rate | 18.9 | 137.5 | 0.0 | 1.834 | 0.730 | +| 25% | equal | 21.0 | 421.8 | 44.3 | 1.248 | 0.164 | +| 25% | delivery_rate | 21.0 | 105.2 | 0.0 | 1.825 | 0.622 | +| 50% | equal | 25.2 | 97.1 | 0.0 | 1.123 | 0.000 | +| 50% | delivery_rate | 25.2 | 84.8 | 0.0 | 1.821 | 0.428 | +| 100% | equal | 33.6 | 75.9 | 0.0 | 1.360 | 0.000 | +| 100% | delivery_rate | 33.6 | 71.5 | 0.0 | 1.828 | 0.103 | + +Duplicate/body counts delivered duplicate payload relative to offered body +bytes. More parity raises the proposer seeding lower bound. Fewer duplicates +can reduce receiver traffic, but concentration on fast suppliers can require +duplicates even with substantial parity. Cancellation also means delivered +wire/body can be below the full codeword ratio. No row establishes a network-wide +optimum. The next comparison must include a proposer with limited upload, +relay forwarding during seeding, direct proposer subscriptions, and concurrent +receivers. Relays can regenerate parity after reconstruction, but relying on +that path changes when parity becomes available. + +## Proposer seeding checks + +`seeding.py` separates first-hop scheduling from downstream reachability. +It uses a 2 MiB body, 40 encoded parts, the same 384-byte framing allowance, +and a 1 Gbps proposer upload limit. All parts are ready at time zero. Peer +rates are fixed independent capacities and every peer grants any-index credit +for up to 40 parts. No ordinary demand, competing blocks, propagation delay, +CPU work, or transport startup consumes the modeled capacity. + +| Peers' usable Mbps | Optimal part counts | Optimal seed ms | Equal-assignment seed ms | +| --- | --- | ---: | ---: | +| 1,000 | 40 | 21.1 | 21.1 | +| 250/250/250/250 | 10/10/10/10 | 21.1 | 21.1 | +| 800/400/200/100/20/5 | 22/11/5/2/0/0 | 21.1 | 632.8 | + +For `N` parts of `w` bytes, proposer byte rate `U`, peer byte rates `c[p]`, +and credits `g[p]`, choose the `N` earliest slots `j*w/c[p]` with +`1<=j<=g[p]`. The resulting counts minimize `max_p(a[p]*w/c[p])`: any earlier +completion threshold contains fewer than `N` eligible service slots. The +shared-upload lower bound is `N*w/U`. Pacing each connection at +`a[p]*w/max(N*w/U, max_p(a[p]*w/c[p]))` meets both rate constraints, so the +larger bound is attainable in this model. The checker verifies that construction +and compares the result with exhaustive count allocations in 240 small cases, +including credit caps. It also rejects insufficient total credit. + +This objective seeds every chosen part once; it is not the earliest time a +receiver can reconstruct from `k` parts. It does not justify trusting advertised +capacity or omitting the only path to a downstream group. + +For reachability, the checker enumerates all connected labeled undirected +graphs with two through five nodes and uses node zero as proposer. The proposer +seeds five distinct parts disjointly among its direct peers for `k=4`. +Every non-proposer edge subscribes to every index. A receiver forwards verified +parts and regenerates the codeword after collecting four distinct indices. + +| Nodes | Connected graphs checked | Incomplete without repair | Complete with header-tree repair | +| --- | ---: | ---: | ---: | +| 2 | 1 | 0 | 1 | +| 3 | 4 | 1 | 4 | +| 4 | 38 | 10 | 38 | +| 5 | 728 | 158 | 728 | + +For all 771 graphs, the result matches the component condition: remove the +proposer, then each remaining component reconstructs exactly when it receives +at least `k` distinct seeds. With fewer than `k`, forwarding cannot create +enough independent information for an arbitrary body. With at least `k`, +all-index forwarding eventually brings those seeds to every node in the +component. This argument assumes honest forwarding, adequate credit, retained +data, no prior body information, and fair service. + +A separate counterexample uses a star with four leaves, `k=4`, and 100% parity. +Each leaf gets one distinct data part and one distinct parity part. None can +decode. Repair supplies two more parts per leaf, raising proposer upload from +eight parts to sixteen. That equals the cut lower bound of four bodies' worth +of independent information. Some parity at every peer is therefore insufficient. + +The header-tree repair closure succeeds in all enumerated graphs. Its parent +relation reaches the source, so a parent can eventually serve a child's deficit +after reconstructing. The checker has no clocks, failed parents, credit +exhaustion, retention expiry, or repair-byte cap. This is a conditional liveness +check, not a bounded-latency guarantee or a proof for sparse part subscriptions. +The [design TODOs](dogwood.md#open-problems-and-todos) track those missing cases +and the proposed seeding-grant semantics. + +## On-arrival Reed–Solomon + +The runnable `online_rs.cpp` example uses the benchmark's eager GF(2^16) +Reed–Solomon decoder. It receives parity part 4, data part 1, duplicate part 4, +data part 3, and data part 0 for a four-data/one-parity codeword. The ranks are +1, 2, 2, 3, and 4. It recovers missing data part 2 and re-encodes the matching +root. Its data includes nonzero high bytes. The decoder verifies membership +before each insertion and ignores duplicate indices. + +A fresh 2 MiB, 25%-parity, parity-first codec run used one warm-up and three +retained repetitions. At a 1 ms synthetic arrival gap, median remaining work +was 24.8 ms for batch decoding, 19.3 ms for forward-only incremental decoding, +and 8.0 ms for eager incremental decoding. The eager run moved about 5.9 ms of +elimination before the last required arrival. Re-encoding and root checking +still cost about 7.8 ms in the eager run. + +These are measured CPU-task durations replayed on one serial worker, not a +wall-clock network run or a production codec benchmark. The host had no CPU +reservation and other experiments ran concurrently. Three repetitions do not +establish stable performance. The result verifies the intended schedule and +keeps the final root check visible; it does not demonstrate post-Tachyon CPU +capacity or feasible decoding for multi-gigabyte bodies. + +## Reproduction and remaining work + +Run these commands in the preserved worktree's `docs/experiments/dogwood` +directory. Choose new output directories; the scripts preserve prior runs. + +```sh +python3 congestion.py results/my-congestion +python3 parity_frontier.py results/my-parity +python3 timestamp_ablation.py results/my-timestamps +python3 seeding.py results/my-seeding +python3 -m unittest -v test_sim.py test_congestion.py +g++ -std=c++20 -O3 -Wall -Wextra -Werror online_rs.cpp -lcrypto -o build/online_rs +build/online_rs +g++ -std=c++20 -O3 -Wall -Wextra -Werror codec.cpp -lcrypto -o build/codec +build/codec --test +build/codec 32 0.25 rs parity_first 1 200 4 +``` + +The final result directories are `results/2026-09-08-congestion-final`, +`results/2026-09-08-parity-final`, `results/2026-09-08-timestamps`, and +`results/2026-09-08-seeding-final`. The timestamp comparison reads the saved +final congestion cases as its paired reference. The codec CSV and example output are in +`results/2026-09-08-congestion`. Result directories include source snapshots +or hashes. The tests passed 19 Python cases and 121 small Reed–Solomon subsets +under both incremental schedules. Tests check constant clock offsets, receive +compression, idle samples, exact-assignment control delay, and failure coverage. + +The earlier September 5 experiments tested arrival ambiguity, coding schedules, +and sparse subscription reachability. They found closed subscription cycles +without a source and controllers that did not consistently beat equal shares. +The new single-receiver results do not resolve those overlay limitations. + +Before selecting a controller, test real transport pacing, RTT/loss/ECN signals, +application-limited samples, misleading timestamps, shared physical bottlenecks, +sender queue residence, bounded rate increases, and multiple adapting receivers. +Before selecting parity, measure proposer encoding and upload together with +relay duplication, receiver bytes, correlated failures, and repair latency. diff --git a/docs/design/dogwood.md b/docs/design/dogwood.md new file mode 100644 index 0000000000..fb826a4cb6 --- /dev/null +++ b/docs/design/dogwood.md @@ -0,0 +1,747 @@ +# Dogwood: block propagation for Zcash + +Proof of work makes the next block's entry point unpredictable. Propagation +delay increases the orphan rate. We need low latency from any proposer across +peers with unequal bandwidth. + +Dogwood pushes block parts along subscriptions established before the block +exists. Each node requests different parts from different peers and forwards +verified parts to its subscribers. It shifts subscriptions toward faster peers. +Parity lets it reconstruct the block without waiting for every part. + +The tradeoff is bandwidth: standing routes avoid request latency, but stale +routes need redundancy and recovery. The [protocol specification](../specs/dogwood.md) +defines the rules. This document explains the design. +The [experiment report](dogwood-experiments.md) records the local codec and +congestion-control estimates and connected-relay tests. They do not establish +overlay convergence or sustained throughput at the planning target. + +## Throughput target + +Assume an initial post-Tachyon workload of 50,000 TPS with every transaction +aggregated to 2 KiB. We need 102.4 MB/s (819.2 Mbps) of block-body throughput, +or roughly 1 Gbps after 25% parity: 1.024 Gbps before proofs, transport, +challenges, and recovery. This is a planning assumption, not a consensus limit. + +For body rate `R`, requested parity/data ratio `r`, additional traffic fraction +`h` relative to body plus parity, and usable-link utilization target `u`: + +```text +required usable bandwidth >= R * (1 + r) * (1 + h) / u +``` + +For example, 25% parity, a provisional 5% traffic allowance, and 80% utilization +require about 1.344 Gbps of usable ingress. The allowance and utilization are +experiment inputs, not measured production values. Canceling unneeded parity +can reduce delivered bytes, but capacity planning should reserve the requested +load. Every forwarding copy also consumes upload capacity. +Duplicate-heavy policies need a larger allowance; use their measured total +wire/body ratio in the capacity budget. +The concurrent experiment's busiest relay averaged about three body copies +per body with two-supplier startup routes: roughly 2.46 Gbps before additional +overhead and headroom. A network-wide average does not size that relay. + +Average throughput does not set block latency. At a block interval of `T` +seconds, this workload produces about `102.4 * T` MB per body. Delivering that +body within `D` seconds needs at least `819.2 * T / D` Mbps before redundancy. +The present GF(2^16) profile also limits one codeword to 65,535 parts. +At 64 KiB and 25% parity, at most 52,428 data parts hold about 3.2 GiB. +This workload reaches that bound in about 33.55 seconds. A larger body would +require a revised profile, such as multiple committed stripes, and new tests. + +These example burst budgets separate the missing sizing decisions. They assume +that body propagation starts after block release and that the proposer seeds +one codeword. They exclude framing, integer padding, CPU, and relay delay. +The intervals and deadlines are examples, not proposed consensus parameters. + +| Block interval | Body bytes | Propagation deadline | Minimum receiver body ingress | Proposer upload for 25% parity | Fits W1 field bound | +| --- | --- | --- | --- | --- | --- | +| 1 s | 102.4 MB | 1 s | 0.8192 Gbps | 1.024 Gbps | Yes | +| 10 s | 1.024 GB | 1 s | 8.192 Gbps | 10.24 Gbps | Yes | +| 30 s | 3.072 GB | 1 s | 24.576 Gbps | 30.72 Gbps | Yes | +| 75 s | 7.68 GB | 5 s | 12.288 Gbps | 15.36 Gbps | No | +| 75 s | 7.68 GB | 1 s | 61.44 Gbps | 76.8 Gbps | No | + +A 1.344 Gbps proposer needs at least 57.14 seconds to send the 75-second +example's codeword. That link can satisfy the average source budget but cannot +satisfy a five-second propagation deadline. Stripes remove the single-codeword +field limit; they do not reduce the source's byte count. Fitting the field +limit also does not establish an affordable decoder. + +The design needs a selected block interval and propagation deadline before it +can select the large-body profile and a meaningful burst experiment. The +50,000 TPS assumption fixes neither value. The synthetic 2 MiB releases in the +concurrent experiment cannot substitute for that decision. Until those values +are selected, this document claims capacity bounds and measured reference +behavior, not a complete post-Tachyon performance design. + +Peers will have widely different usable upload rates after other traffic. +The controller must allocate against observed delivery and aggregate receiver +capacity. A high advertised link rate does not establish either quantity. +Full nodes below the required sustained ingress rate cannot keep up through +congestion control alone. + +## Tradeoffs + +### Topology and the normal propagation path + +The throughput design assumes that participating relay nodes remain connected +after removing the proposer. The intended network has multiple relay paths +and enough aggregate upload to carry standing subscriptions. A star whose +leaves can communicate only through the proposer falls outside this operating +assumption. Its source-cut cost remains a useful failure test, not a reason to +budget several body copies for normal proposer seeding. + +Physical connectivity does not ensure that every per-part subscription graph +has a path from its seed. Normal propagation must establish useful standing +routes and distribute seeds within the initial source budget. Experiments must +measure completion before repair on connected relay graphs, including unfamiliar +proposers. They must not use successful repair to claim that those routes work. + +`FullBlock`-triggered requests for missing parts form a strict fallback. +A stalled receiver can request enough distinct missing indices from a peer +that advertises completion. That peer serves retained or regenerated parts +through bounded block-scoped subscriptions. Normal forwarding continues to +push verified parts without requests or reconstruction delays. + +Fallback pays a request delay and additional upload. Frequent fallback would +make the system behave like pull-based block distribution and defeat the +throughput goal. Reports must separate normal completion, fallback frequency, +fallback bytes, and eventual completion. Attackers and topology failures must +not turn fallback into unlimited grants or unbounded proposer reseeding. + +### Latency, throughput, and robustness + +Block propagation balances latency, throughput, and robustness. + +We include scalability under robustness: a larger network should not require +each node to serve more peers. Like +[Gossipsub](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#gossipsub-the-gossiping-mesh-router), +Dogwood uses bounded local forwarding, with a separate subscription graph for +each part. + +[Rotor](https://www.anza.xyz/blog/alpenglow-a-new-consensus-for-solana) uses +erasure coding and a single relay layer to reduce the proposer's upload burden +and propagation hops. Its routes depend on a known proposer and validator set. + +Celestia's Pull-Based Broadcast Tree +([PBBT](https://github.com/celestiaorg/celestia-app/blob/9c1e04d1dfd090531252f16f34293242d04b1157/specs/src/recovery.md)) +discovers routes as parts propagate. It pipelines authenticated `Have` and +`Want` messages with data transfer. Congestion affects route selection through +FIFO scheduling, but the first transfer still waits for a request. + +Dogwood moves that request before the block. Like +[DOG](https://github.com/cometbft/cometbft/issues/3263), it uses local delivery +measurements to adjust push routes. It starts with selected suppliers, not the +whole block from every peer. Subscriptions divide the traffic across connections +and adapt separately for each proposer. + +Each node retains learned routes for each proposer. Switching between known proposers +does not discard those routes. An unfamiliar proposer uses default routes until +measurements support its own assignments. Congestion or a change in a proposer's +entry point can still make its routes stale. Recovery handles missing parts +while the controller adapts. + +## Parts and subscriptions + +The proposer splits the block body into `k` data parts, with 64 KiB payloads +by default. Systematic Reed–Solomon over GF(2¹⁶) adds `ceil(k / 4)` parity parts. +Any `k` distinct correctly encoded parts reconstruct the body. + +`HeaderMeta` wraps the consensus header with coding parameters, a Merkle root +over the parts, and proposer authentication. Each `BlockPart` carries a proof +against that root. Nodes verify parts before forwarding or decoding them. + +A subscription selects parts. For future blocks, it uses a fixed-width part +mask that maps to indices once the block size and hash are known. Each enabled +bit selects a share of the encoded parts. For an announced block, a subscription +can name exact indices. A part always means one payload, not a group of payloads. + +Each node chooses its suppliers independently. These choices form overlapping +directed graphs: different parts follow different paths through the same +peers. A node can forward a part as soon as it verifies it. Once it reconstructs +and checks the encoded body, it can regenerate parts that never reached it. + +### Block-part lifecycle + +The nodes subscribe before the block exists. The diagram then follows one part. +Both nodes collect other parts through their own subscriptions. + +```mermaid +sequenceDiagram + participant P as Proposer + participant A as Node A + participant B as Node B + B->>A: SubscribeParts + A->>P: SubscribeParts + P->>A: HeaderMeta + A->>A: Verify header and metadata + A->>B: HeaderMeta + B->>B: Verify header and metadata + P->>A: BlockPart + A->>A: Verify block part + A->>B: BlockPart + B->>B: Verify block part + Note over A,B: Repeat for other parts + B->>B: Reconstruct and check block + B->>A: FullBlock +``` + +`FullBlock` stops further parts for that block toward its sender. Node A cancels +queued sends to Node B, but in-flight parts may still arrive. Node B continues +serving its own subscribers. Future subscriptions remain active. +Reconstruction checks do not replace consensus block validation. + +### Messages + +| Message | Purpose | +| --- | --- | +| `HeaderMeta` | Announce the header and authenticated commitment to its encoded body. | +| `BlockPart` | Send one part with its proof and subscription authorization. | +| `SubscribeParts` | Request parts of future blocks or specific parts of an active block. | +| `UnsubscribeParts` | Stop a route or restore inherited subscriptions. | +| `FullBlock` | Report reconstruction and stop receiving parts for this block. | + +Subscriptions grant finite part and byte credit over a bounded height range. +Each part identifies its grant. Canceling a route stops future sends without +making an authorized in-flight part a protocol violation. + +## Subscription state + +Each node keeps incoming and outgoing part masks per peer and proposer. +Incoming masks record what it requests. Outgoing masks record what peers +request from it. When a block arrives, the node resolves these masks into +peer-by-part bitmaps. + +Default masks serve unfamiliar proposers. The steady-state throughput model +uses one supplier per part. The draft retains bounded two-supplier startup +coverage until routes demonstrate delivery. Startup traffic must fit its own +byte budget. Here, each checkmark shows a steady-state request for an announced +block: + +| Incoming peer | Part 0 | Part 1 | Part 2 | Part 3 | +| --- | --- | --- | --- | --- | +| A | ✓ | — | ✓ | — | +| B | — | ✓ | — | — | +| C | — | — | — | ✓ | +| D | — | — | — | — | + +The node learns separate routes for each authenticated proposer. A nearby peer +may provide most of one proposer's block without being the best supplier for +another. For example, learned primary assignments could look like this: + +| Proposer | Part 0 | Part 1 | Part 2 | Part 3 | +| --- | --- | --- | --- | --- | +| X | A | A | A | B | +| Y | C | C | C | A | + +Backup subscriptions supplement these assignments where failure coverage +requires them. Changing X's routes does not change Y's routes. All routes share +the connection's byte budget. + +Outgoing demand is independent. A can request a part from B while B requests +it from A. Either node might receive a part elsewhere first or reconstruct it. +A node suppresses an echo to the peer that supplied the part. Reciprocal +subscriptions do not prove that either peer has the data. + +Block-specific subscriptions request missing parts during recovery without +changing the learned routes for future blocks. The spec defines how these +subscriptions override persistent state. + +## Routing and congestion control + +The receiver chooses suppliers. Transport congestion control paces each +connection. The subscription controller decides how much traffic to assign +to that connection. + +Arrival times alone cannot reveal unused capacity: a peer might be slow because +it received the part late, or because its connection is congested. The receiver +instead tests an alternative under load. It requests the same parts from two +peers and compares verified arrivals on its own clock. No sender timestamp or +RTT estimate is needed for that comparison. +Ordinary deliveries help select candidates. Different-part comparisons alone +can confuse peer performance with upstream part availability. + +### Delivery feedback and sender timestamps + +We should test ordinary-delivery feedback as a way to reduce challenge traffic. +A candidate negotiated extension attaches a connection-local sequence and a +monotonic send timestamp to each `BlockPart`. The sender records the timestamp +when it submits the part to the transport. The receiver records local receipt +time. The sender regenerates +these fields at each hop outside the immutable part commitment. + +Subtracting a remote timestamp from local arrival time does not give one-way +delay without a clock-offset estimate. Clock error affects one-way measurement +as described in [RFC 7679](https://www.rfc-editor.org/rfc/rfc7679.html#section-3.7). +Differences within one connection cancel a constant offset, but drift, batching, +and queueing remain. A timestamp at transport submission also precedes actual +packet transmission. It measures neither upstream propagation nor unused +capacity. A peer can lie about any timestamp it supplies. + +For a contiguous sample of delivered bytes, compare the sender span and the +receiver span. Dividing bytes by the larger span provides a conservative +delivery-rate sample under honest timing. Exclude the first part's bytes when +the span starts at that part's arrival. Keep sample age and application-limited +state. This estimates achieved delivery, not total available bandwidth. +Transport ACK, RTT, loss, ECN, and pacing measurements provide stronger local +signals where the transport exposes them; see +[RFC 9002](https://www.rfc-editor.org/rfc/rfc9002.html#section-7). + +The receiver can use a filtered delivery rate to distribute unique parts and +bound outstanding bytes across active blocks. Test bounded increases under +load to discover additional capacity. Reduce future assignments when queue +delay grows or eligible deliveries miss their deadline. Keep a node-wide +ingress budget so several peers do not overload the same receiver. Expose the +sample window, utilization target, queue-delay target, and maximum assignment +change as local experiment parameters. Finite grants remain the hard limit. + +The local experiment compares this allocation direction with the existing +challenge baseline. Its timestamps mark idealized link service, so its estimate +is more favorable than application submission timestamps may be in practice. +It does not justify replacing all challenges. Ordinary deliveries measure +active routes; bounded exploration still tests unused routes and changed +upstream availability. Holding the allocator fixed and using only receiver +arrival spans gave almost the same latency in the local traces. The experiment +therefore supports testing delivery-aware allocation, but does not establish +that sender timestamps are worth their wire cost. The subsequent real TCP +experiment also found no consistent benefit from sender spans. Keep sender +timestamps out of the baseline wire profile. Receiver-local delivery feedback +still needs joint tests with the full subscription controller. + +The [submission-timestamp and nonce-echo experiment](dogwood-experiments.md#submission-timestamps-nonce-echoes-and-shared-credit) +adds separate proposer routes and shared connection credit over real TCP. +It improves one upstream-delay case but shows no consistent advantage during +capacity drops. Echo calibration rejects a large future timestamp shift while +accepting an 8 ms shift. Deliberately delayed echoes also inflate the learned +credit. Treat remote timing as optional telemetry. Require actual delivery for +credit increases and retain hard limits independently of clock calibration. + +### Baseline challenge controller + +The controller follows five rules: + +1. **Compare like with like.** Add a random challenger for selected parts within + a traffic-funded exploration budget. Compare the same parts from the same + proposer under similar block size and concurrent load. +2. **Move gradually.** Require repeated wins. Keep the old supplier until the + replacement delivers. Preserve failure coverage. +3. **Budget bytes across blocks.** Count active blocks, proposers, backups, and + challenges together per connection. Limit each move and measure its effect + before adding more demand. +4. **Adjust the budget from delivery.** Raise it gradually after success under + increased load. Lower it after repeated uncanceled deadline misses. + Idle time does not establish spare capacity. +5. **Recover independently.** Repair a stalled block within a bounded reserve. + Do not wait for route learning. Do not count canceled copies as failures. + +For selected parts, a successful challenge changes the route as follows. +Arrows show pushed data; subscription requests travel in the opposite direction. + +```text +Before: A ──> Receiver +Challenge: A ──> Receiver <── B +After: Receiver <── B +``` + +The receiver keeps A if it still needs A for failure coverage. Random challenges +continue so peers can recover from past losses. + +Local coverage does not protect global delivery paths. Several nodes can prune +different supplier edges and strand parts that previously reached them. The +connected-relay experiments reproduce this failure despite local coverage +checks. Treat the challenge controller as experimental. Retain startup routes +until a pruning policy demonstrates normal delivery under concurrent changes. + +A part mask's byte cost grows with block size and concurrent block count. +Selecting a quarter of a 40-part block costs 640 KiB at 64 KiB per part. +Two such blocks cost 1.25 MiB. A win at the first load does not establish capacity +for the second. + +Challenge frequency follows block traffic, not just a timer. The receiver funds +extra copies from the encoded size of completed, validated blocks. It spaces trial +starts, shares opportunities across active proposers, and bounds each trial's +lifetime. Idle time adds no budget. Existing backup deliveries can provide +comparisons without adding traffic. + +Standing subscriptions use an estimated workload. When `HeaderMeta` arrives, +the receiver checks actual demand and coverage. Corrections take control-message +latency. The learned budget guides allocation; finite grants and queue limits +bound resource use. + +[Section 7 of the spec](../specs/dogwood.md#7-redundancy-and-route-control) +defines the measurements and update rules. The +[standing-route experiment](dogwood-experiments.md#feedback-driven-standing-routes) +now tests paired route changes and occasional probes of unassigned peers. +At 1,250 Mbps relay upload and 819.2 Mbps body load, adaptation raises completion +within 400 ms from 12.53% to 100% in the tested traces. It does so partly by +removing duplicate routes, which reduces failure coverage. The experiment does +not implement the shared connection-budget controller. That controller and +coverage-preserving route changes still need an integrated test. + +## Proposer subscriptions and seeding + +Proposer upload is a separate scheduling problem. A receiver may request all +parts, but that does not tell the proposer which subset to seed there first. +Sending the whole codeword to every direct peer can multiply proposer upload. +Sending disjoint subsets can strand peers that cannot exchange those subsets. +We need both an authorized seeding policy and a delivery path after seeding. + +### Let the proposer choose a bounded subset + +The candidate is a negotiated `SeedOffer` selection within `SubscribeParts`. +The receiver permits any subset of the selected parts up to its existing part +and byte credits. The proposer chooses the actual indices. This is permission +to receive seeds, not a promise that every selected index will arrive. Ordinary +subscriptions continue to request specific coverage. Candidate payload profile +W1 encodes separate ordinary and seed selections and cancellation actions. The +[spec rules](../specs/dogwood.md#proposer-seeding-candidate-extension) define the +remaining requirements before enabling it. + +The proposer keeps one upload budget across seed transfers, ordinary +subscriptions, repairs, and concurrent blocks. It tracks indices already seeded +or in flight so the initial pass favors new distinct parts. It schedules against +measured service and receiver credit, not advertised bandwidth. A receiver +verifies and forwards each seed immediately through its normal subscriptions. +The proposer retains bounded repair service after the initial pass. + +Seed offers should identify parts the receiver can forward through outgoing +subscriptions. A receiver can also offer a decodable subset for local bootstrap. +The proposer should prefer eligible recipients with useful outgoing demand. +This local hint improved static startup in the connected-relay experiment, but +does not prove downstream reachability. Missing eligible credit must appear as +degraded seeding, not unsolicited sends or hidden normal-path repair. + +### What can be optimal locally + +For equal-size parts, fixed known peer rates, sufficient any-index seed credit, +and a shared proposer upload limit, we can minimize the time to seed a chosen +number of parts. Select the earliest available per-peer service slots, then +pace their aggregate rate under the proposer limit. The +[local proof and exhaustive check](dogwood-experiments.md#proposer-seeding-checks) +establish this limited optimum. It does not minimize network-wide reconstruction +time or infer changing bandwidth. + +| Proposer's peers | Seeding direction | Delivery constraint | +| --- | --- | --- | +| One peer | Send enough distinct parts for that peer to decode; test whether to send remaining parity before cancellation. | That peer is the only exit. No routing or parity choice protects against its loss. | +| Equal-rate peers | Divide the first pass evenly when peers have comparable credit and relay reachability. | Disjoint seeds work only if each downstream group can collect enough distinct parts. | +| A few fast peers and many slow peers | Assign more seed parts to the fast peers; a slow peer need not receive an initial seed. | Seed recipients still need useful outgoing part routes. Separate relay components are outside the normal topology assumption. | + +The 2 MiB local example seeds 40 parts through a 1 Gbps proposer in a minimum +21.1 ms including the model's framing allowance. With peer rates of +800/400/200/100/20/5 Mbps, one optimal allocation is 22/11/5/2/0/0 parts. +Equal assignment takes 632.8 ms to seed every assigned part because it waits +for the slowest peer. Neither number includes downstream delivery. The source +can spend more time or bytes to establish a usable path for every receiver. + +### A delivery condition and its limits + +Assume honest peers, an arbitrary body with no prior body information, +one valid codeword, retained data, adequate credit, fair +eventual service, and subscriptions to every part on every relay edge. Remove +the proposer from that relay graph. Every remaining connected component must +receive at least `k` distinct seeded indices. This condition is necessary and +sufficient for eventual reconstruction in this model: fewer than `k` cannot +create the missing information; `k` distinct parts can spread through the +component and let every member reconstruct. + +Giving each peer some parity does not satisfy that condition. In a star with +four isolated leaves and `k=4, n=8`, each leaf can receive one data part and one +parity part yet remain unable to decode. The proposer must supply additional +parts. More generally, `c` isolated downstream components need at least +`c*k*S` payload bytes across the proposer cut, even if they request the same +indices. A seeding budget of one codeword cannot meet every such topology. + +Real sparse subscriptions have different graphs for different parts. The +proposer also does not know global relay connectivity. The normal design assumes +one connected relay component and tests standing routes within it. A stalled +receiver uses bounded `FullBlock`-based pull repair as a fallback. The earlier +parent-tree repair experiment is not the selected normal propagation path. +Fixed byte caps, deadlines, failed suppliers, and correlated paths still bound +what fallback can recover. The receiver must report degraded service +when it cannot meet them. All-part relay subscriptions are a correctness +baseline, not a selected production fanout policy. + +### Large-body stripe candidate + +The whole-body codeword remains the current profile. A separate large-body +candidate uses equal-shape coding stripes. Nodes retain the incoming suppliers +that delivered one shared reference stripe before reconstruction. They keep +the part-mask mapping and seed recipients fixed for subsequent stripes of that +body. Under unchanged availability, adequate credit, and fair service, these +retained paths can reproduce the reference's delivery. The local paired sweep +reduced relay upload by 45.8–52.5% without fallback in the tested static cases. + +This candidate needs authenticated stripe commitments and identifiers, bounded +pipeline state, stripe completion semantics, and failure recovery. It cannot +reuse `FullBlock` for individual stripes. A changed mapping, shape, seed plan, +or unavailable supplier invalidates the reference argument. The experiment +does not establish concurrent throughput or single-supplier failure coverage. +Do not enable stripe pruning under the present profile. + +The [reference codec scaling test](dogwood-experiments.md#reference-codec-scaling) +compares the same 64 MiB body at different stripe sizes. With 2 MiB stripes, +source encoding/root work takes about 179 ms and receiver work takes 382 ms +under parity-first reception. One 64 MiB codeword takes about 4.46 seconds and +9.13 seconds respectively. These are serial reference-kernel measurements. +They support testing 2 MiB stripes and show why the field bound alone cannot +select a practical codeword size. They do not implement an authenticated +stripe profile or establish production throughput. + +The concurrent follow-up preserves normal delivery at the synthetic 819.2 Mbps +body rate in its steady cases. Temporary upload changes still cause misses. +Restoring startup suppliers after a miss does not consistently restore timely +delivery. The candidate therefore does not complete the adaptive controller. + +### Small blocks and portions + +More parity for small blocks is worth testing because its absolute proposer +cost can be small while a repair round trip remains expensive. The actual +ratio includes rounding: the current `ceil(k/4)` rule already adds 100% parity +at `k=1` and 50% at `k=2`. A candidate experiment uses 100% parity for `k<=8` +and 25% above that threshold. This is not a selected profile. It must beat +duplicate forwarding after accounting for padding, encoding, proposer upload, +and cancellation. At `k=1`, each parity part repeats the same information. + +We can also group several block parts into a local scheduling portion. This +changes assignment granularity, not the coding unit. Every part still has its +own index, proof, grant charge, and send-once state. Larger portions may reduce +scheduling work but place more load on one peer at a time. The experiment starts +with one part per scheduling portion. A portion that changes coding stripes, +Merkle commitments, or wire messages requires a separate profile design. + +## Redundancy and recovery + +One supplier per part is a useful traffic baseline. Duplicate subscriptions +remain a design option for failure coverage and latency. Parity covers missing +parts without requiring a duplicate of each part, but the proposer must first +upload the parity it seeds. + +For body size `B`, parity/data ratio `r`, and proposer upload rate `U`, seeding +each encoded part once takes at least `8*B*(1+r)/U` seconds when `U` is in bits +per second. This assumes the proposer seeds the entire codeword. Direct +duplicate sends, headers, and framing increase that cost. Relays can forward +parts during seeding, but pipelining does not remove the proposer upload work. + +Duplicate subscriptions can instead place redundancy on relays after they +receive the part. That can save proposer upload compared with more parity. +Duplicates requested directly from the proposer still cost proposer upload. +We must measure proposer bytes, proposer encode/root time, receiver bytes, +relay upload, and completion latency together. The local +[parity comparison](dogwood-experiments.md#parity-versus-duplicate-subscriptions) +isolates receiver allocation and calculates the proposer seeding lower bound; +it does not yet model that seeding path. + +For a block with 32 data parts and eight parity parts, any eight parts can be +unavailable. But if one peer supplies more than eight parts exclusively, losing +that peer can prevent reconstruction. The default coverage target therefore +keeps at least 32 distinct parts available after losing any one supplier. + +A fast connection can carry most of the block, provided other peers cover +enough distinct parts. This costs duplicate traffic. The receiver can reduce +that cost only by accepting recovery latency when the fast peer fails. +Distinct peers also need not represent independent physical paths. + +Committed parity and subscribed redundancy are separate choices. Encoding more +parity provides no additional failure coverage unless the receiver requests +enough distinct parts. The draft's 25% parity schedule is not a measured optimum. + +With exactly one supplier per subscribed part, let `m` be the number of distinct +subscribed parts and `a_max` the largest supplier assignment. Surviving that +supplier's loss without repair requires `m - a_max >= k`. If its share is +`f = a_max / m`, the required subscribed parity/data ratio is at least +`f / (1 - f)`, before integer rounding and any additional safety margin. + +| Largest supplier share | Minimum parity/data | Encoded rate at 50,000 TPS | +| --- | --- | --- | +| 20% | 25% | 1.024 Gbps | +| 25% | 33⅓% | 1.092 Gbps | +| ⅓ | 50% | 1.229 Gbps | +| 50% | 100% | 1.638 Gbps | + +These rates exclude proof and transport overhead. With equal assignments to +`d` suppliers, the exact test is `m - ceil(m/d) >= k`. Thus `k=32, m=40` +works with five suppliers carrying eight parts each, but not with four carrying +ten each. Wider bandwidth variation can make equal assignment waste the fast +peers' capacity. Concentrating half the parts on a fast peer instead requires +100% parity to survive its loss without duplicate subscriptions or repair. +Any ratio other than the draft's 25% requires an agreed coding profile. + +For multiple failed peers or a correlated failure group, apply the same test +to their combined exclusive assignment. For late parts, budget the observed +tail of the missing-part count, not just its average. A receiver must choose +between more parity, less concentration, and accepting bounded repair latency. +Congestion control cannot remove this coverage constraint. These bounds apply +to one subscription per part; they do not establish that more parity is better +than duplicate subscriptions. + +Coverage describes assignments, not guaranteed availability. A subscription +cycle may have no source for its parts. When progress stalls, the receiver +requests missing parts from additional peers. Existing full-block download +provides final recovery. Sparse subscriptions alone do not guarantee delivery +from every entry point. +The first authenticated header supplier is one repair candidate, not proof of +part availability. Learned routes avoid request latency; unfamiliar entry +points may still pay discovery or repair latency. + +## Encoding and verification + +The codec uses the systematic Reed–Solomon construction from +[RFC 5510, section 8](https://www.rfc-editor.org/rfc/rfc5510.html#section-8). +The decoder processes each verified part as an equation as it arrives. +It can also reduce existing equations with each new pivot to shorten the final +decode step. The reference benchmarks support testing this eager schedule +without switching to RLNC. Re-encoding and root verification still remain. +Forwarding never waits for decoding. + +For example, take four data parts and one parity part. An eager decoder can +consume the following sequence while the network continues delivering: + +| Verified arrival | Rank afterward | Action | +| --- | --- | --- | +| Parity part 4 | 1 | Normalize and retain its equation. | +| Data part 1 | 2 | Eliminate its pivot from the retained equation. | +| Part 4 again | 2 | Ignore the duplicate index. | +| Data part 3 | 3 | Eliminate its pivot from retained equations. | +| Data part 0 | 4 | Recover missing data part 2. | + +Each pivot operation transforms the payload alongside its coefficient row. +The receiver then re-encodes all five parts and checks the committed root. +The [worked example](../specs/dogwood.md#on-arrival-decoding-example) shows the +field equations. The local runnable example uses the same eager kernel as the +benchmarks and checks recovery with nonzero high bytes. + +A body change requires new parity and a new Merkle tree. A header-only change +does not. After mining, the proposer signs the final block hash and coding metadata. + +A Merkle proof establishes membership in the signed root, not correct encoding. +After reconstruction, the receiver checks padding and re-encodes the body to +verify the root. It combines the body with the admitted header and submits the +block for consensus validation. + +## Param Tuning + +The [spec parameter registry](../specs/dogwood.md#parameter-registry) owns the +definitions, starting values, and change rules. These values make experiments +comparable; they are not tuned production defaults. We should select a joint +operating point against proposer upload, receiver throughput, latency, and +failure recovery. Optimizing one parameter in isolation can move cost elsewhere. + +| Parameter group | Starting point and rationale | What could change it | +| --- | --- | --- | +| Workload and utilization | 50,000 TPS at 2 KiB; 80% usable-link utilization and 5% extra traffic are planning inputs. | Measured transaction sizes, forwarding fanout, transport overhead, and burst size. | +| Part size and mask width | 64 KiB parts; 16 mask bits in experiments bound proof work and route state. | Smaller parts or more bits permit smaller assignment changes but increase overhead. | +| Codec, parity, and subscribed coverage | Systematic GF(2^16) Reed–Solomon with 25% parity; compare 12.5–100% and duplicate subscriptions. | Proposer seeding time, encoding cost, receiver bytes, and recovery latency jointly determine the ratio. A codec change requires a profile revision. | +| Proposer seed budget, peers, and portions | Compare one-codeword seeding with repair and ordinary demand; one part per scheduling portion. | Receiver credit, proposer upload, and downstream component coverage can require more copies or a different assignment. | +| Small-block parity | Keep `ceil(k/4)` in the draft; test 100% parity at `k<=8`. | Absolute upload cost, rounding, and avoided repair delay determine whether a new deterministic profile is useful. | +| Failure model and startup copies | Test any one supplier loss; zero extra safety parts in the experiment; two selected startup copies where affordable. | Correlated failures and cold-route measurements can justify more coverage. Learned routes may use one copy or retain duplicates. | +| Decode schedule | Eager on-arrival elimination is a candidate; verify every part before use. | CPU backlog and memory measurements may favor another equivalent schedule. | +| Delivery and recovery deadlines | 400 ms and 1,200 ms for the 2 MiB reference experiment. | Body size, burst concurrency, and achievable service determine production deadlines. | +| Assignment budget | Start at 20 parts; test additive steps of one part and a 0.75 decrease factor. | Loaded delivery, queue delay, and eligible misses guide changes across all blocks on a connection. | +| Observation and migration | Require three race votes and a two-thirds win share; move at most four parts per trial. | Noise, part-mask granularity, and measured settling time constrain faster adaptation. | +| Challenge funding | Fund extra traffic at 1/32 of completed encoded bytes; use one mask bit per trial. | Ordinary-delivery telemetry or existing duplicates may reduce the needed challenge traffic. | +| Challenge cadence and retention | Start no faster than 250 ms plus jitter; retain at most two trials, 12 blocks, or 20 seconds. | Rare proposers need a longer bounded opportunity window, not faster empty trials. | +| Candidate delivery-rate estimator | Test 100 ms samples, at least four deliveries, and an EWMA weight of 0.5. | Application-limited traffic, transport batching, clock drift, and shared ingress require further tests. | +| Queue and recovery reserves | Test 256 queued parts per link and at most `2k` repair copies per block. | Production needs aggregate byte/work caps and fair service under concurrent assemblies. | +| History, grants, and retained work | Keep finite height, byte, state, and time limits; production values remain open. | Resource measurements set these caps before interoperability. Idle time or new proposer keys must not reset budgets. | +| Wire and authentication | Hashes, mapping hash, signature, chain binding, frame caps, and optional telemetry format remain open. | These choices require an agreed profile; a receiver cannot tune them unilaterally. | + +Local policies can evolve within the spec's bounds as observations accumulate. +They must record parameter versions with results and avoid interpreting stale +samples across material workload changes. Wire parameters require negotiation +before use. Existing grants retain their original authority during a policy +change. No automatic parity tuner or timestamp extension is selected yet. + +## Open problems and TODOs + +The [bounded-recovery follow-up](dogwood-experiments.md#bounded-recovery-follow-up) +tests sparse routes, failed parents, source caps, small-block parity, and +scheduling portions. Its star and bridge cuts test behavior outside the normal +topology assumption. Its repair-heavy completion results do not establish the +normal throughput path. The [connected-network follow-up](dogwood-experiments.md#connected-network-and-transport-follow-up) +measures completion before fallback and tests local pruning. Its failures keep +the complete adaptive controller open. The design is not ready for interoperable +implementation until transport negotiation, chain admission, and production +resource bounds are selected. W1 fixes candidate payload bytes and signatures. + +- [x] Test finite single-block recovery on single-peer, star, bridge, and mesh + topologies with equal and mixed relay upload rates. +- [x] Sweep 25%/100% parity, 16/64 KiB parts, and 1/2/4-part service portions; + measure reference encoding costs for small codewords. +- [x] Specify that repair retries cannot reset credit or the total deadline; + require separate per-part accounting inside a scheduling portion. +- [x] Test sparse connected relay graphs with finite ingress, assumed CPU + queues, one-codeword seeding, and separately accounted `FullBlock` fallback. +- [x] Test simultaneous local pruning and forwardable seed eligibility; + identify failures that local coverage does not prevent. +- [x] Test a fixed-reference stripe pruning candidate under static conditions. +- [x] Run real TCP allocation tests with shared capacity, a capacity drop, + application stalls, loss, and ECN; retain receiver-local feedback as a candidate. +- [x] Exhaust a finite grant model and test cancellation, exploration funding, + loaded cohorts, settling gates, and migration coverage. +- [x] Test concurrent synthetic bodies with shared upload, ingress, encoding, + and reconstruction queues; reject simple restoration as a complete controller. +- [x] Specify and probe a coinbase-output key commitment for transparent-only + V5 transactions; exclude input-script keys backed only by a txid proof. +- [ ] **Proposer grants:** specify and test `SeedOffer` negotiation, eligibility, + credit consumption, expiry, cancellation, and coexistence with ordinary demand. + Lifecycle rules, candidate wire encoding, and a finite grant model exist; + negotiation and concurrent grant validation remain. +- [ ] **Proposer scheduling:** test learned bandwidth against the static optimum + with changing rates, shared bottlenecks, pending work, and insufficient credit. +- [ ] **Bootstrap coverage:** test one peer, equal peers, mixed peers, star cuts, + bridge peers, and failed header parents under bounded source upload and repair. + The finite single-block sweep is complete; add concurrent blocks, changing + failures, and measured coding work in the connected push model. +- [ ] **Overlay delivery:** select and validate a pruning policy that preserves + delivery when several receivers adapt. Local coverage and majority wins failed + this gate; the fixed-reference stripe candidate needs a separate profile. +- [ ] **Parity versus copies:** measure proposer encoding and upload, relay + upload, receiver bytes, and reconstruction latency under the same failure model. +- [ ] **Small blocks and portions:** sweep size-dependent parity, part size, + scheduling group size, and systematic-first versus parity-first seeding. + The initial sweep is complete; test correlated loss and joint CPU/network + costs before selecting a body-size threshold or changing the profile. +- [ ] **Congestion feedback:** integrate receiver-local feedback with standing + push, grants, and receiver-wide queue control. The bounded TCP experiment is + complete. Sender timestamps remain omitted; adopting them would require + separate clock-drift and dishonest-sender tests. +- [ ] **Controller completeness:** implement settling, migration, stale-history, + grant, and cancellation rules omitted by the reduced simulations. +- [x] Measure equal-body reference codec scaling from 2 MiB stripes to one + 64 MiB codeword; separate the field bound from practical CPU cost. +- [ ] **Large bodies:** obtain the intended block interval and propagation + deadline; select the committed-stripe profile and test the resulting burst + with measured coding work, bounded memory, and separately measured fallback. +- [x] Specify W1 payload encoding, tagged hashes, Merkle proofs, signatures, + and separate seed cancellation; test bounds and signature context binding. +- [ ] **Wire and authentication:** finish the production chain adapter, + transport negotiation, and aggregate resource limits in the registry. + +## Headerchain integration + +Headerchain remains responsible for header validation, fork choice, and header +recovery. The node admits the complete header, including proof of work and +contextual difficulty, before authenticating metadata or allocating assembly +state. It then pushes `HeaderMeta` through header gossip without a per-hop +request exchange. Peers without part subscriptions also receive metadata. + +The current header does not commit to the part root or directly identify a +Dogwood proposer key. The proposed wrapper carries a signature from a key bound +to the mined block. The candidate binds a 32-byte key in a zero-value coinbase +output and proves its txid membership at index zero. The +[spec](../specs/dogwood.md#bind-the-proposer-to-the-proof-of-work) fixes the +candidate script. A txid proof alone cannot authenticate a key in the V5 +coinbase input script. W1 selects Ed25519 and a chain-bound signature +transcript. The post-Tachyon adapter remains open. A self-chosen wrapper key would let anyone attach conflicting roots to +someone else's proof of work. + +Nodes accept at most one authenticated metadata variant per block. An +authenticated conflict stops coded propagation for that block and triggers +ordinary block recovery. + +The [W1 payload profile](../specs/dogwood.md#candidate-payload-profile-w1) fixes +canonical bytes and cryptographic commitments. The spec leaves the production +chain adapter, service negotiation, and aggregate resource limits open. Controller simulations and codec measurements must establish the +latency and throughput this design can achieve. diff --git a/docs/specs/dogwood.md b/docs/specs/dogwood.md new file mode 100644 index 0000000000..20897f6daf --- /dev/null +++ b/docs/specs/dogwood.md @@ -0,0 +1,1565 @@ +# Dogwood protocol specification + +Status: protocol draft. This document defines the proposed behavior. +The [design document](../design/dogwood.md) explains the choices. The five message families are +settled for this draft. Section 8 fixes candidate payload profile W1. +Transport negotiation and the production chain adapter remain open. + +`MUST` defines a security or interoperability requirement. `SHOULD` defines +the default policy. An alternative policy must preserve every `MUST`. +`TBD` identifies a choice that blocks interoperable implementation. + +## 1. Scope and integration + +The protocol pushes new proof-of-work blocks over existing authenticated, +encrypted, reliable peer connections. Existing service negotiation MUST select +a common protocol profile before these messages appear. No additional +application handshake is defined here. + +Headerchain MUST own header validation, contextual difficulty, fork choice, +and header recovery. The propagation service MUST submit `HeaderMeta.header` +through that admission path. It MUST NOT create a second header validator. + +The current header-sync service has `Status`, `GetHeaders`, `Headers`, and +`HeadersOutcome`; it has no `HeaderMeta` or coded-part messages. Integration +MUST negotiate the new propagation service and connect it to headerchain. +Existing header discovery and full-block download MUST remain available. +Nodes MUST deduplicate headers across these paths by consensus block hash. + +A node MUST NOT forward metadata, open assembly state, decode parts, or update +proposer measurements before full header admission and metadata authentication. +Bounded parsing and verification are the necessary exceptions. If parent +context is missing, the node MAY retain a bounded metadata envelope while +headerchain recovers that context. It MUST apply independent count, byte, work, +and time limits to this pending state. + +Header admission does not validate the block body. Reconstructed blocks MUST +enter the existing consensus block-validation path. + +The planning workload is 50,000 TPS after Tachyon, assuming 2 KiB per aggregated +transaction. This requires 819.2 Mbps of body throughput and 1.024 Gbps with +25% parity, before proofs, transport, challenges, and recovery. This assumption +does not change consensus limits. The [design throughput budget](../design/dogwood.md#throughput-target) +separates average rate, burst latency, forwarding load, and codeword limits. +The intended block interval and propagation deadline remain required sizing +inputs. A frame-valid codeword is not evidence that decoding fits a work budget. +The [reference scaling experiment](../design/dogwood-experiments.md#reference-codec-scaling) +supports testing smaller stripes; W1 does not yet encode them. + +The normal throughput claim assumes that the participating honest relay +network remains connected after removing the proposer. Experiments MUST state +the relay degree, path diversity, available service, and failures that preserve +this assumption. A connected physical graph does not establish reachability +in its per-part subscription graphs. Experiments MUST measure normal-path +completion independently of pull repair and existing full-block download. +Disconnected relay components are failure cases outside the throughput claim; +implementations MUST still bound their resource use and recovery attempts. + +## 2. Parts and authenticated metadata + +Encode the block body, not the already transmitted header. Let `B` be the +canonical serialization of the transaction vector, including its count prefix. +The existing block serializer concatenates the header and this vector. +Reassembly MUST use the exact admitted header from `HeaderMeta`. + +```text +S = part payload bytes // default 65,536 +k = ceil(len(B) / S) +n = k + ceil(k / 4) +data = split(zero_pad(B, k * S), S) +parts[0..n] = systematic_encode(data, codec) +``` + +The selected codec is deterministic systematic Reed–Solomon over +GF(2^16), with the property that any `k` distinct correctly encoded parts +reconstruct `B`. The construction below fixes its parity bytes. +Every part, including parity, MUST contain exactly `S` payload bytes. +`0 < k < n <= min(MAX_PARTS, 65535)` and +`len(canonical(header)) + len(B) <= MAX_BLOCK_BYTES` MUST hold. +The profile MUST fix `S` and resource bounds within these codec limits. +A peer cannot choose arbitrary coding parameters to increase receiver work. + +### Codec + +Use the systematic Vandermonde construction in +[RFC 5510, section 8](https://www.rfc-editor.org/rfc/rfc5510.html#section-8): + +```text +field polynomial = x^16 + x^12 + x^3 + x + 1 // 0x1100b +a = x // field element 0x0002 +V[r,c] = a^(r*c) // 0 <= r < k, 0 <= c < n +G = inverse(V[:,0..k]) * V // k-by-n systematic matrix +``` + +Arithmetic in this construction MUST use GF(2^16). Each consecutive pair of +payload bytes MUST encode one field element in little-endian order, with bit +`j` representing the coefficient of `x^j`. `S` MUST be even. For each element +offset, multiply the row vector of `k` data elements by `G` to obtain the `n` +encoded elements. Indices `0..k` are the original data parts. The remaining +indices are parity parts. The encoder MUST use one codeword for the whole body. + +A codec-level test vector uses `k = 2`, `n = 3`, and `S = 65536`. Data part 0 +starts with `01 00`; data part 1 starts with `02 00`. Every remaining byte is +zero. Parity part 2 MUST start with `04 00` and contain zeros afterward. +This vector tests the codec, not canonical block-body parsing. + +### Incremental decoding + +The baseline decoder SHOULD maintain an incremental elimination state. Part +`i` supplies coefficient row `G[:,i]` and its payload as the right-hand side. +On each verified arrival, eliminate existing pivots and retain the new pivot. +After `k` distinct parts, finish back-substitution and the checks below. +An eager decoder MAY also eliminate each new pivot from existing rows on +arrival. This moves work out of back-substitution without changing the code. +Systematic parts expose their body bytes directly, but those bytes remain +provisional until verification completes. A decoder MAY use an equivalent +algorithm that produces the same result within the same work bounds. + +The decoder MUST verify membership before incorporating a part. It MUST +incorporate each distinct index at most once. It MUST bound total CPU and +memory, including elimination state, and MUST NOT restart an unbounded job on +each arrival. Received-part forwarding MUST NOT wait for decoding. Serving +reconstructed parts requires complete codeword verification. + +### On-arrival decoding example + +This example uses the `k=2, n=3` vector above. The coefficient rows are +`[1,0]`, `[0,1]`, and `[2,3]`. For each payload element, parity therefore equals +`2*d0 XOR 3*d1`, with multiplication and division in GF(2^16). + +1. Parity part 2 arrives first. Verify its membership and retain equation + `2*d0 XOR 3*d1 = 4` for the first element. Normalize the pivot at `d0`. +2. Data part 1 arrives. Verify its membership and insert `d1 = 2` as a pivot. + Eliminate `d1` from the retained equation immediately. +3. The first equation now yields `d0 = (4 XOR (3*2))/2 = 1`. + The decoder has rank two without a separate batch elimination pass. +4. Reconstruct the padded body and run the padding and codeword-root checks. + The recovered bytes remain provisional until those checks complete. + +Apply the same operations to every payload element. In a general eager +decoder, each verified distinct arrival runs this loop: + +```text +row, value = G[:, index], payload +eliminate all retained pivots from row and value +normalize the first remaining pivot in row and value +eliminate that pivot from every retained row and payload +retain the normalized row and payload; increment rank +if rank == k: reconstruct, check padding, re-encode, and verify the root +``` + +Deduplicate indices before this loop. The any-`k` property applies to correctly +encoded parts; a maliciously committed payload can still fail the final root +check. Incremental work can overlap network arrivals, but a busy decoder can +still accumulate a completion tail. This example does not set a CPU bound. + +### Proposer preparation + +The proposer MUST finish the committed codeword and its Merkle root before +publishing `HeaderMeta`. It SHOULD precompute them for the candidate body while +mining. A change to the body, including its coinbase, invalidates that cached +encoding. A header-only change does not change the encoded body or part root. +The signature still binds the root to the final mined block identifier. +The block-hash-derived part-mask mapping is computed after mining. +Bare-header propagation MAY proceed through headerchain while unfinished +encoding runs; it MUST NOT authorize coded-part processing. + +### Metadata fields + +`HeaderMeta` MUST authenticate all of these values: + +```text +HeaderMeta { + header: ConsensusHeader, + proposer_key: PublicKey, + key_binding: BoundedProof, + codec_id: CodecId, + body_bytes: u64, + part_bytes: u32, + data_parts: u32, // k + total_parts: u32, // n + part_root: Hash, + signature: Signature, +} +``` + +`body_bytes` MUST equal `len(B)`. Admission MUST require +`data_parts = ceil(body_bytes / part_bytes)` and the profile's parity schedule. +It MUST check the combined header/body size with checked arithmetic before +allocating assembly state. + +The block identifier is the consensus hash of `header`. The profile MUST +define one canonical serialization for the metadata fields and one hash `H`. +The proposer signature MUST cover a domain separator, chain identifier, +protocol profile, block identifier, proposer key, codec, size, counts, and root. +The metadata variant identifier MUST hash these signed fields, excluding the +signature and key-binding proof. Alternate encodings of equivalent proof or +signature evidence MUST NOT create apparent proposer equivocation. + +### Bind the proposer to the proof of work + +`key_binding` MUST prove that the mined header commits to `proposer_key`. +A transport identity, self-signed wrapper, or unauthenticated coinbase label +does not satisfy this rule. The key is the proposer identity used for routing; +it does not establish a unique operator or a stable physical entry point. + +The proposed chain adapter commits the key before mining, then signs metadata +after mining. Its candidate uses one zero-value transparent coinbase output +with this exact script for a 32-byte proposer key: + +```text +6a 28 || ASCII("DOGWOOD") || 01 || proposer_key[32] +``` + +`6a` is `OP_RETURN`; `28` directly pushes the following 40 bytes. The verifier +MUST require exactly one matching commitment output. Alternate push encodings, +extra bytes, and a nonzero output value MUST NOT satisfy this candidate. +The candidate does not put the part root in the coinbase. + +The proof carries the canonical coinbase transaction and its transaction-id +Merkle path at index zero. The verifier MUST compute the txid with the existing +chain implementation for the admitted height and transaction version. It MUST +check the path against the admitted header's transaction Merkle root. Bounded +parsing and an output key match do not replace that path check. The profile MUST +bound the coinbase bytes, script bytes, output count, and path depth before +allocation. Oversized or unsupported bindings use existing block propagation. + +Under [ZIP 244](https://zips.z.cash/zip-0244), the txid commits +to transparent outputs. It excludes input scripts from that digest. A key in +the coinbase input script MUST NOT be accepted with only a txid inclusion path. +An adapter that uses authorizing data would need the separate header commitment +proof for that data. It MUST NOT silently substitute one proof for the other. + +The local binding probe covers transparent-only V5 coinbases. It demonstrates +the output commitment and rejects input-only substitution, duplicate key +outputs, wrong position, malformed counts, and stale Merkle roots. It does not +implement a complete chain adapter or metadata signature verifier. The selected +profile still MUST fix supported transaction versions and chain-verification +work bounds. W1 fixes the key/signature scheme and proof byte bounds. In particular, a post-Tachyon format requires its +own commitment check; the V5 result does not establish that future binding. + +This proposal does not place the part root inside the block it encodes. +Implementations MUST NOT assume that the current Zcash header already +authenticates this wrapper. A block without a supported key binding MUST use +existing block propagation. + +The verifier MUST check the header's proof of work and all contextual header +rules before signature verification, part-root admission, or proposer-state +allocation. It MUST bound even invalid-header verification with per-peer and +global work limits. + +A valid header does not limit how many roots its signer can sign. Nodes MUST +retain at most one admitted metadata variant per block hash. Exact duplicates +MUST NOT create additional assembly or forwarding work. On a second distinct, +fully authenticated variant, a node MUST stop coded propagation for that block, +retain bounded conflict evidence, and recover through ordinary block download. +A metadata conflict alone MUST NOT invalidate the consensus header or penalize +an honest relay. Local variant selection is not a consensus rule. + +### Authenticate parts and reconstruction + +The Merkle construction MUST bind part position and coding parameters: + +```text +encoding_id = H(domain_encoding || canonical(codec, body_bytes, S, k, n)) +leaf[i] = H(domain_leaf || encoding_id || u32(i) || parts[i]) +``` + +The profile MUST fix internal-node hashing, tree padding, integer encoding, +proof order, and proof depth. Verification MUST require the exact proof shape +for index `i < n`. Domains for leaves, internal nodes, and other hashes MUST +differ. `part_root` MUST commit to all data and parity parts. + +A valid proof establishes membership in the signed root. It does not establish +that the parts form a valid codeword or a valid block. + +After receiving `k` distinct valid parts, the node MUST reconstruct the body, +check padding and `body_bytes`, and re-encode the body. +The re-encoded root MUST equal `part_root`. The node MUST bound this work and +MUST NOT search combinations of parts after failure. Failure disables coded +propagation for that block and starts ordinary block recovery. + +The node MUST assemble the admitted header and reconstructed body using the +existing canonical block format. It MUST enforce the total block-size bound +and reject trailing or noncanonical body bytes. Header-only bytes MUST NOT be +counted again as data parts. + +Only after codeword and canonical-assembly checks may the node regenerate and +serve missing parts or send `FullBlock`. The existing validator independently +decides whether the block is valid, including whether the body's transaction +commitments match the admitted header. Coding verification does not establish +consensus validity and MUST NOT substitute for that validation. + +## 3. Part selection and route state + +A part is one encoded payload. Persistent subscriptions select parts through a +fixed-width `PartMask`; block-scoped subscriptions select exact indices. +The profile fixes `0 < P = PART_MASK_BITS <= MAX_PARTS`. +For a given block, sort all indices +`i in [0, n)` by `(H(domain_route || block_id || u32(i)), i)`. +Let `rank(i)` be the zero-based position: + +```text +mask_bit(block_id, i) = rank(i) mod P +selected(mask, block_id, i) = mask[mask_bit(block_id, i)] +``` + +Each enabled mask bit selects either `floor(n/P)` or `ceil(n/P)` parts. +A mask bit is a selector, not a part index. This mapping distributes both data +and parity indices. Nodes MUST NOT assume this determinism prevents proposer +grinding or makes arrival times independent. +They MUST evaluate actual part placement for failure coverage. + +Each node maintains these bounded structures: + +```text +incoming[scope][peer, mask_bit] // requests we sent +outgoing[scope][peer, mask_bit] // requests peers sent +block_incoming[block][peer, i] // temporary exact-part overrides +block_outgoing[block][peer, i] +receive_grants[peer, grant_seq] +send_grants[peer, grant_seq] +received_from[block][peer, i] +sent_to[block][peer, i] +completed_by[block][peer] +route_stats[proposer_or_default, size_class, load_class, peer_pair] +load_budget[peer] // shared across proposers and blocks +pending_payload[peer] // local estimate, not sender queue occupancy +``` + +`scope` is `Default` or `Proposer(key)` for persistent routes. +The logical wire scope also permits `Block(block_id)` for exact-part repair. +Each proposer has independent incoming and outgoing routes and route statistics. +A switch between known proposer keys MUST NOT reset these routes or statistics. +An unfamiliar key uses default routes until the receiver installs overrides. +Changing congestion or the entry point behind the same key can stale its routes. +Size and load classes are local measurement contexts, not wire selectors. +One proposer has only one installed persistent row per peer at a time. +Connection grants, pending bytes, budgets, and hard limits MUST remain shared +across these contexts. Changing proposer keys MUST NOT reset them. + +For a particular peer and block, the effective subscription is the first +present row in this order: + +1. The exact-part `Block(block_id)` row. +2. The `Proposer(key)` row, expanded through the part-mask mapping. +3. The `Default` row, expanded through the part-mask mapping. + +An absent row inherits. An empty explicit row disables all its parts. +Changing an override MUST NOT change the row it overrides. +The initial default row is empty. +Receivers MUST remove persistent overrides with the matching inheritance action before retiring +their local copies. Senders MUST NOT silently evict an active persistent row. +Capacity pressure requires rejecting new state or closing the service. +Block-row retirement MUST follow the block retention rules. + +`SubscribeParts` adds bits. When it first creates an override, the handler +MUST copy the currently inherited row before adding bits. The receiver MUST +account for those inherited bits when choosing credit. `UnsubscribeParts` +can remove bits or remove the entire override to restore inheritance. + +A subscriber controls only traffic sent to itself on that connection. +Reciprocal subscriptions are legal. Nodes MUST NOT infer availability from a +subscription, an announced header, or reciprocity. + +## 4. Messages, credit, and ordering + +These are the only five application message families: + +```text +Scope = Default | Proposer(PublicKey) | Block(Hash) +Selection = PartMask | PartRanges | SeedMask | SeedRanges +UnsubscribeAction = Remove(Selection) | InheritOrdinary | InheritSeed + +SubscribeParts { + control_seq: u64, + scope: Scope, + selection: Selection, + first_height: u32, + last_height: u32, + part_credit: u32, + byte_credit: u64, +} + +UnsubscribeParts { + control_seq: u64, + scope: Scope, + action: UnsubscribeAction, +} + +BlockPart { + block_id: Hash, + grant_seq: u64, + part_index: u32, + payload: Bytes, + proof: MerkleProof, +} + +FullBlock { + control_seq: u64, + block_id: Hash, +} +``` + +Section 2 defines `HeaderMeta`. All hashes and integer encodings are fixed by +the selected profile. `PartMask` has exactly `ceil(P/8)` bytes and zero +unused bits. `PartRanges` contains sorted, disjoint, non-adjacent, non-empty +half-open ranges inside `[0,n)`. Selections MUST be non-empty. +Only `Block` scope uses ranges. `SeedMask` and `SeedRanges` define the +`SeedOffer` class in section 6. Ordinary and seed selections MUST use separate +rows with the same inheritance order. A seed update MUST NOT change ordinary +demand or promised coverage. A removal changes only its selection class; +`InheritOrdinary` and `InheritSeed` restore only their respective class. +`FullBlock` terminates both classes for the block. Each send MUST match an +enabled row and an immutable grant of the same class. Connection budgets, +credit accounting, and send-once records remain shared across both classes. + +Route updates and `FullBlock` MUST share one ordered control stream per +connection direction. +Their `control_seq` starts at one and increments without gaps or wraparound. +The sequence of a `SubscribeParts` also identifies its grant. +A connection restart discards its sequence space and every grant. + +The sender MUST put `HeaderMeta` before the first part on each ordered block +data stream. It MUST use at most one such stream per block per direction on a +connection. A metadata duplicate from another peer MUST NOT cause the +receiver to lose the stream's association with the admitted metadata. +Bounded transport buffering may hold parts while metadata admission completes. +Data queues MUST NOT block service of control traffic. + +### Immutable grants + +The receiver MUST record a receive grant before sending `SubscribeParts`. +The sender MUST reserve response work and record the matching send grant before +enabling the update. A grant covers the message's selection, scope, height +interval, part credit, and encoded-byte credit. Grants do not change when +routes change. + +A default grant matches any authenticated proposer. A proposer grant matches +only that key. A block grant matches only that block and its exact indices. +The height interval MUST contain that block's admitted height. +`first_height <= last_height` MUST hold. The inclusive interval MUST be finite +and at most `MAX_GRANT_HEIGHT_SPAN`. Local relevance limits MUST bound how far +ahead a receiver issues credit. + +For a new part send, the effective route MUST enable the index and one grant +MUST cover it. Both remaining counters MUST suffice. The sender consumes one +part and the complete encoded `BlockPart` byte count before queueing it. +The receiver consumes the same counters before proof verification. +Parts MUST NOT combine credit from several grants. + +Every subscription MUST satisfy: + +```text +0 < part_credit <= MAX_GRANT_PARTS +byte_credit = part_credit * MAX_PART_MESSAGE_BYTES +charge = byte_credit + REQUEST_OVERHEAD +``` + +Arithmetic MUST be checked. Senders MUST reserve outstanding response bytes +before accepting the grant. They MUST also pace actual sends within service +budgets. Receivers SHOULD preissue enough credit for several blocks; routes +with exhausted credit cannot deliver, even when their bitmap cells are enabled. + +For each grant, response accounting MUST preserve: + +```text +reserved_bytes = unsent_credit + queued_bytes + sent_bytes + released_bytes +``` + +The fixed request overhead is not refundable. Grant closure MUST release unused +credit exactly once. Queue cancellation MUST release its reservation exactly +once. A route update MUST NOT close a grant still usable by other routes. +The sender MUST NOT restore grant counters for a canceled queued part: the receiver cannot +observe that cancellation. A new grant supplies any replacement credit. + +Unsubscribe stops new sends under the effective route. It cannot revoke +already consumed credit. Receivers MUST classify responses against immutable +grants, not current route bits. The same rule applies after `FullBlock`. + +Nodes MUST cap grants and retire them after exhaustion, connection closure, or +the bounded retention policy for their height interval. IDs MUST never be +reused within a connection. Retirement MUST NOT turn delayed honest data into +a protocol violation. A bounded late-message path MUST drop parts referencing +retired or locally evicted grants without allocation or proof verification. +An unknown ID below the last issued control sequence may be stale; it MUST +receive the same bounded treatment. An ID never yet issued is invalid. + +Nodes MUST bound stale traffic separately, so retiring credit does not create +unlimited receive work. Local capacity pressure MAY pause admission or close +the service without blaming the peer. + +## 5. Message protections + +Every message MUST have one declaration defining frame and field bounds, +admission filters, state changes, sender obligations, and resource costs. +The wire variants, declarations, handlers, and reference model MUST form one +closed inventory. + +The common path is: + +```text +frame cap -> cadence / work bound -> bounded decode + -> authorization -> verification -> relevance -> handler +``` + +A fixed prefix MAY support authorization before variable-field decoding. +Decoders MUST reject non-canonical encoding, trailing bytes, arithmetic +overflow, and invalid field lengths. A frame cap does not replace field +allocation limits. Transports MUST enforce incomplete-frame deadlines. +Each peer and message kind MUST have bounded cadence accounting. The profile +MUST specify sender rates and bursts compatible with the receiver's allowance. +Response credit bounds outstanding work; service budgets bound work over time. +Neither bound replaces the other. + +| Message | Required protection and effect | +| --- | --- | +| `HeaderMeta` | Bound header, key proof, signature, and verification work. Fully admit the header and authenticate metadata before assembly or forwarding. Cap candidates per height, pending parents, and recent blocks. | +| `BlockPart` | Match an admitted block and live grant. Check height, scope, index, credit, exact payload length, and proof shape. Consume credit before proof verification. Store and forward only after verification. | +| `SubscribeParts` | Check sequence, canonical selection, scope, height span, credit arithmetic, and state caps. Reserve maximum response work before atomically adding a grant and route bits. | +| `UnsubscribeParts` | Check sequence, scope, action, and cadence. Remove bits from the effective row, creating a copy if needed, or remove an override. Cancel queued parts that no longer have an effective route. | +| `FullBlock` | Require a previously announced, retained block and bounded cadence. Mark this peer complete and cancel its unsent parts for this block. Do not alter future routes. | + +Additional obligations: + +- A sender MUST send each `(block_id, part_index)` at most once per connection, + across all grants and scopes. It MUST mark the send when queueing. + Canceling a queued part MUST NOT clear that mark. Repair must use another + peer or the existing block-download path if that part is still needed. +- A node MUST deduplicate storage by `(block_id, part_index)`. Copies from + different peers are valid measurements and consume their respective credit. + A repeat from the same peer while its receive record remains live violates + the send-once rule. Local retirement instead uses the bounded stale path. +- A node MUST suppress sending a part back to a peer from which it received + that valid part. Simultaneous crossing sends remain legal. +- A block-scoped subscription requires admitted metadata previously received + from that peer. The handler MUST schedule retained requested parts and watch + for parts that arrive later. It need not already possess them. +- `InheritOrdinary` and `InheritSeed` are valid only for proposer or block scope. Repeated removals and + inheritance requests are idempotent. Control sequences still advance. +- A proposer-scoped update need not follow that proposer's next block, but it + MUST fit the receiver's bounded selector capacity. Only authenticated metadata + can cause data or measurements to use that selector. +- A node MUST send `FullBlock` only after reconstruction and the codeword, + padding, length, and canonical-assembly checks in section 2. This message is + not a claim that full consensus validation has finished. +- `FullBlock` MUST be terminal for that block on the connection. The receiver + MUST suppress later demand from that peer for the completed block. It MUST + still process control sequencing and unrelated future-block demand. A node + that later loses its data MUST recover through another connection or the + existing full-block service. +- Completion is directional: receiving a peer's `FullBlock` stops sends to + that completed peer. It MUST NOT prevent the local incomplete node from + requesting missing parts from that peer within the fallback rules below. +- A node SHOULD send `FullBlock` to each peer that knows the metadata. If a new + peer announces it later, the node SHOULD reply with `FullBlock`. + Repeated completion notices are idempotent and cadence-bounded. +- `FullBlock` is an advisory availability hint. A dishonest peer can stop its + own incoming traffic or falsely attract repair requests. It cannot cancel + anyone else's traffic or establish block validity. + +Admission returns `Continue`, `Drop`, `Delay`, `Disconnect`, or `LocalFault`. +A demonstrably invalid frame, proof, signature, or live-grant response returns +`Disconnect`. A valid duplicate, stale block, stale authorization, or local +loss of relevance returns `Drop`. A missing parent invokes bounded recovery. +Resource exhaustion returns `Delay` or closes the service as a local capacity +event. Delays MUST NOT create an unbounded secondary queue. +Admission waits MUST have a finite timeout. If a wait would indefinitely block +route cancellation or completion on that peer, the node MUST close the service +as a local capacity event and schedule recovery elsewhere. + +Every disconnect rule MUST have a matching sender obligation. Reorganization, +local eviction, stream timing, or a crossed unsubscribe MUST NOT establish +peer misconduct. Nodes MUST log bounded diagnostic records for non-continue +results. A decoder, verifier, or peer-worker fault MUST remain local to that +work; it MUST NOT crash unrelated peer paths or refund consumed receive credit. + +## 6. Forwarding, completion, and recovery + +After admitting metadata, a node SHOULD promptly forward it once to every +eligible peer. Metadata forwarding MUST remain independent of part routes. +Its scheduling MUST avoid a per-hop header request round trip. + +Upon receiving a valid new part, the node MUST store it within assembly bounds +and schedule it for each eligible subscriber. It MUST NOT wait for full-block +assembly. Queue eligibility requires admitted metadata, an enabled route, +credit, no prior send, and no `FullBlock` from that subscriber. +The node MUST suppress a send to a peer from which it has already received a +valid copy of that index. Crossed sends before either copy arrives remain legal. + +A queue limit on one peer MUST NOT block another peer. The scheduler MUST bound +per-peer and global queue bytes, verification concurrency, reconstruction +concurrency, and retained assemblies. The receiver SHOULD prioritize distinct +coverage when allocating its grants. The sender cannot infer which grants +represent challenges or redundant routes: these roles are not on the wire. +The sender SHOULD use a work-conserving, byte-fair scheduler across active +blocks on each connection. It MUST bound starvation of eligible parts. +All block streams on a connection MUST share its transport and queue budgets. +After evicting a block's send-once or completion history, a node MUST NOT resume +sending that block on the same connection. Bounded retirement watermarks or a +service restart MUST enforce this rule. Locally retired inbound data uses the +bounded stale-message path. + +After reconstruction checks succeed, the node MUST cancel its outstanding +block-specific receive demand with `FullBlock`. It SHOULD continue serving +retained and regenerated parts to incomplete peers for a bounded retention +interval. It MUST retain part proofs or regenerate the canonical Merkle tree. + +A node MUST start a monotonic reconstruction deadline when it admits metadata. +It MUST NOT reset that deadline indefinitely on partial progress. Normal +propagation MUST use standing push subscriptions and authorized initial seeds. +On a stall, peer loss, or deadline expiry, the node MUST enter a bounded +fallback state. It SHOULD request missing parts from peers that announced the +same metadata and reported `FullBlock`, subject to independent timeouts and +diversity. Receipt of `FullBlock` alone MUST NOT cause normal-path demand to +expand. An incomplete node already in fallback MAY start a bounded request when +a newly eligible completion report arrives. + +Recovery MUST first request enough distinct missing indices to make decoding +possible. Additional parity indices are valid substitutes for missing data. +The receiver MAY retain the first authenticated header supplier as a bounded +candidate record. Header arrival alone MUST NOT count as part availability or +authorize automatic parent-tree repair in the normal propagation path. +If no eligible peer advertises completion, the node MUST wait within the fixed +fallback deadline or use the existing block-download path. It MUST NOT issue +unbounded speculative requests to propagate a tree of unknown availability. +It MAY add duplicate requests when expected latency justifies their cost. +No separate repair request, acknowledgement, or unavailable message is needed: +the subscription schedules current and future availability, and a local +deadline handles silence. + +A recovery attempt MUST retain its supplier, requested indices, consumed +credit, and progress deadline within the block's bounded recovery state. +Reissuing an unchanged subscription to a silent supplier MUST NOT reset the +total recovery deadline or replenish credit. After a no-progress deadline, +the receiver SHOULD try another eligible supplier when one exists. +An admitted `FullBlock` report MAY prioritize a candidate, but MUST NOT bypass +authorization, verification, or the remaining recovery budget. +The receiver MUST charge concurrent attempts to one block recovery budget +and the node's aggregate limits. Exhausting that budget triggers bounded +fallback even if the physical peer graph remains connected. + +The node MUST record the reason and time of fallback entry, requested and +delivered repair bytes, control bytes, cancellation tails, and the terminal +outcome. Reports MUST exclude fallback completion from normal-path success. +Fallback MUST share transport and aggregate resource limits with normal work; +it MUST NOT reset ordinary grants or authorize unlimited source upload. + +After a bounded number of attempts or a fixed total recovery deadline, the +node MUST use the existing full-block download path. It MUST verify that result +against the consensus header. Bad coding metadata MUST NOT suppress this path. + +Delivery requires a reachable honest source of enough parts or the full block. +Neither parity nor local route counts prove this condition. Implementations +MUST report when they cannot satisfy coverage or recovery policy. + +### Proposer seeding (candidate extension) + +Ordinary subscriptions retain the forwarding and fairness obligations above. +An all-parts selection does not authorize the proposer to reinterpret the +receiver's requested coverage as a discretionary seed subset. The proposed +`SeedOffer` selection would distinguish permission to receive a sender-chosen +subset from a request for ordinary coverage. It would use `SubscribeParts`, +existing scope/height bounds, and immutable part/byte credit. It would not add +a sixth message family. W1 defines its discriminators and cancellation classes. +Implementations MUST NOT enable W1 before selecting transport negotiation, +chain admission, and aggregate production resource bounds. + +A receiver SHOULD offer indices for which it has outgoing demand, or enough +indices to support a decodable local bootstrap. The proposer SHOULD prefer +eligible recipients with useful outgoing demand. An offer is a local forwarding +hint, not proof of a global delivery path. The proposer MUST report a shortage +of distinct eligible seeds within its budget. It MUST NOT hide that shortage +by treating ordinary pull repair as normal seeding. + +A profile that adopts this extension MUST enforce these rules: + +1. A seed offer defines an eligible index set using a mask or block-specific + ranges. It permits any subset of that set within the grant's remaining + credit. It does not promise a minimum delivery count or particular indices. + An all-index offer is required for the unconstrained scheduling bound below. +2. The receiver MUST install the offer before sending it. The sender MUST + consume one matching immutable grant before queueing each complete part. + Scope, admission, proof, height, send-once, cancellation, and replay rules + MUST also apply to seed traffic. Overlapping offers MUST NOT multiply credit + for the same send or create an unbounded per-block ledger. +3. A seed grant MUST NOT establish incoming distinct-part coverage before its + actual indices arrive or an ordinary assignment establishes that coverage. + A receiver MUST NOT count every index in an offer as promised service. +4. The proposer MUST share upload, queued-byte, and work limits across seed + traffic, ordinary subscriptions, repairs, and concurrent blocks. It SHOULD + prioritize unseeded distinct indices during its initial pass. Extra copies + for bootstrap or recovery MUST count against the same aggregate budget. +5. The proposer MUST retain bounded per-block state for indices queued or sent + to each peer. It MUST NOT treat transport submission as receiver verification + or continued downstream availability. A failed path MAY require reseeding + elsewhere within the repair reserve. +6. The receiver MUST verify and forward seeds under the ordinary part rules. + If the offered subset and standing routes fail to complete the block, it + MAY request additional indices only after entering bounded fallback. + A completed receiver sends `FullBlock`; + authorized in-flight seed parts follow the existing cancellation rules. +7. The proposer MUST report insufficient eligible credit, upload budget, or + service as degraded seeding. It MUST NOT send unsolicited parts or starve + ordinary accepted demand to preserve a claimed one-codeword upload budget. + +The selected profile MUST distinguish ordinary selections from seed offers +before either peer installs a seed grant. An unsupported selection MUST NOT +silently become an all-part subscription. A route change, offer replacement, +or switch from seeding to ordinary repair MUST NOT reset consumed counters. +Cancellation releases queued reservations once without restoring part or byte +credit. Grant retirement and delayed parts follow section 4. A scheduling +portion is a local service quantum: the sender MUST authorize and charge each +part separately and MUST NOT wait to fill a portion before serving queued work. + +For a static reference, let `w` be complete part bytes, `N` the chosen number +of distinct seeds with `0 < N <= n`, `U` the proposer byte rate, `c[p]` each +peer's independent byte rate, and `g[p]` each peer's usable any-index part +credit. Compute `g[p]` from both remaining part credit and `floor(byte_credit/w)`. +Assume no existing queued work, fixed positive rates, and a valid codeword. +A count allocation `a[p]` has: + +```text +sum(a[p]) = N; 0 <= a[p] <= g[p] +T(a) = max(N*w/U, max_p(a[p]*w/c[p])) +``` + +Select the `N` earliest slots `j*w/c[p]`, for `1 <= j <= g[p]`. Their peer +counts minimize the second term. The first term is independent of allocation. +Constant rates `a[p]*w/T` construct a feasible schedule under both rate limits, +so this bound is attainable in the stated model. Restricted index sets, +unknown or shared peer bottlenecks, deadlines, and downstream reconstruction +invalidate that optimality claim. Production scheduling MUST use available +measurements and bounded adaptation instead of claiming knowledge of `c[p]`. + +For a separate reachability reference, assume an honest connected graph, +valid parts for an arbitrary body with no prior body information, adequate +credit, retained data, and fair eventual service. Every relay edge subscribes +to all indices. After removing the proposer, each remaining connected +component requires at least `k` distinct seeded indices. That condition is +necessary and sufficient for eventual reconstruction in this model. +Subscription count, some parity at each peer, or `k` seeds spread across +disconnected components MUST NOT be reported as satisfying that condition. + +If removing the proposer leaves `c` components, at least `c*k*S` payload bytes +must cross those cuts in this reference model. Receivers cannot promise +completion under a smaller aggregate source budget. Sparse subscriptions, +Byzantine peers, finite retention, and fixed deadlines require additional +evidence. A rooted repair path only establishes eventual delivery under its +honesty, credit, retention, and service assumptions. Implementations MUST keep +the bounded repair and fallback behavior above; fallback does not remove a +physical upload bottleneck. The design tracks the remaining +[bootstrap work](../design/dogwood.md#open-problems-and-todos). + +## 7. Redundancy and route control + +The following byte-budgeted pairwise controller is the baseline experimental +policy. Its stability and performance have not been established. Implementations +MAY improve its estimator while preserving explicit resource, exploration, +recovery, and coverage bounds. The controller allocates subscriptions; +transport congestion control separately paces bytes. +The [experiment report](../design/dogwood-experiments.md) records tests of a +reduced controller. That controller does not consistently improve on static +allocation. The tests do not validate the full policy or select production +values for its learned budget. + +### Core rules + +The detailed rules below implement this control loop: + +1. The receiver SHOULD select a bounded set of suppliers per part at startup. + It MUST NOT enable every part merely because a peer connected. Additional + subscriptions require a coverage, challenge, or recovery purpose. +2. The receiver SHOULD retain bounded randomized challenges for each active + proposer. It MUST compare the same parts under comparable workload. +3. The receiver SHOULD move demand after repeated wins. It MUST install the + replacement before removing the incumbent and preserve failure coverage. +4. The receiver MUST account for bytes across all proposers and active blocks + sharing a connection. It SHOULD limit that connection to one unsettled move + and collect fresh evidence before moving more demand into it. +5. The receiver SHOULD raise its assignment budget only after a loaded success + and lower it after repeated uncanceled deadline failures. Hard grant and + queue limits MUST remain independent of this learned budget. +6. The receiver MUST recover stalled blocks within bounded deadlines. It MUST + send `FullBlock` after reconstruction checks and MUST NOT treat canceled + demand as a later delivery failure. + +### Distinct-part coverage + +Let `A[p]` be the indices assigned to peer `p` for this block, after resolving +overrides. Count only routes backed by sufficient credit and expected service +capacity. For a set `F` of failed peers or known failure groups: + +```text +coverage(F) = |union(A[p] for p not in F)| +margin(F) = coverage(F) - k +``` + +The receiver SHOULD maintain `coverage(F) >= k + safety_parts` for every +failure set in its configured failure model. `safety_parts` is nonnegative. +The default model SHOULD include loss of any one upstream peer. A receiver +MUST NOT describe distinct peer identities as independent physical paths. + +Let `m = |union(A[p] for all p)|` be subscribed distinct coverage. With one +supplier per subscribed index and no extra routes, tolerance of any one peer +requires `max_p |A[p]| <= m - k - safety_parts`. This equals the `n - k` +parity allowance only when every encoded index is subscribed and the safety +margin is zero. Committing more parity without subscribing to more distinct +indices MUST NOT increase reported coverage. If a fast peer exceeds its allowed +share, the receiver needs extra distinct coverage elsewhere. This permits many +parts per connection without silently abandoning redundancy. + +With one supplier per index and zero safety margin, largest exclusive share +`f = max_p |A[p]| / m` +requires subscribed overhead `(m-k)/k >= f/(1-f)`. With balanced assignments +to `d` suppliers, the exact integer test is `m-ceil(m/d) >= k`. For `k=32` +and `m=40`, five suppliers can satisfy this test with eight parts each. +Four suppliers cannot satisfy it without extra routes or a different parity +profile. A receiver MUST report degraded coverage if it chooses concentration +or reduced traffic that fails its configured failure model. + +The receiver SHOULD compare one supplier per part with duplicate subscriptions +under its proposer-upload, receiver-byte, latency, and coverage targets. +It MAY retain duplicates when they improve that tradeoff within byte limits. +Committed parity MUST remain fixed by the selected profile. Unmeasured default +or proposer routes SHOULD start with two selected suppliers per part where +available. The receiver SHOULD distribute these assignments across eligible +peers, subject to byte limits, rather than assigning every part to the same +two peers. Two-peer networks may necessarily select both peers for every part. +The receiver SHOULD remove startup redundancy only after repeated successful +reconstruction observations. + +A receiver MUST evaluate coverage using the actual `n` and part mapping when +metadata arrives. If it cannot meet its target, it MUST mark the block degraded +and add recovery demand. It cannot retroactively guarantee a fast path for a +new block size, topology, or proposer. + +### Measure opportunity and delay + +All timestamps in this section come from the receiver's monotonic clock. +The baseline MUST NOT depend on a sender timestamp, synchronized clocks, an +application RTT estimate, or an inferred bandwidth-delay product. + +This baseline restriction does not preclude a future negotiated telemetry +extension. A candidate `BlockPart` envelope could carry a connection-local +sequence and a monotonic transport-submission timestamp. These fields would +change at each hop outside the part commitment. +Their units, precision, reset/wrap behavior, authentication, and byte limits +remain profile choices. They are not wire fields in this draft. + +Such an estimator MUST NOT treat remote-clock subtraction as measured one-way +delay without accounting for clock error. It MUST NOT treat achieved delivery +rate as unused capacity or grant authority. It MUST tolerate absent, stale, +reordered, or dishonest telemetry without affecting part validity. Local +arrival and verification measurements remain available when telemetry fails. +The [design](../design/dogwood.md#delivery-feedback-and-sender-timestamps) +describes the candidate estimator and its limits. + +Let `t0[b]` be local metadata admission. Local policy MUST select a bounded +observation duration `D[b]` from the admitted block size and a configured +latency target. The deadline is `t0[b] + D[b]`. It MUST remain fixed for that +observation. Increasing load MUST NOT continually extend existing deadlines. +The controller MUST measure local verification queueing separately from arrival. +Only proof-verified arrivals may win races or establish successful delivery. + +An eligible opportunity requires an enabled route and sufficient reserved +grant credit. The controller MUST exclude assignments that its own insufficient +credit, service closure, or resource exhaustion prevented from completing. +It MUST still report those events as local capacity failures. +For each eligible peer and part that remains requested until the deadline: + +```text +delay[p,i] = min(valid_arrival[p,i] - t0[b], D[b]) +delay[p,i] = D[b] when the part is absent at the deadline +``` + +The controller MUST record cancellation time when it sends `FullBlock`, removes +a route, or abandons an assembly. A still-missing copy at that time is censored, +not a deadline miss. A valid arrival before cancellation remains a delivery +sample. Two copies may still establish an arrival-order comparison before the +cutoff. A later in-flight copy MUST NOT turn canceled demand into a failure. +The receiver MUST NOT delay `FullBlock` to complete a measurement. + +For uncensored eligible observations, define: + +```text +yield[p] = timely_deliveries[p] / eligible_observations[p] +service[p] = timely_valid_payload_bytes[p] / fixed_observation_duration +``` + +Zero-opportunity peers have no sample. The receiver MUST report the censored +fraction alongside yield. Yield under early cancellation is not an unbiased +estimate of deadline reliability. `service` measures achieved delivery under +offered load, not unused capacity or sender queue occupancy. + +Assignments added after metadata admission are recovery observations, not +pre-block race samples. A newly issued standing route is warming until the +receiver has observed a valid response under its grant. It SHOULD participate +in negative race evidence only on subsequent blocks. The protocol has no +subscription acknowledgement, so silence cannot prove when the sender installed +a route. Startup silence still triggers bounded recovery and route exploration. + +For any smoothed metric: + +```text +estimate_next = (1 - alpha) * estimate + alpha * sample +0 < alpha <= 1 +``` + +Measurements SHOULD use this context, with fixed local bucket boundaries: + +```text +size_class(b) = floor(log2(n[b])) +load_class(b) = (floor(log2(max(1, active_block_count))), + floor(log2(max(1, sum_active_blocks(n))))) +context(b) = (authenticated_proposer(b), size_class(b), load_class(b)) +``` + +The receiver MUST snapshot the context at metadata admission, including `b`. +It MUST also record subsequent peak active bytes and block count. A material +change in load makes the observation unsuitable for promoting a route in the +original context; it still informs recovery and block-outcome reports. +The policy MUST define what constitutes a material change. + +The receiver SHOULD use proposer-specific comparisons when enough recent +blocks exist in that context. Otherwise pooled measurements for the same size +and load classes MAY guide candidate selection, followed by randomized exploration. +Pooled measurements MUST NOT supply promotion votes for another proposer or +replace its learned routes merely because a different proposer published a block. +The receiver MUST age out stale comparisons. Idle periods supply no successful +samples. +Different-part delivery statistics MAY guide candidate selection. They MUST +NOT count as paired promotion votes. Randomized assignment can support a +separate estimator under explicit load and censoring assumptions; random +source placement alone does not establish those assumptions. +One large block MUST NOT count as hundreds of independent block observations. +The receiver MUST cap contexts, peer pairs, and tracked proposers. + +### Account for block size and concurrent blocks + +Let `A[p,b]` be the effective assigned indices for peer `p` and admitted block +`b`. Each index appears once per peer even if several grants authorize it. +Redundant suppliers each incur their own cost. Define: + +```text +assigned_bytes[p,b] = S[b] * |A[p,b]| +pending[p,b] = S[b] * count(assigned copies not yet received or retired) +Q[p] = sum_active_blocks(pending[p,b]) + cancellation_tail[p] +x[b,j] = S[b] * count(indices selected by mask bit j of block b) +S[b] * floor(n[b]/P) <= x[b,j] <= S[b] * ceil(n[b]/P) +``` + +`Q[p]` estimates outstanding requested payload, including upstream-unavailable +parts. It is not a measured queue. Removing demand SHOULD transfer its pending +estimate to a bounded cancellation tail rather than instantly crediting spare +capacity. Arrival or a fixed local tail timeout retires that estimate. Retirement +of this soft estimate MUST NOT release immutable grant reservations early. +The receiver MUST NOT count a copy in both pending bytes and the tail. + +Each connection has a soft assignment budget `W[p]` and hard resource bounds. +All proposers and active blocks MUST share `W[p]`. A discretionary addition +of `x` bytes SHOULD require `Q[p] + x <= W[p]`. A loaded trial MAY exceed it +by at most the separately reserved probe allowance. Recovery uses a separate +bounded reserve and MUST NOT bypass hard limits. If no route fits, the receiver +MUST report degraded service rather than inventing capacity. + +Before a block exists, the controller SHOULD evaluate additions against a +bounded workload scenario from recent block sizes and burst concurrency. +For a scenario containing part counts `n_hat[1..m]`, a conservative cost for +one additional mask bit is: + +```text +x_hat = sum_r(S * ceil(n_hat[r] / P)) +``` + +The scenario MUST count future blocks from all proposers that can share the +connection, not reserve a separate full budget for every proposer. Local policy +MUST define its finite sample history, conservative quantiles, and startup +scenario. The scenario is a forecast, not an admission or security bound. + +On every metadata admission, the receiver MUST resolve actual assignments, +credit, coverage, and aggregate `Q`. Standing grants can produce `Q > W` before +control updates take effect. The receiver MUST continue to authorize honest +in-flight parts under their immutable grants. It SHOULD stop discretionary +additions to that connection and rebalance through block-specific overrides. +It MUST NOT claim that those post-header corrections preserve the zero-request +latency of a standing route. + +The receiver SHOULD install standing routes for the workload it expects next. +The wire does not automatically select a different proposer row by block size +or concurrency. An implementation that requires that behavior needs a future +selector extension; it MUST NOT assume the sender knows receiver-local load. + +### Challenge and move load + +A challenge adds a supplier for selected parts before a future block. The +receiver SHOULD first use comparisons from existing backup subscriptions. +When it adds demand, it SHOULD choose an incumbent mask bit weighted by assigned +bytes and an alternative peer uniformly from eligible peers not serving those +parts. Unmeasured and previously slow peers MUST remain eligible for exploration +unless independent service or resource constraints exclude them. + +#### Challenge frequency + +The receiver MUST bound both extra authorized bytes and trial-start frequency. +A timer alone cannot set a useful rate: it churns routes during idle periods, +and the same number of trials costs more under larger or concurrent blocks. +The baseline uses one node-wide exploration balance, not one allowance per key: + +```text +0 < exploration_fraction < 1 +0 <= exploration_initial <= exploration_cap +E = exploration_initial +V[b] = n[b] * MAX_PART_MESSAGE_BYTES +E = min(exploration_cap, E + floor(exploration_fraction * V[b])) + on the first reconstruction and consensus acceptance of block b +E = E - challenge_charge + before authorizing extra challenge responses, only if E >= challenge_charge +``` + +The receiver MUST use checked arithmetic for balances and charges. +It MUST credit each relevant block hash at most once across peers, +proposers, reconnects, and reorganization. It MUST bound the eligible height +window and deduplication history; retired blocks cannot earn credit again. +Idle time, duplicate parts, repeated metadata, and proposer-key changes MUST +NOT replenish or reset `E`. Failed blocks earn no credit; recovery retains its +separate reserve. This balance adapts the token-bucket idea in +[RFC 3290, appendix A](https://www.rfc-editor.org/rfc/rfc3290.html#appendix-A) +to completed block traffic instead of elapsed time. + +`challenge_charge` MUST cover the maximum encoded response bytes authorized +for the trial, including warming, plus bounded subscription-control bytes. +The receiver SHOULD issue dedicated finite challenge grants. If another live +grant can authorize additional responses on the trial route, the receiver MUST +charge those bytes too or isolate the trial from that grant. Forecast demand +alone is insufficient. Renewals require another charge. Cancellation MUST NOT +refund the exploration charge: in-flight responses remain authorized. +Existing backup demand needs no extra charge unless the challenge increases its +authorized work. With no refunds, cumulative challenge authorization is bounded +by `exploration_initial + exploration_fraction * sum(V[b])`. + +The receiver SHOULD queue at most one pending trial per recently active proposer +and serve the queue round-robin. New keys join the tail without a fresh budget. +An eligible waiting trial retains its turn while funds accumulate, provided its +charge fits `exploration_cap`. Local policy MUST bound the active-proposer window, +queue size, and waiting time. Expired or infeasible entries leave the queue. +Sparse proposers get opportunities, not a guaranteed number of observations. + +The receiver MUST space node-wide trial starts by at least +`CHALLENGE_MIN_INTERVAL >= CONTROL_INTERVAL`, with independent bounded jitter. +It MUST NOT start a new trial merely because an epoch elapsed. Admission also +requires an active proposer, an alternative peer, sufficient exploration funds, +connection capacity, and a free trial slot. The baseline SHOULD allow at most +one active trial per proposer and per challenger connection. A global +`MAX_CHALLENGES` bounds simultaneous trials and includes warming routes. + +A trial SHOULD retain the same pair and selection across blocks so it can +collect repeated comparable observations. It MUST end on a decision, peer loss, +credit exhaustion, `CHALLENGE_MAX_BLOCKS` distinct admitted matching blocks, or +`CHALLENGE_MAX_AGE`, whichever comes first. The block cap includes warming and +inconclusive observations. Expiry without enough decisive blocks is inconclusive, +not a challenger loss. Removing the trial MUST preserve pre-existing backup +demand. Cancellation tails remain subject to the existing bounds. The receiver +MUST NOT delay `FullBlock` to prolong a trial. + +For equal-size blocks, let `f` be the fraction of a block duplicated in one +trial observation and `rho = exploration_fraction`. Ignoring startup credit, +proof-size differences, and control bytes, each observation costs about `f/rho` +completed blocks of budget. At `rho = 1/32`, duplicating one quarter costs about +eight blocks; three observations cost about 24 blocks. Warming or inconclusive +observations increase this cost. These are accounting examples, not recommended +parameters. Smaller selections permit more observations for the same budget. +More frequent trials cannot create observations for a proposer that rarely mines. +The receiver SHOULD size trial lifetime against proposer opportunities as well +as byte credit. If a proposer supplies an estimated `lambda_p` blocks per unit +time, an age cap `A` supplies only `lambda_p * A` expected block opportunities. +Useful trials need opportunities for warming and repeated votes. The expectation +does not guarantee arrivals or decisive comparisons. Sparse or unfamiliar +proposers MUST retain usable existing or default routes and independent recovery +when trials cannot obtain enough evidence. Expiry MUST NOT become evidence that +the challenger is slow. + +Simulations MUST sweep the byte fraction, minimum interval, selection width, +and trial lifetime together. They MUST measure adaptation in both elapsed time +and proposer blocks, including warm-up cost and trials that expire inconclusively. +The policy MUST retain a positive exploration fraction, but MUST NOT bypass +hard limits or recovery priority to meet a nominal challenge rate. + +#### Compare and promote + +For the same part, a valid copy wins if it arrives at least `race_epsilon` +before the other copy or before a cutoff at which the other is still absent. +The cutoff is the earlier of the deadline and cancellation. Two absent copies +and arrivals separated by less than `race_epsilon` are ties. A win before +cancellation establishes arrival order, not a later deadline failure. + +Within one block and peer pair, the receiver SHOULD aggregate eligible +same-part comparisons into one vote. A strict majority of decisive part +comparisons wins that block; an equal split or no decisive parts is a tie. +The controller MUST NOT infer independent statistical confidence from parts +that share a block, path, or queue. Define over a bounded recent context: + +```text +win_rate(challenger, incumbent) = challenger_block_wins / + (challenger_block_wins + incumbent_block_wins) +``` + +Promotion SHOULD require at least `MIN_RACE_BLOCKS` decisive blocks and +`win_rate >= SWITCH_THRESHOLD > 1/2`. These are policy thresholds, not a proof +of statistical significance or convergence. A small-block context MUST NOT +authorize an untested large-block or high-concurrency concentration. + +Promotion MUST install the replacement before removing the incumbent. It +MUST preserve distinct-part coverage at both stages. The temporary overlap +MUST fit grants and exploration or migration reserves. The controller SHOULD +observe the winner serving the added load before pruning. It SHOULD limit +each connection to one unsettled promotion across all proposers, with at most +`MIGRATION_BYTES` additional forecast bytes. A mask-bit assignment above that limit +cannot be a routine promotion. The receiver MAY use bounded exact-part recovery +or retain the existing route until it can afford a larger trial. + +After the promotion, the receiver MUST collect fresh observations at the new +load before another promotion into that connection. Old wins MUST NOT justify +an arbitrary sequence of load increases. A miss or peer loss triggers immediate +bounded additions; pruning waits for `STABLE_WINDOWS >= 2` successful windows. + +These local checks do not establish global reachability. Concurrent pruning +can remove causal delivery paths despite sufficient local distinct-part +coverage. Implementations MUST NOT claim convergence or normal-path delivery +from coverage counts and past race wins alone. The default experimental policy +SHOULD retain startup supplier edges until a pruning policy passes connected +overlay tests with concurrent changes and separately accounted fallback. + +A fixed-reference stripe experiment preserves suppliers that delivered before +one reference reconstruction. Its argument requires unchanged mask mapping, +codeword shape, seed placement, availability, retained data, and adequate credit. +It establishes eventual delivery under fair service, not a deadline or failure +coverage. This experiment does not amend the whole-body codeword in section 2. +A stripe profile MUST define authenticated stripe identity, commitments, +completion, pipeline bounds, and recovery before enabling that policy. + +### Adjust the shared byte budget + +The baseline uses additive increase and multiplicative decrease on `W`, not on +a transport congestion window. Local policy fixes a monotonic control epoch +of duration `CONTROL_INTERVAL`, bounds `W_min <= W_initial <= W_max`, an +increment `Delta`, a decrease factor `0 < beta < 1`, and a utilization threshold +`0 < utilization_threshold <= 1`. + +A loaded trial snapshots a cohort of concurrently outstanding assigned copies +and the current `W`. Success requires timely valid delivery of at least +`utilization_threshold * W` payload bytes from that cohort, successful observed +block reconstruction, and acceptable uncensored deadline yield. Sequential +small transfers MUST NOT count as evidence for one large outstanding budget. +The receiver MUST have actually offered the trial load. Estimated unused +capacity, canceled copies, or an idle epoch MUST NOT count as delivered trial +bytes. Sparse or unfinished samples postpone adjustment. Each completed trial +MUST support at most one increase; the receiver MUST NOT reuse its success. +Repeated failure requires deadline misses on at least `MIN_FAILURE_BLOCKS` +distinct comparable blocks with eligible uncanceled demand. Local verification +overload MUST NOT be classified as evidence against the remote peer. + +```text +W_next = max(W_min, floor(beta * W)) if repeated eligible failure +W_next = min(W_max, W + Delta) else if successful loaded trial +W_next = W otherwise +``` + +Each connection MUST update its budget at most once per epoch. The receiver +SHOULD require at least two failure blocks and use the settling rule for +increases. It MUST cap `Delta`, trial bytes, and concurrent trials globally. +Each failed block MUST count toward at most one decrease. Failure histories +MUST have a bounded age and reset after an adjustment. +Failure takes precedence over increase. Lowering `W` changes future allocation; +it does not revoke grant credit or establish peer misconduct. + +Budget failure means the assigned route did not meet the delivery target. It +does not identify physical congestion: upstream waiting can produce the same +result. Proposer-specific losses SHOULD first shift that proposer's routes. +If all alternatives stall together, the receiver SHOULD freeze promotions and +budget increases, retain exploration, and invoke bounded recovery. Pairwise +comparison supplies no winning alternative in that case. + +Transport congestion control MUST pace each connection. Sender queue bounds +and service budgets MUST remain active even while the controller learns. +A receiver MUST NOT allocate all parts from raw first-arrival counts or +assume random source distribution eliminates the need for exploration. + +This mechanism borrows randomized comparison from +[power of two choices](https://brooker.co.za/blog/2012/01/17/two-random.html). +The [underlying survey](https://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf) +studies queueing models and delayed information. Those results do not establish +this controller's behavior with persistent subscriptions, unavailable data, +heterogeneous routes, or Byzantine peers. + +### Evaluate block outcomes + +For a completed observation, let `u` be distinct timely valid parts, `c` all +timely valid copies, and `L = body_bytes`: + +```text +decode_deficit = max(0, k - u) +duplicate_parts = c - u +payload_overhead = (received_payload_bytes / L) - 1 +``` + +The overhead metric measures payload relative to the body, including parity, +padding, duplicates, and late parts through the observation cutoff. Wire +accounting MUST separately include proofs, headers, and metadata. +Reports MUST include reconstruction p50/p95/p99, deadline misses, +fallback frequency, and time spent degraded. A low duplicate count alone is +not success. + +## 8. Profile choices and conformance + +### Candidate payload profile W1 + +W1 is a concrete payload profile for review and conformance tests. It does not +enable a network service. Peers MUST negotiate a complete transport and chain +profile before using it. W1 retains section 2's whole-body codeword and 25% +parity. It does not adopt the large-body stripe candidate. + +All integers below are unsigned little-endian. Concatenation has no implicit +padding. Hashes contain 32 raw bytes. Consensus block identifiers use the chain +hash bytes, not reversed display-hex bytes. Length prefixes count bytes unless +stated otherwise. A parser MUST reject unknown tags, nonzero flags, truncated +fields, trailing bytes, and lengths above these bounds before allocation. +Canonical consensus serialization applies inside header and coinbase fields. + +| W1 limit | Value | +| --- | --- | +| `S`; `P`; codec identifier | 65,536 bytes; 16 bits; 1 | +| `MAX_PARTS`; maximum part-proof depth | 65,535; 16 siblings | +| Complete frame; `MAX_PART_MESSAGE_BYTES` | 131,072 bytes; 66,102 bytes | +| `MAX_GRANT_PARTS`; `MAX_GRANT_HEIGHT_SPAN` | 256; 64 heights inclusive | +| Range count; header bytes; key-binding bytes | 128; 4,096; 17,413 | +| Coinbase bytes; coinbase Merkle siblings | 16,384; 32 | + +These are syntax limits, not assembly reservations or performance guarantees. +The profile MUST impose lower admission bounds where consensus or node-wide +work budgets require them. Checked arithmetic MUST enforce `k=ceil(body_bytes/S)`, +`n=k+ceil(k/4)`, and `0=n`. +The section 2 codec vector has this W1 root: + +```text +b930ceb4cc32a00ecd316e1430447d7e8cfd89de5384faecb32c46d7991ee1e6 +``` + +The balanced mapping in section 3 sorts by +`(T("route", block_id || u32(i)), i)` in lexicographic byte order, then assigns +rank modulo 16. The mask encodes bit zero in the least significant bit of its +first byte. + +Use pure Ed25519 from [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032.html), +with these additional acceptance restrictions. The 32-byte public key and +signature point `R` MUST use canonical compressed Edwards encodings and MUST +be nonidentity points in the prime-order subgroup. Reject mixed-torsion and +small-order points, noncanonical field encodings, and negative zero. The +64-byte signature consists of `R || S`; the little-endian scalar MUST satisfy +`S < 2^252 + 27742317777372353535851937790883648493`. Verify the usual Ed25519 +equation after these checks. Implementations MUST test library behavior against +these restrictions. Ed25519ph and Ed25519ctx do not implement W1. + +The chain identifier is the consensus genesis block's raw 32-byte hash. +The signature covers this exact 171-byte transcript: + +```text +ASCII("Dogwood/metadata/1") || 00 || chain_id || u16(1) || +block_id || proposer_key || coding || part_root +``` + +Header admission MUST supply `block_id`; a sender-supplied identifier does not +substitute for admission. The metadata variant identifier is +`T("variant", transcript)`. It excludes the signature and key-binding proof. +A signature does not replace proof of the coinbase commitment or body validation. + +#### Frame and field grammar + +The complete frame is `u16(type) || u16(0) || u32(payload_length) || payload`. +The payload starts with a duplicate `u8(type)` that MUST match the outer type. +Types 1–5 respectively identify `HeaderMeta`, `BlockPart`, `SubscribeParts`, +`UnsubscribeParts`, and `FullBlock`. The native service carrier and its stream +identifiers remain integration choices; this frame defines application bytes. +The following table lists fields after the payload type, in transmission order. + +| Message | Fields | +| --- | --- | +| `HeaderMeta` | `u16(header_length)`, header, proposer key (32 bytes), `u16(binding_length)`, binding, coding tuple, root (32 bytes), signature (64 bytes) | +| `BlockPart` | block identifier, `u64(grant_seq)`, `u32(index)`, payload (65,536 bytes), `u8(sibling_count)`, sibling hashes | +| `SubscribeParts` | `u64(control_seq)`, scope, selection, `u32(first_height)`, `u32(last_height)`, `u32(part_credit)`, `u64(byte_credit)` | +| `UnsubscribeParts` | `u64(control_seq)`, scope, `u8(action)`, selection only for action 0 | +| `FullBlock` | `u64(control_seq)`, block identifier | + +Header and binding fields MUST be nonempty. The binding contains +`u32(coinbase_length) || canonical_coinbase || u8(sibling_count) || siblings`. +The coinbase MUST be nonempty. The transaction path always starts at index +zero. Its hashing and transaction-id rules come from the selected chain +adapter, not the Dogwood part tree. Parsing this envelope does not establish +that its transaction, header, or commitment is valid. + +A scope starts with one byte: 0 for `Default`, 1 followed by a proposer key, +or 2 followed by a block identifier. A selection starts with one byte: 0 for +ordinary mask, 1 for ordinary ranges, 2 for seed mask, or 3 for seed ranges. +A mask is a nonzero `u16` and is valid only with default or proposer scope. +Ranges are valid only with block scope and contain `u16(count)` followed by +`u32(start), u32(end)` pairs. Require 1–128 sorted, disjoint, non-adjacent, +nonempty half-open ranges with `end<=65535`. Admission MUST also require +`end<=n` for the admitted block. + +Unsubscribe action 0 removes its selection. Action 1 restores ordinary +inheritance; action 2 restores seed inheritance. Inheritance actions have no +selection and MUST NOT use default scope. Empty explicit rows remain distinct +from absent rows under section 3. + +All sequence values MUST be nonzero and obey section 4's ordering rules. +A grant MUST cover 1–64 heights inclusive and 1–256 parts. Its `byte_credit` +MUST equal `part_credit * 66102`. This reserves the largest legal complete +part frame for each authorized part. Actual sends still charge their complete +frame length under the immutable-credit rules. A W1 part at `n=40` occupies +65,782 bytes; the experiments' 384-byte overhead allowance is a model input. +Stream/transport overhead requires additional capacity outside this frame credit. + +### Parameter registry + +This registry is authoritative for the draft's parameter meanings and reference +experiment settings. A value marked experimental defines a reproducible input, +not a production recommendation or evidence of conformance. A value marked +`TBD` MUST be fixed when required by an implementation's selected profile or +local policy before that implementation claims conformance. Optional future +extensions do not block baseline conformance. Reports MUST +record all overrides. The [design rationale](../design/dogwood.md#param-tuning) +does not override this registry or the requirements in earlier sections. + +#### Workload and local policy + +`S` denotes part payload bytes. `k_ref=32` defines the 2 MiB reference body. +Byte-valued controller settings below use payload bytes; grant accounting +continues to count complete encoded messages. Experimental queue and reserve +counts MUST also have byte bounds in an implementation. + +| Parameter | Reference value | Meaning and authority | +| --- | --- | --- | +| Workload | 50,000 TPS; 2,048 bytes/transaction | Planning assumption, not consensus configuration. | +| Utilization target; extra traffic allowance | 0.80; 0.05 | Experimental capacity-planning inputs. The allowance is relative to body plus parity. Neither reserves protocol credit. | +| Reference body and cadence | `k_ref=32`; 20.48 ms | Sustained-load experiment at the planning rate; also test `k=128` at 81.92 ms. These are synthetic releases, not a proposed block interval. | +| Subscribed distinct count `m` | `n` | Experimental starting point. Local policy MAY request fewer indices if it reports and handles the resulting coverage. | +| Failure sets; `safety_parts` | Any one supplier; 0 | Default failure model and experimental margin. Local policy MUST declare correlated groups and any additional safety margin. | +| Startup copies per index | 2 where available and affordable | Default local policy. Learned copy count is selected under coverage and byte limits; one copy is an experimental comparison. | +| Seed distinct target `N`; proposer rate `U`; peer rates `c[p]` | `N=n`; 1 Gbps source in static examples | Experimental seeding objective and model inputs. Production rates come from bounded measurements; they do not authorize traffic. | +| Seed credit `g[p]` | Receiver-granted; `n` per peer in static examples | Hard immutable part and byte credit. An eligible set may further constrain the scheduler. | +| Seed upload budget; seed repair reserve; active seed peers | `TBD` local bounds | Required before enabling `SeedOffer`. Count ordinary demand and concurrent blocks too; a one-codeword budget cannot cover every topology. | +| `SEED_BATCH_PARTS` | 1 | Experimental scheduling portion size; sweep 1/2/4. Each contained part retains separate proof and credit accounting. | +| Small-block parity threshold and ratio | Candidate `k<=8`: 100%; otherwise 25% | Tested in a finite sparse overlay; no profile change selected. The draft still requires `ceil(k/4)` parity. A threshold must also identify `S` or body bytes. | +| Bounded-overlay repair times; control and propagation delay | 100/300/600 ms; 20 ms; 5 ms | Single-block experiment overrides, measured from simultaneous metadata admission. These do not replace the controller's 400 ms observation target. | +| Bounded-overlay source credit; receiver repair credit | `n` or `n + nodes*k` parts; `2k` additional requests per receiver | Finite experiment caps at `S + 384` bytes per part. Source credit includes initial seeds and repair. Production must reserve aggregate bytes across concurrent blocks. | +| Seed ordering | `TBD` | Compare systematic-first, parity-first, and a decodable bootstrap receiver before selecting a policy. | +| Observation duration `D[b]`; total recovery deadline | 400 ms; 1,200 ms at `k_ref` | Experimental values. Production size-to-deadline policy is `TBD`; section 7 fixes each admitted observation's deadline. | +| `CONTROL_INTERVAL` | 250 ms | Experimental minimum interval between shared-budget updates. | +| `W_min`, `W_initial`, `W_max` | `2*S`, `20*S`, `256*S` | Experimental payload assignment bounds per connection, shared across proposers. They are not a transport window. | +| `Delta`; `beta`; `utilization_threshold` | `S`; 0.75; 0.80 | Experimental loaded-increase step, decrease factor, and success threshold. | +| `MIN_FAILURE_BLOCKS` | 2 | Default minimum distinct eligible failures per decrease. | +| `MIN_RACE_BLOCKS`; `SWITCH_THRESHOLD`; `race_epsilon` | 3; 2/3; 1 ms | Experimental decisive block votes, required winning fraction, and ignored timing difference. | +| `STABLE_WINDOWS`; settling interval; acceptable uncensored yield | `TBD` | Required local policy. Reduced simulations omit the complete settling rule and MUST NOT claim conformance. | +| `MIGRATION_BYTES` | `4*S` | Experimental maximum newly assigned payload per move; actual mask cost MUST fit. | +| Loaded-probe allowance; concurrent loaded trials | `TBD` | Required node-wide local bounds in addition to `W`; not implicit spare capacity. | +| `exploration_fraction` | 1/32 | Experimental authorization funding per completed validated encoded byte. | +| `exploration_initial`; `exploration_cap` | `2*k_ref*S`; `4*k_ref*S` | Experimental initial and capped funds. Production node-wide values MUST include control bytes and fund a minimum trial. | +| Challenge selection width | One mask bit | Experimental minimum comparison load; it does not multiply independent block votes. | +| `CHALLENGE_MIN_INTERVAL`; jitter | 250 ms; uniform 0–25% extra | Experimental minimum time between starts and additional delay. | +| `CHALLENGE_MAX_BLOCKS`; `CHALLENGE_MAX_AGE`; `MAX_CHALLENGES` | 12; 20 s; 2 | Experimental trial retention and concurrent count. Stop at either age or block cap. | +| Candidate rate-estimator window; minimum samples; EWMA weight | 100 ms; 4 deliveries; 0.5 new sample | Experimental alternative only. Sender timing is not required by the baseline. | +| Candidate rate-estimator initial rate | 100 Mbps per connection | Experimental prior, not discovered capacity or permission to send. | +| Candidate queue-delay target; maximum rate change | `TBD` | Local experimental policy before deploying a queue-aware estimator; the reduced rate allocator omits these gates. | +| Link queue; recovery-copy reserve | 256 parts; `2*k` copies/block | Experimental bounds. Production MUST also cap node-wide queues, concurrent recovery, and work in bytes. | +| Scheduler | Work-conserving byte fairness across blocks | Default policy under section 6; starvation bound is `TBD`. | +| Decoder schedule | Incremental; eager elimination MAY be used | Equivalent schedules preserve section 2 verification and resource requirements. | +| Context history, staleness, workload quantiles, startup concurrency | `TBD` | Required bounded local forecasting and evidence policy. New keys do not create new connection budgets. | +| Proposer history, active selectors, block retention, cancellation tails | `TBD` | Required count/time/byte bounds under sections 3–7. Soft tail retirement MUST NOT refund hard grant credit. | +| Sender service rate/burst, admission timeout, incomplete-frame timeout | `TBD` | Required local regulation, with any peer-visible limits negotiated by the profile. | +| Verification/decoder workers, assemblies, CPU and memory caps | `TBD` | Required per-peer and node-wide work limits before production. | + +The congestion experiment uses four suppliers with usable upload rates of +800/400/200/100 Mbps and receiver ingress of 1,600 Mbps. It also tests balanced +suppliers, 1,000 Mbps ingress, a capacity drop, delayed upstream availability, +and lower offered load. Its 20 ms control delay and 384-byte per-part framing +allowance are model inputs, not RTT estimates or a specified wire format. +The parity experiment sweeps 12.5%, 25%, 50%, and 100% with the same +single-supplier coverage test. Non-25% ratios are hypothetical profiles. + +The September 9 experiments use these additional overrides. They do not select +production defaults or amend the codec and wire profile. + +| Experiment | Parameters | Scope | +| --- | --- | --- | +| Connected push | 16/64 relays; degree 4/8; 1/4 source neighbors; 1/2 suppliers per mask bit; `k=32`, `n=40`, `S=65536`, `P=16` | Rings with additional local edges, including failure of one relay. Source seeds at most `n` parts. | +| Connected service | 1 Gbps source; 1.6 Gbps relay upload; 2 Gbps relay ingress; mixed rates divide relay service by 1/2/4/8 | Aggregate serialized node service. Per-part proof/framing allowance: 384 bytes. | +| Connected CPU and fallback | 0.02 ms proof; 8 ms reconstruction; 5 ms propagation; 20 ms control; fallback at 400 ms; request timeout 100 ms; total deadline 1,200 ms; `2k` extra requests | Assumed CPU costs, one block, preinstalled metadata. Report direct repair bytes separately. | +| Route pruning | Degree 8; 4 source neighbors, extended to 8 for seed-offer/witness tests; 24 sequential bodies; 6 route seeds; 2 stable windows; 4-block cooldown; 12-vote history | Majority and coverage policies do not establish delivery. Witness tests fix the reference mapping and seed plan across equal-shape stripes. | +| TCP workload | Four suppliers at 80/40/20/10 Mbps; shared 100 Mbps loopback netem with 5 ms delay and 128-packet limit; 120 bodies at 25 ms intervals; `k=4`, `n=5`, `S=65536` | Synthetic exact-part requests after release. Three repetitions per scenario and allocator. | +| TCP disturbances | Fastest supplier drops to 5 Mbps at 1.5 s; 40 ms application stalls; 0.2% netem loss, optionally with ECN | Separate scenarios, not combined WAN conditions. | +| TCP feedback and bounds | 25 Mbps initial estimate; 100 ms window; 4 samples; EWMA weight 0.5; maximum increase 25%; 256-part per-peer outstanding cap; 20 ms cancellation delay; 100 ms soft tail | Reduced allocator. Score every released body against 800 ms; allow an 8 s run horizon for eventual completion. | +| Concurrent bodies | 120 bodies at 20.48 ms; `k=32`, `n=40`; 16/64 relays, degree 8, 4 source peers; 4 route seeds | Synthetic 819.2 Mbps body load, with a separate preinstalled reference route. | +| Concurrent service | 1.25 Gbps source; 2.5 Gbps relay upload/ingress; 256 queued parts per node; 6.38 ms encoding/root; 0.02 ms proof; 8 ms reconstruction | Shared service queues. CPU costs are assumptions. Body and peer turns each serve one part. | +| Concurrent disturbances | 1,000–2,000 ms: four relay links at 1/8 service; source at 750 Mbps; or selected CPU work at 10 times cost | Separate scenarios. Additional upload-only cases halve or divide four relay uploads by eight while preserving ingress. | +| Restoration trial | Restore startup suppliers for future bodies after a miss and 20 ms control delay; no further pruning | Do not restore solely for local CPU backlog. This rule did not consistently improve normal completion. | +| Coinbase binding probe | V5, NU5 branch; transparent-only coinbase; 16 KiB transaction, 128 outputs, 4 KiB script, 32 sibling hashes maximum | Local probe bounds. Production adapter bounds and supported formats remain profile choices. | + +#### Wire profile and changes + +W1 below owns payload choices. Its codec field bound does not authorize that +much memory or change `MAX_BLOCK_BYTES`. Production implementations MUST also +select chain admission, transport negotiation, aggregate work budgets, and +retention limits. Optional sender telemetry remains outside W1. A future +profile MUST define timestamp units, sequence reset/wrap rules, queue-residence +bounds, and an authenticated envelope before enabling telemetry. + +A size-dependent parity profile MUST define a deterministic function of admitted +coding inputs, such as `parity_parts(k)`, and its maximum output. Receivers MUST +check that function before allocation. A proposer MUST NOT select extra parity +from a receiver-local load estimate. Scheduling portions MAY group existing +parts locally; they MUST NOT replace per-part verification, credit, or send-once +accounting. Portions that change coding stripes or serialization require a +separate profile specification. + +A local-policy update MAY change estimates and future assignments within hard +bounds. It MUST preserve immutable grants, coverage reporting, cancellation +handling, and node-wide budgets. Reports MUST identify the policy version and +the workload used for tuning. A codec, parity, part-size, mapping, or wire-format +change MUST use a mutually selected profile before affected metadata or parts +are sent. No proposer MAY vary parity unilaterally. This draft specifies no +automatic parity adaptation mechanism. + +The profile MUST fix these before implementations claim interoperability: + +| Item | Draft choice | +| --- | --- | +| Part payload | W1 fixes 65,536 bytes | +| Codec and parity schedule | Section 2 fixes systematic Reed–Solomon over GF(2^16), little-endian elements, and `ceil(k/4)` parity parts | +| Part-mask width and hash | W1 fixes `P=16` and tagged SHA-256 | +| Proposer authentication | W1 fixes Ed25519 and the transcript; supported chain formats and admission adapter remain `TBD` | +| Serialization | W1 below fixes payload encoding; service identifier, capability, stream carrier, and negotiation remain `TBD` | +| Resource bounds | W1 fixes syntax and grant caps; candidates, selectors, aggregate queues/work, and retention remain `TBD` | +| Regulation | Negotiated sender budgets, cadence/burst allowance, response work, and incomplete-frame deadline `TBD` | + +Local policy MUST define reconstruction and recovery deadlines, failure groups, +safety margin, and retention limits. Controller configuration MUST also define: + +- Context history bounds, staleness, material load changes, workload forecasts, + and the startup size/concurrency scenario. +- `CONTROL_INTERVAL`, `W_min`, `W_initial`, `W_max`, `Delta`, `beta`, + `utilization_threshold`, and acceptable yield. +- `MIN_RACE_BLOCKS`, `MIN_FAILURE_BLOCKS`, `race_epsilon`, `SWITCH_THRESHOLD`, + `STABLE_WINDOWS`, and the settling interval. +- `exploration_fraction`, `exploration_initial`, `exploration_cap`, + `CHALLENGE_MIN_INTERVAL`, its jitter bound, `CHALLENGE_MAX_BLOCKS`, + `CHALLENGE_MAX_AGE`, and `MAX_CHALLENGES`. The cap MUST fund at least one + minimum trial with control overhead. The block and age caps SHOULD allow + warming plus `MIN_RACE_BLOCKS` observations at the expected proposer rate. +- Active-proposer queue bounds, grant isolation, migration, recovery, and + cancellation-tail byte/time limits, plus global limits on concurrent trials. + +These controller values require simulation. The control policy is local; peers do not +negotiate a common estimator or trust each other's measurements. + +The implementation MUST test: + +- Canonical encoding, minimum and maximum fields, malformed proofs, arithmetic + overflow, and allocation bounds for every message. +- Header/body separation, transaction-count prefixes, trailing bytes, total + block-size enforcement, body changes invalidating cached parity, and header + changes requiring a new signature but not a new body encoding. +- Header admission before any part work, missing parent context, forged key + binding, signed equivocation, incorrect parity, and consensus-invalid bodies. +- Grant conservation, exhaustion, cancellation, renewal, retirement, stale + responses, reordered streams, and parts crossing unsubscribe or completion. +- Proposer and block inheritance, empty overrides, reconnect cleanup, terminal + completion, and deduplication across grants and services. +- Alternating known proposers, preserving their distinct routes and votes, + an unfamiliar key using defaults, and changed ingress behind an existing key. +- Two-peer reciprocal waits, larger isolated cycles, arbitrary block entry + points, cold proposers, skewed part placement, peer loss, and failed repair. +- A nearby high-capacity proposer, congestion after route concentration, + correlated failures, withholding, false completion claims, and recovery of + a previously slow peer. +- Mixed block sizes, same-height forks, burst arrivals, and simultaneous + proposers sharing one connection budget. Holding block size fixed while + varying concurrency MUST change aggregate assignment accounting. +- Small-block wins followed by large blocks, a new proposer with no samples, + idle periods, subscription warming, credit exhaustion, and stale histories. +- Races censored by `FullBlock` or unsubscribe, local verification overload, + correlated part arrivals within one block, and peers that perform well only + during probes. Completion MUST NOT create artificial deadline failures. +- Challenge funding under idle time, block bursts, duplicate announcements, + invalid bodies, reconnects, key churn, and block replay after history eviction. + Cumulative charged authorization MUST obey the exploration bound. +- Challenge responses authorized by overlapping grants, renewal, cancellation, + and large blocks exhausting a small trial's credit. A route label alone + MUST NOT bypass exploration accounting. +- Unequal proposer rates, queue fairness, an unaffordable or expired trial, + empty mask selections for small blocks, warming without acknowledgements, + and inconclusive expiry. Timers MUST NOT generate unbounded trial starts. +- Budget growth without loaded evidence, repeated failures, simultaneous + promotions, one mask-bit assignment exceeding the migration budget, and + cancellation tails. No context or key change may create a fresh connection budget. +- A receiver bottleneck shared by every peer, an upstream source stall, and + other receivers adapting concurrently. The simulator MUST include control + delay, sender scheduling, and feedback-driven changes to upstream routes. +- Per-peer isolation under queue pressure, malformed-frame floods, bounded + decoder/verifier faults, and continued control-stream progress. + +Stateful tests MUST compare production admission with an independent reference +model. Conformant sequences MUST NOT produce misconduct disconnects. +Deterministic cases MUST cover each declared rule; random generation alone is +insufficient. Minimized failures MUST serialize into replayable scenarios. +A finite-state explorer MUST report an unfinished frontier as incomplete. + +Simulation MUST set explicit delivery and resource targets before tuning the +controller. It MUST report assumptions about honest connectivity and failure +correlation. No simulation result may imply unconditional connectivity from +local subscription counts. + +Controller experiments MUST compare this policy with static equal shares, +random assignment, global-best selection, and pairwise races without a byte +budget. An offline informed scheduler MAY provide a reference bound, but the +report MUST disclose the information unavailable to real receivers. Experiments +MUST vary block size, arrival rate, burst concurrency, proposer locality, +asymmetric peer bandwidth, and correlated failures independently. Reports MUST +include adaptation time, control bytes, duplicate bytes, and queue peaks as +well as the block outcomes in section 7. No finite controller can meet a fixed +latency target when offered work exceeds available service capacity. + +Codec benchmarks MUST compare batch and incremental implementations of the +selected code on identical arrival traces and redundancy. +They MUST measure encoding/root latency after mining, useful work before the +last required part, the remaining decode tail, root verification, CPU time, +and peak memory. They MUST include systematic-only reception, parity-heavy +reception, withheld parts, invalid codewords, and concurrent assemblies. +Conformance tests MUST extend the vector in section 2 with nonzero high bytes, +field reduction, padding, and recovery from every `k`-part subset for small +codewords. An incremental-input API alone does not demonstrate overlapped work. + +## Source locations + +- [Current header-sync messages](../../crates/zakura-network/src/zakura/header_sync/wire.rs) +- [Header admission](../../crates/zakura-header-chain/src/transition/planner/event_effects/header_admission.rs) +- [Current consensus header](../../crates/zakura-chain/src/block/header.rs) +- [Current block serialization](../../crates/zakura-chain/src/block/serialize.rs)