Skip to content

fix(drive-abci)!: cross-check the contested index of a prefunded voting balance at protocol v14 - #4281

Open
QuantumExplorer wants to merge 1 commit into
v4.2-devfrom
claude/intelligent-lumiere-3aa474
Open

fix(drive-abci)!: cross-check the contested index of a prefunded voting balance at protocol v14#4281
QuantumExplorer wants to merge 1 commit into
v4.2-devfrom
claude/intelligent-lumiere-3aa474

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 4, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A document create transition carries the index its prefunded voting balance is for, in DocumentCreateTransitionV0::prefunded_voting_balance: Option<(String, Credits)>. That name is what keys the contested vote poll, its stored info, its end-date entry and its prefunded specialized balance — but the contested index tree the contender is actually inserted into always comes from DocumentType::find_contested_index(). Nothing tied the two together:

  • the action transformer resolved whatever index was named, as long as it existed on the document type (document_type_indexes.get(index_name));
  • the advanced structure check validated only the amount and dropped the name (if let Some((_, paid_amount)) = ...).

Three consequences, all verified against the pre-fix code by flipping only the validation version and re-running the tests added here:

1. Reachable chain halt. On a contract whose non-contested index is a strict prefix of the contested one (e.g. [normalizedParentDomainName] next to the contested [normalizedParentDomainName, normalizedLabel]), the mismatched vote poll's contenders path is a tree the contested insert creates anyway. The transition therefore executes successfully and registers the poll one level above the contest it describes. When that poll's end date arrives, the tally query finds no lock/abstain sum trees and check_for_ended_vote_polls returns Err(Drive(CorruptedCodeExecution("expected a locked tally"))). That is a per-block event handler with a bare ? (run_dao_platform_eventsrun_block_proposal), so it is not contained into a per-state-transition result — it halts every validator at that block. The end-date entry is never cleaned up, so every subsequent block proposal fails identically. Any user can register such a contract.

2. Contest checks silently skipped. current_store_contest_info is fetched under the vote poll built from the submitter's own index name, so a mismatched name makes that lookup return None, and state_v0/state_v1 then skip the Locked, join-window and already-a-contestant checks entirely while the contender is still inserted into the real contest.

3. Unnecessary contests. In the other direction, a document that resolves to no contested index at all could still supply a prefunded voting balance, storing an ordinary document as a contender that only becomes registered if it wins a masternode vote.

On the live DPNS contract the mismatch currently dies for an unrelated reason: Index::extract_values is a flat map lookup, so the identityId index's dotted records.identity property yields Value::Null → an empty path segment → an unpaid InternalError from the estimated-cost path cache. That is an accident of DPNS's index shape, not a defense.

What was done?

document_create_transition_structure_validation v1 (new advanced_structure_v1 module) compares the full expected ContestedDocumentResourceVotePoll — derived from the document's own properties via contested_vote_poll_for_document_properties — against the one built from the prefunded voting balance, and rejects a prefunded voting balance on a document that resolves to no contested index. Everything else is carried over from v0 unchanged, including the testnet-before-epoch-2080 carve-out.

Two new consensus errors:

Code Error
40118 DocumentContestIndexMismatchError
40119 DocumentContestNotRequiredError

The check changes accept/reject behavior, so it is gated by a new DRIVE_ABCI_VALIDATION_VERSIONS_V10 wired into v14.rs. DRIVE_ABCI_VALIDATION_VERSIONS_V9 is shared with protocol version 13 and stays byte-identical for chain replay; advanced_structure_v0 is untouched.

How Has This Been Tested?

Five unit tests on the validation dispatcher (advanced_structure_v1::tests): mismatched index name, prefunded balance on a non-contested document, the preserved not-paid-for behavior, the accepted matching case, and a test pinning that protocol version 13 does not cross-check — the last one is what documents the gap this PR closes.

Four end-to-end tests through process_raw_state_transitions:

  • test_document_creation_on_contested_unique_index_should_fail_if_prefunding_another_index — DPNS contest funded for identityId; asserts the paid consensus error and that neither a contender nor a document was created.
  • test_contest_can_not_be_joined_after_the_join_window_by_prefunding_another_index — opens a real contest, advances past the one-week join window, and shows a mismatched late join is rejected and the contest keeps exactly its two contenders.
  • contested_index_mismatch_chain_halt::prefunding_a_prefix_index_is_rejected — the halt scenario above, on a new dpns-contract-contested-unique-index-and-prefix-index.json fixture. Run against structure validation v0 this same test shows the transition executing and the resolver returning Err(... "expected a locked tally").
  • contested_index_mismatch_chain_halt::prefunding_the_contested_index_resolves_successfully — control: the identical contest funded for the contested index executes and its poll resolves cleanly, so the index name is the only variable.

Suites: cargo test -p drive-abci --lib 2638 passed / 0 failed, cargo test -p dpp 3808 passed / 0 failed, cargo check --workspace --all-targets clean, cargo clippy clean on the touched crates, and cargo check -p wasm-dpp --target wasm32-unknown-unknown clean.

Worth knowing for anyone extending these tests: the default test PlatformConfig network is Testnet, where the whole contested structure-validation block is skipped before epoch 2080 — a contested-validation test that forgets network: Network::Mainnet passes while proving nothing.

Breaking Changes

Consensus-breaking, gated at protocol version 14. From v14 on, a create transition whose prefunded voting balance names an index other than the contested index its document resolves to, or that supplies one for a non-contested document, is rejected with a paid consensus error instead of being accepted. Clients that derive the field through DocumentType::prefunded_voting_balance_for_document (which returns the contested index) are unaffected. Protocol version 13 and below replay unchanged.

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

Summary by CodeRabbit

  • New Features

    • Added document-creation validation to ensure prefunded voting balances reference the correct contested index.
    • Added clear errors for mismatched or unnecessary contested-document funding.
    • Enabled the updated validation rules in platform version 14.
  • Bug Fixes

    • Prevented invalid contested-document configurations that could disrupt vote-poll processing.
    • Added compatibility handling for testnet and earlier validation versions.
  • Tests

    • Added comprehensive coverage for valid funding, mismatched indexes, missing funding, and contest restrictions.

…ng balance at protocol v14

A document create transition supplies the index its prefunded voting
balance is for. That name keys the vote poll, its stored info, its
end-date entry and its prefunded specialized balance, while the contested
index tree the contender is inserted into always comes from
`DocumentType::find_contested_index`. Nothing tied the two together: the
transformer resolved whatever index was named, and the advanced structure
check validated only the amount, discarding the name.

On a contract whose non-contested index is a strict prefix of the
contested one, the mismatched vote poll's contenders path is a tree the
contested insert creates anyway, so the transition executes and the poll
registers one level above the contest it describes. When that poll ends,
`check_for_ended_vote_polls` finds no lock/abstain tallies and returns
`Err(CorruptedCodeExecution("expected a locked tally"))`, which propagates
through `run_dao_platform_events` -> `run_block_proposal` on a bare `?`
and halts every validator; the end-date entry is never cleaned up, so
every subsequent proposal fails the same way.

The same miss also hides a running contest from the transition: the stored
info is looked up under the supplied vote poll, so a wrong index name
returns `None` and skips the locked, join-window and already-a-contestant
checks. In the other direction, a document resolving to no contested index
could still open a contest and be stored as a contender.

Structure validation v1 compares the whole expected vote poll against the
supplied one and rejects a prefunded voting balance on a non-contested
document, reporting `DocumentContestIndexMismatchError` (40118) and
`DocumentContestNotRequiredError` (40119). It is gated by
`DRIVE_ABCI_VALIDATION_VERSIONS_V10` on protocol version 14; v9 stays
byte-identical for protocol version 13 chain replay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Document contest validation

Layer / File(s) Summary
Contest error contracts
packages/rs-dpp/src/errors/consensus/..., packages/wasm-dpp/src/errors/consensus/consensus_error.rs
Adds serializable consensus errors for mismatched and unnecessary contest indexes, including enum variants, error codes, and WASM conversions.
Document-create validation
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/...
Adds version 1 structure validation for contest funding, index matching, document restrictions, and property checks.
Validation version activation
packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/..., packages/rs-platform-version/src/version/v14.rs
Adds validation configuration v10 and selects it for platform version 14.
Regression coverage
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs, packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs, packages/rs-drive-abci/tests/supporting_files/contract/...
Adds tests for mismatched indexes, unnecessary and missing funding, compatibility behavior, contest-window handling, and chain-halt prevention.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DocumentCreateTransitionAction
  participant DocumentCreateStructureValidationV1
  participant ConsensusError
  participant VotePollResolver
  DocumentCreateTransitionAction->>DocumentCreateStructureValidationV1: validate prefunded contest index
  DocumentCreateStructureValidationV1->>ConsensusError: reject mismatched index
  ConsensusError-->>DocumentCreateTransitionAction: DocumentContestIndexMismatchError
  DocumentCreateTransitionAction-->>VotePollResolver: no vote poll registered
Loading

Suggested reviewers: shumkov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the protocol v14 fix for cross-checking prefunded voting balances against the contested index.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/intelligent-lumiere-3aa474

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.

@thepastaclaw

thepastaclaw commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

⛔ Blockers found — Sonnet deferred (commit e768c06)
Canonical validated blockers: 1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs (1)

332-340: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the stale denomination comment.

Line 332 lists "0.1, 0.3, 0.5, 1.0 DASH", but the array holds 0.03, 0.1, 0.25, 0.5 and 1 DASH. Line 333 already states that 0.3 was retired, so the two comment lines contradict each other. Update the first line to match the values.

📝 Proposed comment fix
-            // 0.1, 0.3, 0.5, 1.0 DASH in credits (1 DASH = 10^8 duffs, CREDITS_PER_DUFF = 1000).
+            // 0.03, 0.1, 0.25, 0.5, 1.0 DASH in credits (1 DASH = 10^8 duffs, CREDITS_PER_DUFF = 1000).
             // v13 revises the v8 set: adds 0.03 and 0.25 DASH, retires 0.3 DASH.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs`
around lines 332 - 340, Update the leading denomination comment above
shielded_identity_create_denominations to list 0.03, 0.1, 0.25, 0.5, and 1.0
DASH, matching the array values and the existing v13 revision note.
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v1/mod.rs (1)

86-96: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reporting the full mismatch, not only the index names.

The comparison at line 87 covers the whole ContestedDocumentResourceVotePoll: contract_id, document_type_name, index_name and index_values. The error only carries the two index names. If the poll differs only in index_values or document_type_name, the emitted error reports the same string for expected_index_name and provided_index_name, which is hard to act on.

Two options: keep the struct comparison and extend the error payload, or narrow the check to index_name and let the existing per-field validations cover the other fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v1/mod.rs`
around lines 86 - 96, The ContestedDocumentResourceVotePoll mismatch handling
around the provided and expected values reports only index names, obscuring
differences in other fields. Update DocumentContestIndexMismatchError and its
construction to carry the full expected and provided poll details, or narrow
this check to index_name so document_type_name and index_values are handled by
their existing validations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/rs-dpp/src/errors/consensus/state/state_error.rs`:
- Around line 101-105: Move the new DocumentContestIndexMismatchError and
DocumentContestNotRequiredError variants to the end of the StateError enum,
preserving the existing order of all prior variants and therefore their bincode
discriminants.

---

Nitpick comments:
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v1/mod.rs`:
- Around line 86-96: The ContestedDocumentResourceVotePoll mismatch handling
around the provided and expected values reports only index names, obscuring
differences in other fields. Update DocumentContestIndexMismatchError and its
construction to carry the full expected and provided poll details, or narrow
this check to index_name so document_type_name and index_values are handled by
their existing validations.

In
`@packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs`:
- Around line 332-340: Update the leading denomination comment above
shielded_identity_create_denominations to list 0.03, 0.1, 0.25, 0.5, and 1.0
DASH, matching the array values and the existing v13 revision note.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1114ecc6-730b-4dc6-924f-dfa7d60d902c

📥 Commits

Reviewing files that changed from the base of the PR and between 97904ed and e768c06.

📒 Files selected for processing (14)
  • packages/rs-dpp/src/errors/consensus/codes.rs
  • packages/rs-dpp/src/errors/consensus/state/document/document_contest_index_mismatch_error.rs
  • packages/rs-dpp/src/errors/consensus/state/document/document_contest_not_required_error.rs
  • packages/rs-dpp/src/errors/consensus/state/document/mod.rs
  • packages/rs-dpp/src/errors/consensus/state/state_error.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/advanced_structure_v1/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rs
  • packages/rs-drive-abci/tests/supporting_files/contract/dpns/dpns-contract-contested-unique-index-and-prefix-index.json
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/wasm-dpp/src/errors/consensus/consensus_error.rs

Comment on lines +101 to +105
#[error(transparent)]
DocumentContestIndexMismatchError(DocumentContestIndexMismatchError),

#[error(transparent)]
DocumentContestNotRequiredError(DocumentContestNotRequiredError),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'enum StateError|derive\(.*Encode|derive\(.*Decode|StateError.*serialize|serialize.*StateError|deserialize.*StateError' \
  packages/rs-dpp packages/rs-platform-version

rg -n -C 3 'STATE_TRANSITION_SERIALIZATION_VERSIONS_V2|STATE_TRANSITION_SERIALIZATION_VERSIONS_V[0-9]+' \
  packages/rs-platform-version

Repository: dashpay/platform

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== state_error.rs outline and relevant section =="
wc -l packages/rs-dpp/src/errors/consensus/state/state_error.rs
sed -n '1,180p' packages/rs-dpp/src/errors/consensus/state/state_error.rs

echo
echo "== targeted version constants =="
rg -n -C 3 'STAT.*STATE_ERROR|STATE_ERROR_SERIALIZATION|STATE_TRANSITION_SERIALIZATION_VERSIONS_V2|STATE_TRANSITION_SERIALIZATION_VERSIONS_V14|V14' packages/rs-platform-version || true

echo
echo "== StateError usages in platform-version =="
rg -n -C 3 'StateError' packages/rs-platform-version || true

Repository: dashpay/platform

Length of output: 26668


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== state_error.rs variants =="
python3 - <<'PY'
from pathlib import Path
p = Path("packages/rs-dpp/src/errors/consensus/state/state_error.rs")
text = p.read_text()
variants = []
enum = None
start = text.index("\n#[derive(")
for line in text[start:].splitlines():
    if line.strip().startswith("pub enum "):
        enum = line.strip()
    elif enum and line.strip().startswith("#[error("):
        variant = next(s.lstrip() for s in text[text.index(line):].splitlines() if s.strip().endswith(","))
        variants.append(variant.replace(" ", ""))
print("variants:", "\n".join(variants))
print("count:", len(variants))
matches = []
for i, v in enumerate(variants, 1):
    if "DocumentContest" in v or "DocumentContestIndexMismatch" in v or "DocumentContestNotRequired" in v:
        print(f"{i}: {v}")
        for j, vn in enumerate(variants, 1):
            if vn > v:
                matches.append((i, j, v, vn))
print("next_variant_count:", len(variants) - matches[0][0] if matches else "-")
print("next_variant_pairs:")
for a,b,c,d in matches:
    print(f"  after {a}: {b}: {d}")
PY

echo
echo "== state_error_ser definitions/usages =="
rg -n -C 5 'struct StateError|StateErrorSer|StateErrorDeserialize|PlatformSerialize|PlatformDeserialize|BinaryProtocol|protocol_version|format_version.*StateError|StateError.*protocol' packages/rs-dpp/packages/platform packages/rs-dpp/src || true

echo
echo "== state transition serialization constants full =="
sed -n '1,140p' packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs

Repository: dashpay/platform

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== targeted state_error.rs sections =="
sed -n '1,180p' packages/rs-dpp/src/errors/consensus/state/state_error.rs
sed -n '180,371p' packages/rs-dpp/src/errors/consensus/state/state_error.rs

echo
echo "== targeted usages of StateErrorSer/StateErrorDeserialize/state_error serialization =="
rg -n -C 4 'StateErrorSer|StateErrorDeserialize|StateError\\[' packages/rs-dpp/packages packages/rs-dpp/src 2>/dev/null | head -n 200 || true

echo
echo "== derive macros and platform_serialize behavior references =="
rg -n -C 3 'derive\\(Platform|PlatformSerialize|PlatformDeserialize|#\\[platform_serialize|fn encode|fn decode' packages platform crates platform-serial 2>/dev/null | head -n 300 || true

echo
echo "== focused platform serialization derives =="
find . -path ./target -prune -o \( -path '*/platform_serialization_derive/src/*' -o -path '*/platform-serialization-derive/*' -o -path '*/platform-serial/*' -o -path '*/platform-serialization/*' \) -type f -print | sed -n '1,120p'

Repository: dashpay/platform

Length of output: 17860


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Cargo references for platform_serialization_derive =="
rg -n 'platform_serialization_derive|platform-serialization|platform-serial' Cargo.toml Cargo.lock packages 2>/dev/null | head -n 120 || true

echo
echo "== repository files mentioning PlatformSerialize derive implementation =="
rg -n 'PlatformSerialize|platform_serialize|enum_variant_names|PlatformDeserialize|decode_enum|encode_enum' . \
  --glob '!target/**' --glob '!docs/**' --glob '!*.log' \
  | head -n 250 || true

echo
echo "== inspect source around PlatformSerialize attribute syntax from nearby error enums =="
rg -n -C 2 '#\\[platform_serialize\\(' packages/rs-dpp/src/errors | head -n 120 || true

echo
echo "== inspect bincode derive usage around StateError =="
grep -n 'bincode::Decode, Decode, PlatformDeserialize\\|Decode, PlatformDeserialize' packages/rs-dpp/src/errors/consensus/state/state_error.rs || true

echo
echo "== git diff stat and relevant diff for state_error if available =="
git diff -- packages/rs-dpp/src/errors/consensus/state/state_error.rs | sed -n '1,200p' || true

Repository: dashpay/platform

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== platform serialization enum derive =="
cat -n packages/rs-platform-serialization-derive/src/serialize_enum.rs
cat -n packages/rs-platform-serialization-derive/src/deserialize_enum.rs

echo
echo "== platform serialization lib dispatch =="
sed -n '88,185p' packages/rs-platform-serialization-derive/src/lib.rs
echo
sed -n '35,180p' packages/rs-platform-serialization/src/lib.rs

echo
echo "== enum_variant_names version in lockfile =="
rg -n 'name = "enum_variant_names"|enum_variant_names' Cargo.lock | head -n 10 || true

echo
echo "== StateError variant ordering as byte offsets in enum shape =="
python3 - <<'PY'
from pathlib import Path
p = Path("packages/rs-dpp/src/errors/consensus/state/state_error.rs")
text = p.read_text().splitlines()
start = next(i for i,l in enumerate(text) if l.strip().startswith('pub enum StateError'))
variants = []
idx = None
for i, line in enumerate(text[start:], start):
    if line.strip().startswith('#['):
        idx = i
    elif idx is not None and '#[' not in line and '///' not in line and '*/' not in line and line.strip():
        prefix = line.lstrip()
        name = prefix.split('(', 1)[0].strip()
        variants.append((name, start + idx + 1))
        idx = None
for i, v in enumerate(variants, 1):
    if 'DocumentContest' in v[0] or 'DocumentNotFoundError' in v[0] or 'DocumentNotForSaleError' in v[0] or 'Wrong' in v[0]:
        print(f"{i}: {v[1]} {v[0]}")
PY

Repository: dashpay/platform

Length of output: 35689


Append the new StateError variants instead of inserting them mid-variant list.

StateError is bincode::Encode/bincode::Decode and has the enum “do not change order of variants” constraint. V14 still uses STATE_TRANSITION_SERIALIZATION_VERSIONS_V2, so moving DocumentContestIndexMismatchError and DocumentContestNotRequired_ERROR shifts the encoded discriminants for every variant that follows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-dpp/src/errors/consensus/state/state_error.rs` around lines 101 -
105, Move the new DocumentContestIndexMismatchError and
DocumentContestNotRequiredError variants to the end of the StateError enum,
preserving the existing order of all prior variants and therefore their bincode
discriminants.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.30052% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.54%. Comparing base (97904ed) to head (e768c06).

Files with missing lines Patch % Lines
...ate_transition_action/advanced_structure_v1/mod.rs 92.17% 18 Missing ⚠️
...lidation/state_transition/state_transitions/mod.rs 98.05% 3 Missing ⚠️
.../document/document_create_transition_action/mod.rs 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           v4.2-dev    #4281    +/-   ##
==========================================
  Coverage     87.54%   87.54%            
==========================================
  Files          2679     2680     +1     
  Lines        341312   341697   +385     
==========================================
+ Hits         298799   299145   +346     
- Misses        42513    42552    +39     
Components Coverage Δ
dpp 88.55% <ø> (+<0.01%) ⬆️
drive 86.26% <ø> (ø)
drive-abci 89.56% <94.30%> (-0.01%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 49.60% <ø> (ø)
🚀 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.

Preliminary review — Codex only

The protocol-v14 vote-poll cross-check is correctly version-dispatched and has strong regression coverage. One blocking compatibility issue remains: the new StateError variants were inserted into a positionally serialized enum, shifting the wire discriminants of all existing variants after the insertion point. The pre-2080 Testnet carve-out does not expose the fix on a reachable historical configuration because validation v1 exists only at protocol v14, while the carve-out is intentional compatibility behavior covered by an existing test.

Source: reviewers codex/general=gpt-5.6-sol(completed); codex/security-auditor=gpt-5.6-sol(completed); codex/rust-quality=gpt-5.6-sol(completed); codex/ffi-engineer=gpt-5.6-sol(completed); verifier=codex/verifier=gpt-5.6-sol(completed); coordinator=openclaw-agent/cliproxy/gpt-5.6-sol(orchestration-only).

Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 1 blocking

🤖 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/errors/consensus/state/state_error.rs`:
- [BLOCKING] packages/rs-dpp/src/errors/consensus/state/state_error.rs:101-105: Append the new StateError variants to preserve wire discriminants
  StateError is encoded by ordinal variant position: its bincode and platform-serialization implementations write the variant index as a u32, and the enum explicitly prohibits reordering without a new serialization version. Inserting these variants here changes DocumentNotFoundError from ordinal 8 to 10 and shifts every subsequent existing variant by two. ErrorWithCode values do not protect this representation. Consensus errors are serialized into ABCI/DAPI responses and decoded by WASM and JavaScript clients, so upgraded and older components can fail decoding or interpret an existing error as a different variant. Move both new variants after InsufficientShieldedFeeError so every pre-existing discriminator remains unchanged; add a frozen-byte compatibility test for an existing variant after the current insertion point.

Comment on lines +101 to +105
#[error(transparent)]
DocumentContestIndexMismatchError(DocumentContestIndexMismatchError),

#[error(transparent)]
DocumentContestNotRequiredError(DocumentContestNotRequiredError),

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.

🔴 Blocking: Append the new StateError variants to preserve wire discriminants

StateError is encoded by ordinal variant position: its bincode and platform-serialization implementations write the variant index as a u32, and the enum explicitly prohibits reordering without a new serialization version. Inserting these variants here changes DocumentNotFoundError from ordinal 8 to 10 and shifts every subsequent existing variant by two. ErrorWithCode values do not protect this representation. Consensus errors are serialized into ABCI/DAPI responses and decoded by WASM and JavaScript clients, so upgraded and older components can fail decoding or interpret an existing error as a different variant. Move both new variants after InsufficientShieldedFeeError so every pre-existing discriminator remains unchanged; add a frozen-byte compatibility test for an existing variant after the current insertion point.

source: ['codex']

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