Skip to content

feat(platform)!: add family-specific state transition size limits and the large contract envelope decode path - #4717

Open
DCG-Claude wants to merge 9 commits into
v5.0-devfrom
dashvm/r06-05
Open

feat(platform)!: add family-specific state transition size limits and the large contract envelope decode path#4717
DCG-Claude wants to merge 9 commits into
v5.0-devfrom
dashvm/r06-05

Conversation

@DCG-Claude

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

Part 1 of 2 for R06-05 of the smart-contract plan (#4626, workstream #4684).

The plan extends the contract create and update state transitions so they can carry large canonical code and declarations, with larger bounded limits applied consistently on every ingress path rather than a separate upload route. Today every state transition family shares one 20 KiB cap, the block decoder maps a not-yet-active variant to a node fault, the Tenderdash block size is a genesis value no protocol version can move, and the transport caps of rs-dapi and Drive would refuse a multi-megabyte transaction before Drive saw it.

This part lands the consensus-side scaffolding for the large envelopes without producing one yet: the version-table limits, the family detection from the wire prefix, the decode generation that applies the family cap and budget, the Tenderdash block parameter push, and the transport and node configuration sized for the cap. Part 2 adds the contract-code capable generation of the two transitions, the bundle type, its validation and its costs.

Refs #4684

What was done?

Version tables (packages/rs-platform-version)

  • Protocol versions 15 to 17 are introduced in the same shape the smart-contract computation limits branch (feat(platform): define protocol-versioned smart-contract computation limits and their gas representation #4705) uses: v15.rs and v16.rs are struct-update placeholders, v17.rs is the 5.0 version, LATEST_VERSION is 17. Whichever of the two branches lands second rebases; the shared files merge as a union.
  • SystemLimits gains max_contract_code_state_transition_size, max_contract_code_state_transition_decode_budget, max_contract_code_bundle_bytes and max_contract_code_modules_per_bundle, all Option, None on SYSTEM_LIMITS_V1 to V4 and the mock tables, and set on the new SYSTEM_LIMITS_V5 with a compile-time consistency assertion. The doc comment on max_state_transition_size now states the relation to the Tenderdash max-tx-bytes correctly (at least the largest family cap, no longer equal).
  • ConsensusVersions gains block_max_bytes and block_max_gas, None on every shipped version and the mocks, set on v17.
  • DRIVE_ABCI_METHOD_VERSIONS_V11 selects decode_raw_state_transitions 1 and consensus_params_update 2; DRIVE_ABCI_QUERY_VERSIONS_V4 selects proofs_query 1. Both are referenced by PLATFORM_V17 only.

dpp (packages/rs-dpp/src/state_transition/envelope_kind.rs, new)

  • StateTransition::peek_envelope_kind reads at most ten bytes (the outer and inner bincode variant indices) and returns StateTransitionEnvelopeKind::Ordinary or ContractCodeCapable { family } for the contract create and update transitions in the generation that will carry a code bundle (outer index 0 or 1, inner index 1). Unknown or truncated prefixes are Ordinary. The discriminants are pinned against real serialized transitions so a reordering of either enum fails a test.
  • family_max_size and family_decode_budget read the cap and the budget from the tables, falling back to the ordinary values where the table holds None.
  • deserialize_from_bytes_with_budget decodes under StateTransitionDecodeBudget::Historical (the shipped decode) or Bounded(67_108_864); any other bounded value is CorruptedCodeExecution, unreachable while the tables hold that number and pinned over every registered version.
  • deserialize_from_bytes_in_version_bounded is the entry point of the paths that admit large envelopes: it decodes under the family budget with the version's value depth limit and reports a not-active variant as BasicError::StateTransitionNotActiveError inside ProtocolError::ConsensusError. The existing deserialize_from_bytes_in_version is untouched.
  • StateTransitionFamilyMaxSizeExceededError (code 10604) names the family whose cap a large envelope exceeded; ordinary families keep StateTransitionMaxSizeExceededError. wasm-dpp gets the mapping arm.

drive-abci

  • execution/platform_events/state_transition_processing/decode_raw_state_transitions/v1 compares the raw length with the family cap before decoding, decodes through the bounded entry point, and files a not-active variant as InvalidEncoding (an unpaid consensus rejection) instead of FailedToDecode (a node fault). v0 is byte-identical; its tests pin protocol version 16, the last one whose tables select it.
  • execution/engine/consensus_params_update/v2 pushes ConsensusParams.block when the new protocol version sets both block caps and the previous version did not carry the same pair, on top of the version parameters v1 pushes. init_chain diffs the first version against the genesis one, so a fresh 5.0 network gets the block size at genesis.
  • query/proofs/v1 applies the same family cap and budget to the transition of a getProofs request.
  • server.rs caps decoding and encoding at MAX_GRPC_MESSAGE_BYTES (34 MiB) on the Platform, DriveInternal and ABCI CheckTx services; a test asserts the constant exceeds every family cap of every registered version. Tonic's 4 MiB default would have rejected a large CheckTx before Drive saw it.
  • mimic/mod.rs: MimicExecuteBlockOutcome carries the consensus parameter update of both proposal paths, and the strategy harness records every round of every block with its app hash (BlockProposalRounds). Two fixes the new strategy test needed: the validator path's response status is asserted (a rejection used to surface as a missing block execution context), and the genesis rewind to the init-chain savepoint is gated on the genesis height rather than on an open transaction, which a retried round of any block also has.

rs-dapi

  • server/grpc.rs: the Platform service decodes up to 34 MiB.
  • services/platform_service/broadcast_state_transition.rs: the pre-filter reads the family from the wire prefix and applies that family's cap from PlatformVersion::latest() (a static upper bound; Drive enforces the active version's cap).
  • clients/drive_client.rs: the client to Drive encodes up to 34 MiB, since waitForStateTransitionResult re-sends the whole transaction inside GetProofsRequest.
  • clients/tenderdash_websocket.rs: the Tenderdash event listener connects with explicit tungstenite limits (MAX_TENDERDASH_WS_MESSAGE_BYTES, 64 MiB for both the message and the frame). Tenderdash writes every event as one uncompressed frame, and tungstenite's default 16 MiB frame limit would disconnect the listener on the Tx event of a family-cap transaction (44.7 MB of base64) or the NewBlock event of a full 36 MiB block, exactly when waitForStateTransitionResult needs it. A test pins the cap above the base64 size of the largest block of every registered version, and two tests deliver a family-cap Tx event and a block-cap NewBlock event through a local server: the configured connection receives both, the default connection is refused at the frame limit.

dashmate

  • platform.drive.tenderdash.mempool.maxTxBytes (default 33,554,432) and platform.drive.tenderdash.rpc.maxBodyBytes (default 50,000,000) replace the hard-coded max-tx-bytes and max-body-bytes in the Tenderdash template; the stock p2p sendRate and recvRate rise to 20,480,000 bytes per second. A 5.0.0 migration adds the options and moves only stock bandwidth caps. Schema, docs and a migration unit test are updated. Genesis block.max_bytes stays 2 MiB: Drive raises it at the protocol boundary.

Book: state-transitions/lifecycle.md (caps, budgets, the prefix peek, the block parameter push), versioning/feature-versions.md and platform-version.md (the new fields), error-handling/error-codes.md (10604).

Finding recorded for FIX-06. The #[platform_serialize(limit = 100000)] on StateTransition is inert: the derive reads only the first platform_serialize attribute (unversioned), so deserialize_from_bytes applies with_no_limit() and the ordinary families are bounded by the wire cap alone (a 5 MB payload decodes through it; the test should_decode_a_large_real_payload_under_both_budgets documents this). That decode is deliberately left as shipped, because changing it retroactively could change which historical blocks decode. The contract-code envelope is the first family decoded under an explicit bincode budget. StateTransitionDecodeBudget::Historical names the shipped behaviour so the distinction is visible at every call site.

Benchmark and chunking decision. Micro-benchmark on this machine (Apple Silicon, release build, bincode 2.0.1, sha2 0.10), one module plus a 16 KiB schema, rounded:

Payload Encoded bincode decode SHA-256 of the module signable bytes plus SHA-256d
1 MiB 1.07 MB 0.11 ms 3.1 ms 3.2 ms
8 MiB 8.4 MB 0.54 ms 15.7 ms 13.9 ms
16 MiB 16.8 MB 0.84 ms 26.3 ms 27.6 ms
32 MiB 33.6 MB 1.8 ms 52.2 ms 55.5 ms

A crafted 8 GB length claim in a 10 byte payload is rejected before allocation under the bounded budget. A 32 MiB envelope decodes in under 2 ms and hashes twice in about 110 ms, inside a CheckTx or block slot; Tenderdash's hard block cap is 100 MB and its ABCI socket reader accepts 100 MB, so a 32 MiB transaction needs configuration, not protocol work. Chunking would add staging state, expiry, cleanup fees and refunds the owner asked to specify only if measurements require it; they do not. The remaining cost is gossip bandwidth (about 1.6 s per hop at the new default rate), the one number the testnet rehearsal must confirm before the cap is final. The #[ignore] test measure_decode_gate_at_the_family_cap in envelope_kind.rs reproduces the decode-gate half in-tree.

Plan review finding R4 (use a hashing operation that can represent large envelopes). No hashing operation is recorded in this part. Part 2 widens HashBlockCount from u16 to u32 so a DoubleSha256 operation over up to 32 MiB of signable bytes can be priced, records it in the transformer, and prices each module hash through the hashing fee group; the disposition is unchanged.

Left for FIX-06. The family cap and the bincode budget stop over-limit input before allocation, but the per-module bound of part 2 is checked after the envelope is decoded into owned buffers. A zero-copy walk that checks each module length against the table before it is materialised, and the DAPI-side rejection of a bundle-carrying envelope before it reaches Tenderdash, are FIX-06's work.

Sibling branches. #4705 (R06-01) introduces the same v15.rs, v16.rs, v17.rs, version/mod.rs, protocol_version.rs and system_limits/{mod,v5}.rs edits; whichever lands second rebases and takes the union (SYSTEM_LIMITS_V5 carries both feature groups, PLATFORM_V17 both change lists). #4648 (v4.2-dev) introduces a DRIVE_ABCI_METHOD_VERSIONS_V11 on its own v15; on the forward merge the incoming generation keeps its number and this one is renumbered.

How Has This Been Tested?

Local gate, exit codes captured (private CARGO_TARGET_DIR):

cargo fmt --all -- --check
cargo clippy -p dpp -p drive -p drive-abci -p platform-version -p rs-dapi --all-features --all-targets -- -D warnings
cargo check --workspace --all-targets
cargo test -p platform-version --features mock-versions
cargo test -p dpp --features all_features envelope_kind
cargo test -p drive-abci --lib -- decode_raw_state_transitions consensus_params_update query::proofs server::tests
cargo test -p drive-abci --test strategy_tests -- run_chain_upgrade_to_v17_pushes_block_params_on_both_proposal_paths run_chain_lock_update_quorums_not_changing run_chain_v12_to_v13_locks_in_before_activation run_chain_comprehensive_mixed_operations_with_epoch_change_and_quorum_rotation
cargo test -p rs-dapi -- broadcast_state_transition message_size_boundary large_events

Tests added:

  • platform-version: the contract-code limits are absent below 17 and present from 17; the block parameters are absent below 17 and paired from 17 (both loop PLATFORM_VERSIONS, since the decoder's cap choice and the push depend on them).
  • dpp envelope_kind: classification of every serialized fixture, the contract-code prefix of both families pinned against real V0 bytes, truncated and unknown prefixes, cap and budget selection on both sides of the gate, every table budget supported, an unsupported budget rejected, a crafted length claim rejected by the limit under the bounded budget, a compact wide-container transition decoding identically through the historical and the bounded path at 14 and at latest, not-active reported as a consensus error only on the bounded path.
  • drive-abci decode_raw_state_transitions/v1: ordinary cap kept for every ordinary family and for V0 contract transitions, a contract-code envelope one byte over the family cap rejected with 10604 and one at the cap reaching the decode step, not-active classified as InvalidEncoding; dispatcher tests run the same inputs through v0 (at 16) and v1 (at latest). consensus_params_update/v2: push when set, no push when unchanged or between versions that set none, never a zeroed gas cap, emergency updates keep priority, genesis push; dispatcher arm for 2. query_proofs_v1: at the family cap reaches decode, above it is rejected, ordinary cap kept. server.rs: the gRPC cap exceeds every family cap. Strategy test run_chain_upgrade_to_v17_pushes_block_params_on_both_proposal_paths: seeded upgrade 16 to 17 with independent process-proposal verification and signed chain locks; the activation block itself is proposed in round 0, abandoned without finalization, and proposed again in round 1, and both rounds return the same block parameters from prepare_proposal and process_proposal over the same app hash; the committed state is the finalized round's (app hash and GroveDB root agree, the abandoned round left nothing behind); every block before the activation and every round of the three blocks after it pushes none.
  • rs-dapi: the broadcast pre-filter for both kinds and one byte over each cap; a local tonic server behind the configured client receives a GetProofsRequest carrying a family-cap transaction intact and never sees one above the client's outbound cap; the WebSocket cap test and the two large-event delivery tests described above.
  • dashmate: migrateConfigFileFactory.spec.js covers the 5.0.0 migration (options added, stock rates raised, tuned rate kept, migrated config loads). The mocha suite in this checkout needs a built wasm-dpp through js-dapi-client, which was not available locally; the same assertions plus the template rendering of all four default configs were run directly through the dashmate factories with yarn node (all pass), and CI runs the suite.

Breaking Changes

This part is a consensus change at protocol version 17: at the activation boundary Drive pushes ConsensusParams.block { max_bytes: 37_748_736, max_gas: 57_631_392_000 } to Tenderdash from both proposal paths (consensus_params_update v2), so every validator raises the block size limit at the same height, and the block decoder of that version applies a 32 MiB cap to the contract-code capable contract transitions (decode_raw_state_transitions v1). Nodes that disagree on either would fork at that height, which is what the ! marks.

Historical behaviour is preserved: every shipped vN module is byte-identical, the new tables are read only at protocol version 17, which no network runs yet, and every family cap at every active version is unchanged. The dashmate config format moves to 5.0.0 with a migration that adds two required options. Part 2 adds the wire generation that can actually carry a code bundle and carries ! as well.

Decisions taken (provisional values):

  • Contract-family transition cap 32 MiB (33_554_432): register "Signed contract create/update transition".
  • Contract-family decode budget 64 MiB (67_108_864): the wire cap plus headroom for the container claims bincode charges while decoding a contract schema.
  • Canonical bytes per bundle 16 MiB (16_777_216): register "Canonical WASM 16 MiB per version", read as the bundle total.
  • Modules per bundle 16: engineering guess, the register gives no number.
  • Tenderdash block.max_bytes at 5.0: 36 MiB (37_748_736), one maximal transaction plus 4 MiB for header, commit and evidence; block.max_gas 57,631,392,000, the value every network launched with, carried so the push keeps it.
  • max-tx-bytes 33,554,432 and max-body-bytes 50,000,000 (base64 inflation of the RPC body): node-local dashmate defaults.
  • Tenderdash WebSocket message and frame limit 64 MiB in rs-dapi: the base64 size of a full 36 MiB block plus the JSON envelope, node-local.
  • p2p send and receive rates 20,480,000 bytes per second: node-local; the gossip time is the number the testnet rehearsal confirms.
  • Wire discriminants: outer 0 (create) and 1 (update), inner 1 for the contract-code capable generation, pinned by serialising the real V0 variants.
  • The not-active case is classified as an unpaid consensus rejection only from the v1 decoder (protocol version 17); the frozen v0 decoder keeps the legacy node-fault classification for every earlier version.
  • The historical decode of the ordinary families is left unbounded as shipped (see the finding above); only the contract-code envelope gets a bincode budget.
  • Chunking: not selected (benchmark above).

All numbers are measured and revised before any network is asked to propose protocol version 17; the register in #4684 is the place they are tracked.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Posted autonomously by DashVM (Claude Fable 5.1) on behalf of pasta.

🤖 Generated with Claude Code

Automated reviewer consensus (Fable 5.1 implementer, GPT-6 Astra reviewer)

Reviewer consensus

Plan Review consensus

  • R1 [blocker] Preserve the historical bincode decode budget -> resolved
    • at PLAN.md:157, D7 and Part 1
  • R2 [blocker] Correct the contract-update wire discriminant -> resolved
    • at PLAN.md:147, D6 and section 7 prefix tests
  • R3 [major] Handle pre-activation rejection on the decoder actually selected -> withdrawn
    • at PLAN.md:127, D4 and D5
  • R4 [major] Use a hashing operation that can represent large envelopes -> still_open
    • at PLAN.md:215, D10 and Part 2 execution_operation changes
  • R5 [major] Raise the outbound GetProofs message cap -> resolved
    • at PLAN.md:242, D12
  • R6 [major] Update client deserialization paths as well as builders -> resolved
    • at PLAN.md:443, Part 2 wasm-dpp2
  • R7 [major] Exercise block-limit activation through both proposal paths -> resolved
    • at PLAN.md:491, section 7
  • R8 [minor] Update the remaining GetProofs decoder references -> resolved
    • at PLAN.md:290 and PLAN.md:393
  • R9 [minor] Move the V1 pre-activation regression to part 2 -> resolved
    • at PLAN.md:576, section 7 Part 1
    • round 1 R1: accept: Confirmed: the iterative Value decoder claims container memory against the bincode budget (packages/rs-platform-value/src/lib.rs:268,284), so the 100,000 budget bounds allocation claims, not encoded bytes. D7 now separates the wire cap (plain length comparison) from the decode budget: deserialize_fr
    • round 1 R2: accept: Confirmed at packages/rs-dpp/src/state_transition/mod.rs:463: bincode order is Create 0, Update 1, Batch 2; I had used the StateTransitionType numbering. D6 now uses outer 0 and 1 with inner 1, and the pinning test serialises real create and update transitions (V0 in part 1, V0 and V1 in part 2) plu
    • round 1 R3: partial: The observation is right: decode v0 is frozen and selected below 17, so no shared-helper change can alter pre-17 classification without editing shipped code, and D4 as written promised something the dispatch could not deliver. The suggested fix (a new replay-safe unsupported-envelope path for pre-17
    • round 1 R4: accept: Confirmed: HashBlockCount = u16 at execution_operation/mod.rs:69, at most 4 MiB per hashing operation. D10 widens it to u32 (the alias is used only in that module, ValidationOperation is never serialised, and base + per_block * count as u64 is unchanged for every existing count, so shipped pricing i
    • round 1 R5: accept: Confirmed at packages/rs-dapi/src/clients/drive_client.rs:67: MAX_ENCODING_BYTES is 32 MiB and wait_for_state_transition_result sends the whole transaction inside GetProofsRequest. D12 and part 1 raise the client's encoding cap to 34 MiB and add a boundary test that encodes a GetProofsRequest carryi
    • round 1 R6: accept: Confirmed at packages/wasm-dpp2/src/state_transitions/base/state_transition.rs:243-263: fromBytes, fromHex and fromBase64 use the 100,000-budget entry point. Part 2 routes the three umbrella factories through deserialize_from_bytes_in_version_bounded with PlatformVersion::latest() (client code choos
    • round 1 R7: accept: Confirmed: the mimic discards consensus_param_updates (mimic/mod.rs:178) and MimicExecuteBlockOutcome has no field for it. Part 1 adds the prepare and process consensus_param_updates to the mimic outcome and an upgrade strategy test (16 to 17, independent process_proposal verification on) asserting
    • round 2 R1: accept: Settled in round 1 (same finding, same text). D7 separates the wire cap from the decode budget; deserialize_from_bytes_in_version and the derive's deserialize_from_bytes keep the 100,000 budget and their error mapping for the frozen decode v0; the new deserialize_from_bytes_in_version_bounded is cal
    • round 2 R2: accept: Settled in round 1. D6 uses outer indices 0 (create) and 1 (update) with inner index 1, verified at packages/rs-dpp/src/state_transition/mod.rs:463, and the pinning test serialises real create and update transitions (V0 in part 1, V0 and V1 in part 2) instead of hand-written constants.
    • round 2 R3: partial: Settled in round 1. The observation stands; the suggested new pre-17 path is not taken because decode v0 is frozen and the shipped semantics for every not-yet-active variant are FailedToDecode, proposer removes, validator rejects the block. D4 keeps that legacy behaviour unchanged below 17 and pins
    • round 2 R4: accept: Settled in round 1. HashBlockCount widens from u16 to u32 (module-local alias, in-memory type, unchanged price formula), the byte-to-block conversion is a checked u32::try_from, and exact fee tests above 4 MiB and at the bundle limit plus a pricing-parity test for existing counts are in section 7.
    • round 2 R5: accept: Settled in round 1. The rs-dapi to Drive client's MAX_ENCODING_BYTES rises to 34 MiB (packages/rs-dapi/src/clients/drive_client.rs:67) with a GetProofsRequest boundary test through the configured client against a local tonic server.
    • round 2 R6: accept: Settled in round 1. The wasm-dpp2 umbrella factories fromBytes, fromHex and fromBase64 decode through deserialize_from_bytes_in_version_bounded with PlatformVersion::latest(), with bytes, hex and base64 round trips of signed V1 create and update envelopes carrying a 4 MiB module.
    • round 2 R7: accept: Settled in round 1. The mimic outcome gains the prepare and process consensus_param_updates; part 1 adds the 16 to 17 upgrade strategy test asserting both proposal paths carry the same BlockParams at the boundary block only, retry through cached proposer results, and the raised max_tx_bytes on the n
    • round 2 R8: accept: Two stale references remained after the round 1 rewrite of D7 (D12 and the part 1 file checklist for query/proofs/v1). Both now name deserialize_from_bytes_in_version_bounded with the active version, and the proofs v1 entry gains a test that a family-cap payload reaches decode under the contract-fam
    • round 2 R9: accept: Correct: in part 1 no V1 variant exists, so an envelope with outer index 0 and inner index 1 fails serialization as UnpaidConsensusError(SerializedObjectParsingError), not as the not-active InternalError. Part 1 now keeps an unknown-variant rejection test with exactly that expectation; the PV14 not-

Review consensus

  • R1 [major] Raise the Tenderdash WebSocket frame limit -> resolved
    • at packages/rs-dapi/src/clients/tenderdash_websocket.rs:126
  • R2 [major] Test retries on the activation block itself -> resolved
    • at packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs:719
  • R3 [minor] Mark this part as a consensus change -> resolved
    • at PR.md:103
  • R4 [nit] Use an allowed scope for the documentation commit -> resolved
    • at .github/workflows/pr.yml:34; commit 5ef97a3985
    • round 1 R1: accept: Confirmed: both connect paths used connect_async with tungstenite defaults (16 MiB frame, 64 MiB message) and Tenderdash writes each event as one uncompressed frame, so a family-cap Tx event (44.7 MB base64) or a full 36 MiB NewBlock event would drop the listener. Both connections now go through con
    • round 1 R2: accept: The harness now records every round of every block (BlockProposalRounds) with both paths' consensus parameter updates and the app hash the round produced. The test runs 120 one-second blocks so 17 is locked in and the chain stops one block short of the activation, then continues for exactly one bloc
    • round 1 R3: accept: PR-title.txt is now feat(platform)!: and the Breaking Changes section of PR.md names the consensus change this part carries at protocol version 17 (the ConsensusParams.block push of 37_748_736 / 57_631_392_000 from both proposal paths and the 32 MiB family cap in decode_raw_state_transitions v1), st
    • round 1 R4: accept: book is not in the scope allowlist of .github/workflows/pr.yml. The docs commit was amended in place (HEAD asserted to be 5ef97a3985 before the amend; the branch is unpushed) to docs(platform): describe the per-family state transition caps, decode budgets and block parameter push.

DCG-Claude and others added 9 commits September 12, 2026 14:00
…ameters to the version tables

Introduce protocol versions 15 to 17 in the shape the smart-contract
computation limits branch uses (15 and 16 as struct-update placeholders,
17 as the 5.0 version) so the two branches merge as a union.

SystemLimits gains the contract-code envelope, decode budget, bundle
byte and module count limits, None on every shipped table and the mocks
and provisional register values on SYSTEM_LIMITS_V5. ConsensusVersions
gains the Tenderdash block byte and gas caps Drive pushes at activation,
None before 17. DRIVE_ABCI_METHOD_VERSIONS_V11 selects the family-cap
decode generation and the block parameter push;
DRIVE_ABCI_QUERY_VERSIONS_V4 selects the proof query that decodes under
the family budget. The implementations follow in later commits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d decode under its budget

StateTransition::peek_envelope_kind reads at most ten bytes (the outer
and inner bincode variant indices) and names the contract-code capable
generations of the contract create and update transitions without
decoding or allocating; anything else is Ordinary and bounded exactly as
today. family_max_size and family_decode_budget read the cap and budget
from the version tables, deserialize_from_bytes_with_budget applies the
fixed set of bincode budgets the tables may hold, and
deserialize_from_bytes_in_version_bounded is the entry point of the
paths that admit large envelopes: it decodes under the family budget
and reports a not-active variant as the consensus error it is instead
of a protocol error. The untouched deserialize_from_bytes_in_version
keeps its behaviour for the frozen v0 block decoder.

Finding recorded in the tests: the limit attribute on the enum is not
read by the derive, so the shipped decode has no bincode budget and the
ordinary families are bounded by the wire cap alone. That decode is kept
as is; the contract-code envelope is the first family decoded under an
explicit budget.

StateTransitionFamilyMaxSizeExceededError (code 10604) names the family
whose cap a large envelope exceeded, with the wasm-dpp mapping arm.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… and push block parameters

decode_raw_state_transitions v1 compares the raw length with the cap of
the family the wire prefix names, decodes under that family's budget
and files a variant the active version does not admit as an unpaid
consensus rejection instead of a node fault. Version 0 stays
byte-identical and its tests pin protocol version 16, the last one
whose tables select it.

consensus_params_update v2 pushes ConsensusParams.block when the new
protocol version sets the block byte and gas caps and the previous one
did not carry the same pair, on top of the version parameters v1
pushes, so every validator raises the Tenderdash block size at the same
height and a fresh network gets it from init_chain.

query_proofs v1 applies the same family cap and budget to the
transition of a getProofs request. The Drive gRPC server caps decoding
and encoding at 34 MiB on the Platform, DriveInternal and ABCI CheckTx
services, above every family cap of every registered version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… cap

The Platform service decodes requests up to 34 MiB, the broadcast
pre-filter reads the family from the wire prefix and applies the cap
of that family, and the client to Drive encodes up to 34 MiB so a
waitForStateTransitionResult proof request carrying a maximal
transition reaches Drive; a local server test pins both sides of that
boundary.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ade on both proposal paths

The block mimic keeps the consensus parameter update of prepare_proposal
and, when process_proposal runs independently, of that path as well, and
the strategy harness records both per height. A seeded upgrade from 16
to 17 with independent process-proposal verification asserts that the
boundary block pushes the block byte and gas caps from both paths, every
other block pushes none, and a retried round after activation is served
from the cached proposer results without a push.

Two mimic fixes the test needed: the validator path's response status is
asserted instead of assuming a block execution context, and the genesis
rewind to the init-chain savepoint is gated on the genesis height rather
than on an open transaction, which a retried round of any block also has.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…th limits for contract-code envelopes

max-tx-bytes and max-body-bytes were hard-coded in the Tenderdash
template; they become the options
platform.drive.tenderdash.mempool.maxTxBytes (32 MiB, the contract-code
state transition cap) and platform.drive.tenderdash.rpc.maxBodyBytes
(50,000,000: the base64 inflation of such a transition inside
broadcast_tx_sync plus the JSON envelope). The stock p2p send and
receive rates rise to 20,480,000 bytes per second so a maximal
transaction gossips in seconds. A 5.0.0 migration adds the options and
moves only stock bandwidth caps; operator-tuned values are left alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… budgets and block parameter push

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ents

Tenderdash writes every event as one uncompressed WebSocket frame, so
tungstenite's default 16 MiB frame limit disconnected the listener on
the Tx event of a contract-code state transition at the 32 MiB family
cap (44.7 MB of base64) and on the NewBlock event of a full 36 MiB
block, exactly when waitForStateTransitionResult was waiting for the
transaction. Both connections now use an explicit 64 MiB message and
frame limit. A test pins the cap above the base64 size of the largest
block of every registered protocol version, and two tests deliver a
family-cap Tx event and a block-cap NewBlock event through a local
server: the configured connection receives both, the default one is
refused at the frame limit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…block parameter push

The harness now records every round of every block with the consensus
parameter update of both proposal paths and the app hash the round
produced. The upgrade test runs the chain to the block before the
activation of 17, proposes the activation block in round 0, abandons it
without finalization, proposes it again in round 1 and finalizes that:
both rounds return the same block parameters from prepare_proposal and
process_proposal over the same app hash, the committed state is the
finalized round's (app hash and GroveDB root agree), and every block
before the activation and every round of the blocks after it pushes
none.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 8b690616-c2ce-4d59-afcd-591ab9b23465

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-09-12T20:41:48.262Z

@thepastaclaw

thepastaclaw commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 7a257f1) · triage: critical

@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.75445% with 121 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.92%. Comparing base (5f1e0cc) to head (7a257f1).

Files with missing lines Patch % Lines
...kages/rs-dpp/src/state_transition/envelope_kind.rs 92.70% 35 Missing ⚠️
..._processing/decode_raw_state_transitions/v1/mod.rs 88.16% 31 Missing ⚠️
...rc/execution/engine/consensus_params_update/mod.rs 85.38% 19 Missing ⚠️
packages/rs-drive-abci/src/server.rs 35.71% 18 Missing ⚠️
...ion_processing/decode_raw_state_transitions/mod.rs 87.67% 9 Missing ⚠️
packages/rs-drive-abci/src/query/proofs/v1/mod.rs 94.48% 7 Missing ⚠️
...execution/engine/consensus_params_update/v2/mod.rs 98.61% 1 Missing ⚠️
packages/rs-drive-abci/src/query/proofs/mod.rs 50.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v5.0-dev    #4717      +/-   ##
============================================
- Coverage     86.36%   84.92%   -1.44%     
============================================
  Files          2766     2770       +4     
  Lines        366105   373168    +7063     
============================================
+ Hits         316191   316921     +730     
- Misses        49914    56247    +6333     
Components Coverage Δ
dpp 84.52% <92.70%> (-2.78%) ⬇️
drive 83.91% <ø> (-0.35%) ⬇️
drive-abci 87.33% <87.73%> (-2.34%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 49.78% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 1 + Phase 2

The new bounded decoder has one confirmed diagnostic error: it reports a byte-valued budget as kilobytes. Source inspection did not establish an in-scope blocker among the remaining findings; the proposed activation collision is historical, and the transport concerns do not demonstrate a failure under the current implementation and deployment controls.

🟡 1 suggestion(s)

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: rust-quality); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: security-auditor); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: critical by gpt-6-astra (effort low) — This large, intricate diff changes consensus rules in rs-drive-abci's decode_raw_state_transitions/v1 and consensus_params_update/v2, plus peer-facing deserialization in rs-dpp's envelope_kind.rs, introducing version-gated family size limits, decode budgets, and block parameter updates whose consistency across ingress and upgrade paths is consensus-critical.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — security-auditor (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-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; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — security-auditor (completed, effort xhigh); agent phase2-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-dpp/src/state_transition/envelope_kind.rs`:
- [SUGGESTION] packages/rs-dpp/src/state_transition/envelope_kind.rs:198-203: Convert the bounded decode budget to kilobytes in the error
  `BUDGET` is 67,108,864 bytes, but `ProtocolError::MaxEncodedBytesReachedError` formats `max_size_kbytes` as `Payload reached a {max_size_kbytes}KB limit`. A limit hit therefore reports 67,108,864 KB instead of 65,536 KB, overstating the configured budget by 1,024 times. The new v1 block decoder also converts this error to a client-visible `SerializedObjectParsingError`. Set `max_size_kbytes` to `BUDGET / 1024`, leave `size_hit` in bytes, and extend the crafted-length-claim test to assert the reported budget rather than only the error variant.

Comment on lines +198 to +203
.map_err(|error| match error {
DecodeError::Io { .. } | DecodeError::LimitExceeded => {
ProtocolError::MaxEncodedBytesReachedError {
max_size_kbytes: BUDGET,
size_hit: bytes.len(),
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Convert the bounded decode budget to kilobytes in the error

BUDGET is 67,108,864 bytes, but ProtocolError::MaxEncodedBytesReachedError formats max_size_kbytes as Payload reached a {max_size_kbytes}KB limit. A limit hit therefore reports 67,108,864 KB instead of 65,536 KB, overstating the configured budget by 1,024 times. The new v1 block decoder also converts this error to a client-visible SerializedObjectParsingError. Set max_size_kbytes to BUDGET / 1024, leave size_hit in bytes, and extend the crafted-length-claim test to assert the reported budget rather than only the error variant.

source: muse-spark-1.3-contributor (phase1-reviewer: general, rust-quality, security-auditor); gpt-6-astra (phase2-reviewer: general, rust-quality, security-auditor)

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.

2 participants