Skip to content

feat(platform)!: delta-based data contract update transition for protocol version 15 - #4730

Open
QuantumExplorer wants to merge 7 commits into
v4.3-devfrom
feat/delta-contract-update-pv15
Open

QuantumExplorer wants to merge 7 commits into
v4.3-devfrom
feat/delta-contract-update-pv15

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 14, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A V0 DataContractUpdateTransition embeds the whole contract, so a one-keyword change re-sends (and re-validates, and re-prices) every document type. PR #3021 set out to fix this with a delta-based V1 update transition, but it targeted protocol version 12, is 1800+ commits stale, and carried a review blocker; a rebase was not practical. This PR reimplements the update half of that idea from scratch on the 4.3 line, as the first consensus change of protocol version 15.

What was done?

Protocol version 15 (v15.rs, DRIVE_ABCI_VALIDATION_VERSIONS_V11, STATE_TRANSITION_SERIALIZATION_VERSIONS_V4, CONTRACT_VERSIONS_V7, DRIVE_VERSION_V10 with DRIVE_VERIFY_METHOD_VERSIONS_V3). Everything else matches v14; the v10 validation table, the V3 serialization table, the v6 contract table and drive v9 stay byte-identical for v14 replay. Every shipped generation the delta touches keeps its logic: the delta form lives in new generations only PV15 selects (basic structure 2, state 2, identity contract nonce 1, transform_into_action 1, prover 1, execution-proof verifier 1), and the dispatchers reject a V1 delta ahead of any earlier generation as an UnsupportedVersionError. The only edit inside shipped generations is the mechanical handling of the update accessor that now returns an Option, an internal error the dispatchers make unreachable. The V1 activation boundary is DATA_CONTRACT_UPDATE_V1_INITIAL_PROTOCOL_VERSION in feature_initial_protocol_versions.rs.

DataContractUpdateTransitionV1 (rs-dpp) carries only the delta, keyed by data_contract_id, owner_id and the new version:

  • config: Option<DataContractConfig>
  • updated_schema_defs / new_schema_defs, updated_document_schemas / new_document_schemas
  • new_groups, new_tokens (positions continue the stored ones)
  • add_keywords / remove_keywords
  • description: DescriptionUpdate { Keep | Clear | Set(String) } (a tri-state enum rather than Option<Option<String>>, so JSON round-trips)

from_contract_update computes the delta between two contracts and rejects what a delta cannot express: removed or changed document types, definitions, groups or tokens, a different id, and reordered keywords (a delta keeps the stored order and appends additions), and a change in whether the optional $defs map is present.

Materialize, then reuse. DataContract::apply_update (new contract_versions.methods.apply_update slot, an OptionalFeatureVersion that is None on every table before CONTRACT_VERSIONS_V7) merges the delta onto the stored contract and rebuilds a DataContractInSerializationFormatV1. The result then goes through exactly what a V0 update goes through: the generation-1 validate_update rules, the identity checks for new groups and tokens, external token costs and refersTo reference validation. Only delta-shape checks are new:

  • the submitter must own the contract (DataContractUpdatePermissionError)
  • updated entries must exist and new entries must not (DataContractUpdateEntryNotFoundError 40011, DataContractUpdateEntryAlreadyExistsError 40010)
  • the sections of one transition must not overlap (DataContractUpdateOverlappingEntriesError 10277)

drive-abci gets generation 2 of contract update basic_structure and state and transform_into_action 1: fetch the stored contract (fee-charged), turn a missing contract into a consensus error with a nonce bump (never an execution error), apply the delta, then run the generation-1 checks on the merged contract. A V1 transition that reaches a generation-0/1 validator (a pre-v15 node checking a newer transition) is an UnsupportedVersionError consensus error, not an execution error, so a mis-versioned transition cannot stall a proposal.

rs-drive adds a V1 update action transformer (try_from_borrowed_v1_transition) that still produces DataContractUpdateTransitionActionV0; the action carries the delta's registration cost, so a delta pays only for what it adds (new/updated document types and their indexes, new tokens, added keywords, no base fee) where a V0 update pays for the whole contract again. Proving fetches the stored contract to learn keeps_history.

Execution-proof verification of a delta needs the pre-update contract. A delta embeds no contract, so the verifier takes the contract the delta was built against from the known-contracts provider (the way document verification takes the schema), requires it to be at version - 1, materializes the delta on it with DataContractUpdateValues::merge_onto (the one definition of the merge, versioned on the same apply_update slot so execution and verification always pick the same generation) and compares the whole result with the proven contract. A missing or wrong-version known contract is ProofError::MissingContextRequirement, a malformed delta (overlapping sections, entries that do not apply) is InvalidTransition, and a proven state the delta did not produce is IncorrectProof. platform-wallet registers the pre-update contract with the SDK's context provider before broadcasting and the confirmed one afterwards.

API changes (see Breaking Changes): data_contract() returns Option, set_data_contract returns Result, new data_contract_id(). new_from_data_contract always builds V0; new new_from_contract_update(old, new, ...) / from_contract_update pick V0 or V1 from the platform version's default (V1 from v15). platform-wallet's update_data_contract_with_signer uses the delta form; wasm-dpp exposes getDataContractId, wasm-dpp2 exposes dataContractId / ownerId getters and declares the V0/V1 union of the object and JSON forms with the serialized $identity-contract-nonce key.

Not included: the create transition V1 from #3021 (flat fields, id derived from owner and nonce). It was dead code there (never selected by any version table), saves about 40 bytes per registration, and touches every create validator and SDK registration path. It belongs in its own PR.

Note: #4706 also introduces v15.rs on this base. Whichever merges second rebases; the conflict is the v15 doc comment and the two slots each PR changes.

How Has This Been Tested?

  • cargo check -p platform-version -p dpp -p drive -p drive-abci --all-targets: clean.
  • cargo test -p platform-version: 20 passed (the version array and the v13/v14 gating tests).
  • cargo test -p dpp --lib data_contract_update: 26 passed. Covers V1 serialization round-trips (bincode, JSON, platform value), from_contract_update delta extraction including the keyword-order rejection, the merge, registration_cost, and the latest-version default being the delta form.
  • cargo test -p drive-abci --lib -- data_contract_update execution_event: 61 passed. Includes the generation-2 state tests (a delta adding a document type, keywords and a description; a missing contract as a paid consensus error with a nonce bump; an owner that does not own the contract; a new document type that already exists; a delta held to the full-contract update rules; delta_update_reaching_a_pre_v15_validator_is_an_unsupported_version_error, where a V1 handed to the v14 basic-structure validator and to the V0 Drive transformer yields UnsupportedVersionError rather than an execution error; a missing contract in the transformer is not an execution error) and the check_tx tests (data_contract_update_check_tx_latest_protocol_version_delta pins the delta's processing fee against the V0 fee for the same change).
  • cargo test -p drive-abci --lib data_contract_update::basic_structure: 4 passed, new in this PR: a non-overlapping delta passes; a document type, a schema definition, or a keyword named in two conflicting sections is rejected with DataContractUpdateOverlappingEntriesError (10277).
  • cargo test -p dpp --lib apply_update: the merge is inactive below protocol version 15 (apply_update_is_not_active_before_protocol_version_15).
  • state/v2 delta_update_keeping_a_legacy_config_is_rejected: a delta without config on a stored v0-config contract is rejected by the merged contract's config update rules (version floor), with a nonce bump.
  • cargo test -p drive-abci --lib delta_execution_proof: 5 passed, new in this PR: an executed delta's proof verifies against the pre-update contract and yields the whole updated contract; verification without the pre-update contract, or with one at another version, is MissingContextRequirement; a delta that did not produce the proven state is IncorrectProof; a delta with overlapping sections is InvalidTransition.
  • cargo check -p drive --no-default-features --features verify: clean (the merge the verifier runs is outside the validation feature).
  • cargo test -p drive --lib -- data_contract_update prove_state_transition verify_state_transition: V0 and V1 action transformers, prover and verifier generations.
  • cargo clippy -p platform-version -p dpp -p drive -p drive-abci --all-targets --all-features -- -D warnings: clean.
  • cargo fmt --all -- --check: clean.

Breaking Changes

  • Protocol version 15 is introduced; from v15 clients default to the V1 (delta) contract update.
  • DataContractUpdateTransitionAccessorsV0::data_contract() returns Option<&DataContractInSerializationFormat> (V1 embeds no contract); set_data_contract returns Result.
  • new_from_data_contract always builds V0; use new_from_contract_update / from_contract_update for the delta form.
  • New consensus errors 10277, 40010, 40011 (appended to their enums).

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

🤖 Generated with Claude Code

…ocol version 15

A V0 data contract update re-sends the whole contract even for a one-keyword
change. Protocol version 15 adds DataContractUpdateTransitionV1, which carries
only the delta: new and updated document schemas and shared definitions, new
groups and tokens, added and removed keywords, an optional config and a
tri-state description change, keyed by the contract id, the owner and the new
version.

Validation merges the delta onto the stored contract (DataContract::apply_update,
new contract method version slot) and then holds the merged contract to exactly
the checks a full-contract update gets: the generation-1 update rules, the
identities new groups and tokens name, external token costs and reference
declarations. The delta shape itself adds three checks with their own consensus
errors: the submitter must own the contract, updated entries must exist and new
entries must not (DataContractUpdateEntryNotFoundError 40011,
DataContractUpdateEntryAlreadyExistsError 40010), and the sections of one
transition must not overlap (DataContractUpdateOverlappingEntriesError 10277).
A missing contract is a consensus error with a nonce bump, never an execution
error.

Drive gains a V1 update action that carries the delta's registration cost, so a
delta pays only for what it adds where a full-contract update pays for the whole
contract again. Proving fetches the stored contract to learn whether it keeps
history; verification checks every field of the delta against the proven
contract.

Protocol version 15 is introduced here as the first consensus change on the 4.3
line: v15.rs, DRIVE_ABCI_VALIDATION_VERSIONS_V11 (contract update basic
structure 2, state 2, transform_into_action 1) and
STATE_TRANSITION_SERIALIZATION_VERSIONS_V4 (the V1 update joins the wire and
becomes the client default). The v10 validation table and the V3 serialization
table stay byte-identical for protocol version 14 replay, and a delta reaching a
pre-15 validator is an UnsupportedVersionError consensus error.

The update accessor now returns the embedded contract as an Option, with a
data_contract_id accessor beside it. new_from_data_contract keeps building V0;
new_from_contract_update and from_contract_update take the stored and the
updated contract and pick the form the platform version defaults to (V1 from
protocol version 15). platform-wallet's contract update uses the delta form;
wasm-dpp and wasm-dpp2 expose the new variant.

Reimplements the update half of PR #3021 from scratch. The create transition V1
from that attempt is deliberately left for a follow-up.

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

coderabbitai Bot commented Sep 14, 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: fd91fe73-c5dc-4c0e-8aae-2ff944c1fca5

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

github-actions Bot commented Sep 14, 2026

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-18T01:48:58.651Z

@thepastaclaw

thepastaclaw commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

⚠️ DEGRADED — Final review complete — no blockers (commit 53cb036) · triage: critical · stand-in models (primary models out of quota)

@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.11902% with 893 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v4.3-dev@cb6514e). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...e_transitions/data_contract_update/state/v2/mod.rs 66.46% 224 Missing ⚠️
...s-drive/src/prove/prove_state_transition/v1/mod.rs 68.61% 113 Missing ⚠️
...tion/state_transitions/data_contract_update/mod.rs 72.25% 111 Missing ⚠️
...ges/rs-drive-abci/src/execution/check_tx/v0/mod.rs 68.28% 72 Missing ⚠️
...ons/data_contract_update/basic_structure/v2/mod.rs 62.01% 68 Missing ⚠️
...ntity_data_contract_nonce_action/v0/transformer.rs 13.46% 45 Missing ⚠️
...contract/data_contract_update_transition/v1/mod.rs 91.94% 41 Missing ⚠️
packages/rs-dpp/src/data_contract/update_values.rs 75.67% 27 Missing ⚠️
...ns/contract/data_contract_update_transition/mod.rs 60.60% 26 Missing ⚠️
...contract_update_transition/v1/registration_cost.rs 62.31% 26 Missing ⚠️
... and 19 more
Additional details and impacted files
@@             Coverage Diff             @@
##             v4.3-dev    #4730   +/-   ##
===========================================
  Coverage            ?   78.34%           
