Skip to content

feat(platform)!: propertyConstraints, integer rules between document properties (PV14) - #4962

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/schema-property-expressions-5375a9
Sep 24, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/schema-property-expressions-5375a9

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

JSON Schema bounds one property at a time. A contract could not say "the deposit covers price plus fee times quantity" or "these three shares add up to 100", so rules like that lived in Rust hooks per contract (the moderation charters' reward split) or nowhere.

This adds propertyConstraints, a document type keyword (protocol version 14) that names rules over a document's integer properties. Each rule compares two integer expressions built from property paths, integer values, arithmetic operators and ifAbsent. Consensus checks every rule on each create and replace.

What was done?

The keyword

"propertyConstraints": {
  "depositCoversOrder": {
    "lessThanOrEqual": [
      { "multiply": [{ "add": ["price", "fee"] }, "quantity"] },
      "deposit"
    ]
  },
  "wholeLots": { "equal": [{ "modulo": ["quantity", 10] }, 0] },
  "minimumOrder": {
    "greaterThanOrEqual": [{ "multiply": ["price", { "ifAbsent": ["quantity", 1] }] }, 100]
  }
}
  • A rule is an object with one key, its comparison: equal, notEqual, lessThan, lessThanOrEqual, greaterThan or greaterThanOrEqual, listing the left and the right expression.
  • An expression is an integer value, a string naming an integer property (dotted for a nested one), { "ifAbsent": [path, value] }, add/multiply over two or more operands, or subtract/divide/modulo/power over exactly two.
  • A property the document leaves out counts as 0. ifAbsent gives it another value.
  • A JSON number is always a value and a string always a path, so the tree needs no parser and no precedence rules, and a property named 100 is never confused with the number.

Arithmetic (consensus rules)

  • Exact i128, operands evaluated left to right. An intermediate result that does not fit, a divisor of 0, a negative exponent or a property value that is not an integer (a float with no fractional part passes the schema's integer type) refuses the document; nothing wraps.
  • divide and modulo are Euclidean: the remainder is never negative (-7 by 2 is -4 remainder 1). For operands that are not negative this is ordinary integer division.
  • 0 to the power 0 is 1. Only integer properties can be read (no floats), so every node computes the same bits.

Registration

Checked on every parse (parser generation 3, parse_property_constraints 0): the shape; every path names an integer property that is neither transient nor inside a transient object; every rule reads at least one property; no literal 0 divisor or negative exponent; no operand deeper than MAX_PROPERTY_CONSTRAINT_PARSE_DEPTH (64, a constant far above any registrable rule, so a parse without full validation cannot recurse without bound). A literal written as a float with no fractional part (100.0) reads as that integer, since the meta-schema's integer type admits it. Under full validation, the limits: at most 16 rules per type (SystemLimits::max_property_constraints) and 32 nodes per rule (max_property_constraint_nodes). The meta-schema v3 grammar reports malformed shapes when a contract registers.

Before (protocol version 14 without this change): the keyword is unknown and the meta-schema refuses the contract.

"propertyConstraints": { "sum": { "equal": [{ "add": ["a", "b"] }, 100] } }
-> JsonSchemaError: additionalProperties (the document type level is closed)

After:

{ "sum": { "equal": [{ "add": ["a", "b"] }, 100] } }         -> registers
{ "sum": { "equal": [{ "add": ["a", "note"] }, 100] } }      -> InvalidContractStructure: rule "sum" reads "note", which has type string, not integer
{ "sum": { "equal": [{ "divide": ["a", 0] }, 1] } }          -> InvalidContractStructure: rule "sum" at equal[0].divide divides by 0
{ "sum": { "equal": [{ "add": [1, 2] }, 3] } }               -> InvalidContractStructure: rule "sum" reads no property

Enforcement

DataContract::validate_document_properties 0 calls validate_property_constraints (validate_property_constraints 0) after the schema validation, so document create and replace (and an indexOnly delete, whose row carries every property) apply the rules, and so does every client that validates a document before sending it. Rules run in name order; the first broken one fails with DocumentPropertyConstraintViolatedError (basic, code 10422), naming the document type, the rule and why (NotMet, Overflow, DivisionByZero, NegativeExponent, NotAnInteger). The check reads no state and changes nothing stored, so it costs no fee.

With the depositCoversOrder rule above:

create { price: 100, fee: 10, quantity: 2, deposit: 220 }   -> accepted
create { price: 100, fee: 10, quantity: 2, deposit: 219 }   -> DocumentPropertyConstraintViolatedError (10422), rule "depositCoversOrder", NotMet
replace of the first with quantity: 4                       -> DocumentPropertyConstraintViolatedError (10422), document unchanged

Before this change the same contract could not be registered, so there is no earlier behaviour for these writes.

Updates

The rules are fixed when the document type is created: propertyConstraints joins the frozen doctype rules of the v1 schema compatibility check, so adding, removing or changing a rule (an operator or a value inside one included) is IncompatibleDocumentTypeSchemaError.

Moderation charters

The submittedCharter type declares its first rule, so the reward split is held to 100 by consensus on every create:

"rewardSplitIsWhole": {
  "equal": [{ "add": ["rewardSplit.leader", "rewardSplit.equal", "rewardSplit.actions"] }, 100]
}

Before: { leader: 10, equal: 40, actions: 40 } was stored (only validate_submitted_charter, not yet wired into consensus, checked the sum). After: refused with 10422, rule rewardSplitIsWhole.

#4957 shipped maxBytes (code 10421) and dropped sumOfProperties; this rule takes its place for the split, with the next document code, 10422. validate_submitted_charter no longer checks the split, so the split has one definition: 11001 is no longer produced (the BasicError variant keeps its place, the encoding being positional) and ModerationCharterRewardSplit::total is gone. With maxBytes holding the description's byte cap, SubmittedCharter::validate now reads a proposal and holds no rule of its own; its versioned step stays for the path that seats a team.

Clients

  • wasm-dpp maps the error; wasm-dpp2 adds DocumentPropertyConstraintErrorCode and ConsensusError.documentPropertyConstraintErrorCode, re-exported by @dashevo/evo-sdk.
  • Book (data-model/documents.md, error codes), evo-sdk README and charter docs describe the keyword.

In-place changes to shipped generations

  • DataContract::validate_document_properties 0 (dpp), selected at every protocol version, gains the validate_property_constraints call, next to the maxBytes call feat(platform)!: maxBytes, a UTF-8 byte cap on document strings (PV14) #4957 added the same way. Before protocol version 14 validate_property_constraints is None in every method table, so the call returns an empty result without reading the document, and the function's result is the same as before. That gate is the proof: meta-schema v0 (protocol versions 1 to 11) leaves the document type level open, so the meta-schemas are not. Tested through replace structure validation 0 at protocol version 13 and 14. The drive-abci structure validations are unchanged.
  • SystemLimits V1 to V3 and the contract version tables V1 to V5 gain the new fields (limits backfilled, gates None). Only parser generation 3 reads the limits, and it is never reached before protocol version 14.
  • Generation 3 reads the keyword off stored contracts too, including ones admitted under meta-schema v0, which left the document type level open. meta_schema_v0_stray_keyword_tests holds the final census of stray keys on those contracts, and propertyConstraints is not one of them, so no stored contract changes meaning.

How Has This Been Tested?

  • dpp: evaluator and parser unit tests (every operator, Euclidean negatives, overflow, zero divisor, huge exponents, a float value, float literals, the parse depth cap, absent and ifAbsent operands, left fault first), generation 3 parser tests (both parse paths, integer and transient checks, limits under full validation only, meta-schema refusals, protocol version 13 refuses the keyword when registering and ignores it when reading, platform serialization round trip, update freeze), schema compatibility test, frozen BasicError discriminant (199), charter rule test through DataContract::validate_document.
  • drive-abci: end-to-end batch tests for create and replace (met, not met, absent as 0, ifAbsent, a replace leaving out an operand the stored document had, division by zero, overflow, stored document untouched) and the replace structure gate at protocol version 13 and 14.
  • wasm-dpp2: error code mirror test.
  • Totals on top of the current v4.2-dev: dpp lib 4858 passed; drive-abci document batch, genesis, protocol upgrade and contract create tests 606 passed; wasm-dpp2 error code mirrors; platform-version.
  • cargo clippy --all-features --all-targets -D warnings on dpp, platform-version and drive-abci; wasm32 checks of wasm-dpp and wasm-dpp2.
  • Not run locally: the JS specs (the charter contract spec and wasm-dpp2 specs need a wasm-dpp rebuild) and the strategy tests.

Breaking Changes

Consensus: protocol version 14 admits a new document type keyword and refuses documents that break its rules, with a new basic error (10422). The moderation charters contract's submittedCharter type gains a rule.

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

PR Hygiene · da2489c

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs — this one is beyond the limit; it waits until one merges
  • Build running
  • Approvals
    • files with no dedicated owner — you own it
    • js-wasm-sdk (packages/js-evo-sdk/README.md) — shumkov
    • dpp — you own it
    • rs-drive-abci — you own it

When every box is checked the PR Hygiene check passes and this can merge.

…properties (PV14)

A document type can now name rules its documents' integer properties must
meet, where JSON Schema bounds one property at a time: each rule compares
two integer expressions built from property paths, integer values, add,
subtract, multiply, divide, modulo and power. A property the document
leaves out counts as 0, or as the value of an ifAbsent operand naming it.
Arithmetic is exact i128; divide and modulo are Euclidean; an overflow, a
zero divisor, a negative exponent or a value that is not an integer
refuses the document.

Parser generation 3 reads the keyword on every parse (paths must name
integer properties that are not transient, operands nest at most 64
deep) and holds the limits under full validation (16 rules per type, 32
nodes per rule). DataContract::validate_document_properties 0, extended
in place and inert before protocol version 14, checks the rules after the
schema validation, so document create and replace refuse a broken rule
with DocumentPropertyConstraintViolatedError (10422) and clients
validating a document see the same answer. The rules are frozen on
contract update.

The moderation charters contract holds a proposal's reward split to 100
with its first rule, rewardSplitIsWhole, so validate_submitted_charter no
longer checks the split and error 11001 is no longer produced. With
maxBytes holding the description's byte cap, the proposal step now reads
a proposal and holds no rule of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 15 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: 38915ff1-fc36-4fd5-89e7-1966a6207bed

📥 Commits

Reviewing files that changed from the base of the PR and between 6c3b388 and da2489c.

📒 Files selected for processing (49)
  • book/src/data-model/documents.md
  • book/src/error-handling/error-codes.md
  • docs/protocol/moderation-charters.md
  • packages/js-evo-sdk/README.md
  • packages/moderation-charters-contract/README.md
  • packages/moderation-charters-contract/schema/v1/moderation-charters-contract-documents.json
  • packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json
  • packages/rs-dpp/src/data_contract/document_type/accessors/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/accessors/v2/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/property_constraints_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/versioned_methods.rs
  • packages/rs-dpp/src/data_contract/document_type/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property_constraints/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property_constraints/tests.rs
  • packages/rs-dpp/src/data_contract/document_type/schema/validate_schema_compatibility/v1/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/v2/accessors.rs
  • packages/rs-dpp/src/data_contract/document_type/v2/mod.rs
  • packages/rs-dpp/src/data_contract/methods/validate_document/v0/mod.rs
  • packages/rs-dpp/src/errors/consensus/basic/basic_error.rs
  • packages/rs-dpp/src/errors/consensus/basic/document/document_property_constraint_violated_error.rs
  • packages/rs-dpp/src/errors/consensus/basic/document/mod.rs
  • packages/rs-dpp/src/errors/consensus/basic/moderation_charter/moderation_charter_reward_split_not_one_hundred_error.rs
  • packages/rs-dpp/src/errors/consensus/codes.rs
  • packages/rs-dpp/src/moderation_charter/mod.rs
  • packages/rs-dpp/src/moderation_charter/tests.rs
  • packages/rs-dpp/src/moderation_charter/v0/mod.rs
  • packages/rs-dpp/src/system_data_contracts.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/property_constraints.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/mod.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v1.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v2.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v3.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v4.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v5.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_contract_versions/v6.rs
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v10.rs
  • packages/rs-platform-version/src/version/mocks/v2_test.rs
  • packages/rs-platform-version/src/version/system_limits/mod.rs
  • packages/rs-platform-version/src/version/system_limits/v1.rs
  • packages/rs-platform-version/src/version/system_limits/v2.rs
  • packages/rs-platform-version/src/version/system_limits/v3.rs
  • packages/rs-platform-version/src/version/system_limits/v4.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/wasm-dpp/src/errors/consensus/consensus_error.rs
  • packages/wasm-dpp2/src/consensus_error.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 24, 2026
@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-24T03:42:23.639Z

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 24, 2026
@thepastaclaw

thepastaclaw commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 7th in line, estimated start in ~1.9 h (commit da2489c)
Estimated review time once started: ~35 min (two-phase automated review; median of recent runs).

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

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants