From 7a804b3e3bf8ebfd67153e65a01023df9a2210f1 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 8 Jan 2026 17:13:26 +0700 Subject: [PATCH 1/4] feat(sdk): retry and settings for address sync --- packages/rs-drive-proof-verifier/src/proof.rs | 26 ++++ packages/rs-drive-proof-verifier/src/types.rs | 35 ++++++ packages/rs-sdk/src/mock/requests.rs | 22 +++- .../rs-sdk/src/platform/address_sync/mod.rs | 113 +++++++++++------- .../rs-sdk/src/platform/address_sync/types.rs | 5 + packages/rs-sdk/src/platform/fetch.rs | 4 + packages/rs-sdk/src/platform/query.rs | 18 ++- 7 files changed, 174 insertions(+), 49 deletions(-) diff --git a/packages/rs-drive-proof-verifier/src/proof.rs b/packages/rs-drive-proof-verifier/src/proof.rs index 0a83e9dec86..6eaecad7c40 100644 --- a/packages/rs-drive-proof-verifier/src/proof.rs +++ b/packages/rs-drive-proof-verifier/src/proof.rs @@ -1083,6 +1083,32 @@ impl FromProof for GroveTrunkQueryResul } } +impl FromProof for PlatformAddressTrunkState { + type Request = platform::GetAddressesTrunkStateRequest; + type Response = platform::GetAddressesTrunkStateResponse; + + fn maybe_from_proof_with_metadata<'a, I: Into, O: Into>( + request: I, + response: O, + network: Network, + platform_version: &PlatformVersion, + provider: &'a dyn ContextProvider, + ) -> Result<(Option, ResponseMetadata, Proof), Error> + where + PlatformAddressTrunkState: 'a, + { + let (result, metadata, proof) = GroveTrunkQueryResult::maybe_from_proof_with_metadata( + request, + response, + network, + platform_version, + provider, + )?; + + Ok((result.map(PlatformAddressTrunkState), metadata, proof)) + } +} + impl FromProof for DataContract { type Request = platform::GetDataContractRequest; type Response = platform::GetDataContractResponse; diff --git a/packages/rs-drive-proof-verifier/src/types.rs b/packages/rs-drive-proof-verifier/src/types.rs index ed3b5c7f58b..ce8d58c2026 100644 --- a/packages/rs-drive-proof-verifier/src/types.rs +++ b/packages/rs-drive-proof-verifier/src/types.rs @@ -724,3 +724,38 @@ impl RecentCompactedAddressBalanceChanges { self.0 } } + +/// Platform address trunk state for address balance synchronization. +/// +/// This is a newtype wrapper around [`GroveTrunkQueryResult`](drive::grovedb::GroveTrunkQueryResult) +/// that represents the result of querying the trunk (top levels) of the address funds tree. +/// +/// The trunk query returns: +/// - Elements (addresses with balances) found at the queried depth +/// - Leaf boundary keys that indicate subtrees requiring further branch queries +/// +/// This type implements [`FromProof`](crate::FromProof) by delegating to the underlying +/// `GroveTrunkQueryResult` implementation. +#[derive(Debug)] +pub struct PlatformAddressTrunkState(pub drive::grovedb::GroveTrunkQueryResult); + +impl PlatformAddressTrunkState { + /// Get the inner `GroveTrunkQueryResult`. + pub fn into_inner(self) -> drive::grovedb::GroveTrunkQueryResult { + self.0 + } +} + +impl std::ops::Deref for PlatformAddressTrunkState { + type Target = drive::grovedb::GroveTrunkQueryResult; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for PlatformAddressTrunkState { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/packages/rs-sdk/src/mock/requests.rs b/packages/rs-sdk/src/mock/requests.rs index 4ff2932a579..7236315e504 100644 --- a/packages/rs-sdk/src/mock/requests.rs +++ b/packages/rs-sdk/src/mock/requests.rs @@ -35,9 +35,10 @@ use drive_proof_verifier::types::token_status::TokenStatuses; use drive::grovedb::GroveTrunkQueryResult; use drive_proof_verifier::types::{ AddressInfo, Contenders, ContestedResources, CurrentQuorumsInfo, ElementFetchRequestItem, - IdentityBalanceAndRevision, IndexMap, MasternodeProtocolVote, PrefundedSpecializedBalance, - ProposerBlockCounts, RecentAddressBalanceChanges, RecentCompactedAddressBalanceChanges, - RetrievedValues, TotalCreditsInPlatform, VotePollsGroupedByTimestamp, Voters, + IdentityBalanceAndRevision, IndexMap, MasternodeProtocolVote, PlatformAddressTrunkState, + PrefundedSpecializedBalance, ProposerBlockCounts, RecentAddressBalanceChanges, + RecentCompactedAddressBalanceChanges, RetrievedValues, TotalCreditsInPlatform, + VotePollsGroupedByTimestamp, Voters, }; use std::{collections::BTreeMap, hash::Hash}; @@ -521,3 +522,18 @@ impl MockResponse for GroveTrunkQueryResult { unimplemented!("GroveTrunkQueryResult does not support mock deserialization - the Tree type is not serializable") } } + +/// MockResponse for PlatformAddressTrunkState - panics when called because the underlying +/// Tree type doesn't support serialization. Address sync operations should not be mocked. +impl MockResponse for PlatformAddressTrunkState { + fn mock_serialize(&self, _sdk: &MockDashPlatformSdk) -> Vec { + unimplemented!("PlatformAddressTrunkState does not support mock serialization - the Tree type is not serializable") + } + + fn mock_deserialize(_sdk: &MockDashPlatformSdk, _buf: &[u8]) -> Self + where + Self: Sized, + { + unimplemented!("PlatformAddressTrunkState does not support mock deserialization - the Tree type is not serializable") + } +} diff --git a/packages/rs-sdk/src/platform/address_sync/mod.rs b/packages/rs-sdk/src/platform/address_sync/mod.rs index 93e2c94f84c..996a92b4cf0 100644 --- a/packages/rs-sdk/src/platform/address_sync/mod.rs +++ b/packages/rs-sdk/src/platform/address_sync/mod.rs @@ -47,11 +47,12 @@ pub use types::{ }; use crate::error::Error; +use crate::platform::Fetch; +use crate::sync::retry; use crate::Sdk; use dapi_grpc::platform::v0::{ get_addresses_branch_state_request, get_addresses_branch_state_response, - get_addresses_trunk_state_request, GetAddressesBranchStateRequest, - GetAddressesTrunkStateRequest, + GetAddressesBranchStateRequest, }; use dpp::version::PlatformVersion; use drive::drive::Drive; @@ -59,8 +60,11 @@ use drive::grovedb::{ calculate_max_tree_depth_from_count, Element, GroveBranchQueryResult, GroveTrunkQueryResult, LeafInfo, }; +use drive_proof_verifier::types::PlatformAddressTrunkState; use futures::stream::{FuturesUnordered, StreamExt}; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; +use rs_dapi_client::{ + DapiRequest, ExecutionError, ExecutionResponse, InnerInto, IntoInner, RequestSettings, +}; use std::collections::{BTreeSet, HashMap}; use tracing::{debug, trace, warn}; use tracker::KeyLeafTracker; @@ -114,7 +118,8 @@ pub async fn sync_address_balances( } // Step 1: Execute trunk query - let (trunk_result, checkpoint_height) = execute_trunk_query(sdk, &mut result.metrics).await?; + let (trunk_result, checkpoint_height) = + execute_trunk_query(sdk, config.request_settings, &mut result.metrics).await?; result.checkpoint_height = checkpoint_height; trace!( @@ -171,6 +176,7 @@ pub async fn sync_address_balances( checkpoint_height, &mut result.metrics, config.max_concurrent_requests, + config.request_settings, platform_version, ) .await?; @@ -217,28 +223,14 @@ pub async fn sync_address_balances( /// Execute the trunk query and return the verified result. async fn execute_trunk_query( sdk: &Sdk, + settings: RequestSettings, metrics: &mut AddressSyncMetrics, ) -> Result<(GroveTrunkQueryResult, u64), Error> { - let request = GetAddressesTrunkStateRequest { - version: Some(get_addresses_trunk_state_request::Version::V0( - get_addresses_trunk_state_request::GetAddressesTrunkStateRequestV0 {}, - )), - }; - - let response: dapi_grpc::platform::v0::GetAddressesTrunkStateResponse = request - .execute(sdk, RequestSettings::default()) - .await? - .into_inner(); + let (trunk_state, metadata) = + PlatformAddressTrunkState::fetch_with_metadata(sdk, (), Some(settings)).await?; metrics.trunk_queries += 1; - // Parse and verify the proof using the standard SDK pattern - let (trunk_state, metadata) = sdk - .parse_proof_with_metadata::( - request, response, - ) - .await?; - let trunk_state = trunk_state.ok_or_else(|| { Error::Protocol(dpp::ProtocolError::CorruptedCodeExecution( "Trunk query returned no state".to_string(), @@ -247,7 +239,7 @@ async fn execute_trunk_query( metrics.total_elements_seen += trunk_state.elements.len(); - Ok((trunk_state, metadata.height)) + Ok((trunk_state.into_inner(), metadata.height)) } /// Process the trunk query result. @@ -341,6 +333,7 @@ async fn execute_branch_queries( checkpoint_height: u64, metrics: &mut AddressSyncMetrics, max_concurrent: usize, + settings: RequestSettings, platform_version: &PlatformVersion, ) -> Result, Error> { let mut futures = FuturesUnordered::new(); @@ -358,6 +351,7 @@ async fn execute_branch_queries( depth_u32, expected_hash, checkpoint_height, + settings, platform_version, ) .await @@ -397,13 +391,17 @@ async fn execute_branch_queries( Ok(results) } -/// Execute a single branch query. +/// Execute a single branch query with retry logic. +/// +/// If proof verification fails, the request will be retried with a different node +/// according to the retry settings. async fn execute_single_branch_query( sdk: &Sdk, key: LeafBoundaryKey, depth: u32, expected_hash: [u8; 32], checkpoint_height: u64, + settings: RequestSettings, platform_version: &PlatformVersion, ) -> Result { let request = GetAddressesBranchStateRequest { @@ -416,31 +414,58 @@ async fn execute_single_branch_query( )), }; - let response: dapi_grpc::platform::v0::GetAddressesBranchStateResponse = request - .execute(sdk, RequestSettings::default()) - .await? - .into_inner(); - - // Extract merk proof - let proof_bytes = match response.version { - Some(get_addresses_branch_state_response::Version::V0(v0)) => v0.merk_proof, - None => { - return Err(Error::Protocol(dpp::ProtocolError::CorruptedCodeExecution( - "Missing version in branch response".to_string(), - ))); + let fut = |settings: RequestSettings| { + let request = request.clone(); + let key = key.clone(); + async move { + let ExecutionResponse { + address, + retries, + inner: response, + } = request + .execute(sdk, settings) + .await + .map_err(|execution_error| execution_error.inner_into())?; + + // Extract merk proof + let proof_bytes = match response.version { + Some(get_addresses_branch_state_response::Version::V0(v0)) => v0.merk_proof, + None => { + return Err(ExecutionError { + inner: Error::Protocol(dpp::ProtocolError::CorruptedCodeExecution( + "Missing version in branch response".to_string(), + )), + address: Some(address), + retries, + }); + } + }; + + // Verify the proof + let branch_result = Drive::verify_address_funds_branch_query( + &proof_bytes, + key, + depth as u8, + expected_hash, + platform_version, + ) + .map_err(|e| ExecutionError { + inner: e.into(), + address: Some(address.clone()), + retries, + })?; + + Ok(ExecutionResponse { + inner: branch_result, + address, + retries, + }) } }; - // Verify the proof - let branch_result = Drive::verify_address_funds_branch_query( - &proof_bytes, - key, - depth as u8, - expected_hash, - platform_version, - )?; + let settings = sdk.dapi_client_settings.override_by(settings); - Ok(branch_result) + retry(sdk.address_list(), settings, fut).await.into_inner() } /// Process a branch query result. diff --git a/packages/rs-sdk/src/platform/address_sync/types.rs b/packages/rs-sdk/src/platform/address_sync/types.rs index 4f5903258c4..9d8582c8ef7 100644 --- a/packages/rs-sdk/src/platform/address_sync/types.rs +++ b/packages/rs-sdk/src/platform/address_sync/types.rs @@ -1,6 +1,7 @@ //! Types for address synchronization. use dpp::fee::Credits; +use rs_dapi_client::RequestSettings; use std::collections::{BTreeMap, BTreeSet}; /// A 32-byte address key that we're searching for in the address funds tree. @@ -42,6 +43,9 @@ pub struct AddressSyncConfig { /// /// Default: 50 pub max_iterations: usize, + + /// Request settings for undergoing address sync queries. + pub request_settings: RequestSettings, } impl Default for AddressSyncConfig { @@ -50,6 +54,7 @@ impl Default for AddressSyncConfig { min_privacy_count: 32, max_concurrent_requests: 10, max_iterations: 50, + request_settings: RequestSettings::default(), } } } diff --git a/packages/rs-sdk/src/platform/fetch.rs b/packages/rs-sdk/src/platform/fetch.rs index c6d9533501c..27c6394dfe5 100644 --- a/packages/rs-sdk/src/platform/fetch.rs +++ b/packages/rs-sdk/src/platform/fetch.rs @@ -324,3 +324,7 @@ impl Fetch for drive_proof_verifier::types::RecentAddressBalanceChanges { impl Fetch for drive_proof_verifier::types::RecentCompactedAddressBalanceChanges { type Request = platform_proto::GetRecentCompactedAddressBalanceChangesRequest; } + +impl Fetch for drive_proof_verifier::types::PlatformAddressTrunkState { + type Request = platform_proto::GetAddressesTrunkStateRequest; +} diff --git a/packages/rs-sdk/src/platform/query.rs b/packages/rs-sdk/src/platform/query.rs index 06065618361..eb9bdaa4427 100644 --- a/packages/rs-sdk/src/platform/query.rs +++ b/packages/rs-sdk/src/platform/query.rs @@ -17,10 +17,10 @@ use dapi_grpc::platform::v0::get_status_request::GetStatusRequestV0; use dapi_grpc::platform::v0::get_total_credits_in_platform_request::GetTotalCreditsInPlatformRequestV0; use dapi_grpc::platform::v0::{ self as proto, get_address_info_request, get_addresses_infos_request, - get_current_quorums_info_request, get_identity_keys_request, + get_addresses_trunk_state_request, get_current_quorums_info_request, get_identity_keys_request, get_identity_keys_request::GetIdentityKeysRequestV0, get_path_elements_request, get_total_credits_in_platform_request, AllKeys, GetAddressInfoRequest, - GetAddressesInfosRequest, GetContestedResourceVoteStateRequest, + GetAddressesInfosRequest, GetAddressesTrunkStateRequest, GetContestedResourceVoteStateRequest, GetContestedResourceVotersForIdentityRequest, GetContestedResourcesRequest, GetCurrentQuorumsInfoRequest, GetEpochsInfoRequest, GetEvonodesProposedEpochBlocksByIdsRequest, GetEvonodesProposedEpochBlocksByRangeRequest, GetIdentityKeysRequest, GetPathElementsRequest, @@ -300,6 +300,20 @@ impl Query for BTreeSet { } } +impl Query for () { + fn query(self, prove: bool) -> Result { + if !prove { + unimplemented!("queries without proofs are not supported yet"); + } + + Ok(GetAddressesTrunkStateRequest { + version: Some(get_addresses_trunk_state_request::Version::V0( + get_addresses_trunk_state_request::GetAddressesTrunkStateRequestV0 {}, + )), + }) + } +} + impl Query for DriveDocumentQuery<'_> { fn query(self, prove: bool) -> Result { if !prove { From 0d3c6bde439c95782ec614fea4ecb362a452f816 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 8 Jan 2026 17:53:46 +0700 Subject: [PATCH 2/4] refactor: use appropriate errors --- packages/rs-sdk/src/platform/address_sync/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/rs-sdk/src/platform/address_sync/mod.rs b/packages/rs-sdk/src/platform/address_sync/mod.rs index 996a92b4cf0..2fc78499ba4 100644 --- a/packages/rs-sdk/src/platform/address_sync/mod.rs +++ b/packages/rs-sdk/src/platform/address_sync/mod.rs @@ -232,9 +232,7 @@ async fn execute_trunk_query( metrics.trunk_queries += 1; let trunk_state = trunk_state.ok_or_else(|| { - Error::Protocol(dpp::ProtocolError::CorruptedCodeExecution( - "Trunk query returned no state".to_string(), - )) + Error::InvalidProvedResponse("Trunk query returned no state".to_string()) })?; metrics.total_elements_seen += trunk_state.elements.len(); @@ -432,9 +430,7 @@ async fn execute_single_branch_query( Some(get_addresses_branch_state_response::Version::V0(v0)) => v0.merk_proof, None => { return Err(ExecutionError { - inner: Error::Protocol(dpp::ProtocolError::CorruptedCodeExecution( - "Missing version in branch response".to_string(), - )), + inner: Error::Proof(drive_proof_verifier::Error::EmptyVersion), address: Some(address), retries, }); From d8e7f2c580aff44832dee0053bbea77123e238f7 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 8 Jan 2026 18:00:29 +0700 Subject: [PATCH 3/4] fix: missning request settings --- packages/rs-sdk-ffi/src/address_sync/mod.rs | 3 +++ packages/rs-sdk/src/platform/address_sync/mod.rs | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/rs-sdk-ffi/src/address_sync/mod.rs b/packages/rs-sdk-ffi/src/address_sync/mod.rs index 209f9e6b040..9d5fe55c5ec 100644 --- a/packages/rs-sdk-ffi/src/address_sync/mod.rs +++ b/packages/rs-sdk-ffi/src/address_sync/mod.rs @@ -14,6 +14,7 @@ use crate::sdk::SDKWrapper; use crate::types::SDKHandle; use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; use dash_sdk::platform::address_sync::{AddressSyncConfig, AddressSyncResult}; +use rs_dapi_client::RequestSettings; use tracing::{debug, error, info}; /// Synchronize address balances using trunk/branch chunk queries. @@ -56,6 +57,7 @@ pub unsafe extern "C" fn dash_sdk_sync_address_balances( min_privacy_count: (*config).min_privacy_count, max_concurrent_requests: (*config).max_concurrent_requests as usize, max_iterations: (*config).max_iterations as usize, + request_settings: RequestSettings::default(), }) }; @@ -134,6 +136,7 @@ pub unsafe extern "C" fn dash_sdk_sync_address_balances_with_result( min_privacy_count: (*config).min_privacy_count, max_concurrent_requests: (*config).max_concurrent_requests as usize, max_iterations: (*config).max_iterations as usize, + request_settings: RequestSettings::default(), }) }; diff --git a/packages/rs-sdk/src/platform/address_sync/mod.rs b/packages/rs-sdk/src/platform/address_sync/mod.rs index 2fc78499ba4..4df63c61915 100644 --- a/packages/rs-sdk/src/platform/address_sync/mod.rs +++ b/packages/rs-sdk/src/platform/address_sync/mod.rs @@ -231,9 +231,8 @@ async fn execute_trunk_query( metrics.trunk_queries += 1; - let trunk_state = trunk_state.ok_or_else(|| { - Error::InvalidProvedResponse("Trunk query returned no state".to_string()) - })?; + let trunk_state = trunk_state + .ok_or_else(|| Error::InvalidProvedResponse("Trunk query returned no state".to_string()))?; metrics.total_elements_seen += trunk_state.elements.len(); From cd743d40f0fa158acd4f0df9ae9cd97141f160a5 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 8 Jan 2026 18:09:34 +0700 Subject: [PATCH 4/4] fix: invalid import --- packages/rs-sdk-ffi/src/address_sync/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-sdk-ffi/src/address_sync/mod.rs b/packages/rs-sdk-ffi/src/address_sync/mod.rs index 9d5fe55c5ec..95df2fa7d49 100644 --- a/packages/rs-sdk-ffi/src/address_sync/mod.rs +++ b/packages/rs-sdk-ffi/src/address_sync/mod.rs @@ -14,7 +14,7 @@ use crate::sdk::SDKWrapper; use crate::types::SDKHandle; use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; use dash_sdk::platform::address_sync::{AddressSyncConfig, AddressSyncResult}; -use rs_dapi_client::RequestSettings; +use dash_sdk::RequestSettings; use tracing::{debug, error, info}; /// Synchronize address balances using trunk/branch chunk queries.