diff --git a/packages/rs-sdk-ffi/src/types.rs b/packages/rs-sdk-ffi/src/types.rs index efdaa4e45be..268fb40a32a 100644 --- a/packages/rs-sdk-ffi/src/types.rs +++ b/packages/rs-sdk-ffi/src/types.rs @@ -94,8 +94,10 @@ pub struct DashSDKConfig { /// immediately, caller may free after the FFI call returns. pub quorum_url: *const c_char, /// Pin to a specific Dash Platform protocol version. - /// `0` keeps the SDK default (auto-detect / latest); any non-zero value - /// is forwarded to `SdkBuilder::with_version` and rejected if unknown. + /// `0` keeps the SDK default — auto-detect seeded at the default initial + /// protocol-version floor, ratcheting up to the network's version; any + /// non-zero value is forwarded to `SdkBuilder::with_version` and rejected + /// if unknown. pub platform_version: u32, } diff --git a/packages/rs-sdk/src/mock/sdk.rs b/packages/rs-sdk/src/mock/sdk.rs index b9147e3bb48..397a30c417b 100644 --- a/packages/rs-sdk/src/mock/sdk.rs +++ b/packages/rs-sdk/src/mock/sdk.rs @@ -520,9 +520,15 @@ impl MockDashPlatformSdk { let key = Key::new(&request); let data = match self.from_proof_expectations.get(&key) { + // Report the latest protocol version so the proof path's ratchet + // (`maybe_update_protocol_version`) fires as it would against a real + // network; `default()` reports 0, which the ratchet ignores. Some(d) => ( Option::::mock_deserialize(self, d), - ResponseMetadata::default(), + ResponseMetadata { + protocol_version: dpp::version::LATEST_VERSION, + ..Default::default() + }, Proof::default(), ), None => { diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 2100e7ea75c..05fdfe35d8a 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -50,6 +50,38 @@ pub const DEFAULT_CONTRACT_CACHE_SIZE: usize = 100; pub const DEFAULT_TOKEN_CONFIG_CACHE_SIZE: usize = 100; /// How many quorum public keys fit in the cache. pub const DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE: usize = 100; +/// Initial protocol version for the default auto-detect mode — i.e. when the +/// caller does not pin a [`PlatformVersion`] via [`SdkBuilder::with_version`]. +/// +/// Set BELOW the latest version on purpose: ratchet-up autodetection +/// (`maybe_update_protocol_version`) converges to the network's real version, +/// so starting low keeps requests compatible with not-yet-upgraded nodes during +/// an upgrade window. Bump this constant as the network's supported floor advances. +/// +/// # v3.1+-only query surfaces +/// +/// At the default floor the local encoder rejects the +/// v3.1+-only surfaces — `Count` (`SelectProjection::count_star`), `group_by`, +/// and `having` — with [`Error::Config`] *before* any network round-trip. To use +/// them either pin a higher version via [`SdkBuilder::with_version`] (which also +/// disables auto-detect), or issue one floor-compatible ratcheting query (no v3.1+ +/// surfaces) right after `build()` — e.g. the `ExtendedEpochInfo::fetch_current` +/// current-state fetch below. +/// Its response metadata lifts the SDK to the network's version, after which `Count` / +/// `group_by` / `having` encode correctly. +/// +/// ```no_run +/// # use dash_sdk::{Sdk, SdkBuilder}; +/// # use dash_sdk::platform::fetch_current_no_parameters::FetchCurrent; +/// # use dpp::block::extended_epoch_info::ExtendedEpochInfo; +/// # async fn warm_up() -> Result<(), dash_sdk::Error> { +/// let sdk: Sdk = SdkBuilder::new_mock().build()?; +/// // Ratchets the SDK up to the network's version; Count/group_by/having then encode. +/// let _ = ExtendedEpochInfo::fetch_current(&sdk).await?; +/// # Ok(()) +/// # } +/// ``` +pub const DEFAULT_INITIAL_PROTOCOL_VERSION: u32 = dpp::version::v10::PROTOCOL_VERSION_10; /// The default metadata time tolerance for checkpoint queries in milliseconds const ADDRESS_STATE_TIME_TOLERANCE_MS: u64 = 31 * 60 * 1000; @@ -335,7 +367,6 @@ impl Sdk { } } - // TODO: Changed to public for tests /// Retrieve object `O` from proof contained in `request` (of type `R`) and `response`. /// /// This method is used to retrieve objects from proofs returned by Dash Platform. @@ -348,8 +379,8 @@ impl Sdk { /// ## Protocol version bootstrapping /// /// On a fresh auto-detect SDK (i.e. one built without [`SdkBuilder::with_version()`]), the - /// first call to this method uses [`PlatformVersion::latest()`] as a fallback because no - /// network response has been received yet to teach the SDK the real network version. + /// first call to this method uses [`DEFAULT_INITIAL_PROTOCOL_VERSION`] as a fallback because + /// no network response has been received yet to teach the SDK the real network version. /// /// The actual network version is learned only *after* proof parsing succeeds, when /// [`Self::verify_response_metadata()`] processes `metadata.protocol_version`. If the @@ -388,6 +419,10 @@ impl Sdk { } }?; + // Security invariant: proof+signature verification above (the `?`) must + // precede this call, which ratchets the protocol version from the now-trusted + // `metadata.protocol_version`. Never reorder — the ratchet must not consume + // unverified metadata. self.verify_response_metadata(method_name, &metadata) .inspect_err(|err| { tracing::warn!(%err,method=method_name,"received response with stale metadata; try another server"); @@ -480,7 +515,7 @@ impl Sdk { /// Return [Dash Platform version](PlatformVersion) information used by this SDK. /// - /// When auto-detection is enabled (default), returns [`PlatformVersion::latest()`] + /// When auto-detection is enabled (default), returns [`DEFAULT_INITIAL_PROTOCOL_VERSION`] /// until the first network response is received, then tracks the network's version. /// When pinned via [`SdkBuilder::with_version()`], always returns the pinned version. pub fn version<'v>(&self) -> &'v PlatformVersion { @@ -756,7 +791,8 @@ impl Default for SdkBuilder { cancel_token: CancellationToken::new(), - version: PlatformVersion::latest(), + version: PlatformVersion::get(DEFAULT_INITIAL_PROTOCOL_VERSION) + .expect("DEFAULT_INITIAL_PROTOCOL_VERSION must be a known PlatformVersion"), version_explicit: false, #[cfg(not(target_arch = "wasm32"))] ca_certificate: None, @@ -876,41 +912,31 @@ impl SdkBuilder { /// Configure platform version. /// - /// Select specific version of Dash Platform to use. + /// Select specific version of Dash Platform to use. This pins the version and + /// disables auto-detection. /// - /// Defaults to [PlatformVersion::latest()]. + /// When unset, the SDK starts at [`DEFAULT_INITIAL_PROTOCOL_VERSION`] and + /// ratchets upward via auto-detection. pub fn with_version(mut self, version: &'static PlatformVersion) -> Self { self.version = version; self.version_explicit = true; self } - /// Set the *initial* protocol version seed for auto-detect mode. - /// - /// Unlike [`Self::with_version`], this leaves auto-detect active — - /// the SDK starts at `version.protocol_version` and ratchets upward - /// (via `fetch_max` in `maybe_update_protocol_version`) once the - /// network's actual version is observed in response metadata. - /// - /// Use this when an SDK built against `PlatformVersion::latest()` - /// must talk to a network running an older protocol version (e.g. - /// a v3.0 testnet from a v3.1+ binary). Without an explicit initial - /// version, the SDK's `version()` fallback returns `latest()` until - /// the first response is parsed, and the upward-only `fetch_max` - /// guard can never ratchet *down* to the older network — leaving - /// any version-dispatched encoders (e.g. the documents query) to - /// ship a too-new wire shape that the network rejects. - /// - /// Seeds `self.version` and resets `version_explicit` to `false`, so - /// auto-detect is (re-)enabled. Builder chains use last-write-wins: - /// calling `with_initial_version` after `with_version` restores - /// auto-detect rather than silently keeping it disabled. - /// - /// **Caveat**: this protection only holds for encoders whose - /// `drive_abci.query..default_current_version` is correctly pinned per - /// historical PV. New versioned encoders must follow the same per-PV pinning - /// pattern as `document_query`. - pub fn with_initial_version(mut self, version: &'static PlatformVersion) -> Self { + /// Test-only seed for the auto-detect atomic — NOT the public way to enable + /// auto-detect (auto-detect is the default; [`Self::with_version`] is the opt-out). + /// + /// Auto-detect already starts every unpinned SDK at + /// [`DEFAULT_INITIAL_PROTOCOL_VERSION`] and ratchets upward via `fetch_max` in + /// `maybe_update_protocol_version` once the network's version is observed. This + /// seed exists only to let unit tests start *below* that floor — exercising the + /// upward-only ratchet from an older network's version without disabling auto-detect. + /// + /// Seeds `self.version` and keeps `version_explicit` `false`, so auto-detect stays + /// on. Builder chains are last-write-wins: a later `with_initial_version` re-enables + /// auto-detect that an earlier `with_version` disabled. + #[cfg(test)] + pub(crate) fn with_initial_version(mut self, version: &'static PlatformVersion) -> Self { self.version = version; self.version_explicit = false; self @@ -1660,6 +1686,126 @@ mod test { ); } + #[test] + fn test_default_builder_seeds_initial_protocol_version_floor() { + // A default builder must seed the SDK at the floor, not latest(). + let sdk = SdkBuilder::new_mock() + .build() + .expect("mock Sdk should be created"); + + assert_eq!( + sdk.protocol_version_number(), + super::DEFAULT_INITIAL_PROTOCOL_VERSION, + "unpinned SDK must boot at the upgrade-safe floor, not latest()" + ); + assert_eq!( + sdk.version().protocol_version, + super::DEFAULT_INITIAL_PROTOCOL_VERSION + ); + assert!( + sdk.auto_detect_protocol_version, + "default SDK must keep auto-detect enabled" + ); + } + + #[test] + fn test_default_floor_ratchets_up_but_never_down() { + let sdk = SdkBuilder::new_mock() + .build() + .expect("mock Sdk should be created"); + let floor = super::DEFAULT_INITIAL_PROTOCOL_VERSION; + assert_eq!(sdk.protocol_version_number(), floor); + + // Ratchet to a fixed known target (PV12), not `floor + N`: stays valid as the + // floor advances, and `maybe_update_protocol_version` only accepts known versions. + let target = dpp::version::v12::PROTOCOL_VERSION_12; + assert!( + target > floor, + "ratchet test target must exceed the floor; bump it if the floor reaches v12" + ); + sdk.maybe_update_protocol_version(target); + assert_eq!( + sdk.protocol_version_number(), + target, + "auto-detect must ratchet upward from the floor" + ); + + // Never down: an older network version is ignored. + sdk.maybe_update_protocol_version(floor - 1); + assert_eq!( + sdk.protocol_version_number(), + target, + "ratchet must never downgrade below the highest observed version" + ); + } + + /// Regression guard for the verify-before-ratchet security invariant. + /// + /// The full tampered-*signed*-proof path isn't unit-testable here: it needs a + /// quorum BLS signature, a context provider, and a `FromProof` verifier round-trip. + /// That path's safety rests on `parse_proof_with_metadata_and_proof` running proof + /// verification (the `?`) BEFORE `verify_response_metadata` → `maybe_update_protocol_version` + /// (see the guard comment at that call site). Here we lock in the ratchet's own gates: + /// it must NOT raise the stored version off untrustworthy inputs (unknown / zero / lower), + /// so even a metadata value that slipped past verification can't move the SDK to a bogus + /// protocol version. + #[test] + fn test_ratchet_rejects_unknown_and_non_upward_versions() { + let sdk = SdkBuilder::new_mock() + .build() + .expect("mock Sdk should be created"); + let floor = super::DEFAULT_INITIAL_PROTOCOL_VERSION; + assert_eq!(sdk.protocol_version_number(), floor); + + // Unknown (above LATEST_VERSION): rejected, version unchanged. + sdk.maybe_update_protocol_version(dpp::version::LATEST_VERSION + 1); + assert_eq!( + sdk.protocol_version_number(), + floor, + "unknown protocol version must not move the stored version" + ); + + // Zero (e.g. metadata default / stripped field): ignored. + sdk.maybe_update_protocol_version(0); + assert_eq!( + sdk.protocol_version_number(), + floor, + "zero protocol version must be ignored" + ); + + // Equal: no-op (no spurious downgrade or churn). + sdk.maybe_update_protocol_version(floor); + assert_eq!(sdk.protocol_version_number(), floor); + + // Lower known version: ignored by the upward-only guard. + sdk.maybe_update_protocol_version(floor - 1); + assert_eq!( + sdk.protocol_version_number(), + floor, + "lower known version must not downgrade the stored version" + ); + } + + #[test] + fn test_explicit_pin_overrides_default_floor() { + use dpp::version::PlatformVersion; + + // Pin off the floor so the override is observable wherever the floor sits. + let pinned_number = super::DEFAULT_INITIAL_PROTOCOL_VERSION - 1; + let pinned = PlatformVersion::get(pinned_number).expect("pinned PV exists"); + let sdk = SdkBuilder::new_mock() + .with_version(pinned) + .build() + .expect("mock Sdk should be created"); + + assert_eq!( + sdk.protocol_version_number(), + pinned_number, + "explicit with_version must win over the default floor" + ); + assert!(!sdk.auto_detect_protocol_version); + } + #[test_matrix([90,91,100,109,110], 100, 10, false; "valid time")] #[test_matrix([0,89,111], 100, 10, true; "invalid time")] #[test_matrix([0,100], [0,100], 100, false; "zero time")] diff --git a/packages/rs-sdk/tests/fetch/common.rs b/packages/rs-sdk/tests/fetch/common.rs index 51e4fb4b3d0..5b2d4a49c17 100644 --- a/packages/rs-sdk/tests/fetch/common.rs +++ b/packages/rs-sdk/tests/fetch/common.rs @@ -1,8 +1,13 @@ +use dash_sdk::platform::fetch_current_no_parameters::FetchCurrent; +use dash_sdk::platform::types::epoch::EpochQuery; +use dash_sdk::platform::LimitQuery; use dash_sdk::{ mock::Mockable, platform::{Query, QuerySettings}, Sdk, }; +use dpp::block::extended_epoch_info::v0::{ExtendedEpochInfoV0, ExtendedEpochInfoV0Getters}; +use dpp::block::extended_epoch_info::ExtendedEpochInfo; use dpp::data_contract::config::DataContractConfig; use dpp::{data_contract::DataContractFactory, prelude::Identifier}; use hex::ToHex; @@ -98,6 +103,52 @@ pub fn mock_data_contract( .data_contract_owned() } +/// Ratchet a fresh auto-detect mock SDK from the protocol-version floor up to the +/// network's latest version, exactly as production does on its first proven response. +/// +/// An unpinned SDK boots at `DEFAULT_INITIAL_PROTOCOL_VERSION` (the upgrade-safe floor) +/// and only learns the real network version after a *proven* fetch, when response +/// metadata drives `maybe_update_protocol_version`. Mock tests that need the latest +/// wire (e.g. Count / `group_by`, or V2 document types) must therefore perform one +/// proven fetch before encoding their real request. This registers a cheap proven +/// `ExtendedEpochInfo::fetch_current` expectation and consumes it, leaving the SDK +/// ratcheted to `LATEST_VERSION`. +pub(crate) async fn bootstrap_mock_sdk_to_latest(sdk: &mut Sdk) { + let query = LimitQuery { + query: EpochQuery { + start: None, + ascending: false, + }, + limit: Some(1), + start_info: None, + }; + + let epoch = ExtendedEpochInfo::from(ExtendedEpochInfoV0 { + index: 0, + first_block_time: 0, + first_block_height: 0, + first_core_block_height: 0, + fee_multiplier_permille: 0, + protocol_version: dpp::version::LATEST_VERSION, + }); + + sdk.mock() + .expect_fetch::(query, Some(epoch.clone())) + .await + .expect("register epoch bootstrap expectation"); + + let fetched = ExtendedEpochInfo::fetch_current(sdk) + .await + .expect("bootstrap fetch_current should ratchet the SDK to latest"); + + assert_eq!(fetched.index(), epoch.index()); + assert_eq!( + sdk.version().protocol_version, + dpp::version::LATEST_VERSION, + "bootstrap must ratchet the auto-detect SDK to the network's latest protocol version" + ); +} + /// Enable logging for tests pub fn setup_logs() { let make_writer = if should_emit_test_logs_to_stdout() { diff --git a/packages/rs-sdk/tests/fetch/document_count.rs b/packages/rs-sdk/tests/fetch/document_count.rs index 68d3c238d56..02012630300 100644 --- a/packages/rs-sdk/tests/fetch/document_count.rs +++ b/packages/rs-sdk/tests/fetch/document_count.rs @@ -28,13 +28,18 @@ //! `DocumentSplitCounts`), each `expect_fetch` call carries an //! explicit turbofish so the mock recorder knows which response //! type to register. +//! +//! Count / `group_by` need the latest (v3.1+) wire, so tests build via +//! [`count_capable_mock_sdk`], which boots a plain auto-detect mock SDK and then +//! ratchets it up via a cheap proven `fetch_current` — exactly as production +//! converges to the network's real protocol version on its first proven response. use std::sync::Arc; -use super::common::{mock_data_contract, mock_document_type}; +use super::common::{bootstrap_mock_sdk_to_latest, mock_data_contract, mock_document_type}; use dash_sdk::{ platform::{documents::document_query::DocumentQuery, Fetch}, - Sdk, + Sdk, SdkBuilder, }; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::platform_value::Value; @@ -43,9 +48,21 @@ use drive::query::ordering::OrderClause; use drive::query::SelectProjection; use drive_proof_verifier::{DocumentCount, DocumentSplitCounts, SplitCountEntry}; +/// Auto-detect mock SDK ratcheted to the latest protocol version (the first release +/// wiring `DRIVE_ABCI_QUERY_VERSIONS_V1`, so Count / `group_by` encode). The mock +/// short-circuits the wire verifier, so the proven `fetch_current` bootstrap is what +/// teaches the SDK the network version — no fixed pin needed. +async fn count_capable_mock_sdk() -> Sdk { + let mut sdk = SdkBuilder::new_mock() + .build() + .expect("mock Sdk should be created"); + bootstrap_mock_sdk_to_latest(&mut sdk).await; + sdk +} + #[tokio::test] async fn test_mock_fetch_document_count_returns_expected() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); @@ -71,7 +88,7 @@ async fn test_mock_fetch_document_count_returns_expected() { #[tokio::test] async fn test_mock_fetch_document_count_zero() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); @@ -96,7 +113,7 @@ async fn test_mock_fetch_document_count_zero() { #[tokio::test] async fn test_mock_fetch_document_count_not_found() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); @@ -126,7 +143,7 @@ async fn test_mock_fetch_document_count_not_found() { /// per-value shape. #[tokio::test] async fn test_mock_fetch_document_split_counts_with_in_clause() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); @@ -178,7 +195,7 @@ async fn test_mock_fetch_document_split_counts_with_in_clause() { /// requests to the server's `RangeDistinctProof` dispatch. #[tokio::test] async fn test_mock_fetch_document_split_counts_with_distinct_range() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); @@ -236,7 +253,7 @@ async fn test_mock_fetch_document_split_counts_with_distinct_range() { /// the dispatch even when the caller asks for a single `u64`. #[tokio::test] async fn test_mock_fetch_document_count_with_distinct_range_sums_entries() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); @@ -278,7 +295,7 @@ async fn test_mock_fetch_document_count_with_distinct_range_sums_entries() { /// entries silently would fail here. #[tokio::test] async fn test_mock_fetch_document_split_counts_preserves_none_for_absent_in_values() { - let mut sdk = Sdk::new_mock(); + let mut sdk = count_capable_mock_sdk().await; let document_type = mock_document_type(); let data_contract = mock_data_contract(Some(&document_type)); diff --git a/packages/rs-sdk/tests/fetch/document_query_v0_v1.rs b/packages/rs-sdk/tests/fetch/document_query_v0_v1.rs index 28081a70a03..eb139437649 100644 --- a/packages/rs-sdk/tests/fetch/document_query_v0_v1.rs +++ b/packages/rs-sdk/tests/fetch/document_query_v0_v1.rs @@ -17,17 +17,17 @@ //! - Dispatch by SDK version: a `DocumentQuery` whose //! `protocol_version_override` field points at a V0 PlatformVersion //! round-trips through `TryFrom` as V0; default falls back to V1. -//! - `SdkBuilder::with_initial_version` semantics: builder seeds the -//! per-instance protocol_version atomic to the requested value -//! without flipping `version_explicit`, so auto-detect remains -//! active and `maybe_update_protocol_version` can still ratchet -//! upward via `fetch_max`. +//! +//! Builder seeding semantics (auto-detect default vs. the internal +//! `with_initial_version` seed) are covered by the in-crate unit tests in +//! `dash_sdk::sdk`. use std::sync::Arc; use super::common::{mock_data_contract, mock_document_type}; use dapi_grpc::platform::v0::get_documents_request::Version as ReqVersion; use dapi_grpc::platform::v0::GetDocumentsRequest; +use dash_sdk::sdk::DEFAULT_INITIAL_PROTOCOL_VERSION; use dash_sdk::{platform::documents::document_query::DocumentQuery, Error as SdkError, SdkBuilder}; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::platform_value::Value; @@ -219,35 +219,14 @@ fn encoder_dispatches_v0_via_query_settings_without_sdk() { } #[test] -fn sdk_builder_with_initial_version_seeds_atomic_without_pinning() { - // Auto-detect default: the atomic seeds to `self.version` (which - // defaults to `latest()`). `version()` therefore returns `latest()` - // until the first response ratchets the atomic upward. +fn sdk_builder_default_seeds_atomic_to_floor() { + // Auto-detect default: the atomic seeds to the floor + // `DEFAULT_INITIAL_PROTOCOL_VERSION`, which `version()` returns until the + // first response ratchets it upward. let sdk_default = SdkBuilder::new_mock().build().expect("mock sdk"); assert_eq!( sdk_default.version().protocol_version, - PlatformVersion::latest().protocol_version - ); - - // `with_initial_version` seeds the atomic to the requested PV's - // protocol_version. Auto-detect REMAINS on (this is the contract - // distinguishing it from `with_version`): a future - // `maybe_update_protocol_version(higher)` call would ratchet - // upward via `fetch_max`. - let pv_first = PlatformVersion::get(1).expect("v1 is always known"); - let sdk_initial = SdkBuilder::new_mock() - .with_initial_version(pv_first) - .build() - .expect("mock sdk with initial version"); - assert_eq!( - sdk_initial.protocol_version_number(), - pv_first.protocol_version - ); - // `version()` reflects the seeded value — no auto-detect bump - // has occurred yet (no network responses parsed). - assert_eq!( - sdk_initial.version().protocol_version, - pv_first.protocol_version + DEFAULT_INITIAL_PROTOCOL_VERSION ); } @@ -277,10 +256,10 @@ fn protocol_version_for_v3_1_dev_keeps_document_query_v1() { assert_eq!(pv.drive_abci.query.document_query.max_version, 1); } -/// Wallet-team end-to-end shape: an SDK built with -/// `with_initial_version(PROTOCOL_VERSION_11)` (Dash Platform v3.0) must -/// dispatch to the V0 encoder — proving the full plumbing works -/// without monkey-patching `PlatformVersion::latest()` clones. +/// Wallet-team end-to-end shape: a query whose `QuerySettings.protocol_version` +/// is `PROTOCOL_VERSION_11` (Dash Platform v3.0) must dispatch to the V0 encoder — +/// proving the full plumbing works without monkey-patching +/// `PlatformVersion::latest()` clones. #[test] fn document_query_dispatches_v0_when_sdk_initial_version_is_v3_0_pv() { use dash_sdk::platform::{Query, QuerySettings}; diff --git a/packages/rs-sdk/tests/fetch/mock_fetch.rs b/packages/rs-sdk/tests/fetch/mock_fetch.rs index 1b96614ecd4..a8c98b4d575 100644 --- a/packages/rs-sdk/tests/fetch/mock_fetch.rs +++ b/packages/rs-sdk/tests/fetch/mock_fetch.rs @@ -1,9 +1,9 @@ //! Tests of mocked Fetch trait implementations. -use super::common::{mock_data_contract, mock_document_type}; +use super::common::{bootstrap_mock_sdk_to_latest, mock_data_contract, mock_document_type}; use dash_sdk::{ platform::{DocumentQuery, Fetch}, - Sdk, + Sdk, SdkBuilder, }; use dpp::{ data_contract::{ @@ -90,7 +90,15 @@ async fn test_mock_fetch_identity_not_found() { /// Given some data contract, when I fetch it by ID, I get it. #[tokio::test] async fn test_mock_fetch_data_contract() { - let mut sdk = Sdk::new_mock(); + // `mock_data_contract` builds V2 document types that only decode at the latest + // protocol version; the unpinned default floor would downgrade the type and + // mismatch. A proven `fetch_current` bootstrap ratchets the auto-detect SDK up + // to the network's latest version before the data contract round-trips, exactly + // as production converges. + let mut sdk = SdkBuilder::new_mock() + .build() + .expect("mock Sdk should be created"); + bootstrap_mock_sdk_to_latest(&mut sdk).await; let document_type: DocumentType = mock_document_type(); let expected = mock_data_contract(Some(&document_type));