Skip to content

perf(drive): delete a consumed document from what its lookup read - #5360

Open
QuantumExplorer wants to merge 1 commit into
claude/wizardly-driscoll-44c981from
claude/dpns-consume-single-read
Open

QuantumExplorer wants to merge 1 commit into
claude/wizardly-driscoll-44c981from
claude/dpns-consume-single-read

Conversation

@QuantumExplorer

Copy link
Copy Markdown
Member

Basic explanation

Registering a DPNS name from protocol version 14 deletes the name's preorder. The platform was reading that preorder from storage twice: once to check it is yours and old enough, and once more to delete it. The second read was billed to the user. Now the delete uses what the first read already found, so a name costs a little less to register (10,580 credits less in processing fees). Nothing else changes: the same preorder is deleted, and you get the same storage refund.

This applies to any contract whose refersTo lookup declares consume, not only DPNS. Stacked on #5337, which adds DPNS v3.

Issue being fixed or feature implemented

A create that consumes a commitment (a refersTo lookup with a computed findBy key and consume: true, protocol version 14) read the commitment twice:

  1. document reference validation found it through the lookup's index query (billed);
  2. converting the create to Drive operations deleted it with force_delete_document_for_contract_operations, which reads the document again by id to know which index entries to remove and whom to refund (billed again).

What was done?

  • fetch_document_through_lookup runs the same index query through query_documents_with_flags and returns the document with its stored element's storage flags. It is the same path query at the same cost: no other fee pin moved.
  • ConsumedDocument carries the document and storage flags the lookup read, instead of only its id.
  • New Drive::force_delete_read_document_for_contract_operations: the operations of force_delete_document_for_contract_operations less its read, with the same guards (refuses indexOnly and history-keeping types). It dispatches on the same delete_document_for_contract_operations method version, so there is no new version field.
  • AddDocumentAndDeleteConsumed takes the ConsumedDocuments and deletes each with the new method. A contested create's consume uses the new ForceDeleteReadDocument operation instead of ForceDeleteDocument, which keeps serving moderator deletes and ttl expiry.
  • v14 notes 61 and 71 updated in place (protocol version 14 is unreleased).

Before and after

A DPNS v3 domain create (should_charge_a_domain_create_at_protocol_versions_13_and_14):

processing fee storage fee refund to the writer
before 2,525,540 42,687,000 15,181,239
after 2,514,960 42,687,000 15,181,239

Protocol version 13 is unchanged (1,876,280 / 42,660,000 / 0).

In-place changes to shipped generations

  • Document reference validation 0 and batch state 0 (fetch_document_through_lookup) and create action conversion 0: every protocol version selects these. The lookup query and the consumed-document delete are reached only for a refersTo with a computed key, which only meta-schema v3 (protocol version 14) admits. Before 14 no create carries a consumed document and no lookup runs, so nothing changes there.
  • query_documents_with_flags runs the same path query as query_documents for a non-indexOnly type. A consumed type is never indexOnly (registration refuses it), and no other fee pin moved.

How Has This Been Tested?

  • New Drive test should_delete_a_read_document_as_a_force_delete_does_less_its_read: for a stored document, the new method's operations equal the force delete's after its single read, the storage removal and refund included.
  • should_charge_a_domain_create_at_protocol_versions_13_and_14: the protocol version 14 processing fee pin moves from 2,525,540 to 2,514,960; storage and refund unchanged.
  • cargo test -p drive --lib -- consume document::delete drive_op_batch: 88 passed.
  • cargo test -p drive-abci --lib -- dpns creation_tests masternode_vote check_tx::v0 protocol_upgrade data_triggers commit_reveal owner_balance consumed: 344 passed, and the DPNS fee pin failed as expected before its update; after it, cargo test -p drive-abci --lib -- dpns creation_tests commit_reveal consumed owner_balance: 166 passed.
  • cargo test -p drive-abci --test strategy_tests -- test_cases::voting_tests:: test_cases::upgrade_fork_tests::: 13 passed, 4 ignored.
  • cargo clippy -p drive -p drive-abci --all-targets and cargo fmt --all -- --check: clean.

Breaking Changes

None for clients. The processing fee of a create that consumes a commitment drops by one document read at protocol version 14, which is unreleased.

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

A create that consumes a commitment (a `refersTo` lookup with a computed
key declaring `consume`, protocol version 14) read the commitment twice:
the lookup in reference validation found it, and the delete read it again
by id to know what to remove and refund. The second read was billed.

The lookup now keeps the stored element's storage flags beside the
document (`query_documents_with_flags`, the same path query and cost),
`ConsumedDocument` carries both, and the delete runs from them through
`Drive::force_delete_read_document_for_contract_operations`: the
operations of `force_delete_document_for_contract_operations` less its
read, dispatched on the same method version. A contested create's consume
uses the new `ForceDeleteReadDocument` operation, the plain create's
`AddDocumentAndDeleteConsumed`.

