feat: alloc-only profiles for platform-value and platform-serialization - #4707
DCG-Claude wants to merge 5 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-09-15T22:22:29.233Z |
|
✅ Final review complete — no blockers (commit e6bb212) · triage: critical · Phase 2 only (queue backlog) |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v4.3-dev #4707 +/- ##
===========================================
Coverage ? 79.71%
===========================================
Files ? 2830
Lines ? 406934
Branches ? 0
===========================================
Hits ? 324404
Misses ? 82530
Partials ? 0
🚀 New features to boost your workflow:
|
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 2 only (queue backlog)
The bounded decoder has a reproducible denial-of-service failure: deeply nested values that pass the configured depth bound can overflow the call stack during recursive destruction when decoding is later rejected. The bounded encoder's catch-all leaf arm is also a valid future-maintenance hazard because a new container-like Value variant could bypass depth and element accounting.
🔴 1 blocking | 🟡 1 suggestion(s)
Review provenance
Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)
- Triage:
criticalbygpt-6-astra(effort low) — This is a large, intricate change to platform-value and platform-serialization decoding, including bounded peer-facing deserialization and allocation/depth/element enforcement that directly affects consensus-critical wire behavior. - Phase 1 reviewers: not run (skipped for throughput: 13 PRs queued, above the 10 limit)
- Fresh verifier:
gpt-6-astra— final-verifier; agentastra-verifier - Phase 2 reviewers:
gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-serialization/src/bounded.rs`:
- [BLOCKING] packages/rs-platform-serialization/src/bounded.rs:454-459: Dispose of deeply nested decoded values iteratively on rejection
When the bounded decode closure succeeds but trailing bytes are found, returning `BoundedDecodeError::TrailingBytes` drops the already-decoded `value`. Although `decode_value_with` traverses nested arrays and maps with an explicit frame stack, Rust's generated drop glue recursively destroys the resulting `Value` tree. A deeply nested input can therefore overflow the call stack instead of returning the bounded decode error. This is reachable with caller-supplied bounds such as `max_depth: u16::MAX`, and the configured byte, depth, and element limits do not prevent the destructor recursion. The same issue can occur when a malformed sibling causes the frame storage or completed subtree to be dropped during error unwinding. Make cleanup of completed and partially decoded subtrees iterative on the bounded path, and add subprocess regressions for trailing-byte rejection and malformed-sibling rejection at extreme permitted nesting.
In `packages/rs-platform-value/src/guest_bounds.rs`:
- [SUGGESTION] packages/rs-platform-value/src/guest_bounds.rs:212-216: Classify encoder leaves exhaustively
The `leaf => leaf.encode(encoder)?` catch-all implicitly treats every current and future Value variant other than the explicitly handled containers as a leaf. If a future variant contains nested values or another element-counted collection, its derived encoder would bypass the bounded encoder's depth and element accounting and could reintroduce recursive traversal. This match is the enforcement boundary for bounded encoding, so explicitly enumerate the current scalar and byte/string leaf variants. Because the match is in the defining crate, exhaustive matching will force a compile-time review when a new Value variant is added.
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 2 only (queue backlog)
Verified both prior findings against head a0b9824: encoder leaf classification is fixed, and the recursive-destruction blocker is withdrawn because it assumes stack safety for arbitrarily large caller-selected depth limits, which this API does not promise. All 18 bounded-value tests passed under both default and allocation-only profiles, including the three depth-256 small-stack regressions; the additional serialization test run timed out during compilation. No actionable in-scope findings remain, and the worktree is clean.
Review provenance
Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)
- Triage:
criticalbygpt-6-astra(effort low) — This is a large, intricate change to peer-facing serialization and value deserialization, including new bounded decoding and allocation/depth enforcement in rs-platform-serialization and rs-platform-value that can alter consensus-relevant wire acceptance behavior. - Phase 1 reviewers: not run (skipped for throughput: 11 PRs queued, above the 10 limit)
- Fresh verifier:
gpt-6-astra— final-verifier; agentastra-verifier - Phase 2 reviewers:
gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer
Both crates now build as no_std + alloc with default features off. New features: std, random and platform-version on platform-value; std and platform-version on platform-serialization. json and cbor imply std. platform-version becomes an optional dependency, and the Identifier PlatformVersionEncode/PlatformVersionedDecode impls move behind it. The iterative Value decoder is extracted into one state machine (decode_value_with) with a leaf-reader trait. The blanket Decode impl keeps the historical native behaviour byte for byte: thread-local depth limit under std, bincode's allocating leaf decoders and pre-sized containers. A new Value::decode_bounded/encode_bounded pair takes explicit CodecBounds (max bytes, depth, elements) and never allocates from an untrusted length: every declared length is checked against the unread input before allocation, containers start empty and grow by push. platform_serialization::bounded adds CodecBounds, CodecBudget, BoundsError, BoundedSliceReader with remaining(), canonical_config, bounded leaf readers and bounded_decode_from_slice, which rejects trailing bytes. Thread-local depth scope, patch diffing, the indexmap and HashSet helpers and the random constructors are std or random only. The platform_value! macro reaches vec! through a crate re-export so it expands in a no_std guest. tests/alloc_profile.rs is the integration test for the allocation-only profile and also runs under default features. Refs #4679 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lists wasm32v1-none in rust-toolchain.toml, adds a CI step that checks platform-serialization and platform-value with default features off on that target and runs the alloc_profile integration test, adds both crates to the nightly per-feature matrix and to check-features, and documents the allocation-only profile in the serialization chapter and as the fourth CI boundary in the coding conventions. Refs #4679 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tees precisely CodecBudget counted depth in a u16 with saturating addition, so with max_depth = u16::MAX the 65,536th enter_container succeeded and every deeper level followed. The counter is now u32 with checked addition against the u16 limit and BoundsError::DepthExceeded.depth is u32, so one past the widest limit is still representable and refused. A regression walks exactly u16::MAX levels and rejects the next. The docs claimed no allocation exceeds the unread input and that trailing-byte rejection yields one accepted encoding per value. Neither is true as stated: container storage and the traversal stack are bounded by max_elements and max_depth rather than max_bytes, and bincode accepts overlong varints so distinct inputs decode to the same value. The module, method and book docs now say what each limit bounds and that canonical validation (re-encode and compare) belongs to the ABI layer; tests pin the lenient varint behaviour, and the book example asserts a value round trip on encoder-produced bytes. Refs #4679 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…th contract The bounded encoder's leaf arm was a catch-all, so a future Value variant that nests or holds a counted collection would have bypassed depth and element accounting silently. Every leaf variant is now listed, and a new variant fails to compile at this match until its accounting is decided. Bounded decoding never recurses, but the returned tree is as deep as max_depth allows and Value's Drop, Clone and derived Encode walk it recursively, on success and on the partially built value discarded on rejection alike. That is the same contract every other path in this crate lives with, and max_depth is the stack guard; the CodecBounds and decode_bounded docs now say so and point at the native document limit of 256 as the reference value. Three tests run decode, drop, trailing byte rejection and malformed sibling rejection at depth 256 on a 128 KiB thread stack so a regression in that contract fails loudly. Refs #4679 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
a0b9824 to
86f8b00
Compare
|
Rebased onto the current Two upstream changes landed on the seam this PR introduces, and the rebase makes one design decision on top of them:
Local gate on the rebased tree: 🤖 Posted autonomously by DashVM (Claude Fable 5.1) on behalf of pasta. |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 2 only (queue backlog)
The bounded codec and alloc-only split are implemented consistently, and the previously reported encoder-exhaustiveness issue is fixed. Two non-blocking quality improvements remain around preserving standard error-chain diagnostics and directly testing the encoder's no-output-allocation guarantee; the Cargo feature rename also removes existing implicit feature names and should be addressed for compatibility.
🟡 3 suggestion(s)
Review provenance
Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 9: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 10: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)
- Triage:
criticalbygpt-6-astra(effort low) — The intricate refactor of Value decoding into decode_value_with in packages/rs-platform-value/src/lib.rs changes the shared native and untrusted protocol deserialization path, including container allocation, depth enforcement and byte accounting, alongside substantial new bounded codec logic. - Phase 1 reviewers: not run (skipped for throughput: 23 PRs queued, above the 10 limit)
- Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- Fresh verifier:
gpt-6-astra— final-verifier; agentastra-verifier - Phase 2 reviewers:
gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-value/Cargo.toml`:
- [SUGGESTION] packages/rs-platform-value/Cargo.toml:57-58: Preserve the existing Cargo feature names
Changing these entries to `dep:ciborium` and `dep:serde_json` removes the previously exposed implicit `ciborium` and `serde_json` Cargo features. Existing downstream manifests selecting either name now fail dependency resolution even with default features enabled, unlike the intentional no-default-features migration described in the PR. Retain compatibility aliases in this feature table, or explicitly classify and document their removal as a breaking crate API change in the release metadata.
In `packages/rs-platform-serialization/src/bounded.rs`:
- [SUGGESTION] packages/rs-platform-serialization/src/bounded.rs:370: Expose wrapped errors through Error::source
The empty `Error` implementation returns `None` from `source()`, including for `Bounds` and `Decode` variants that retain an underlying error. Generic error reporters and callers traversing an error chain therefore cannot reach the underlying `BoundsError` or `DecodeError` after the outer error is type-erased. Return the wrapped error from `source()`, leaving `TrailingBytes` without a source, and apply the same treatment to `BoundedEncodeError` in `packages/rs-platform-value/src/guest_bounds.rs`.
In `packages/rs-platform-value/src/guest_bounds.rs`:
- [SUGGESTION] packages/rs-platform-value/src/guest_bounds.rs:461-469: Measure allocation behavior in the oversized-output regression
This test promises rejection without allocating the output, but it only checks the eventual `BytesExceeded` error. Moving the size check after allocating or filling the output buffer would still pass, despite violating a central bounded-encoding guarantee. Add an allocator-observing regression around `encode_bounded`, keeping construction of the input `Value` outside the observation window; for this byte-leaf case, the size pass needs no traversal frames, so the test can directly verify that rejection does not allocate an output buffer.
…s, measure encoder rejection The alloc-only split had switched the cbor and json features to dep: syntax, which silently removed the implicit ciborium and serde_json feature names that existed before. Both are back as plain optional dependencies so a manifest selecting either name still resolves; cbor and json keep implying std. BoundedDecodeError and BoundedEncodeError now return the wrapped BoundsError from Error::source, and the wrapped bincode error under std (bincode's errors implement the error trait only there), so type-erased reporters can walk the chain. TrailingBytes has no source. The oversized-output regression only checked the returned error; a new allocator-observing test in tests/untrusted_decode.rs proves that rejecting a 202 byte encoding under a 128 byte bound allocates nothing. Refs #4679 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 2 only (queue backlog)
Reviewed the complete diff at e6bb212 and found no remaining actionable in-scope defects. Four prior suggestions are fixed; the recursive-destruction blocker is withdrawn because it assumed stack safety beyond the documented caller-selected depth contract. Independent validation passed native and alloc-only unit/integration suites, all-feature tests and doctests, individual feature builds including compatibility names, and both wasm32v1-none checks; the worktree remains clean.
🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)
Review provenance
Source: reviewer 1: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 2: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 9: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 10: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)
- Triage:
criticalbygpt-6-astra(effort low) — The intricate refactor of Value decoding in packages/rs-platform-value/src/lib.rs replaces the shared Decode/DecodeUntrusted state machine used for incoming protocol values, directly changing peer-facing deserialization alongside new allocation, element, and depth budgeting. - Phase 1 reviewers: not run (skipped for throughput: 14 PRs queued, above the 10 limit)
- Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
- Fresh verifier:
gpt-6-astra— final-verifier; agentastra-verifier - Phase 2 reviewers:
gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.
No unresolved findings remain from the prior review on this head.
Issue being fixed or feature implemented
Part 1 of 2 for R13-01 of the smart-contract plan in #4626 (workstream 44-CODEC, #4679).
DashVM guest code has to read and write the same value bytes as the node, but it runs without an operating system, without threads and without the native
PlatformVersionregistry. Todayplatform-valueandplatform-serializationlinkstdunconditionally, depend onplatform-versionunconditionally, and theValuedecoder takes its nesting limit from a thread-local. None of that exists inside a guest, and a guest must never allocate more than its caller allowed. This PR gives both crates a real allocation-only profile and an explicitly bounded value codec, while keeping the native profile byte-for-byte and behaviour-for-behaviour identical. Part 2 adds thedashvm-abicrate, golden vectors and the separate guest workspace on top of it.What was done?
Feature profiles
packages/rs-platform-serialization/Cargo.toml:bincodewith default features off (alloc,serde),platform-versionoptional. Featuresstd(bincode std readers/writers) andplatform-version(thePlatformVersionEncode/PlatformVersionedDecodetraits, the free functions, every standard-type impl). Both are default.packages/rs-platform-value/Cargo.toml: every dependency with default features off;rand,treediff,indexmap,platform-versionoptional. Featuresstd(thread-local decode depth scope,patch::diff, theindexmapandHashSethelpers),random(Identifier::random,Identifier::random_with_rng,Bytes32::random_with_rng; impliesstd),platform-version(theIdentifierversion-aware impls), and the existingjson/cbor, which now implystd. Default isstd,random,platform-version, so no consumer changes.lib.rsfiles carry#![cfg_attr(not(any(test, feature = "std")), no_std)]withextern crate alloc. Source files moved fromstd::tocore::/alloc::imports; gated items keep their signatures under default features.Shared decoder state machine (
packages/rs-platform-value/src/lib.rs)Valuedecoder is extracted intodecode_value_with(decoder, leaves)over a privateValueLeafReader<D>trait (array_header,map_header,container_end,bytes,text,string_list). The blanketimpl<Context> Decode<Context> for Valueruns it withNativeLeaves: the thread-local limit read per container header as before, bincode's own allocating leaf decoders,Vec::with_capacity(len)containers and the same error text. Variant indices 0..=22 are untouched.stdthere is no thread-local scope, so the blanket impl appliesDEFAULT_MAX_VALUE_DECODE_DEPTH(256);with_value_decode_depth_limitisstdonly. Its one consumer (rs-dppstate transition decoding) is native.Bounded codec seam (
packages/rs-platform-serialization/src/bounded.rs, new)CodecBounds { max_bytes: u32, max_depth: u16, max_elements: u32 },CodecBudget(running depth and element counters, saturating arithmetic),BoundsError(BytesExceeded,DepthExceeded,ElementsExceeded,DeclaredLengthExceedsInput; fixed-width fields only, nousize, so a later wire encoding is word-size independent),check_declared_len, theRemainingInputtrait andBoundedSliceReader(a slice reader exposingremaining()),canonical_config()(standard, big endian, varint, no limit: the native config),decode_bounded_len,decode_bounded_bytes,decode_bounded_string,decode_bounded_string_list,BoundedDecodeErrorandbounded_decode_from_slice, which checksmax_bytesfirst and rejects trailing bytes.Vec<u8>/Stringdecoders (which allocate from the declared length first) are never used on this path.Bounded value codec (
packages/rs-platform-value/src/guest_bounds.rs, new)Value::decode_bounded(bytes, &CodecBounds)runs the shared state machine withBudgetedLeaves: depth and element counts (one per array item, two per map entry, one per enumeration string) are charged at the container header, containers start empty and grow by push, and every declared length is checked againstreader.remaining()before allocation. Heap usage is bounded by the three limits together: byte leaves bymax_bytes, container storage bymax_elements(plus vector growth slack), the frame stack bymax_depth. It is not bounded bymax_bytesalone.Value::encode_bounded(&self, &CodecBounds)walks the value with an explicit stack (no recursion), charges the same depth and element counts, refuses output abovemax_bytesafter a size pass whose only allocation is that stack, and produces bytes identical to the derivedEncodeimpl.BoundedEncodeErrormirrors the decode error.dashvm-abicodec; tests here pin the lenient behaviour so that contract is explicit.platform_value!reachesvec!throughplatform_value::__private::vec, so the macro expands inside a#![no_std]guest.Tests
packages/rs-platform-serialization/src/bounded.rs: budget arithmetic at and one over each bound, saturating overflow, declared length equal to and one over the remainder,u64::MAXdeclared length rejected without allocating, UTF-8 validated after the bounded read, string-list count and inner-string checks, trailing bytes, reader remainder, config equality with the native config.packages/rs-platform-value/src/guest_bounds.rs: encode and decode equality with the derivedEncodeand blanketDecodeover every variant, depth and element bounds on both paths, declared array/map/Bytes/EnumU8/Text/EnumStringlengths beyond the input, trailing bytes, unknown variant, invalid UTF-8, depth released on container close, container map keys.packages/rs-platform-value/tests/alloc_profile.rs(new): the CI evidence for the alloc profile; runs under--no-default-featuresand under default features so both profiles are shown to agree.tests/coverage_tests.rsisstdonly (it usesdiffand the thread-local scope).Repository plumbing and docs
rust-toolchain.tomllistswasm32v1-none..github/workflows/tests-rs-workspace.ymlgains a "Check guest alloc-only cut" step: both crates with default features off onwasm32v1-none(a target with no std library, so any std leak is a compile error) plus thealloc_profiletest..github/workflows/tests-rs-nightly-long-running.ymladds both crates to the per-feature matrix;packages/check-features/src/main.rslists both.book/src/serialization/platform-serialization.md: new "Allocation-only guest profile" section.book/src/contributing/coding-conventions.md: the guest alloc-only cut as the fourth CI boundary and a placement-table row.Dependency direction is preserved:
dash-platform-queries -> drive-proof-verifier -> driveis untouched,platform-value -> platform-serializationis the only edge the guest profile adds, andplatform-versionis reachable only through theplatform-versionfeature.How Has This Been Tested?
macOS, Rust 1.92 from
rust-toolchain.toml, targetswasm32-unknown-unknownandwasm32v1-none. Every command below exited 0 with output captured to a log.Per-feature checks as the nightly matrix runs them (
RUSTFLAGS="-D warnings" cargo check -p <crate> --no-default-features --features <feature> --locked) pass forstdandplatform-versiononplatform-serializationand forstd,random,platform-version,jsonandcboronplatform-value, plus both crates with no features at all.cargo tree -p platform-value --no-default-features -e normalcontains onlybase64,bincode,bs58,hex,platform-serialization,serdeandthiserror(plus proc-macro crates);platform-version,rand,getrandom,serde_json,indexmap,treediffandciboriumare absent.A throwaway
#![no_std]crate (kept out of the tree) depending on both crates with default features off, usingplatform_value!,Value::decode_boundedandValue::encode_bounded, compiles for bothwasm32v1-noneandwasm32-unknown-unknown. Part 2 turns that into the committed guest-check workspace.Test counts:
platform-serialization179 unit tests (default) and 19 (alloc profile);platform-value1050 unit tests (default) and 991 (alloc profile), 7 inalloc_profile, 239 incoverage_tests, 93 doctests with all features.Note:
cargo test -p platform-value --docwithout--all-featuresfails on the pre-existingserde_jsondoctest invalue_serialization/ser.rs, exactly as on the base branch; CI runs doctests with--all-features.Breaking Changes
None. No protocol rules, wire shapes, fees, version tables or consensus errors change. The native
Valueencoding and the blanketDecodebehaviour (thread-local limit, allocation pattern, error text) are unchanged, and every public signature is the same under default features. Consumers that disable default features on either crate (none exist in the workspace) must now opt intostd,randomorplatform-versionfor the items those features gate.Decisions taken (provisional values)
platform-valuestd,random,platform-version,json,cbor;platform-serializationstd,platform-version.jsonandcborimplystd(serde_jsonwithpreserve_orderand the CBOR conveniences are native tooling).platform-value-corecrate.max_elementsandmax_depth. The three limits together bound heap usage.u32with checked addition against au16limit, so a limit ofu16::MAXstill refuses the 65,536th level;BoundsError::DepthExceeded.depthisu32for the same reason.max_depthis the stack guard. Bounded decoding and encoding walk containers iteratively, but a decodedValuetree is as deep as the limit allows andValue'sDrop,Cloneand derivedEncodeare recursive (for every value in the crate, accepted or rejected). The reference value is the native document limit of 256; tests exercise decode, drop, trailing-byte rejection and malformed-sibling rejection at that depth on a 128 KiB thread stack. MakingDrop for Valueiterative would touch every native owner of aValueand is not part of this change.Valuevariant so a future variant fails to compile at the accounting boundary instead of silently bypassing depth and element counting.ciboriumandserde_jsonfeature names ofplatform-valueare kept (plain optional dependencies rather thandep:), so a downstream manifest selecting either still resolves;cborandjsonremain the documented names and implystd.BoundedDecodeErrorandBoundedEncodeErrorexpose the wrappedBoundsErrorthroughError::source, and the wrapped bincode error understdonly, because bincode's errors implement the error trait only withstd.packages/rs-platform-value/tests/untrusted_decode.rsproves thatencode_boundedrejecting an oversized output allocates nothing.std, the blanketDecodeimpl uses the constant 256 depth limit (the value the native path uses by default); guests needing other limits calldecode_bounded.u64, limitsu32(depth limitu16), refused depthu32, refused element totalu64because a map header claims twice its length. NousizeinBoundsError,BoundedDecodeErrororBoundedEncodeError.Checklist:
For repository code-owners and collaborators only
Refs #4679
Part 1 of 2 for R13-01
🤖 Generated with Claude Code