===========================================
  Files               ?     2873           
  Lines               ?   420759           
  Branches            ?        0           
===========================================
  Hits                ?   329643           
  Misses              ?    91116           
  Partials            ?        0           
Components Coverage Δ
dpp 79.71% <0.00%> (?)
drive 78.81% <0.00%> (?)
drive-abci 77.39% <0.00%> (?)
sdk ∅ <0.00%> (?)
dapi-client ∅ <0.00%> (?)
platform-version ∅ <0.00%> (?)
platform-value 88.51% <0.00%> (?)
platform-wallet ∅ <0.00%> (?)
drive-proof-verifier 28.82% <0.00%> (?)
🚀 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 delta transition is integrated across the versioned protocol, execution, storage, and client layers, but the new delta representation has correctness gaps. In particular, keyword reordering is silently lost during delta extraction, and execution-proof verification authenticates only the fields mentioned by the delta rather than the complete materialized contract, allowing false-positive verification. The WASM declarations and wallet FFI also do not expose the full V1 delta surface.

🔴 3 blocking | 🟡 2 suggestion(s)

2 finding(s) not shown inline (the lines are not part of this PR's diff)

🟡 Suggestion: Expose the V1 delta shape in the WASM TypeScript declarations
packages/wasm-dpp2/src/data_contract/transitions/update.rs:19-41

The Rust transition crossing this boundary is now the DataContractUpdateTransition enum, which can contain either the V0 full-contract form or the V1 delta form. The generic conversion methods can consequently return V1 values, but DataContractUpdateTransitionObject and DataContractUpdateTransitionJSON still require the V0-only dataContract field and omit the V1 fields such as dataContractId, ownerId, version, delta maps, keyword changes, config, and description. Typed consumers cannot construct valid V1 objects for fromObject/fromJSON, and the declared return type does not describe V1 values returned by those methods. Update the declarations to represent the V0/V1 union and the corresponding JSON/object field types.

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

🟡 Suggestion: Provide an FFI representation for clearing the contract description
packages/rs-platform-wallet-ffi/src/data_contract.rs:90-102

V1 uses DescriptionUpdate as a tri-state value: Keep, Clear, or Set. The wallet FFI API exposes description as an optional C string, and its current conversion treats NULL or an empty string as absence/preserve rather than as an explicit clear operation. As a result, C and Swift callers using this API can retain or replace a description but cannot construct the supported Clear delta. Add an explicit clear flag or another unambiguous sentinel while preserving the existing distinction between omitted description and an empty description.

source: muse-spark-1.3-contributor (phase1-reviewer: general, ffi-engineer, rust-quality, 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: critical by gpt-6-astra (effort low) — This large, intricate change directly modifies consensus validation in packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v2/mod.rs and basic_structure/v2/mod.rs, introducing protocol-v15 delta materialization, ownership and entry checks, nonce handling, and delta-based fee calculation across versioned execution paths.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — ffi-engineer (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 100% 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 — ffi-engineer (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/state_transitions/contract/data_contract_update_transition/v1/mod.rs`:
- [BLOCKING] packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/v1/mod.rs:272-283: Reject keyword reordering instead of silently dropping it
  The delta extractor computes keyword changes only as set differences. If the old contract contains `["one", "two"]` and the new contract contains `["two", "one"]`, both delta lists are empty, so applying the resulting transition retains the old order rather than producing the supplied contract. The same loss of ordering occurs when removals and additions alter the relative order: removals preserve the remaining old order and additions are appended. Keyword order is part of the stored contract representation and existing contract comparison treats the vectors positionally, so this API can successfully produce a transition whose applied contract differs from the caller's contract. Reject reorder-only changes as not expressible, or extend the delta format to encode ordering explicitly.

In `packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs`:
- [BLOCKING] packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs:121-134: Verify the complete materialized contract for V1 update proofs
  V0 update proof verification compares the complete proven contract with the contract embedded in the signed transition. The V1 path instead calls `first_mismatch`, which checks only fields named by the delta and intentionally ignores untouched fields. The proof supplies only the resulting contract, and for a non-history contract does not supply the pre-update contract. Therefore a proven contract with the same ID, owner, version, and carried delta values can contain arbitrary changes to untouched configuration, descriptions, document types, definitions, groups, tokens, or keyword ordering and still pass verification. This permits a light client to report that a particular signed delta executed when the authenticated state was produced by a different update. The proof must authenticate the pre-state or otherwise bind and compare the complete materialized result against the signed delta.

In `packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/v1/first_mismatch.rs`:
- [BLOCKING] packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/v1/first_mismatch.rs:44-63: Reject contradictory delta sections during proof verification
  The V1 proof verifier checks that entries in `new_document_schemas` and `updated_document_schemas` match the proven contract, but it does not reject the same document type appearing in both sections. The same omission exists for `new_schema_defs` versus `updated_schema_defs`, and duplicate additions/removals are not checked here either. Basic-structure validation rejects these malformed transitions on the normal platform path, but `first_mismatch` is independently used by proof verification and does not run that validation. A malformed signed transition can therefore be rejected by the platform while an authenticated proof of the existing contract still passes `first_mismatch`, causing the client to report successful execution. Proof verification must validate delta shape and reject overlapping or contradictory sections before checking the resulting contract.

In `packages/wasm-dpp2/src/data_contract/transitions/update.rs`:
- [SUGGESTION] packages/wasm-dpp2/src/data_contract/transitions/update.rs:19-41: Expose the V1 delta shape in the WASM TypeScript declarations
  The Rust transition crossing this boundary is now the `DataContractUpdateTransition` enum, which can contain either the V0 full-contract form or the V1 delta form. The generic conversion methods can consequently return V1 values, but `DataContractUpdateTransitionObject` and `DataContractUpdateTransitionJSON` still require the V0-only `dataContract` field and omit the V1 fields such as `dataContractId`, `ownerId`, `version`, delta maps, keyword changes, config, and description. Typed consumers cannot construct valid V1 objects for `fromObject`/`fromJSON`, and the declared return type does not describe V1 values returned by those methods. Update the declarations to represent the V0/V1 union and the corresponding JSON/object field types.

In `packages/rs-platform-wallet-ffi/src/data_contract.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/data_contract.rs:90-102: Provide an FFI representation for clearing the contract description
  V1 uses `DescriptionUpdate` as a tri-state value: `Keep`, `Clear`, or `Set`. The wallet FFI API exposes description as an optional C string, and its current conversion treats NULL or an empty string as absence/preserve rather than as an explicit clear operation. As a result, C and Swift callers using this API can retain or replace a description but cannot construct the supported `Clear` delta. Add an explicit clear flag or another unambiguous sentinel while preserving the existing distinction between omitted description and an empty description.

@github-actions github-actions Bot added this to the v4.3.0 milestone Sep 14, 2026
QuantumExplorer and others added 2 commits September 15, 2026 10:41
…oduces

Execution-proof verification of a delta-based (V1) contract update checked
only the fields the delta named, so a proven state produced by some other
update passed as this one. The pure merge now lives outside the validation
gate as DataContractUpdateValues::merge_onto (apply_update calls it and then
runs the group and token rules only the merged result can show), and the
verifier applies the delta to the pre-update contract it takes from the
known-contracts provider, requires that contract to sit one version below the
delta's, and compares the whole materialized contract with the proven one. A
missing or wrong-version known contract is MissingContextRequirement, a delta
that does not apply is InvalidTransition, a proven state the delta did not
produce is IncorrectProof. platform-wallet registers the pre-update contract
with the SDK's context provider before broadcasting and the confirmed one
afterwards.

The partial first_mismatch is gone. The overlap check is
DataContractUpdateTransitionV1::overlapping_entry in dpp, shared by the
generation-2 basic structure validator and the verifier, which refuses a
malformed delta before touching the proof.

from_contract_update recomputes the keywords the delta would produce (stored
order minus removals, additions appended) and rejects a reordered keyword list
as not expressible instead of silently dropping the reorder.

The wasm-dpp2 TypeScript declarations describe the V0/V1 union of the update
transition's object and JSON forms.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v4.3-dev was fast-forwarded to v4.2-dev's tip (cb6514e). The only conflict
was in the data contract update test module, where this branch adds the delta
processing test and the base adds the contract cache coherence tests (#4755);
both are kept.

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

@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.

Re-review — Final validation — Phase 1 + Phase 2

The v15 delta transition and proof materialization changes are correctly integrated, and the previously reported proof-soundness and keyword-order issues are fixed. Three functional gaps remain: the FFI cannot express description clearing, the WASM declarations use the wrong serialized nonce key, and the legacy WASM wrapper cannot expose the target contract ID. The v2 validator also fails to validate an inherited legacy configuration version after materialization, allowing an effective v0 configuration to pass the new v15 path.

🔴 1 blocking | 🟡 5 suggestion(s)

2 finding(s) not shown inline (the lines are not part of this PR's diff)

🟡 Suggestion: Provide an FFI representation for clearing the contract description
packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116

The V1 transition distinguishes DescriptionUpdate::Keep, Clear, and Set, but this C ABI accepts only a nullable string and decodes it with read_optional_str. Both NULL and an empty string become None; downstream None means preserve the existing description, so an FFI caller cannot clear a non-empty description. Add an explicit clear flag or another tagged representation at the ABI boundary and propagate it through the wallet update path.

source: muse-spark-1.3-contributor (phase1-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality); gpt-6-astra (phase2-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality, security-auditor)

🟡 Suggestion: Expose the V1 target contract ID in the legacy WASM wrapper
packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs:115-119

The wrapper can deserialize the new V1 delta transition, but getDataContract() correctly fails because V1 carries no full contract. The wrapper exposes getOwnerId but does not expose the transition's new data_contract_id() accessor, leaving JavaScript callers unable to identify which contract a deserialized delta targets. Add a getDataContractId binding for parity with the Rust accessor and the newer WASM2 surface.

    #[wasm_bindgen(js_name=getDataContractId)]
    pub fn get_data_contract_id(&self) -> IdentifierWrapper {
        self.0.data_contract_id().into()
    }

source: gpt-6-astra (phase2-reviewer: ffi-engineer)

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: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: ffi-engineer); reviewer 4: muse-spark-1.3-contributor (agent: phase1-reviewer, role: platform-versioning); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 6: muse-spark-1.3-contributor (agent: phase1-reviewer, role: security-auditor); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6-astra (agent: phase2-reviewer, role: ffi-engineer); reviewer 10: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 12: 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 validation and serialization, peer-facing state-transition deserialization, execution-proof verification, storage action transformation, and protocol-version behavior in files such as rs-drive-abci data-contract-update validation and rs-platform-version v15.rs.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — architecture-layering (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — ffi-engineer (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — platform-versioning (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 0% left, weekly 53% 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 — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (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-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v2/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v2/mod.rs:54-71: Reject delta updates that inherit a legacy configuration version
  The v2 basic-structure validator checks the configuration minimum only when the delta explicitly supplies `config`. When `config` is `None`, the merge retains the stored configuration, so a contract created with configuration version 0 can be updated through the v15 delta path while retaining that unsupported effective configuration. The full-contract validator checks the embedded configuration version, but this delta path does not apply that check to the materialized contract. Validate the effective merged configuration, or otherwise reject an inherited version below `platform_version.dpp.contract_versions.config.min_version`, before accepting the delta.

In `packages/rs-platform-wallet-ffi/src/data_contract.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116: Provide an FFI representation for clearing the contract description
  The V1 transition distinguishes `DescriptionUpdate::Keep`, `Clear`, and `Set`, but this C ABI accepts only a nullable string and decodes it with `read_optional_str`. Both NULL and an empty string become `None`; downstream `None` means preserve the existing description, so an FFI caller cannot clear a non-empty description. Add an explicit clear flag or another tagged representation at the ABI boundary and propagate it through the wallet update path.

In `packages/wasm-dpp2/src/data_contract/transitions/update.rs`:
- [SUGGESTION] packages/wasm-dpp2/src/data_contract/transitions/update.rs:43-46: Use the actual hyphenated nonce field in the V1 WASM declarations
  The Rust V1 transition serializes its nonce as `$identity-contract-nonce` via `serde(rename = "$identity-contract-nonce")`, while the TypeScript V1 object and JSON declarations require `identityNonce`. Thus the declared shape does not describe the object emitted by `toObject`/`toJSON`, and callers following the declaration cannot construct the canonical serialized form without a cast or workaround. Change both V1 declarations to use the actual `$identity-contract-nonce` property.

In `packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs`:
- [SUGGESTION] packages/wasm-dpp/src/data_contract/state_transition/data_contract_update_transition/mod.rs:115-119: Expose the V1 target contract ID in the legacy WASM wrapper
  The wrapper can deserialize the new V1 delta transition, but `getDataContract()` correctly fails because V1 carries no full contract. The wrapper exposes `getOwnerId` but does not expose the transition's new `data_contract_id()` accessor, leaving JavaScript callers unable to identify which contract a deserialized delta targets. Add a `getDataContractId` binding for parity with the Rust accessor and the newer WASM2 surface.

In `packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs`:
- [SUGGESTION] packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs:26-36: Gate the delta merge method from protocol version 15
  `apply_update` is introduced by this PR as an unconditional `FeatureVersion` slot and is populated in historical contract-version tables as well as the v15 table. That makes the new method appear available on pre-v15 `PlatformVersion` snapshots, even though the transition itself is not admitted there. Represent the method as an `OptionalFeatureVersion`, use `None` before activation and `Some(0)` from v15, and fail closed when it is unavailable.

In `packages/rs-dpp/src/state_transition/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/state_transition/mod.rs:895-896: Use a version-table activation constant for V1 contract updates
  The V1 transition's `active_version_range` hard-codes `15..=LATEST_VERSION`, whereas other newly activated transition kinds use named constants from `feature_initial_protocol_versions.rs`. Define a `DATA_CONTRACT_UPDATE_V1_INITIAL_PROTOCOL_VERSION` constant and use it here so the activation boundary is centralized with the protocol-version tables.

Comment thread packages/wasm-dpp2/src/data_contract/transitions/update.rs
Comment thread packages/rs-dpp/src/state_transition/mod.rs Outdated
…sh the client surface

apply_update is an OptionalFeatureVersion: None on every shipped contract
versions table and Some(0) on the new CONTRACT_VERSIONS_V7 that PLATFORM_V15
selects, so the delta merge does not appear available on snapshots that admit
no delta transition; the dispatcher fails closed before activation. The V1
activation boundary is DATA_CONTRACT_UPDATE_V1_INITIAL_PROTOCOL_VERSION in
feature_initial_protocol_versions.rs, used by active_version_range.

A delta that supplies no config keeps the stored one; the merged contract is
held to the config update rules like any full-contract update, which reject a
version below the floor. A state test pins that a stored legacy (v0) config
can not ride through the delta path.

The wasm-dpp2 TypeScript declarations use the serialized nonce key
($identity-contract-nonce) on both forms, and both wasm wrappers expose the
target contract id (and wasm-dpp2 the owner id) so a deserialized delta can
be attributed.

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

@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.

Re-review — Final validation — Phase 2 only (queue backlog)

The protocol-15 delta implementation addresses the prior merge, proof-completeness, keyword-ordering, activation-gating, and WASM-surface findings. However, several consensus-critical paths were extended in shipped v0 implementations while protocol 15 continues selecting those same generations, so the new behavior is not isolated by the required version tables. The platform-wallet FFI also still cannot express the new description-clear operation.

🔴 6 blocking | 🟡 1 suggestion(s)

2 finding(s) not shown inline (the lines are not part of this PR's diff)

🔴 Blocking: Version the V1 execution-proof prover instead of changing Drive v0
packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs:74-102

The V1 update branch now fetches the stored contract and selects the history query inside prove_state_transition_v0, while all current Drive versions, including protocol 15, still select prove_state_transition: 0. This changes a shipped consensus-visible proof-generation path without a new version boundary. Add a new prove-method generation and select it in the protocol-15 Drive version table, leaving v0 unchanged for historical versions.

source: gpt-6-astra (phase2-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality, security-auditor)

🟡 Suggestion: Provide an FFI representation for clearing the contract description
packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116

The V1 transition introduces DescriptionUpdate::Clear, but this C ABI exposes only description: *const c_char and passes it through read_optional_str. Null and empty inputs are consequently treated as omitted, while the wallet update path treats omission as keeping the existing description; only a non-empty string can set a description. Callers of this public FFI cannot remove a description once set. Add an explicit clear flag or description-update mode and propagate the tri-state value through the wallet API.

source: gpt-6-astra (phase2-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality)

1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

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: ffi-engineer); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); 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-critical protocol versioning, peer-facing state-transition serialization and validation, contract storage actions, fees, and execution-proof verification in files such as rs-dpp DataContractUpdateTransitionV1 and rs-drive-abci data-contract-update validation.
  • Phase 1 reviewers: not run (skipped for throughput: 17 PRs queued, above the 10 limit)
  • 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 — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (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-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs`:
- [BLOCKING] packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs:105-164: Version the V1 execution-proof verifier instead of changing Drive v0
  The V1 delta proof-verification path was added directly to `verify_state_transition_was_executed_with_proof_v0`. The dispatcher still selects only generation 0, including for protocol 15, so this consensus-visible verifier behavior is not isolated from historical versions. Add a new verifier generation and select it from the protocol-15 Drive version table, preserving the existing v0 implementation for versions that already shipped.

In `packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs`:
- [BLOCKING] packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs:74-102: Version the V1 execution-proof prover instead of changing Drive v0
  The V1 update branch now fetches the stored contract and selects the history query inside `prove_state_transition_v0`, while all current Drive versions, including protocol 15, still select `prove_state_transition: 0`. This changes a shipped consensus-visible proof-generation path without a new version boundary. Add a new prove-method generation and select it in the protocol-15 Drive version table, leaving v0 unchanged for historical versions.

In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/identity_contract_nonce/v0/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/identity_contract_nonce/v0/mod.rs:43-54: Keep V1 contract-nonce validation out of the shipped v0 generation
  The v0 identity-contract-nonce validator was changed to use the generic transition accessors so it can process V1 deltas. Protocol 15 still selects `nonce: Some(0)`, meaning the new transition behavior runs through the historical v0 generation. Add a V1 nonce-validation generation and select it in the protocol-15 validation table, or reject unsupported transition versions before invoking v0 while preserving the old implementation.

In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v0/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/basic_structure/v0/mod.rs:27-36: Do not modify the frozen v0 contract-update validator for V1 rejection
  The v0 basic-structure validator now calls `embedded_data_contract` and returns an error when a transition has no embedded contract. Pre-v15 tables still select `basic_structure: Some(0)`, so this is new behavior in the historical generation. Put the unsupported-version guard in the dispatcher or a new protocol-15 generation and restore v0 for historical validation.

In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs:173-184: Do not modify the frozen v0 contract-update state validator for V1 rejection
  The v0 state validator now handles a missing embedded contract by producing a nonce-bump result and consensus error. Protocol versions through 14 still select `state: 0`, so historical state validation behavior has been changed inside a shipped generation. Preserve `state/v0` and perform the unsupported-version handling in a version dispatcher or newly versioned validator selected for protocol 15.

In `packages/rs-platform-wallet-ffi/src/data_contract.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116: Provide an FFI representation for clearing the contract description
  The V1 transition introduces `DescriptionUpdate::Clear`, but this C ABI exposes only `description: *const c_char` and passes it through `read_optional_str`. Null and empty inputs are consequently treated as omitted, while the wallet update path treats omission as keeping the existing description; only a non-empty string can set a description. Callers of this public FFI cannot remove a description once set. Add an explicit clear flag or description-update mode and propagate the tri-state value through the wallet API.

QuantumExplorer and others added 2 commits September 16, 2026 18:26
The delta-based update had been threaded through generations that protocol
versions up to 14 still select: the prover and the execution-proof verifier
in Drive, and the basic structure, state and identity contract nonce
validators in drive-abci. Those modules are restored to what shipped; the
only remaining difference is the mechanical handling of the accessor that
now returns an Option, an internal error that the dispatchers make
unreachable.

The delta form lives in new generations selected only by protocol version
15: prove_state_transition v1 and verify_state_transition_was_executed_with_proof
v1 through DRIVE_VERSION_V10 (verify table v3), and identity contract nonce
v1 through DRIVE_ABCI_VALIDATION_VERSIONS_V11. The dispatchers reject a V1
update ahead of any earlier generation as an unsupported transition version,
a consensus error, so a delta reaching a pre-15 node can not stall a
proposal.

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

The state dispatcher now rejects a delta-based (V1) contract update before
generations 0 and 1 run, with the same unsupported-version consensus error the
other dispatchers return, and a test pins it on a protocol version 14 platform.

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

@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.

Re-review — Final validation — Phase 2 only (queue backlog)

The protocol-15 delta implementation correctly isolates new consensus behavior behind new version generations and preserves the shipped pre-v15 paths. Two correctness gaps remain: delta extraction conflates absent and explicitly empty schema-definition maps, and the native wallet FFI cannot express clearing a contract description. The shared unversioned materialization helper also bypasses the versioned apply_update dispatch in proof verification, creating a future protocol-generation drift hazard.

🟡 3 suggestion(s)

1 finding(s) not shown inline (the lines are not part of this PR's diff)

🟡 Suggestion: Provide an FFI representation for clearing the contract description
packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116

The V1 transition distinguishes Keep, Clear, and Set for descriptions, but this ABI exposes only a nullable C string. read_optional_str maps both a null pointer and an empty string to None, and the wallet update path interprets that value as keeping the existing description. Native FFI callers therefore cannot request the supported Clear operation. Add an explicit clear flag or another tri-state representation and pass it through to the wallet API.

source: gpt-6-astra (phase2-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality, security-auditor)

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: ffi-engineer); reviewer 4: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6-astra (agent: phase2-reviewer, role: ffi-engineer); reviewer 10: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 12: 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 is a large, intricate consensus and peer-facing deserialization change spanning protocol-version dispatch, state-transition validation, storage actions, proofs, and execution paths, notably in DataContractUpdateTransitionV1 and the v15 platform-version tables.
  • Phase 1 reviewers: not run (skipped for throughput: 15 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; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (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, gpt-6-astra — general (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6-astra — platform-versioning (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-platform-wallet-ffi/src/data_contract.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116: Provide an FFI representation for clearing the contract description
  The V1 transition distinguishes Keep, Clear, and Set for descriptions, but this ABI exposes only a nullable C string. `read_optional_str` maps both a null pointer and an empty string to `None`, and the wallet update path interprets that value as keeping the existing description. Native FFI callers therefore cannot request the supported Clear operation. Add an explicit clear flag or another tri-state representation and pass it through to the wallet API.

In `packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/v1/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/state_transition/state_transitions/contract/data_contract_update_transition/v1/mod.rs:199-220: Preserve the optional `$defs` presence when extracting a delta
  `schema_defs()` returns an `Option<BTreeMap<...>>`, but the extractor normalizes both `None` and `Some(empty_map)` to the same empty map before computing the delta. A change from `None` to `Some(empty)` produces no delta and materializes back to `None`; a change from `Some(empty)` to `None` likewise preserves the old representation. The resulting transition can therefore differ from the target supplied to `from_contract_update`, and one direction can silently request an unrepresentable removal. Compare the option presence first and reject changes that the delta format cannot encode, or add an explicit operation for this distinction. Add regression coverage for both directions.

In `packages/rs-dpp/src/data_contract/update_values.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/update_values.rs:88-111: Version the shared delta materialization used by proof verification
  `DataContractUpdateValues::merge_onto` is consensus-visible contract materialization, but it is an unversioned helper with no `PlatformVersion` parameter. Consensus execution reaches it through the versioned `DataContract::apply_update` slot, while the V1 execution-proof verifier calls `merge_onto` directly. The current protocol-15 implementation is consistent, but a future protocol version could select a new apply_update generation while proof verification continues using the old merge semantics. Expose materialization through a versioned method, or otherwise make both execution and proof verification select the same versioned implementation.

Comment thread packages/rs-dpp/src/data_contract/update_values.rs
…a delta

DataContractUpdateValues::merge_onto now dispatches on the same
contract_versions.methods.apply_update slot as DataContract::apply_update, so
consensus execution and execution-proof verification select the same merge
generation; the generation-0 body is merge_onto_v0.

from_contract_update rejects a change in the presence of the optional $defs
map (absent to empty or empty to absent) as not expressible instead of
silently producing a delta that materializes to a different contract; tests
cover both directions.

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

@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.

⚠️ DEGRADED — Re-review — Final validation — Phase 1 + Phase 2

⚠️ DEGRADED review. The primary review models were unavailable (gpt-6-astra unavailable: All credentials for model gpt-6-astra are cooling down (last error: usage_limit_reached: The usage limit has been reache), so this review ran on stand-in models: gpt-5.6-lunamuse-spark-1.3-contributor, gpt-5.6-solmuse-spark-1.3-contributor, gpt-5.6-terramuse-spark-1.3-contributor, gpt-6-astramuse-spark-1.3-contributor. Both review phases and the independent verifiers still ran, but on weaker models, with Phase 1 capped at high effort. Treat the verdict as provisional; a full-strength re-review will run on the next push once the primary models are back.

Delta-based V1 contract updates are correctly isolated to protocol 15 with versioned merge, frozen v0 generations, and full-contract proof verification. All 16 other prior findings verify fixed at this head. One client-surface gap remains: the FFI/wallet path cannot express DescriptionUpdate::Clear.

🟡 1 suggestion(s) | 💬 1 nitpick(s)

1 finding(s) not shown inline (the lines are not part of this PR's diff)

🟡 Suggestion: Provide an FFI representation for clearing the contract description
packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116

The wallet update path switched by this PR to building a delta via new_from_contract_update, but neither it nor its FFI caller can produce the new Clear variant: read_optional_str maps NULL and empty to None, update_data_contract_with_signer filters empty to None, and merge_contract_update_payload only touches the description on Some. A caller that sets a description can therefore never remove it through FFI/wallet, while native DPP and WASM both support clear. Add a tri-state or explicit clear flag and thread it to DescriptionUpdate::Clear so mobile callers keep parity with the protocol.

source: gemini-3.8-flash-high (phase1-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality); muse-spark-1.3-contributor (phase2-reviewer: general, architecture-layering, ffi-engineer, platform-versioning, rust-quality)

Review provenance

Source: reviewer 1: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 2: gemini-3.8-flash-high (agent: phase1-reviewer, role: architecture-layering); reviewer 3: gemini-3.8-flash-high (agent: phase1-reviewer, role: ffi-engineer); reviewer 4: gemini-3.8-flash-high (agent: phase1-reviewer, role: platform-versioning); reviewer 5: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); reviewer 6: gemini-3.8-flash-high (agent: phase1-reviewer, role: security-auditor); reviewer 7: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: general); reviewer 8: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: architecture-layering); reviewer 9: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: ffi-engineer); reviewer 10: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: platform-versioning); reviewer 11: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: rust-quality); reviewer 12: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: security-auditor); reviewer 13: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: general); reviewer 14: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: architecture-layering); reviewer 15: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: ffi-engineer); reviewer 16: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: platform-versioning); reviewer 17: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: rust-quality); reviewer 18: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: phase2-reviewer, role: security-auditor); final verifier: muse-spark-1.3-contributor (standing in for gpt-6-astra) (agent: astra-verifier, role: final-verifier)

  • Degraded mode: gpt-6-astra unavailable: All credentials for model gpt-6-astra are cooling down (last error: usage_limit_reached: The usage limit has been reache (detected by probe, since 2026-09-18T05:22:01Z); stand-ins gpt-5.6-lunamuse-spark-1.3-contributor, gpt-5.6-solmuse-spark-1.3-contributor, gpt-5.6-terramuse-spark-1.3-contributor, gpt-6-astramuse-spark-1.3-contributor; Phase 1 effort capped at high
  • Triage: critical by muse-spark-1.3-contributor (standing in for gpt-6-astra) (effort low) — Large 84-file (+8457) intricate change adds consensus-critical DataContractUpdateTransitionV1 with new validation in drive-abci state/v2 and DataContract::apply_update.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — architecture-layering (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — ffi-engineer (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — platform-versioning (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — security-auditor (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 100% left, 5h 100% left
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: muse-spark-1.3-contributor (standing in for gpt-6-astra) — final-verifier; agent astra-verifier
  • Phase 2 reviewers: muse-spark-1.3-contributor (standing in for gpt-6-astra) — general (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — architecture-layering (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — platform-versioning (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — rust-quality (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — security-auditor (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — general (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — architecture-layering (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — platform-versioning (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for gpt-6-astra) — rust-quality (completed, effort xhigh); agent phase2-reviewer, muse-spark-1.3-contributor (standing in for 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-platform-wallet-ffi/src/data_contract.rs`:
- [SUGGESTION] packages/rs-platform-wallet-ffi/src/data_contract.rs:90-116: Provide an FFI representation for clearing the contract description
  The wallet update path switched by this PR to building a delta via new_from_contract_update, but neither it nor its FFI caller can produce the new Clear variant: read_optional_str maps NULL and empty to None, update_data_contract_with_signer filters empty to None, and merge_contract_update_payload only touches the description on Some. A caller that sets a description can therefore never remove it through FFI/wallet, while native DPP and WASM both support clear. Add a tri-state or explicit clear flag and thread it to DescriptionUpdate::Clear so mobile callers keep parity with the protocol.

In `packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs`:
- [NITPICK] packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs:2356-2360: Duplicated comment describing delta fee
  Two consecutive comments in the new check_tx test say the same thing: the delta registers only the added document type and costs less than the full-contract form. The repetition is harmless but should be one comment.

Comment on lines +2356 to +2360
// A delta registers only the added document type, so it costs less
// than the full-contract form of the same update (27002879350).
// A delta registers only the added document type, where the
// full-contract form of the same update pays for the whole contract
// again (27002879350).

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.

💬 Nitpick: Duplicated comment describing delta fee

Two consecutive comments in the new check_tx test say the same thing: the delta registers only the added document type and costs less than the full-contract form. The repetition is harmless but should be one comment.

Suggested change
// A delta registers only the added document type, so it costs less
// than the full-contract form of the same update (27002879350).
// A delta registers only the added document type, where the
// full-contract form of the same update pays for the whole contract
// again (27002879350).
// A delta registers only the added document type, so it costs less
// than the full-contract form of the same update (27002879350).

source: muse-spark-1.3-contributor (phase2-reviewer: general)

@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

PR Hygiene

State: waiting-bots · commit 53cb036efc244155d88a30c829148f90d7903d3e

  • Bot review threads remain unresolved
  • Proceeded without coderabbitai: no review within the configured window

Self-review is an author attestation that you have read the diff:
/self-reviewed — covers everything pushed so far; post it again after a new push.

This check passes when the policy is satisfied; the repository decides whether merging requires it.

@github-actions github-actions Bot added the bot-review-missed A required review bot did not report in time; it was waived. label Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot-review-missed A required review bot did not report in time; it was waived.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants