Skip to content

Wire serde_2026 serialization into chia_rs - #1437

Merged
richardkiss merged 9 commits into
mainfrom
serde-2026-wiring
Jul 16, 2026
Merged

Wire serde_2026 serialization into chia_rs#1437
richardkiss merged 9 commits into
mainfrom
serde-2026-wiring

Conversation

@richardkiss

@richardkiss richardkiss commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Wire the serde_2026 serialization format into chia_rs as a foundation for the post-HF2 INTERNED_GENERATOR cost model. This PR ships the producer/consumer surface only — nothing here changes consensus behavior until #1438 wires it into validation.

Depends on clvmr ≥ 0.18.0 (serde_2026 shipped to crates.io in June), which main already uses — no Cargo.toml changes.

Changes

  • chia_consensus::serde_2026::node_from_bytes_auto: deserialize CLVM bytes, auto-detecting classic / backrefs / serde_2026 by magic prefix. Callers supply the max blob size; the per-atom cap (1 MiB) is fixed. Accepts non-minimal encodings (strict = false) — rationale documented inline: post-HF2, identity and cost come from the interned tree, so overlong varints only hurt their sender.
  • chia_consensus::serde_2026::max_canonical_blob_size(max_cost, cost_per_byte): the provable upper bound on the canonical serde_2026 wire size of any within-cost generator (max_cost / cost_per_byte + 5 + prefix). The proof is in its doc comment; at mainnet constants the bound is 916,677 bytes. This is what callers should derive the node_from_bytes_auto size cap from — the limit is a property of the cost constants, not a hardcoded number. How much headroom to allow for non-minimal encodings is caller policy (input welcome).
  • solution_generator_2026(): builds a generator using serialize_2026, at a named SERDE_2026_COMPRESSION_LEVEL (level 0; levels are producer-side only, all decodable by the one deserializer).
  • Python bindings: solution_generator_2026() and SERDE_2026_MAGIC_PREFIX, stub generator updated.

Tests

  • Unit tests for auto-dispatch (all three formats), size-cap boundary, and solution_generator_2026 round-trip through node_from_bytes_auto.
  • The size bound is tested at its adversarial tight shapes (max-length atom — exactly tight, slack 0; >63 distinct atom lengths; >4096 unique pairs; doubling DAG) and shown general across arbitrary cost constants, from (0, 7) to (u64::MAX, u64::MAX) — not just mainnet's 11e9 / 12,000.
  • New fuzz target serde-2026-size-bound: arbitrary trees and arbitrary cost constants per run; asserts the encoding bound, the derived cap for affordable trees, and round-trip identity. A deterministic seed generator (gen_serde_2026_fuzz_seeds example) produces corpus entries at 0.98–0.999 of the bound so mutation starts at the boundary. Fuzzing already earned its keep: it caught an integer overflow in the bound function at extreme constants (fixed with saturation).

Depends on

#1436 (InternedBlockBuilder) — merged; this PR is based on current main.

