Skip to content

feat(dpp): consume and minimumAgeBlocks on any reference judged on the create alone - #5365

Open
QuantumExplorer wants to merge 1 commit into
v5.0-devfrom
claude/consume-create-only-on-v5-dev
Open

QuantumExplorer wants to merge 1 commit into
v5.0-devfrom
claude/consume-create-only-on-v5-dev

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

A document can point at another document and, when it is created, delete that document (consume) or require it to be old enough (minimumAgeBlocks). Until now that only worked for one kind of pointer: a commit-and-reveal hash. The real requirement was never the hash. It was that the pointer is only checked once, when the document is created, because a later edit would check it again and find the consumed document gone.

A document type whose documents can never be edited (documentsMutable: false) has exactly that property: nothing ever re-checks its pointers. This PR lets such types use consume and minimumAgeBlocks with any findBy, hash or not. It is the first prerequisite for DPNS name tombstones, where a tombstone consumes the name's domain.

Issue being fixed or feature implemented

consume and minimumAgeBlocks were refused unless the reference's findBy held a hash function. The reason, in DocumentReferenceLookup::is_checked_on_create_only, was that such a reference is judged on the create alone and never again. But a reference declared on a type whose documents are never replaced is judged on the create alone too: a replace is the one write that re-validates a reference (transfers, purchases, price updates, moderator field changes and restores never do, and changeFields may not name a property a reference reads).

What was done?

  • DocumentReferenceLookup::is_checked_on_create_only(documents_mutable): a computed key, or a declaring type whose documents are never replaced.
  • The refusal of minimumAgeBlocks and consume moves from parse_find_by, which has no document type, to validate_reference_lookup_sources (new age_or_consume_error), which runs on every parse once the type is known. Meta-schema v3 now only requires findBy beside either keyword.
  • The rules that follow from "judged on the create alone" follow the new definition:
    • a "$ownerId" findBy source on a transferable or tradeable referring type, now admitted on any reference judged on the create alone (a function in findBy or a type never replaced): a transfer moves the writer part of the key, but nothing checks the reference again;
    • creatorRefersTo with a deletableDocument found by findBy;
    • the where and anyOf rule (create_only_leaf_error) and the immutable-property exception, both no-ops on an immutable type.
  • Two callers that meant "has a hash key" now say so (hash_key().is_some()): the string or byte array carrier, which still needs a function, and the repeated-key preimage refusal.
  • Reference validation 0 skips a create-only lookup on a replace by the new definition.
  • Meta-schema v3 descriptions, v14 note 61, the book (refers-to-lookup, documents, refers-to, contract-keywords) and the wasm-dpp2 TypeScript docs updated.

Before and after

A redemption that consumes the writer's voucher, found by its serial without a function:

"redemption": {
  "documentsMutable": false,
  "properties": {
    "voucherSerial": {
      "type": "identifier",
      "position": 0,
      "refersTo": {
        "type": "deletableDocument",
        "documentType": "voucher",
        "findBy": { "serial": "." },
        "where": { "$ownerId": "$ownerId" },
        "minimumAgeBlocks": 1,
        "consume": true
      }
    }
  },
  "required": ["voucherSerial"],
  "additionalProperties": false
}
  • Before: registration refused it. Meta-schema v3 required a findBy holding a function beside either keyword, and the parser refused it as well ("refersTo minimumAgeBlocks and consume need a findBy function computing the key").
  • After: it registers; creating a redemption in a later block than the voucher deletes the voucher. The same declaration on a type with documentsMutable: true is still refused (10231): "the reference must be judged on the create alone: give findBy a function, or make the document type immutable".

In-place changes to shipped generations

  • Document reference validation 0 (binds_a_changed_property, validate_reference_target_v0): every protocol version selects it. Only lookups reach the changed lines, and only meta-schema v3 (protocol version 14) admits findBy, so nothing changes before 14. A replace never reaches either line for a type whose documents are never replaced (document replace advanced structure refuses it first), so the behaviour at 14 is unchanged too.
  • Meta-schema v3, parser generation 3 and v14 note 61 are edited in place; only protocol version 14, unreleased, selects them.

How Has This Been Tested?

  • dpp:
    • should_parse_age_and_consume_without_a_function_on_a_type_never_replaced;
    • should_refuse_age_and_consume_without_a_function_on_a_type_whose_documents_can_be_replaced (validating and stored-contract parses);
    • should_parse_a_deletable_creator_lookup_without_a_computed_key_on_a_type_never_replaced and its refused twin on a replaceable type;
    • the existing "without findBy" refusals kept.
  • drive-abci, through the full ABCI pipeline (commit_reveal_lookup.rs, a voucher / redemption fixture):
    • should_consume_a_document_a_plain_lookup_finds_on_a_type_never_replaced;
    • should_refuse_a_plain_lookup_redemption_in_the_block_of_its_voucher (40142);
    • should_refuse_redeeming_another_identitys_voucher_through_a_plain_lookup (40127).
  • Commands: cargo test -p dpp --lib, cargo test -p drive-abci --lib -- commit_reveal refers_to reference dpns document moderation, cargo clippy -p dpp -p drive-abci -p dash-sdk --all-targets, cargo check -p wasm-dpp2, cargo fmt --all -- --check.

Breaking Changes

None. Contracts that registered before still register; this only admits declarations that were refused.

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 · 60859a6

  • Bots — coderabbitai skipped after its own rate limit · thepastaclaw requested changes — dismiss the review or push a fix, 1 thread unresolved — resolve it
  • Self-review — post /self-reviewed
  • Build green
  • Approvals — you own every area touched; none needed

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

…e create alone

`consume` and `minimumAgeBlocks` needed a findBy function because only a
commit-and-reveal key was judged on the create alone. A reference on a
document type whose documents are never replaced (documentsMutable:
false) is judged on the create alone too: a replace is the one write that
re-validates a reference.

`DocumentReferenceLookup::is_checked_on_create_only(documents_mutable)`
covers both. The age and consume refusal moves from `parse_find_by`,
which has no document type, to `validate_reference_lookup_sources`
(`age_or_consume_error`); meta-schema v3 only requires findBy beside
either keyword. The `$ownerId` source rule, `creatorRefersTo` with a
deletable lookup and the create-only skips in reference validation 0
follow the new definition; the string carrier and the repeated-key
preimage refusal keep testing for a hash key. Docs: meta-schema v3, v14
note 61, the book and the wasm-dpp2 TypeScript docs.

Tests: dpp parses age and consume beside a plain findBy on an immutable
type and refuses them on a mutable one, both parses; creatorRefersTo the
same. drive-abci: a voucher/redemption contract consumes the voucher,
refuses a same-block redemption (40142) and another identity's (40127).

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

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next included review available in 3 minutes.

Check out review usage here.

View limit details

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: eec90a13-3807-4c6d-aaf7-dba764a9da7b

📥 Commits

Reviewing files that changed from the base of the PR and between a35c4ce and 60859a6.


📒 Files selected for processing (14)
  • book/src/contract-keywords.md
  • book/src/contract-keywords/refers-to-lookup.md
  • book/src/contract-keywords/refers-to.md
  • book/src/data-model/documents.md
  • packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json
  • 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/commit_reveal_lookup_tests.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/property/lookup_preimage.rs
  • packages/rs-dpp/src/data_contract/document_type/property/reference_lookup.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/commit_reveal_lookup.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/wasm-dpp2/src/data_contract/document_type_reference.rs


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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 Oct 9, 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-10-09T16:40:59.939Z

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix, 1 thread unresolved — resolve it. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 9, 2026
@github-actions github-actions Bot added this to the v5.0.0 milestone Oct 9, 2026
@thepastaclaw

thepastaclaw commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

⛔ Final review complete — 1 blocking finding(s) (commit 60859a6) · triage: critical

@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

Verified the supplied findings against head 60859a6. The schema relaxation introduces one blocking validation bypass: minimumAgeBlocks is accepted on inList references but discarded before enforcement; two non-blocking suggestions address owner-source regression coverage and public Rust API release metadata. This was a static review; the supplied CI snapshot still had Rust workspace tests and several functional/E2E checks pending.

🔴 1 blocking | 🟡 2 suggestion(s)

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 6: gemini-3.8-flash-high (agent: phase1-reviewer, role: general); reviewer 7: gemini-3.8-flash-high (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — The diff makes an intricate, coordinated change to consensus contract acceptance and document-reference validation, notably validate_reference_lookup_sources in rs-dpp and validate_reference_target_v0 in rs-drive-abci, expanding which references can consume documents and enforce minimum ages.
  • Phase 1 reviewers: gemini-3.8-flash-high — general (completed, effort high); agent phase1-reviewer, gemini-3.8-flash-high — rust-quality (completed, effort high); agent phase1-reviewer
  • Phase 1 model: gemini-3.8-flash-high — antigravity quota: weekly 24% left, 5h 100% left
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — 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/schema/meta_schemas/document/v3/document-meta.json`:
- [BLOCKING] packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json:871-873: Reject minimumAgeBlocks on inList references before its guard is discarded
  Requiring only findBy now admits declarations such as {"type":"permanentDocument","documentType":"grant","findBy":{"$id":"grantId"},"inList":"members","minimumAgeBlocks":100}. However, parse_reference_target returns through parse_list_element_reference before reaching parse_find_by, and ListElementReference has no field for the minimum age. Its document-reference declaration exposes lookup: None, so validate_reference_lookup_sources skips it and runtime reference validation never reaches the age check, which requires Some(lookup). An immutable capability-use type can therefore declare this ownerRefersTo gate yet allow a listed member to create immediately after the grant, bypassing the declared activation delay. The previous meta-schema rejected this combination because its findBy contained only a string source. Reject minimumAgeBlocks with inList in both the meta-schema and parser unless list references explicitly preserve and enforce it, and add registration and same-block regression coverage.

In `packages/rs-dpp/src/data_contract/document_type/property/reference_lookup.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/property/reference_lookup.rs:440-449: Cover the newly admitted owner-derived lookup keys
  This guard newly admits LookupKeySource::OwnerId on transferable or tradeable referring types through two routes: a plain lookup on an immutable type, or a computed lookup on a mutable type. The added voucher tests compare "$ownerId" in where rather than reading it in findBy, and the added creator lookup reads only ".", so neither exercises this relaxation. Existing writer-source tests cover refusal on mutable plain lookups. Add focused acceptance tests for both create-only routes, through validating and stored-contract parses, while retaining the mutable/plain refusal. These cases protect the distinction between using ownership to assemble the lookup key and comparing ownership after fetching the target.
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/property/reference_lookup.rs:313-314: Record the breaking public DPP method signature
  DocumentReferenceLookup is publicly re-exported from dpp::data_contract::document_type, and the base exposes is_checked_on_create_only(&self). Adding the required documents_mutable argument makes downstream calls to lookup.is_checked_on_create_only() fail to compile regardless of their PlatformVersion. The protocol-14 parser gate protects consensus compatibility, not Rust source compatibility. Mark the PR title as feat(dpp)! and replace the Breaking Changes section's "None" with the signature change and caller migration so release metadata records it. Alternatively, preserve the existing signature and introduce the type-aware operation under a new name. This does not require another PlatformVersion slot.

Comment on lines 871 to 873
"then": {
"required": ["findBy"],
"properties": {
"findBy": {
"not": { "additionalProperties": { "type": "string" } }
}
}
"required": ["findBy"]
}

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: Reject minimumAgeBlocks on inList references before its guard is discarded

Requiring only findBy now admits declarations such as {"type":"permanentDocument","documentType":"grant","findBy":{"$id":"grantId"},"inList":"members","minimumAgeBlocks":100}. However, parse_reference_target returns through parse_list_element_reference before reaching parse_find_by, and ListElementReference has no field for the minimum age. Its document-reference declaration exposes lookup: None, so validate_reference_lookup_sources skips it and runtime reference validation never reaches the age check, which requires Some(lookup). An immutable capability-use type can therefore declare this ownerRefersTo gate yet allow a listed member to create immediately after the grant, bypassing the declared activation delay. The previous meta-schema rejected this combination because its findBy contained only a string source. Reject minimumAgeBlocks with inList in both the meta-schema and parser unless list references explicitly preserve and enforce it, and add registration and same-block regression coverage.

Suggested change
"then": {
"required": ["findBy"],
"properties": {
"findBy": {
"not": { "additionalProperties": { "type": "string" } }
}
}
"required": ["findBy"]
}
"then": {
"required": ["findBy"],
"properties": {
"inList": false
}
}

source: gpt-6.1-sol (phase2-reviewer: security-auditor)

Comment on lines 440 to +449
LookupKeySource::OwnerId => {
if owner_can_change(declaring) {
// A lookup judged when the document is created only, so a later transfer
// or purchase moving the writer moves no key
if !create_only && owner_can_change(declaring) {
return Some(format!(
"findBy \"{index_property}\" reads \"$ownerId\", which a transfer or \
a purchase of the referring document changes without re-validating \
the reference: findBy may read the writer only on a document type \
that cannot be transferred or traded"
that cannot be transferred or traded, beside a function, or on a \
type whose documents are never replaced"

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: Cover the newly admitted owner-derived lookup keys

This guard newly admits LookupKeySource::OwnerId on transferable or tradeable referring types through two routes: a plain lookup on an immutable type, or a computed lookup on a mutable type. The added voucher tests compare "$ownerId" in where rather than reading it in findBy, and the added creator lookup reads only ".", so neither exercises this relaxation. Existing writer-source tests cover refusal on mutable plain lookups. Add focused acceptance tests for both create-only routes, through validating and stored-contract parses, while retaining the mutable/plain refusal. These cases protect the distinction between using ownership to assemble the lookup key and comparing ownership after fetching the target.

source: gpt-6.1-sol (phase2-reviewer: rust-quality)

Comment on lines +313 to +314
pub fn is_checked_on_create_only(&self, documents_mutable: bool) -> bool {
self.hash_key().is_some() || !documents_mutable

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: Record the breaking public DPP method signature

DocumentReferenceLookup is publicly re-exported from dpp::data_contract::document_type, and the base exposes is_checked_on_create_only(&self). Adding the required documents_mutable argument makes downstream calls to lookup.is_checked_on_create_only() fail to compile regardless of their PlatformVersion. The protocol-14 parser gate protects consensus compatibility, not Rust source compatibility. Mark the PR title as feat(dpp)! and replace the Breaking Changes section's "None" with the signature change and caller migration so release metadata records it. Alternatively, preserve the existing signature and introduce the type-aware operation under a new name. This does not require another PlatformVersion slot.

source: gpt-6.1-sol (phase2-reviewer: general, architecture-layering, platform-versioning, rust-quality, security-auditor)

@github-actions

Copy link
Copy Markdown
Contributor

Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. and removed waiting-bots Waiting for the review bots to report on this head labels Oct 10, 2026

This branch has not been deployed

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

Labels

bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants