Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/rs-drive-proof-verifier/src/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,32 @@ impl FromProof<platform::GetAddressesTrunkStateRequest> for GroveTrunkQueryResul
}
}

impl FromProof<platform::GetAddressesTrunkStateRequest> for PlatformAddressTrunkState {
type Request = platform::GetAddressesTrunkStateRequest;
type Response = platform::GetAddressesTrunkStateResponse;

fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
request: I,
response: O,
network: Network,
platform_version: &PlatformVersion,
provider: &'a dyn ContextProvider,
) -> Result<(Option<Self>, 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<platform::GetDataContractRequest> for DataContract {
type Request = platform::GetDataContractRequest;
type Response = platform::GetDataContractResponse;
Expand Down
35 changes: 35 additions & 0 deletions packages/rs-drive-proof-verifier/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
3 changes: 3 additions & 0 deletions packages/rs-sdk-ffi/src/address_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 dash_sdk::RequestSettings;
use tracing::{debug, error, info};

/// Synchronize address balances using trunk/branch chunk queries.
Expand Down Expand Up @@ -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(),
})
};

Expand Down Expand Up @@ -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(),
})
};

Expand Down
22 changes: 19 additions & 3 deletions packages/rs-sdk/src/mock/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<u8> {
unimplemented!("PlatformAddressTrunkState does not support mock serialization - the Tree type is not serializable")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like feature request to grovedb?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

fn mock_deserialize(_sdk: &MockDashPlatformSdk, _buf: &[u8]) -> Self
where
Self: Sized,
{
unimplemented!("PlatformAddressTrunkState does not support mock deserialization - the Tree type is not serializable")
}
}
118 changes: 69 additions & 49 deletions packages/rs-sdk/src/platform/address_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,24 @@ 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;
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;
Expand Down Expand Up @@ -114,7 +118,8 @@ pub async fn sync_address_balances<P: AddressProvider>(
}

// 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!(
Expand Down Expand Up @@ -171,6 +176,7 @@ pub async fn sync_address_balances<P: AddressProvider>(
checkpoint_height,
&mut result.metrics,
config.max_concurrent_requests,
config.request_settings,
platform_version,
)
.await?;
Expand Down Expand Up @@ -217,37 +223,20 @@ pub async fn sync_address_balances<P: AddressProvider>(
/// 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::<GetAddressesTrunkStateRequest, GroveTrunkQueryResult>(
request, response,
)
.await?;

let trunk_state = trunk_state.ok_or_else(|| {
Error::Protocol(dpp::ProtocolError::CorruptedCodeExecution(
"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();

Ok((trunk_state, metadata.height))
Ok((trunk_state.into_inner(), metadata.height))
}

/// Process the trunk query result.
Expand Down Expand Up @@ -341,6 +330,7 @@ async fn execute_branch_queries(
checkpoint_height: u64,
metrics: &mut AddressSyncMetrics,
max_concurrent: usize,
settings: RequestSettings,
platform_version: &PlatformVersion,
) -> Result<Vec<(LeafBoundaryKey, GroveBranchQueryResult)>, Error> {
let mut futures = FuturesUnordered::new();
Expand All @@ -358,6 +348,7 @@ async fn execute_branch_queries(
depth_u32,
expected_hash,
checkpoint_height,
settings,
platform_version,
)
.await
Expand Down Expand Up @@ -397,13 +388,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<GroveBranchQueryResult, Error> {
let request = GetAddressesBranchStateRequest {
Expand All @@ -416,31 +411,56 @@ 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::Proof(drive_proof_verifier::Error::EmptyVersion),
address: Some(address),
retries,
});
}
};

// Verify the proof
let branch_result = Drive::verify_address_funds_branch_query(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use FromProof trait?

This skips metadata + signature verification, etc.

We should use FromProof and interpret returned errors as needed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to verify tenderdash proof and metadata here because it's already validted and we are passing here root has to check with merk proof

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer use of FromProof if it's easy to ensure it will not change in future in a direction that introduces security issue. However, if you are sure we don't want it here, please add a comment explaining that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, that's wha @QuantumExplorer told me: the idea is that they must be on the same app hash and we verify it here. @QuantumExplorer please confirm. But you still can get other invalid parts of the proof and metadata and we just ignore this. I don't like this neigher.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want it here. This query does not need to prove a tenderdash proof, because well... we already did for that root hash.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment to the fn that root hash must have been already verified, so that AI will see it and hopefully respect when it changes that part of code in 2 years from now ;-)

&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.
Expand Down
5 changes: 5 additions & 0 deletions packages/rs-sdk/src/platform/address_sync/types.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -42,6 +43,9 @@ pub struct AddressSyncConfig {
///
/// Default: 50
pub max_iterations: usize,

/// Request settings for undergoing address sync queries.
pub request_settings: RequestSettings,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

impl Default for AddressSyncConfig {
Expand All @@ -50,6 +54,7 @@ impl Default for AddressSyncConfig {
min_privacy_count: 32,
max_concurrent_requests: 10,
max_iterations: 50,
request_settings: RequestSettings::default(),
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/rs-sdk/src/platform/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
18 changes: 16 additions & 2 deletions packages/rs-sdk/src/platform/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -300,6 +300,20 @@ impl Query<GetAddressesInfosRequest> for BTreeSet<PlatformAddress> {
}
}

impl Query<GetAddressesTrunkStateRequest> for () {
fn query(self, prove: bool) -> Result<GetAddressesTrunkStateRequest, Error> {
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<DocumentQuery> for DriveDocumentQuery<'_> {
fn query(self, prove: bool) -> Result<DocumentQuery, Error> {
if !prove {
Expand Down
Loading