Uh oh!
There was an error while loading. Please reload this page.
fix(sdk): deserialization error due to outdated contract cache - #3052
Conversation
📝 WalkthroughWalkthroughThis PR introduces a new Changes
Sequence DiagramsequenceDiagram
participant Client as SDK Consumer
participant SDK as Sdk
participant Cache as ContextProvider<br/>(Cache)
participant Network as Network/DAPI
participant Parser as FromProof<br/>Parser
Client->>SDK: fetch_document(query with old_contract)
SDK->>Cache: get cached old_contract
Cache-->>SDK: returns old_contract
SDK->>Network: fetch with old_contract
Network-->>Parser: send proof
Parser->>Parser: attempt parse with old_contract
Parser-->>SDK: ProtocolError(CorruptedSerialization)
rect rgba(255, 100, 100, 0.5)
Note over SDK: Detect deserialization error
SDK->>Network: refetch_contract_for_query()
Network-->>SDK: returns fresh_contract
SDK->>Cache: update_data_contract(fresh_contract)
Cache->>Cache: store in contract cache
end
rect rgba(100, 200, 100, 0.5)
Note over SDK: Retry with fresh contract
SDK->>SDK: clone_with_contract(fresh_contract)
SDK->>Network: fetch_document(query with fresh_contract)
Network-->>Parser: send proof
Parser->>Parser: parse with fresh_contract
Parser-->>SDK: Success(Document)
end
SDK-->>Client: Document
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 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 |
✅ gRPC Query Coverage Report |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/rs-sdk/tests/fetch/mock_document_contract_refresh.rs`:
- Around line 114-235: Rename the three tests so their function names start with
"should_" per guidelines: change test_fetch_document_retries_on_stale_contract
to should_fetch_document_retries_on_stale_contract,
test_fetch_many_documents_retries_on_stale_contract to
should_fetch_many_documents_retries_on_stale_contract, and
test_fetch_document_does_not_retry_on_other_errors to
should_fetch_document_does_not_retry_on_other_errors; update the async fn
identifiers accordingly wherever they are referenced (e.g., in this file) so the
test runner and imports continue to work.
| /// Test: Document::fetch retries once when the first attempt fails with a deserialization error. | ||
| /// | ||
| /// Setup: | ||
| /// 1. Create old and new versions of a data contract | ||
| /// 2. Build a DocumentQuery using the OLD contract | ||
| /// 3. Mock the document query with old contract to return a CorruptedSerialization error | ||
| /// 4. Mock DataContract::fetch to return the NEW contract | ||
| /// 5. Mock the document query with new contract to return the expected document | ||
| /// 6. Verify that Document::fetch succeeds (retry worked) | ||
| #[tokio::test] | ||
| async fn test_fetch_document_retries_on_stale_contract() { | ||
| let mut sdk = Sdk::new_mock(); | ||
| let (old_contract, new_contract, new_doc_type) = make_old_and_new_contracts(); | ||
| let expected_document = new_doc_type | ||
| .random_document(None, sdk.version()) | ||
| .expect("create document"); | ||
| let document_id = expected_document.id(); | ||
| // Build a query using the old contract (simulates stale cache) | ||
| let old_query = DocumentQuery::new(old_contract.clone(), "document_type_name") | ||
| .expect("create document query with old contract") | ||
| .with_document_id(&document_id); | ||
| // 1) Old query should fail with a deserialization error | ||
| sdk.mock() | ||
| .expect_fetch_proof_error::<Document, _>(old_query.clone()) | ||
| .await | ||
| .expect("set error expectation"); | ||
| // 2) DataContract::fetch should return the new contract (refetch) | ||
| sdk.mock() | ||
| .expect_fetch(new_contract.id(), Some(new_contract.clone())) | ||
| .await | ||
| .expect("set contract refetch expectation"); | ||
| // 3) New query (with fresh contract) should succeed | ||
| let new_query = old_query.clone_with_contract(std::sync::Arc::new(new_contract)); | ||
| sdk.mock() | ||
| .expect_fetch(new_query, Some(expected_document.clone())) | ||
| .await | ||
| .expect("set document fetch expectation with new contract"); | ||
| // Execute: the retry logic should handle the error and succeed | ||
| let result = Document::fetch(&sdk, old_query) | ||
| .await | ||
| .expect("fetch should succeed after retry"); | ||
| let fetched = result.expect("document should be returned"); | ||
| assert_eq!(fetched.id(), expected_document.id()); | ||
| } | ||
| /// Test: Document::fetch_many retries once when the first attempt fails with a deserialization error. | ||
| #[tokio::test] | ||
| async fn test_fetch_many_documents_retries_on_stale_contract() { | ||
| let mut sdk = Sdk::new_mock(); | ||
| let (old_contract, new_contract, new_doc_type) = make_old_and_new_contracts(); | ||
| let expected_document = new_doc_type | ||
| .random_document(None, sdk.version()) | ||
| .expect("create document"); | ||
| let expected_documents = | ||
| Documents::from([(expected_document.id(), Some(expected_document.clone()))]); | ||
| // Build a query using the old contract (simulates stale cache) | ||
| let old_query = DocumentQuery::new(old_contract.clone(), "document_type_name") | ||
| .expect("create document query with old contract"); | ||
| // 1) Old query should fail with a deserialization error | ||
| // We use expect_fetch_proof_error with Document since DocumentQuery is the | ||
| // request type for both Fetch and FetchMany | ||
| sdk.mock() | ||
| .expect_fetch_proof_error::<Document, _>(old_query.clone()) | ||
| .await | ||
| .expect("set error expectation"); | ||
| // 2) DataContract::fetch should return the new contract (refetch) | ||
| sdk.mock() | ||
| .expect_fetch(new_contract.id(), Some(new_contract.clone())) | ||
| .await | ||
| .expect("set contract refetch expectation"); | ||
| // 3) New query (with fresh contract) should succeed | ||
| let new_query = old_query.clone_with_contract(std::sync::Arc::new(new_contract)); | ||
| sdk.mock() | ||
| .expect_fetch_many(new_query, Some(expected_documents.clone())) | ||
| .await | ||
| .expect("set document fetch_many expectation with new contract"); | ||
| // Execute: the retry logic should handle the error and succeed | ||
| let result = Document::fetch_many(&sdk, old_query) | ||
| .await | ||
| .expect("fetch_many should succeed after retry"); | ||
| assert_eq!(result.len(), 1); | ||
| let (doc_id, doc) = result.into_iter().next().unwrap(); | ||
| assert_eq!(doc_id, expected_document.id()); | ||
| assert!(doc.is_some()); | ||
| } | ||
| /// Test: When the error is NOT a deserialization error, no retry happens and the error propagates. | ||
| #[tokio::test] | ||
| async fn test_fetch_document_does_not_retry_on_other_errors() { | ||
| let sdk = Sdk::new_mock(); | ||
| let doc_type = mock_document_type(); | ||
| let contract = mock_data_contract(Some(&doc_type)); | ||
| // Build a query but don't set any expectations — the mock will have no matching | ||
| // response for execute, which should result in an error that is NOT a deserialization error. | ||
| let query = DocumentQuery::new(contract, doc_type.name()).expect("create document query"); | ||
| let result = Document::fetch(&sdk, query).await; | ||
| // Should fail — non-deserialization errors propagate without retry | ||
| assert!( | ||
| result.is_err(), | ||
| "expected error when no mock expectations are set" | ||
| ); |
There was a problem hiding this comment.
Rename new tests to start with should_… for guideline compliance.
✏️ Suggested renames
-async fn test_fetch_document_retries_on_stale_contract() {+async fn should_retry_document_fetch_on_stale_contract() {-async fn test_fetch_many_documents_retries_on_stale_contract() {+async fn should_retry_fetch_many_documents_on_stale_contract() {-async fn test_fetch_document_does_not_retry_on_other_errors() {+async fn should_not_retry_document_fetch_on_other_errors() {As per coding guidelines: **/tests/**/*.{js,jsx,ts,tsx,rs}: Name tests descriptively, starting with 'should …'.
🤖 Prompt for AI Agents
In `@packages/rs-sdk/tests/fetch/mock_document_contract_refresh.rs` around lines
114 - 235, Rename the three tests so their function names start with "should_"
per guidelines: change test_fetch_document_retries_on_stale_contract to
should_fetch_document_retries_on_stale_contract,
test_fetch_many_documents_retries_on_stale_contract to
should_fetch_many_documents_retries_on_stale_contract, and
test_fetch_document_does_not_retry_on_other_errors to
should_fetch_document_does_not_retry_on_other_errors; update the async fn
identifiers accordingly wherever they are referenced (e.g., in this file) so the
test runner and imports continue to work.
Uh oh!
There was an error while loading. Please reload this page.
Issue being fixed or feature implemented
When a data contract is updated on the network, the SDK may hold a cached (old) version of the contract. Documents serialized with the new schema
fail to deserialize against the old cached contract, producing a
CorruptedSerializationerror that propagates to the caller with no recovery.What was done?
ProtocolErrorindrive-proof-verifierinstead of stringifying it, enabling pattern matching on specific error variantsdownstream.
Document::fetchandDocument::fetch_many: onCorruptedSerializationerror, the SDK refetches the contract from thenetwork, updates the context provider cache, and retries with the fresh contract.
update_data_contractmethod to theContextProvidertrait so the cache can be updated after a successful refetch.clone_with_contracttoDocumentQueryto rebuild the query with a fresh contract.How Has This Been Tested?
packages/rs-sdk/tests/fetch/mock_document_contract_refresh.rs:test_fetch_document_retries_on_stale_contract— verifiesDocument::fetchretries with a fresh contracttest_fetch_many_documents_retries_on_stale_contract— verifiesDocument::fetch_manyretries with a fresh contracttest_fetch_document_does_not_retry_on_other_errors— verifies non-deserialization errors propagate without retryis_document_deserialization_error()inpackages/rs-sdk/src/platform/fetch.rscargo clippyandcargo fmtclean on all affected cratesBreaking Changes
drive_proof_verifier::Error::ProtocolErrorchanged from a struct variant{ error: String }to a tuple variant(dpp::ProtocolError). Codematching on this variant needs updating.
Checklist:
For repository code-owners and collaborators only
Summary by CodeRabbit
New Features
Bug Fixes