fix(platform)!: decode remote input with untrusted bincode and disk loads with trusted decoders - #4625
Conversation
|
Important Review skippedToo many files! This PR contains 684 files, which is 584 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (684)
You can disable this status message by setting the 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 |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## v4.2-dev #4625 +/- ##
============================================
+ Coverage 81.72% 83.49% +1.76%
============================================
Files 2827 2828 +1
Lines 390006 387629 -2377
============================================
+ Hits 318730 323646 +4916
+ Misses 71276 63983 -7293
🚀 New features to boost your workflow:
|
|
🕓 Queued for automated review — 2nd in line, estimated start in ~20 min (commit 175714d)
|
… items Clippy's items_after_test_module lint (denied under -D warnings in CI) rejects the test module that sat between two exported FFI functions. Move it to the end of the file; the tests are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
librocksdb-sys 0.19.0+11.8.1 (pulled in by rocksdb 0.25) targets the RocksDB 11 C API, which dropped the deprecated in_range callback from rocksdb_slicetransform_create. The Docker image and the librocksdb GitHub action still built RocksDB 10.4.2 and 9.9.3, so bindgen produced the six-argument signature and the rocksdb crate failed to compile against it. Build 11.8.1 in both places to match the vendored version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GroveDB #948 and rust-dashcore #1005 are merged. Point at the merge commits on grovedb develop (985ece65) and rust-dashcore dev (057e79cd) instead of the pull-request heads. The rust-dashcore tree is identical; the grovedb merge only adds the #947 release workflow change on top of the previously pinned tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… it in derives Every derive that spelled bincode::DecodeUntrusted now names DecodeUntrusted and imports it beside Decode and Encode, the way the other bincode derives are written. The two files that had no bincode import at all (credit operations and the compacted address balance proof) import Encode and Decode the same way instead of path-qualifying them. The Txid adapter in serialization/untrusted.rs keeps its fully qualified call: that module has no imports by design. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ng untrusted decoding CI diffs every @immutable and @append_only block against the base branch and rejects any change to the former and any deleted line in the latter. Adding DecodeUntrusted to those derive lists reflowed them, which read as a change or a deletion. BlockInfo is tagged immutable, so its derive list is restored verbatim and DecodeUntrusted is implemented by hand outside the block, reading the fields in declaration order like the derived Decode; a test pins the two decoders to each other. The four append-only enums keep their derive lists and carry the new derive as a separate attribute; a comment between the two keeps rustfmt from merging them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GroveDB now refuses a V1 layer proof that is read in the other op family (grovedb #863): a layer proof is emitted entirely in the family of the direction its query walks. When a document query carries a startAt or startAfter cursor, the prover merges the one-key cursor lookup into the page proof, and since grovedb's merge needs every input to walk the same way, the cursor layer is emitted in the page's orderBy direction. The verifier rebuilt that lookup as a fresh ascending single-key query, so every proved descending page with a cursor failed with InvalidProof. Read the cursor layer in the page's direction when it is verified as a subset of the page proof. The direction comes from the same query builder the prover uses and never depends on the cursor document; a single key decodes identically either way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keep the workspace bincode alias and the GroveDB pin from this branch; take the crates.io versioned-feature-core from #4631.
cargo fmt --check fails on v4.2-dev since #4218 landed this test unformatted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
QuantumExplorer
left a comment
There was a problem hiding this comment.
Reviewed commit 462faae and found one P1 regression, detailed inline. Before posting, I checked the new head, 5dd5d62: it aligns the cursor verification query with the page's direction and appears to address the finding. I have not rerun the four failing pagination tests against that fix, so this is a review comment rather than a request for changes on the new head.
Validation on the originally reviewed commit: 1,708 local tests passed across platform-value, platform-serialization, and the DPP untrusted-decoding regression suite. FFI inspection found no additional actionable issue; the FFI tests could not run because a dependency build attempted a download that failed. No live network upgrade or browser/device execution was performed.
…der and cursor The proved contestant-votes helper verified every proof with a fixed ascending, unlimited query regardless of the request it had just sent. GroveDB now refuses a layer proof read in the other op family, so the descending request failed with InvalidProof. Mirror the request's order, count and cursor into the verifier query, as the SDK's proof verifier does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The @immutable and @append_only check text-diffs a tagged block against the base branch, and the derive attribute sits inside that block, so adding a derive (which rustfmt reflows onto separate lines) read as a change to the structure. A derive list cannot change how the existing fields or variants serialize. Strip #[derive(...)] attributes, single- or multi-line, before diffing; fields, variants and the attributes that do shape the encoding (serde, repr, platform_serialize) stay compared verbatim. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With the immutable structure check ignoring derive lists, BlockInfo and the four append-only enums derive DecodeUntrusted like every other type. This replaces the hand-written BlockInfo decoder and the separate derive attributes that only existed to keep those blocks byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…de 2.1.0 Advance GroveDB to develop 985ece65 (6.0.0) and rust-dashcore to dev 057e79cd, use rocksdb 0.25.0 from crates.io instead of the rust-rocksdb fork, and make the workspace bincode dependency an alias for the published grovedb-bincode 2.1.0 so GroveDB, rust-dashcore and Platform share one set of bincode traits without a Cargo patch. Adapt to the GroveDB API changes: batch options in Drive's grove operations, element verification aligned with the prover's right-to-left traversal, which GroveDB now checks strictly, and the new backward reference element variants rendered by the FFI and wasm SDK path element queries. BREAKING CHANGE: Rust consumers serializing Platform or Core types must use the published grovedb-bincode 2.1.0 crate; bincode trait identities now come from it. Wire encodings and the C ABI are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
librocksdb-sys 0.19.0+11.8.1 (pulled in by rocksdb 0.25) targets the RocksDB 11 C API, which dropped the deprecated in_range callback from rocksdb_slicetransform_create. The Docker image and the librocksdb GitHub action still built RocksDB 10.4.2 and 9.9.3, so bindgen produced the six-argument signature and the rocksdb crate failed to compile against it. Build 11.8.1 in both places to match the vendored version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GroveDB now refuses a V1 layer proof that is read in the other op family (grovedb #863): a layer proof is emitted entirely in the family of the direction its query walks. When a document query carries a startAt or startAfter cursor, the prover merges the one-key cursor lookup into the page proof, and since grovedb's merge needs every input to walk the same way, the cursor layer is emitted in the page's orderBy direction. The verifier rebuilt that lookup as a fresh ascending single-key query, so every proved descending page with a cursor failed with InvalidProof. Read the cursor layer in the page's direction when it is verified as a subset of the page proof. The direction comes from the same query builder the prover uses and never depends on the cursor document; a single key decodes identically either way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…der and cursor The proved contestant-votes helper verified every proof with a fixed ascending, unlimited query regardless of the request it had just sent. GroveDB now refuses a layer proof read in the other op family, so the descending request failed with InvalidProof. Mirror the request's order, count and cursor into the verifier query, as the SDK's proof verifier does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cargo fmt --check fails on v4.2-dev since #4218 landed this test unformatted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… calls The new history-keeping cursor round trip calls `execute_with_proof_only_get_elements`, which exists only with both the `server` and `verify` features, so the test now carries the same gate as its sibling in query_tests.rs (review suggestion). Also declare the `serde` feature on the two crates that call `bincode::serde` directly (wasm-dpp2, rs-platform-wallet-storage) instead of relying on `dpp` enabling it for them (review suggestion). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Picks up the fix that keeps each partial-batch DeleteTree removal's cleanup behavior on the observed deletion instead of a path-keyed map. No Drive change; rs-drive and the fee baselines are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Test-only change on the GroveDB side (coverage for the twin ops); no Drive change and the fee baselines are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GroveDB #953 (per-operation backward-reference declarations through the DontCheckForBackwardsReferences twins) is merged; the pins move from the PR head to the develop merge commit. The Rust tree is identical to the previous pin; only CI workflow files differ. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Final validation — Phase 1 + Phase 2
The PR correctly adopts untrusted decoding across the principal external-input paths and adds effective allocation-sensitive wallet blob coverage. Two issues remain: the immutable-structure CI guard removes every derive attribute instead of only the new untrusted-decoder derive, and two host-supplied xpub decoders still use ordinary unbounded bincode decoding. Both are in scope for the PR’s stated external-input hardening and should be addressed.
🟡 2 suggestion(s)
2 finding(s) not shown inline (GitHub refused the PR diff as too large)
🟡 Suggestion: Do not remove all derive attributes from immutable-structure comparisons
.github/workflows/tests-rs-workspace.yml:287-296
strip_derives() removes every #[derive(...)] attribute before comparing @immutable and @append_only blocks. Although adding DecodeUntrusted itself is wire-layout neutral, blanket removal also hides changes to other derives, including serialization-related derives such as Encode, Decode, Serialize, Deserialize, or project-specific encoding derives. A future derive-only change could therefore alter serialization behavior or remove a required serialization implementation without the immutable-structure guard detecting it. Normalize only the DecodeUntrusted addition, including its multiline form, while retaining the other derives in the comparison.
source: muse-spark-1.3-contributor (phase1-reviewer: general, ffi-engineer, rust-quality); gpt-6-astra (phase2-reviewer: ffi-engineer, rust-quality, security-auditor)
🟡 Suggestion: Bound the remaining ordinary decoding of host-supplied xpubs
packages/rs-platform-wallet-ffi/src/persistence.rs:5039-5041
The ProviderPlatformKeys EdDSA path and the fallback ExtendedPubKey path still call ordinary bincode::decode_from_slice(..., config::standard()) on xpub_bytes received through the host persistence boundary. The PR’s stated policy is to use untrusted decoding or an explicit budget for foreign type graphs that cannot implement it. Even if the current key structures are fixed-width, leaving these decoders unbounded makes the boundary depend on that implementation detail and is inconsistent with the newly bounded BLS path; the ignored consumed length also permits trailing bytes. Apply the same small explicit limit used for BLS and validate that the entire input was consumed, or migrate these type graphs to DecodeUntrusted.
source: muse-spark-1.3-contributor (phase1-reviewer: general, ffi-engineer, rust-quality, security-auditor); gpt-6-astra (phase2-reviewer: security-auditor)
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: ffi-engineer); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 4: muse-spark-1.3-contributor (agent: phase1-reviewer, role: security-auditor); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: ffi-engineer); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 8: 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, cross-cutting change that directly modifies peer-facing and externally supplied bincode deserialization across DPP, proof verification, SDK/FFI/WASM inputs, wallet persistence, and consensus-relevant data structures. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— ffi-engineer (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— security-auditor (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 11% left, 5h 100% left),glm-5.3-flash(zai below 15% reserve: 5h 99% left, weekly 13% left) - Fresh verifier:
gpt-6-astra— final-verifier; agentastra-verifier - Phase 2 reviewers:
gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— ffi-engineer (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 `.github/workflows/tests-rs-workspace.yml`:
- [SUGGESTION] .github/workflows/tests-rs-workspace.yml:287-296: Do not remove all derive attributes from immutable-structure comparisons
`strip_derives()` removes every `#[derive(...)]` attribute before comparing `@immutable` and `@append_only` blocks. Although adding `DecodeUntrusted` itself is wire-layout neutral, blanket removal also hides changes to other derives, including serialization-related derives such as `Encode`, `Decode`, `Serialize`, `Deserialize`, or project-specific encoding derives. A future derive-only change could therefore alter serialization behavior or remove a required serialization implementation without the immutable-structure guard detecting it. Normalize only the `DecodeUntrusted` addition, including its multiline form, while retaining the other derives in the comparison.
In `packages/rs-platform-wallet-ffi/src/persistence.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/persistence.rs:5039-5041: Bound the remaining ordinary decoding of host-supplied xpubs
The `ProviderPlatformKeys` EdDSA path and the fallback `ExtendedPubKey` path still call ordinary `bincode::decode_from_slice(..., config::standard())` on `xpub_bytes` received through the host persistence boundary. The PR’s stated policy is to use untrusted decoding or an explicit budget for foreign type graphs that cannot implement it. Even if the current key structures are fixed-width, leaving these decoders unbounded makes the boundary depend on that implementation detail and is inconsistent with the newly bounded BLS path; the ignored consumed length also permits trailing bytes. Apply the same small explicit limit used for BLS and validate that the entire input was consumed, or migrate these type graphs to `DecodeUntrusted`.
…rom v4.2-dev ShieldFromIdentity (#4708) and IdentityTopUpFromShieldedPool (#4711) landed on v4.2-dev after this branch adopted untrusted decoding, so they derived Decode only and no longer satisfied the StateTransition graph's DecodeUntrusted bound. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…f dropping them Stripping every #[derive(...)] attribute let a swap of a serialization derive (Serialize_repr for Serialize, or dropping Encode) through the @immutable/@append_only comparison. Rewrite each derive attribute as one canonical line with only DecodeUntrusted removed, so a rustfmt reflow of the list still reads as unchanged while every other derive is compared. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The upstream key-wallet xpub decoders do not implement DecodeUntrusted, so the ECDSA and EdDSA restoration branches and platform_wallet_account_xpub_to_string now run under the same explicit ACCOUNT_XPUB_DECODE_LIMIT_BYTES budget the BLS branch already had, and the stale RUSTSEC-2025-0141 note goes away now that the workspace is on grovedb-bincode. A regression test pins that a BLS xpub whose public-key length prefix claims a gigabyte fails on the budget rather than on an allocation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Rebased onto Both remaining automated-review suggestions are addressed:
🤖 Addressed by Claude Code |
…sted derives Disk loads and remote input now choose their decoder explicitly. The single PlatformDeserialize derive becomes PlatformDeserializeTrusted, which runs bincode's ordinary decoder and carries the PlatformVersionedDecode body, and PlatformDeserializeUntrusted, which runs the untrusted decoder and requires DecodeUntrusted on the type graph. Each implements its own trait family with suffixed methods (deserialize_from_bytes_trusted / _untrusted, versioned_deserialize_trusted / _untrusted, ...), so no plain name survives and every call site reads its trust level. Call sites: Drive fetches from GroveDB, the vote-poll query key decoders, drive-abci state and checkpoint loads, the wallet's own persisted state transitions and the strategy-tests formats decode trusted; proof verification, check_tx and state-transition processing, request cursors, DAPI error mapping, the SDKs, WASM, FFI parse entry points and host-supplied bytes decode untrusted. Types derive both unless only ever decoded one way; the proof-verifier mock formats derive Trusted alone, replacing the `trusted` attribute flag. The hand-written DataContract and CreatedDataContract impls gain trusted twins, and the wire-only helpers are named deserialize_from_bytes_untrusted_in_version and deserialize_many_untrusted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…inary bincode App-private storage the wallet wrote itself is a disk load, not remote input: SQLite blobs, the persisted identity-key and asset-lock rows, the changeset serde adapter and the host-persisted asset-lock proof go back to ordinary decoding under their existing size bounds, and the untrusted BlobDecode opt-in plus its allocation test go away. Bytes arriving live from Core through the FFI keep the untrusted path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rusted-bincode # Conflicts: # packages/rs-dpp/src/state_transition/serialization.rs
|
Reworked per discussion: disk loads decode trusted, remote input decodes untrusted, and the choice is explicit at every call site.
Measured cost that motivated the split (release, pinned grovedb-bincode 2.1.0): untrusted decoding is 6% to 25% slower than ordinary on collection-heavy structures, so block execution's stored-object decodes stay trusted. Local: workspace check, CI-flag clippy and fmt clean; dpp 226 + 9 untrusted suites, Drive verify 257, drive-abci 82, wallet-ffi 366, wallet-storage 983, wallet shielded 180 passed. 🤖 Addressed by Claude Code |
The SpecificKeys tests landed on v4.2-dev against the old PlatformDeserializable name; response key bytes decode untrusted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…guard The tagged types now derive PlatformDeserializeTrusted and PlatformDeserializeUntrusted where the base derives PlatformDeserialize. A derive that only generates a decoder cannot change the wire layout, so the normalizer drops that family alongside DecodeUntrusted while every other derive stays compared. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pins that the trusted entry points agree with the untrusted ones on well-formed bytes, consumed length included, and reject garbage and empty input with PlatformDeserializationError. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resolves conflicts with the trusted/untrusted decoder split (#4625) and the new shielded identity transitions (#4708, #4711): - scoped consensus errors and the AuthenticationScope family derive DecodeUntrusted plus PlatformDeserializeTrusted/Untrusted - AuthenticationScope::from_bytes decodes with the untrusted bincode decoder - Identity keeps the 256 MiB scoped-key decode budget on the new derives - tests use the *_untrusted decoder entry points Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Issue being fixed or feature implemented
Follows #4635 (merged), which advanced GroveDB to 6.0.0, moved the workspace to the published
grovedb-bincode2.1.0 and adapted to the GroveDB API changes. This PR contains only the untrusted-decoding adoption.Adopt GroveDB #938 and its explicit untrusted decoding APIs. A short input containing an inflated collection length must fail without first reserving memory for the claimed payload. Updating the dependency alone does not change ordinary
Decodeimplementations, so Platform's external input paths must opt in throughout their type graphs.What was done?
Disk loads and remote input choose their decoder explicitly:
PlatformDeserializederive is split intoPlatformDeserializeTrusted(bincode's ordinary decoder, which reserves each collection from its length prefix; also carries the type'sPlatformVersionedDecodebody) andPlatformDeserializeUntrusted(the untrusted decoder, which reserves nothing before the announced elements are read; requiresDecodeUntrustedon the type graph). Each implements its own trait family with suffixed methods (deserialize_from_bytes_trusted/_untrusted,versioned_deserialize_trusted/_untrusted,versioned_limit_deserialize_...,versioned_deserialize_with_bytes_len_...), so no plain name survives and every call site states its trust level;grep deserialize_from_bytes_trustedis the audit list of disk paths. The wire-only helpers are nameddeserialize_from_bytes_untrusted_in_versionanddeserialize_many_untrusted. The hand-writtenDataContractandCreatedDataContractimpls gain trusted twins.check_txand state-transition processing, request cursors and state-transition bytes in query handlers, DAPI error mapping, rs-sdk, rs-sdk-ffi, all WASM crates, the wallet FFI parse entry points, and asset-lock proofs arriving live from Core. Native untrusted derives are added across DPP with custom wire schemas, validation, depth limits and configured budgets preserved; the immutable-structure CI check rewrites each#[derive(...)]as one canonical line with onlyDecodeUntrustedremoved before diffing, so the tagged types derive it like everything else while every other derive, the fields, the variants and the encoding attributes stay guarded.trustedattribute flag). A type deriving only one side makes the other entry point a compile error, which is how a wire-only type is kept off the trusted path.key-walletaccount xpubs for ECDSA, BLS and EdDSA; BLS keys; FFI payloads) receive explicit byte budgets. CustomValuecollections grow only after decoding entries.Cost of the split, measured on the pinned
grovedb-bincode2.1.0 in release mode: untrusted decoding is 6% to 25% slower than ordinary on collection-heavy structures (a document-like struct 643 ns vs 794 ns; a 100-document page 61 µs vs 75 µs; byte vectors within 6%), which is why block execution's stored-object decodes stay on the trusted side.How Has This Been Tested?
Local macOS Rust checks on the final head (merged with
v4.2-devat 4.2.0-dev.9):cargo check --workspace --all-targets --all-features, CI-flag clippy (--workspace --all-targets --all-features --locked -- --no-deps -D warnings) andcargo fmt --all --checkclean.CreatedDataContracttests: 226 passed; the untrusted-decode integration suites (DPP + platform-value): 9 passed, covering malformed lengths, observed allocation sizes, owned/borrowed decoding, wire compatibility, custom validation and the Trusted-only fixture.verify::257 passed; drive-abcidecode_raw_state_transitions,platform_state,fetch_platform_stateandcheck_txfilters 82 passed.Encodeis removed fromBlockInfo's derive list.Earlier rounds ran the CI Rust job's full nextest package set locally (14,864 passed) before the trusted/untrusted split. Device/browser execution and a live network upgrade were not exercised.
Breaking Changes
PlatformDeserializeand thePlatformDeserializable*traits are replaced by theirTrusted/Untrustedtwins with suffixed method names; downstream code must derive the side(s) it decodes as and rename its calls. Custom wallet blob types are unaffected (the base'sPersistableBlobshape stands).Existing bincode encoding and the C ABI are unchanged. Ordinary
Decodeand the separatePlatformVersionedDecodeAPIs retain their existing contracts.Checklist:
For repository code-owners and collaborators only