feat(drive): add document history retrieval#3725
Conversation
📝 WalkthroughWalkthroughThis PR introduces a complete document history query feature spanning gRPC protocol definitions, Drive storage operations, proof generation/verification, and SDKs. Adds the ChangesDocument History Query & Proof Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v3.1-dev #3725 +/- ##
============================================
- Coverage 87.17% 87.16% -0.02%
============================================
Files 2601 2600 -1
Lines 318160 318021 -139
============================================
- Hits 277370 277211 -159
- Misses 40790 40810 +20
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs (1)
25-27: ⚡ Quick winAdd freeze assertions for the new historical query method slots.
These fields become part of historical method tables too; adding explicit freeze checks (like the existing
primary_key_tree_typeguard) will prevent accidental version bumps in already-shipped protocol tables.Proposed test additions
#[cfg(test)] mod historical_method_table_freeze { @@ #[test] fn v3_primary_key_tree_type_selects_v1_dispatch() { assert_eq!( DRIVE_DOCUMENT_METHOD_VERSIONS_V3.primary_key_tree_type, 1, @@ ); } + + #[test] + fn v2_document_history_query_slots_are_frozen_at_v0_dispatch() { + assert_eq!(DRIVE_DOCUMENT_METHOD_VERSIONS_V2.query.fetch_document_history_query, 0); + assert_eq!(DRIVE_DOCUMENT_METHOD_VERSIONS_V2.query.fetch_document_history, 0); + assert_eq!(DRIVE_DOCUMENT_METHOD_VERSIONS_V2.query.prove_document_history, 0); + } + + #[test] + fn v3_document_history_query_slots_are_frozen_at_v0_dispatch() { + assert_eq!(DRIVE_DOCUMENT_METHOD_VERSIONS_V3.query.fetch_document_history_query, 0); + assert_eq!(DRIVE_DOCUMENT_METHOD_VERSIONS_V3.query.fetch_document_history, 0); + assert_eq!(DRIVE_DOCUMENT_METHOD_VERSIONS_V3.query.prove_document_history, 0); + } }🤖 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_versions/drive_document_method_versions/mod.rs` around lines 25 - 27, Add freeze assertions for the new historical method slots so they cannot be changed after shipping: in the same place where primary_key_tree_type is guarded, add checks that fetch_document_history_query, fetch_document_history, and prove_document_history are frozen (or assert their versions/flags) to prevent accidental bumps in historical method tables; mirror the exact pattern used for primary_key_tree_type to locate and implement these guards inside the drive_document_method_versions module.
🤖 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-drive/src/verify/document/verify_document_history/v0/mod.rs`:
- Around line 17-27: The current verify_document_history_v0 decodes every proved
history entry using a single DocumentTypeRef which breaks when the contract
schema changed; update verify_document_history_v0 so that for each history entry
you resolve the DocumentTypeRef that was active at that entry's timestamp (i.e.
lookup the contract revision for the entry's revision/timestamp and use that
revision's document type) before decoding, or alternatively detect if the
requested range spans multiple contract revisions and return a clear error
rejecting cross-revision history requests; make this change where entries are
iterated/decoded (and similarly for the logic referenced around lines 62-69) so
each Document is decoded against the correct historical schema.
---
Nitpick comments:
In
`@packages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rs`:
- Around line 25-27: Add freeze assertions for the new historical method slots
so they cannot be changed after shipping: in the same place where
primary_key_tree_type is guarded, add checks that fetch_document_history_query,
fetch_document_history, and prove_document_history are frozen (or assert their
versions/flags) to prevent accidental bumps in historical method tables; mirror
the exact pattern used for primary_key_tree_type to locate and implement these
guards inside the drive_document_method_versions module.
🪄 Autofix (Beta)
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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5743c558-0198-4f06-ab5c-4d3a5b2526c6
📒 Files selected for processing (41)
packages/dapi-grpc/build.rspackages/dapi-grpc/protos/platform/v0/platform.protopackages/js-evo-sdk/src/documents/facade.tspackages/js-evo-sdk/tests/unit/facades/documents.spec.tspackages/rs-dapi-client/src/transport/grpc.rspackages/rs-drive-abci/src/query/document_history/mod.rspackages/rs-drive-abci/src/query/document_history/v0/mod.rspackages/rs-drive-abci/src/query/mod.rspackages/rs-drive-abci/src/query/service.rspackages/rs-drive-proof-verifier/src/proof.rspackages/rs-drive-proof-verifier/src/types.rspackages/rs-drive/src/drive/document/get_fetch/fetch_document_history/mod.rspackages/rs-drive/src/drive/document/get_fetch/fetch_document_history/v0/mod.rspackages/rs-drive/src/drive/document/get_fetch/mod.rspackages/rs-drive/src/drive/document/mod.rspackages/rs-drive/src/drive/document/prove/mod.rspackages/rs-drive/src/drive/document/prove/prove_document_history/mod.rspackages/rs-drive/src/drive/document/prove/prove_document_history/v0/mod.rspackages/rs-drive/src/drive/document/query/fetch_document_history_query/mod.rspackages/rs-drive/src/drive/document/query/fetch_document_history_query/v0/mod.rspackages/rs-drive/src/drive/document/query/mod.rspackages/rs-drive/src/error/drive.rspackages/rs-drive/src/verify/document/mod.rspackages/rs-drive/src/verify/document/verify_document_history/mod.rspackages/rs-drive/src/verify/document/verify_document_history/v0/mod.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rspackages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/mod.rspackages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v1.rspackages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v2.rspackages/rs-platform-version/src/version/drive_versions/drive_document_method_versions/v3.rspackages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rspackages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rspackages/rs-platform-version/src/version/mocks/v2_test.rspackages/rs-sdk/src/mock/sdk.rspackages/rs-sdk/src/platform.rspackages/rs-sdk/src/platform/documents/document_history_query.rspackages/rs-sdk/src/platform/documents/mod.rspackages/rs-sdk/src/platform/fetch.rspackages/rs-sdk/src/platform/query.rspackages/wasm-sdk/src/queries/document.rs
| pub(super) fn verify_document_history_v0( | ||
| proof: &[u8], | ||
| contract_id: [u8; 32], | ||
| document_type_name: &str, | ||
| document_type: DocumentTypeRef, | ||
| document_id: [u8; 32], | ||
| start_at_ms: u64, | ||
| limit: Option<u16>, | ||
| offset: Option<u16>, | ||
| platform_version: &PlatformVersion, | ||
| ) -> Result<(RootHash, Option<BTreeMap<u64, Document>>), Error> { |
There was a problem hiding this comment.
Resolve the document type per historical revision.
This implementation decodes every proved history entry with one DocumentTypeRef. That only holds if the contract schema never changed. Once a data contract update modifies this document type, older revisions in the same history range will be verified against the latest schema and can fail to decode or be interpreted incorrectly. Please either resolve the contract revision that was active for each timestamp, or explicitly reject history ranges that cross contract revisions until that lookup is wired in.
Also applies to: 62-69
🤖 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/src/verify/document/verify_document_history/v0/mod.rs`
around lines 17 - 27, The current verify_document_history_v0 decodes every
proved history entry using a single DocumentTypeRef which breaks when the
contract schema changed; update verify_document_history_v0 so that for each
history entry you resolve the DocumentTypeRef that was active at that entry's
timestamp (i.e. lookup the contract revision for the entry's revision/timestamp
and use that revision's document type) before decoding, or alternatively detect
if the requested range spans multiple contract revisions and return a clear
error rejecting cross-revision history requests; make this change where entries
are iterated/decoded (and similarly for the logic referenced around lines 62-69)
so each Document is decoded against the correct historical schema.
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
The document-history plumbing is mostly coherent, but three user-visible issues remain in the shipped surface. One is blocking: the protobuf service added getDocumentHistory, but the checked-in @dashevo/dapi-grpc generated clients were not regenerated, so published consumers of that package cannot call the new RPC. The other two issues are pagination-semantics regressions: empty history pages are treated as missing resources in the non-proof path and collapsed to None in proof verification.
Note: Inline posting through review_poster.py failed with GitHub HTTP 422, so I posted the same verified findings as a top-level review body.
Reviewed commit: b5a9d0b
🔴 1 blocking | 🟡 2 suggestion(s)
Verified findings
blocking: The new RPC is declared in the proto, but the checked-in generated DAPI clients do not expose it
packages/dapi-grpc/protos/platform/v0/platform.proto (line 38)
@dashevo/dapi-grpc ships generated client artifacts from packages/dapi-grpc/clients and re-exports them from node.js and browser.js. After adding rpc getDocumentHistory(...), the repository still contains no generated getDocumentHistory / GetDocumentHistory* symbols under packages/dapi-grpc/clients/platform/v0, so workspace and published-package consumers that rely on those checked-in clients cannot construct or invoke the new method. The Rust path works because it regenerates from the proto during build, but the package that advertises generated clients is stale in the committed source.
suggestion: Empty document-history pages are converted into `NotFound` in the non-proof path
packages/rs-drive-abci/src/query/document_history/v0/mod.rs (line 120)
This branch turns any empty result set into QueryError::NotFound, which breaks normal pagination semantics. Requests such as offset past the last revision or start_at_ms after the newest revision are valid queries against an existing document history and should return an empty page, not a transport error. As written, raw DAPI callers using prove = false cannot paginate until exhaustion without treating the final page as failure, and they cannot distinguish an empty page from an actually invalid document ID.
suggestion: Proof verification collapses an empty history page into `None`
packages/rs-drive/src/verify/document/verify_document_history/v0/mod.rs (line 83)
verify_document_history_v0() returns None whenever the verified map is empty. That loses information for paginated history queries, because a valid empty page and a genuinely absent history become indistinguishable after proof verification. The analogous contract-history verifier returns Some(empty_map), which preserves collection semantics. Returning Some(documents) here keeps the proof-decoded result lossless and aligns the verifier with the collection behavior expected by limit/offset queries.
Ok((root_hash, Some(documents)))
🤖 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.
- [BLOCKING] In `packages/dapi-grpc/protos/platform/v0/platform.proto`:38-39: The new RPC is declared in the proto, but the checked-in generated DAPI clients do not expose it
`@dashevo/dapi-grpc` ships generated client artifacts from `packages/dapi-grpc/clients` and re-exports them from `node.js` and `browser.js`. After adding `rpc getDocumentHistory(...)`, the repository still contains no generated `getDocumentHistory` / `GetDocumentHistory*` symbols under `packages/dapi-grpc/clients/platform/v0`, so workspace and published-package consumers that rely on those checked-in clients cannot construct or invoke the new method. The Rust path works because it regenerates from the proto during build, but the package that advertises generated clients is stale in the committed source.
- [SUGGESTION] In `packages/rs-drive-abci/src/query/document_history/v0/mod.rs`:120-123: Empty document-history pages are converted into `NotFound` in the non-proof path
This branch turns any empty result set into `QueryError::NotFound`, which breaks normal pagination semantics. Requests such as `offset` past the last revision or `start_at_ms` after the newest revision are valid queries against an existing document history and should return an empty page, not a transport error. As written, raw DAPI callers using `prove = false` cannot paginate until exhaustion without treating the final page as failure, and they cannot distinguish an empty page from an actually invalid document ID.
- [SUGGESTION] In `packages/rs-drive/src/verify/document/verify_document_history/v0/mod.rs`:83-89: Proof verification collapses an empty history page into `None`
`verify_document_history_v0()` returns `None` whenever the verified map is empty. That loses information for paginated history queries, because a valid empty page and a genuinely absent history become indistinguishable after proof verification. The analogous contract-history verifier returns `Some(empty_map)`, which preserves collection semantics. Returning `Some(documents)` here keeps the proof-decoded result lossless and aligns the verifier with the collection behavior expected by limit/offset queries.
Issue being fixed or feature implemented
Adds an end-to-end public read path for document history when
documentsKeepHistoryis enabled. Today Drive stores historical revisions, but SDK users cannot retrieve them through DAPI / Evo SDK APIs.What was done?
getDocumentHistoryto DAPI proto and Rust gRPC transport.DocumentHistory.rs-sdk,wasm-sdk, andjs-evo-sdk.How Has This Been Tested?
cargo check -p dapi-grpc -p platform-version -p drive -p drive-abcicargo check -p drive-proof-verifier -p rs-dapi-client -p dash-sdk -p wasm-sdkcargo check -p drive-abcicargo test -p drive document_history -- --nocapturePATH="/opt/homebrew/opt/llvm/bin:$PATH" CC_wasm32_unknown_unknown="/opt/homebrew/opt/llvm/bin/clang" CXX_wasm32_unknown_unknown="/opt/homebrew/opt/llvm/bin/clang++" yarn workspace @dashevo/wasm-sdk buildyarn workspace @dashevo/evo-sdk buildyarn workspace @dashevo/evo-sdk test:unitBreaking Changes
None.
Checklist:
For repository code-owners and collaborators only
This pull request was created by Codex.
Summary by CodeRabbit
Release Notes
getDocumentHistory()andgetDocumentHistoryWithProofInfo()for accessing document historical data