Follow-up (#1438 / FullBlock #1456)

Arvid's merged FullBlock format (#1456) adds v1 blocks that store the generator as a raw length-prefixed Vec<u8> (transactions_generator_buffer), giving a place to inspect bytes for SERDE_2026_MAGIC_PREFIX before HF2 and reject serde_2026 generators while INTERNED_GENERATOR is inactive — without parsing CLVM first. This PR does not wire that guard; #1438 adds check_generator_quote / consensus-path checks, applying the prefix check to both legacy Program bytes and the v1 raw buffer path.

Test plan

  • fmt / clippy / full test suite pass locally
  • from chia_rs import solution_generator_2026, SERDE_2026_MAGIC_PREFIX works
  • solution_generator_2026(...) output starts with SERDE_2026_MAGIC_PREFIX (unit-tested)
  • Fuzz target: ~750k executions clean
  • CI green on current head

Note

Medium Risk
New deserialization and provable size-bound logic touch generator wire format and pre-charge decoding limits; behavior is not yet on the consensus path but mistakes here would matter for the HF2 follow-up.

Overview
Adds serde_2026 as a producer/consumer surface ahead of post-HF2 interned-generator work, without changing live consensus validation in this PR.

New chia_consensus::serde_2026 exposes node_from_bytes_auto (magic-prefix dispatch to classic/backrefs/serde_2026 with caller-supplied max_blob_size, non-minimal encodings allowed via strict = false), max_canonical_blob_size (cost-derived wire cap, ~916 677 bytes at mainnet constants), and SERDE_2026_COMPRESSION_LEVEL. solution_generator_2026 builds generators with serialize_2026.

Python chia_rs gains solution_generator_2026 and SERDE_2026_MAGIC_PREFIX (stubs updated). Coverage includes unit tests on tight encoding shapes and format dispatch, a serde-2026-size-bound fuzz target, and gen_serde_2026_fuzz_seeds to seed near-bound corpora.

Reviewed by Cursor Bugbot for commit 2cd665e. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR wires the new serde_2026 serialization format into chia_rs/chia-consensus to support upcoming post-HF2 INTERNED_GENERATOR work, including a new generator serializer and Python-facing APIs/constants.

Changes:

  • Add solution_generator_2026() (Rust + Python binding) that serializes generators via serialize_2026.
  • Introduce chia_consensus::serde_2026::node_from_bytes_auto() with consensus-oriented caps and a program_bytes re-export for downstream consumers.
  • Pin clvmr to a specific git revision (via [patch.crates-io]) to access the new serde_2026 functionality, and expose SERDE_2026_MAGIC_PREFIX to Python.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
wheel/src/api.rs Adds Python binding for solution_generator_2026() and exports SERDE_2026_MAGIC_PREFIX.
wheel/python/chia_rs/chia_rs.pyi Updates Python type stubs for the new function and magic-prefix constant.
wheel/generate_type_stubs.py Updates stub generator to include the new solution_generator_2026() function.
crates/chia-consensus/src/solution_generator.rs Adds Rust solution_generator_2026() using serialize_2026.
crates/chia-consensus/src/serde_2026.rs Adds consensus-tuned auto-deserializer dispatching between classic/backrefs/serde_2026.
crates/chia-consensus/src/program_bytes.rs Re-exports node_from_bytes_auto() for easier cross-crate consumption.
crates/chia-consensus/src/lib.rs Exposes the new program_bytes and serde_2026 modules.
Cargo.toml Adds a [patch.crates-io] pin for clvmr to the serde_2026 git revision.
Cargo.lock Reflects clvmr being sourced from the pinned git revision.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wheel/python/chia_rs/chia_rs.pyi
Comment thread wheel/generate_type_stubs.py
Comment thread crates/chia-consensus/src/solution_generator.rs
Comment thread crates/chia-consensus/src/solution_generator.rs
Comment thread wheel/generate_type_stubs.py
@coveralls-official

coveralls-official Bot commented May 9, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 29473136351

Coverage decreased (-0.08%) to 81.815%

Details

  • Coverage decreased (-0.08%) from the base build.
  • Patch coverage: 51 uncovered changes across 2 files (141 of 192 lines covered, 73.44%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
crates/chia-consensus/examples/gen_serde_2026_fuzz_seeds.rs 43 0 0.0%
wheel/src/api.rs 11 3 27.27%
Total (4 files) 192 141 73.44%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 19884
Covered Lines: 16268
Line Coverage: 81.81%
Coverage Strength: 11726295.61 hits per line

💛 - Coveralls

@richardkiss
richardkiss force-pushed the interned-block-builder branch from 4016bb3 to 66bb8aa Compare May 20, 2026 17:11
Comment thread crates/chia-consensus/src/solution_generator.rs Outdated
Comment thread crates/chia-consensus/src/solution_generator.rs Outdated
Comment thread crates/chia-consensus/src/build_interned_block.rs Outdated
@richardkiss
richardkiss force-pushed the interned-block-builder branch from b919dca to 90286c2 Compare June 2, 2026 00:11
Base automatically changed from interned-block-builder to main July 2, 2026 19:46
Comment thread crates/chia-consensus/src/program_bytes.rs Outdated
@richardkiss
richardkiss force-pushed the serde-2026-wiring branch 2 times, most recently from a4a3ce7 to 1d716da Compare July 14, 2026 19:51

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1d716da. Configure here.

Comment thread crates/chia-consensus/src/serde_2026.rs
- Pin clvmr to clvm_rs PR #708 (serde_2026 git rev) via [patch.crates-io]
- Add chia_consensus::serde_2026: node_from_bytes_auto with consensus caps
  (CONSENSUS_MAX_ATOM_LEN = 1 MiB, CONSENSUS_MAX_BLOB_SIZE = 10 MiB)
- Add chia_consensus::program_bytes: re-exports node_from_bytes_auto
- Add solution_generator_2026() using serialize_2026 (serde_2026 format)
- Expose solution_generator_2026() and SERDE_2026_MAGIC_PREFIX to Python
Add max_canonical_blob_size(max_cost, cost_per_byte), which computes the
largest canonical serde_2026 wire size any within-cost generator can have
(max_cost / cost_per_byte + 5 + prefix). The proof lives in its doc
comment: the canonical encoding never exceeds interned_vbytes + 5 (tight
at atom length 2^20; requires atom lengths < 2^27, guaranteed by
CONSENSUS_MAX_ATOM_LEN), and the cost ceiling bounds interned_vbytes.
At mainnet constants the bound is 916,677 bytes.

CONSENSUS_MAX_BLOB_SIZE stays 10 MiB but its comment now states the
principled floor (~917 KB), that the value was inherited from clvm_rs's
DeserializeLimits default, and that tightening it (and consensus-vs-peer
layering) is deliberately left open for review.

Tests:
- adversarial shapes where the bound's slack is smallest (max-length
  atom, >63 distinct atom lengths, >4096 unique pairs, doubling DAG),
  including exact tightness at the 2^20 atom
- generality across arbitrary cost constants, (0, 7) to
  (u64::MAX, u64::MAX) - the bound is a theorem parameterized by the
  constants, not a property of 11e9/12000
- new fuzz target serde-2026-size-bound: arbitrary trees AND arbitrary
  cost constants per run; checks the encoding bound, the derived cap for
  affordable trees, and blob round-trip identity. Seeded with inputs at
  0.98-0.999 of the bound (fuzz/seeds/, corpus/ is gitignored). Fuzzing
  already caught an overflow in the bound function at extreme constants
  (fixed with saturating_add).

Also: solution_generator_2026 round-trip test, auto-dispatch and size-cap
tests for node_from_bytes_auto, and a named SERDE_2026_COMPRESSION_LEVEL
constant replacing the bare 0 at the serialize_2026 callsite.

Derivation provenance: SERDE2026_UPPER_BOUND.md and
SERIALIZATION_DOS_ANALYSIS.md in the generator-identity-hf-analysis repo.
Drop CONSENSUS_MAX_BLOB_SIZE (10 MiB, inherited from clvm_rs's
DeserializeLimits default, unconnected to any consensus property).
node_from_bytes_auto now takes max_blob_size, which callers derive from
the network's cost constants via max_canonical_blob_size — the limit is
a property of max_block_cost_clvm / cost_per_byte and the proof, not a
standalone number. Any headroom multiplier (e.g. to tolerate the
non-minimal encodings that strict=false admits) is caller policy.
Drop the 9 binary near-bound seed files in favor of a
gen_serde_2026_fuzz_seeds example that reproduces them (fixed RNG seed):
a fullness-maximizing search over the fuzz target's input format, writing
seeds at 0.98-0.999 of the proven bound directly into the gitignored
corpus dir. Reviewable and regenerable instead of opaque blobs in git.
Comment thread crates/chia-consensus/examples/gen_serde_2026_fuzz_seeds.rs Outdated
Comment thread crates/chia-consensus/src/serde_2026.rs
Comment thread crates/chia-consensus/src/serde_2026.rs Outdated
Comment thread crates/chia-consensus/src/solution_generator.rs
Per review: rand (with small_rng) is already a dev-dependency. The corpus
is regenerable scratch, so cross-version drift of seeded SmallRng output
is acceptable; a fixed seed keeps runs reproducible per rand version.

Co-authored-by: Cursor <cursoragent@cursor.com>
There is no independent max-atom-length constant: atoms appear as
literals in the canonical serialization, so an atom of length L forces
a canonical blob of at least L bytes. Any atom too big for the
caller-derived blob cap therefore cannot be part of a cost-valid
generator, and max_blob_size doubles as the per-atom cap passed to
deserialize_2026. Per review feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
@richardkiss
richardkiss enabled auto-merge (squash) July 16, 2026 17:57
@richardkiss
richardkiss merged commit a77cec9 into main Jul 16, 2026
84 checks passed
@richardkiss
richardkiss deleted the serde-2026-wiring branch July 16, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants