Skip to content

test(dpp): guard doctype keyword names against stray keys of contracts admitted under meta-schema v0 - #4855

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/pin-stray-doctype-keywords-683b58
Sep 20, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/pin-stray-doctype-keywords-683b58

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 20, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

From protocol version 14, stored contracts are parsed by try_from_schema_generation_3 without full validation, and it reads doctype-level keywords (indexOnly, immutable, immutableAllowSetting, actionFees, the aggregate keywords, the keeps*History flags) that not every stored contract was validated against. The document meta-schema v0 admitted every contract created at protocol versions 1 to 11 and does not refuse unknown doctype-level keys, so such a contract may carry a key of one of those names, of any shape. A malformed stray would make the contract impossible to load from the block that activates protocol version 14, and a well-formed stray indexOnly: true would change the storage layout the parser assumes for documents already written.

A reviewer of #4851 asked for one decision in the generation 3 driver instead of per-keyword leniency. #4851 has since merged with the leniency removed. This PR records the decision and the evidence behind it, and guards the one part of the hazard that is still open.

The hazard is not real on mainnet or testnet. A census of every contract create and update transition on both networks (2026-09-20), decoded from the raw bytes with dpp:

Mainnet Testnet
Transitions decoded 72 4581
Successful contract versions 59 4342
Admitted under meta-schema v0 (before protocol version 12) 54, below height 398435 3347, below height 362782
Of those, carrying any generation 3 doctype-level keyword 0 0
Successful versions that fail the protocol version 14 stored path 0 0

The set of contracts admitted under meta-schema v0 closed when protocol version 12 activated: every create and update since is validated by a meta-schema that refuses unknown doctype-level keys, and the property and index levels were closed from v0 on. So the result is final.

What is still open is the name of a future keyword. Strays do exist: a mainnet contract carries a doctype-level mutable, and testnet contracts carry mutable, comment, position, tokenCosts, indexes, bls_public_key and keywords. That list is exhaustive, so a new doctype-level keyword is safe exactly when its name is not on it. A keyword named after one of them would silently change the meaning of a live contract.

What was done?

No behaviour changes.

  • The rule, stated once on try_from_schema_generation_3: every doctype-level keyword of the generation is read wherever it appears, and its shape is enforced on both the validating and the stored path; no keyword gets stored-path leniency. The comment gives the reasons (the parser cannot tell which meta-schema admitted a contract, full_validation: false is also check_tx and client parsing, refusing is the safe failure) and says that the safety rests on the census. The inline restatement feat(platform)!: document action fees paid to the contract owner and moderators, with a fee claim state transition #4851 added next to actionFees becomes a pointer to it.
  • Two comments corrected in try_from_schema/common/mod.rs. parse_property_name_list_keyword claimed a stored contract "can only ever have passed" the meta-schema, and parse_doctype_aggregate_keywords claimed meta-schema v0 allows the aggregate keywords as optional booleans. Neither is true for contracts admitted under meta-schema v0. Comments only; the file is shared with shipped generations and its code is untouched.
  • One guard test, should_not_name_a_doctype_keyword_after_a_stray_key_of_a_meta_schema_v0_contract, in a new try_from_schema/v3/meta_schema_v0_stray_keyword_tests.rs. It records the stray keys and fails if any document meta-schema other than v0 declares one of them as a doctype-level keyword. It scans the meta-schema directory, so a future meta-schema version is covered without touching the test.

The behaviour pinned in immutable_tests.rs (rejects_a_non_array_value_on_both_modes, rejects_a_non_string_entry_on_both_modes) is kept, not reversed. Only its stated justification was wrong.

How Has This Been Tested?

  • cargo test -p dpp --features all_features_without_client --lib -- try_from_schema action_fees: 237 passed.
  • Mutation check: adding a mutable keyword to the v3 meta-schema makes the guard fail with "document meta-schema v3 declares mutable, which contracts admitted under meta-schema v0 already carry as a stray key".
  • cargo clippy -p dpp --features all_features_without_client --lib --tests -- -D warnings and cargo fmt.

The census used the platform explorer's transaction list (transaction_type 0 and 4) for both networks and decoded every raw transition with StateTransition::deserialize_from_bytes_untrusted, then loaded each contract with DataContract::try_from_platform_versioned at protocol versions 11, 13 and 14, with and without validation. The explorer is a third-party indexer; its own contract count equals successful creates plus successful updates plus the system contracts on both networks. Running the same scan over a node state snapshot during the protocol version 14 upgrade rehearsal would remove that dependency.

Breaking Changes

None. No consensus effect.

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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 56 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 7e31e2d5-dd3d-4103-9d2f-4a66f4997556

📥 Commits

Reviewing files that changed from the base of the PR and between 1ebf2df and 9897e51.

📒 Files selected for processing (3)
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/meta_schema_v0_stray_keyword_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/mod.rs

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 added this to the v4.2.0 milestone Sep 20, 2026
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

PR Hygiene

State: waiting-bots · commit 9897e51649ac509353ed4824f21875f13b936f30

  • coderabbitai has not reported for the current head
  • thepastaclaw has not reported for the current head
  • Bot review threads remain unresolved

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

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-20T11:39:15.459Z

@thepastaclaw

thepastaclaw commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 4th in line, estimated start in ~1.4 h (commit 9897e51)
Estimated review time once started: ~0.9 h (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.71%. Comparing base (ddc6f61) to head (9897e51).
⚠️ Report is 2 commits behind head on v4.2-dev.

Files with missing lines Patch % Lines
...ument_type/class_methods/try_from_schema/v3/mod.rs 0.00% 40 Missing ⚠️
...t_type/class_methods/try_from_schema/common/mod.rs 0.00% 11 Missing ⚠️

❌ Your patch status has failed because the patch coverage (0.00%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4855      +/-   ##
============================================
- Coverage     83.92%   75.71%   -8.22%     
============================================
  Files          3100     3105       +5     
  Lines        416777   454575   +37798     
============================================
- Hits         349785   344178    -5607     
- Misses        66992   110397   +43405     
Components Coverage Δ
dpp 72.76% <0.00%> (-12.21%) ⬇️
drive 76.58% <ø> (-6.88%) ⬇️
drive-abci 77.87% <ø> (-7.11%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 86.29% <ø> (-6.68%) ⬇️
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 26.76% <ø> (-4.49%) ⬇️
🚀 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

At dbc8fff, the changes are limited to tests and documentation; no blocking production or versioning defect was found. Two actionable suggestions remain: make the future-keyword guards detect omitted schema generations, and correct the description of the recorded keywords field. Verification included source inspection, schema inventory checks, and a successful diff whitespace check; Rust tests and the external network census were not independently rerun.

🟡 2 suggestion(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: platform-versioning); reviewer 4: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 5: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 6: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 7: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); final verifier: gpt-6-astra (agent: astra-verifier, role: final-verifier)

  • Triage: normal by gpt-6-astra (effort low) — The diff adds a substantial regression-test matrix and future-keyword coverage guards plus documentation, requiring review of versioned parsing assumptions but changing no runtime behavior or critical surface.
  • 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 — platform-versioning (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (lane failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6-astra — final-verifier; agent astra-verifier
  • Phase 2 reviewers: gpt-6-astra — general (completed, effort high); agent phase2-reviewer, gpt-6-astra — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6-astra — platform-versioning (completed, effort high); agent phase2-reviewer, gpt-6-astra — rust-quality (completed, effort high); 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/data_contract/document_type/class_methods/try_from_schema/v3/meta_schema_v0_stray_keyword_tests.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/meta_schema_v0_stray_keyword_tests.rs:43-58: Make the keyword guards detect an omitted schema generation
  Both future-keyword guards inspect only the manually maintained v1/v2/v3 list. If a later protocol activates document meta-schema v4 without extending this list, an unclassified keyword—or a recorded stray name such as `mutable`—introduced only in v4 escapes both guards. The existing maintenance comment does not make that omission fail, and the new book checklist promises that adding a keyword to a meta-schema triggers these checks. Add a completeness check against the document meta-schema versions selected by the platform-version tables, and include registration of new generations in the checklist.
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/meta_schema_v0_stray_keyword_tests.rs:21-22: Distinguish keywords absent from closed schemas from never-validated strays
  The description says no meta-schema declares the recorded keys, but `document/v0/document-meta.json` declares `properties.keywords` as an array of bounded strings, with at most 20 entries. Consequently, the recorded testnet `keywords` field was subject to v0 validation, unlike the other stray names. It is absent from the closed v1/v2/v3 schemas, so retaining it in the collision guard is consistent with that guard's purpose. Qualify this description as keys that no closed meta-schema (v1 and later) declares, and make the same correction in the new book paragraph at lines 516–521.

Comment on lines +43 to +58
/// Every document meta-schema that closes the doctype level. A new
/// meta-schema version is added here when it is created.
const CLOSED_DOCUMENT_META_SCHEMAS: &[(&str, &str)] = &[
(
"v1",
include_str!("../../../../../../schema/meta_schemas/document/v1/document-meta.json"),
),
(
"v2",
include_str!("../../../../../../schema/meta_schemas/document/v2/document-meta.json"),
),
(
"v3",
include_str!("../../../../../../schema/meta_schemas/document/v3/document-meta.json"),
),
];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Make the keyword guards detect an omitted schema generation

Both future-keyword guards inspect only the manually maintained v1/v2/v3 list. If a later protocol activates document meta-schema v4 without extending this list, an unclassified keyword—or a recorded stray name such as mutable—introduced only in v4 escapes both guards. The existing maintenance comment does not make that omission fail, and the new book checklist promises that adding a keyword to a meta-schema triggers these checks. Add a completeness check against the document meta-schema versions selected by the platform-version tables, and include registration of new generations in the checklist.

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

Comment on lines +21 to +22
/// Every doctype-level key that no meta-schema declares, carried by a contract
/// admitted under meta-schema v0. From a census of every contract create and

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Suggestion: Distinguish keywords absent from closed schemas from never-validated strays

The description says no meta-schema declares the recorded keys, but document/v0/document-meta.json declares properties.keywords as an array of bounded strings, with at most 20 entries. Consequently, the recorded testnet keywords field was subject to v0 validation, unlike the other stray names. It is absent from the closed v1/v2/v3 schemas, so retaining it in the collision guard is consistent with that guard's purpose. Qualify this description as keys that no closed meta-schema (v1 and later) declares, and make the same correction in the new book paragraph at lines 516–521.

source: muse-spark-1.3-contributor (phase1-reviewer: general, architecture-layering, platform-versioning, rust-quality)

…s admitted under meta-schema v0

The document meta-schema v0 admitted every contract created at protocol
versions 1 to 11 and does not refuse unknown doctype-level keys, so such a
contract may carry a key named like a generation 3 keyword, of any shape,
that no validator ever looked at. From protocol version 14 the stored path
reads those keywords without validation.

A census of every contract create and update transition on mainnet and
testnet (2026-09-20) found no contract admitted under meta-schema v0 that
carries any of them, and that set closed when protocol version 12 activated
(mainnet height 398435, testnet height 362782). So the rule stays as it is:
a keyword is read wherever it appears and its shape is enforced on both the
validating and the stored path, with no stored-path leniency.

- State the rule once on try_from_schema_generation_3 and point the inline
  actionFees comment at it.
- Correct two comments that claimed a stored contract must have passed the
  meta-schema.
- Record the stray doctype-level keys those contracts do carry (mainnet has
  `mutable`) and fail if any closed document meta-schema declares one as a
  keyword. That is the one hazard still open: a future keyword named after a
  stray key would change the meaning of a live contract.

No behaviour change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer force-pushed the claude/pin-stray-doctype-keywords-683b58 branch from dbc8fff to 9897e51 Compare September 20, 2026 14:14
@QuantumExplorer QuantumExplorer changed the title test(dpp): pin how generation 3 reads doctype keywords on contracts admitted under meta-schema v0 test(dpp): guard doctype keyword names against stray keys of contracts admitted under meta-schema v0 Sep 20, 2026

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed

@QuantumExplorer
QuantumExplorer merged commit abd1002 into v4.2-dev Sep 20, 2026
17 of 18 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/pin-stray-doctype-keywords-683b58 branch September 20, 2026 14:17
QuantumExplorer added a commit that referenced this pull request Sep 20, 2026
…delete-by-type-03196d

#4855 added its stray keyword guard test module at the same spot as the
moderators' deletion tests: both kept. `canBeDeletedByModerators` is on
neither network's list of stray doctype keys, and it is read on the stored
path as on the validating one, as the rule #4855 states asks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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