A DPNS v3 domain create's processing fee drops from 2,525,540 to
2,514,960 credits; storage fee and the refund to the writer are
unchanged. A Drive test checks the read-document delete's operations
equal the force delete's after its read.

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

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4b62b4b1-3fb4-4389-ac21-d4df84e306cc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • 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.

@thepastaclaw

thepastaclaw commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit cec6d0a) · 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 cec6d0a and the PR's base-to-head diff. No blocking defects were confirmed; the overlapping cloning findings combine into one performance suggestion, and the new deletion guards lack direct negative-path coverage. This was static verification only: no builds or tests were run, and the supplied CI snapshot shows Kotlin checks queued without completed Rust validation.

🟡 2 suggestion(s)

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

🟡 Suggestion: Move consumed document payloads instead of deep-cloning them
packages/rs-drive/src/state_transition_action/action_convert_to_operations/batch/document/document_create_transition.rs:58

ConsumedDocument now owns a full Document and its storage flags, so this to_vec() recursively clones each document's property map and values. The reference-validation block also clones the owned lookup result into ConsumedDocument before dropping the original. Both copies are avoidable: this converter owns mut self, and the subsequent owned document conversion discards the original consumed_documents field. The PR therefore introduces copying proportional to the consumed document bodies on every consuming create, including contracts with larger commitments than DPNS preorders. After completing the borrowed reference checks, move the lookup tuple into ConsumedDocument; add a take_consumed_documents accessor using std::mem::take and use it here, matching the existing take_* accessors.

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

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: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 7: muse-spark-1.3-contributor (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 cross-layer change to lookup state, create conversion, and batch deletion is intricate and directly changes consensus-visible fee accounting and storage-refund inputs through force_delete_read_document_for_contract_operations_v0 and DocumentOperationType conversion.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (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)
  • 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-drive/src/state_transition_action/action_convert_to_operations/batch/document/document_create_transition.rs`:
- [SUGGESTION] packages/rs-drive/src/state_transition_action/action_convert_to_operations/batch/document/document_create_transition.rs:58: Move consumed document payloads instead of deep-cloning them
  ConsumedDocument now owns a full Document and its storage flags, so this to_vec() recursively clones each document's property map and values. The reference-validation block also clones the owned lookup result into ConsumedDocument before dropping the original. Both copies are avoidable: this converter owns mut self, and the subsequent owned document conversion discards the original consumed_documents field. The PR therefore introduces copying proportional to the consumed document bodies on every consuming create, including contracts with larger commitments than DPNS preorders. After completing the borrowed reference checks, move the lookup tuple into ConsumedDocument; add a take_consumed_documents accessor using std::mem::take and use it here, matching the existing take_* accessors.

In `packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive/src/drive/document/delete/delete_document_for_contract_operations/v0/mod.rs:222-236: Add negative-path tests for the new read-document deletion guards
  The new helper independently duplicates the indexOnly and history-keeping guards from the existing force-delete path. The added operation-equivalence test uses a normal document type, so it does not verify either rejection; the existing history-deletion test exercises the old deletion path rather than this helper. Contract registration currently excludes these types from consumption, so this is a defensive API coverage gap, not a demonstrated execution defect. Add direct assert_matches! cases for the new entry point with an indexOnly type and a history-keeping type, asserting CorruptedCodeExecution and InvalidDeletionOfDocumentThatKeepsHistory respectively, to pin the guard parity promised by this PR.

Comment on lines +222 to +236
if document_type.index_only() {
return Err(Error::Drive(DriveError::CorruptedCodeExecution(
"indexOnly documents cannot be deleted by id: there is no primary-storage \
row; use delete_index_only_document_for_contract_operations with the \
document's values",
)));
}

if document_type.documents_keep_history() {
return Err(Error::Drive(
DriveError::InvalidDeletionOfDocumentThatKeepsHistory(
"this document type keeps history and therefore can not be deleted",
),
));
}

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: Add negative-path tests for the new read-document deletion guards

The new helper independently duplicates the indexOnly and history-keeping guards from the existing force-delete path. The added operation-equivalence test uses a normal document type, so it does not verify either rejection; the existing history-deletion test exercises the old deletion path rather than this helper. Contract registration currently excludes these types from consumption, so this is a defensive API coverage gap, not a demonstrated execution defect. Add direct assert_matches! cases for the new entry point with an indexOnly type and a history-keeping type, asserting CorruptedCodeExecution and InvalidDeletionOfDocumentThatKeepsHistory respectively, to pin the guard parity promised by this PR.

source: muse-spark-1.3-contributor (phase1-reviewer: general, rust-quality)

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants