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
30 changes: 29 additions & 1 deletion packages/rs-dpp/src/identity/identity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::address_funds::PlatformAddress;
use crate::identity::v0::IdentityV0;
use crate::identity::{IdentityPublicKey, KeyID};
use crate::prelude::Revision;
use crate::prelude::{AddressNonce, Revision};

#[cfg(feature = "identity-hashing")]
use crate::serialization::PlatformSerializable;
Expand Down Expand Up @@ -118,6 +119,33 @@ impl Identity {
}
}

/// Create a new identity using input [PlatformAddress]es.
///
/// This function derives the identity ID from the provided input addresses.
///
/// ## Arguments
///
/// * `inputs` - A map of `PlatformAddress` to `(AddressNonce, Credits)`.
/// The identity id is derived from the addresses and nonces (credits are ignored for the id derivation).
/// The nonces should represent state after creation of the identity (e.g. be incremented by 1).
/// * `public_keys` - A map of KeyID to IdentityPublicKey tuples representing the public keys for the identity.
/// * `platform_version` - The platform version to use for identity creation.
///
/// ## Returns
///
/// * `Result<Identity, ProtocolError>` - Returns the newly created Identity or a ProtocolError if the operation fails.
#[cfg(feature = "state-transitions")]
pub fn new_with_input_addresses_and_keys(
inputs: &BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
public_keys: BTreeMap<KeyID, IdentityPublicKey>,
platform_version: &PlatformVersion,
) -> Result<Identity, ProtocolError> {
use crate::state_transition::identity_id_from_input_addresses;

let identity_id = identity_id_from_input_addresses(inputs)?;
Self::new_with_id_and_keys(identity_id, public_keys, platform_version)
}

/// Convenience method to get Partial Identity Info
pub fn into_partial_identity_info(self) -> PartialIdentity {
match self {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
use std::collections::BTreeMap;

use crate::address_funds::PlatformAddress;
use crate::fee::Credits;
use crate::prelude::AddressNonce;
use crate::state_transition::StateTransitionWitnessSigned;
use crate::util::hash::hash_double;
use crate::ProtocolError;
use platform_value::Identifier;

pub trait StateTransitionIdentityIdFromInputs: StateTransitionWitnessSigned {
/// Get the identity id from inputs
/// Get the identity id from inputs.
///
/// Inputs should represent state after creation of the identity (eg. be incremented by 1).
fn identity_id_from_inputs(&self) -> Result<Identifier, ProtocolError> {
if self.inputs().is_empty() {
return Err(ProtocolError::ParsingError(
"Identity creation requires at least one input".to_string(),
));
}

// Build a map containing only (PlatformAddress, KeyOfTypeNonce) pairs,
// ignoring the Credits in the input values.
let address_nonce_map: BTreeMap<&PlatformAddress, &AddressNonce> = self
.inputs()
.iter()
.map(|(address, (nonce, _credits))| (address, nonce))
.collect();

use crate::util::hash::hash_double;

let input_bytes = bincode::encode_to_vec(&address_nonce_map, bincode::config::standard())
.map_err(|e| {
ProtocolError::EncodingError(format!("Failed to encode inputs: {}", e))
})?;
let inputs = self.inputs();
identity_id_from_input_addresses(inputs)
}
}

let hash = hash_double(input_bytes);
Ok(Identifier::new(hash))
/// Helper that computes the identity ID from input addresses and nonces.
/// Nonces should represent state after creation of the identity (eg. be incremented by 1).
///
/// Internal use only; see `StateTransitionIdentityIdFromInputs` trait.
pub(crate) fn identity_id_from_input_addresses(
input_addresses: &BTreeMap<PlatformAddress, (AddressNonce, Credits)>,
) -> Result<Identifier, ProtocolError> {
if input_addresses.is_empty() {
return Err(ProtocolError::ParsingError(
"Identity creation requires at least one input".to_string(),
));
}
// Build a map containing only (PlatformAddress, KeyOfTypeNonce) pairs,
// ignoring the Credits in the input values.
let address_nonce_map: BTreeMap<&PlatformAddress, &AddressNonce> = input_addresses
.iter()
.map(|(address, (nonce, _credits))| (address, nonce))
.collect();
let input_bytes = bincode::encode_to_vec(&address_nonce_map, bincode::config::standard())
.map_err(|e| ProtocolError::EncodingError(format!("Failed to encode inputs: {}", e)))?;

let hash = hash_double(input_bytes);
Ok(Identifier::new(hash))
}
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@ impl Drive {
})?;
let (root_hash_identity, identity) = Drive::verify_full_identity_by_identity_id(
proof,
false,
true,
identity_id.into_buffer(),
platform_version,
)?;
Expand All @@ -1015,7 +1015,7 @@ impl Drive {
) = Drive::verify_addresses_infos(
proof,
addresses_to_check,
false,
true,
platform_version,
)?;

Expand Down
5 changes: 3 additions & 2 deletions packages/rs-sdk-ffi/src/address_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub unsafe extern "C" fn dash_sdk_sync_address_balances_with_result(
fn convert_sync_result(result: AddressSyncResult) -> DashSDKAddressSyncResult {
// Convert found addresses
let mut found_entries: Vec<DashSDKFoundAddress> = Vec::with_capacity(result.found.len());
for ((index, key), balance) in result.found.iter() {
for ((index, key), funds) in result.found.iter() {
let key_data = key.clone().into_boxed_slice();
let key_len = key_data.len();
let key_ptr = Box::into_raw(key_data) as *mut u8;
Expand All @@ -173,7 +173,8 @@ fn convert_sync_result(result: AddressSyncResult) -> DashSDKAddressSyncResult {
index: *index,
key: key_ptr,
key_len,
balance: *balance,
nonce: funds.nonce,
balance: funds.balance,
});
}

Expand Down
17 changes: 13 additions & 4 deletions packages/rs-sdk-ffi/src/address_sync/provider.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! FFI-compatible address provider implementation using callbacks

use super::types::DashSDKPendingAddressList;
use dash_sdk::platform::address_sync::{AddressIndex, AddressKey, AddressProvider};
use dash_sdk::platform::address_sync::{AddressFunds, AddressIndex, AddressKey, AddressProvider};
use std::os::raw::c_void;

/// Function pointer type for getting pending addresses
Expand All @@ -25,12 +25,13 @@ pub type GetHighestFoundIndexFn = unsafe extern "C" fn(context: *mut c_void) ->

/// Function pointer type for handling a found address
///
/// Called when an address is found in the tree with a balance.
/// Called when an address is found in the tree with a balance and nonce.
pub type OnAddressFoundFn = unsafe extern "C" fn(
context: *mut c_void,
index: u32,
key: *const u8,
key_len: usize,
nonce: u32,
balance: u64,
);

Expand Down Expand Up @@ -126,10 +127,17 @@ impl<'a> AddressProvider for CallbackAddressProvider<'a> {
}
}

fn on_address_found(&mut self, index: AddressIndex, key: &[u8], balance: u64) {
fn on_address_found(&mut self, index: AddressIndex, key: &[u8], funds: AddressFunds) {
unsafe {
let vtable = &*self.ffi.vtable;
(vtable.on_address_found)(self.ffi.context, index, key.as_ptr(), key.len(), balance);
(vtable.on_address_found)(
self.ffi.context,
index,
key.as_ptr(),
key.len(),
funds.nonce,
funds.balance,
);
}
}

Expand Down Expand Up @@ -214,6 +222,7 @@ mod tests {
_index: u32,
_key: *const u8,
_key_len: usize,
_nonce: u32,
_balance: u64,
) {
}
Expand Down
3 changes: 3 additions & 0 deletions packages/rs-sdk-ffi/src/address_sync/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ pub struct DashSDKFoundAddress {
/// Length of the key in bytes
pub key_len: usize,

/// Nonce associated with this address
pub nonce: u32,

/// Balance in credits at this address
pub balance: u64,
}
Expand Down
73 changes: 48 additions & 25 deletions packages/rs-sdk/src/platform/address_sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ mod types;

pub use provider::AddressProvider;
pub use types::{
AddressIndex, AddressKey, AddressSyncConfig, AddressSyncMetrics, AddressSyncResult,
LeafBoundaryKey,
AddressFunds, AddressIndex, AddressKey, AddressSyncConfig, AddressSyncMetrics,
AddressSyncResult, LeafBoundaryKey,
};

use crate::error::Error;
Expand All @@ -54,6 +54,7 @@ use dapi_grpc::platform::v0::{
get_addresses_branch_state_request, get_addresses_branch_state_response,
GetAddressesBranchStateRequest,
};
use dpp::prelude::AddressNonce;
use dpp::version::PlatformVersion;
use drive::drive::Drive;
use drive::grovedb::{
Expand Down Expand Up @@ -81,7 +82,7 @@ use tracker::KeyLeafTracker;
/// - `config`: Optional configuration; uses defaults if `None`.
///
/// # Returns
/// - `Ok(AddressSyncResult)`: Contains found addresses with balances and absent addresses.
/// - `Ok(AddressSyncResult)`: Contains found addresses with balances/nonces and absent addresses.
/// - `Err(Error)`: If the sync fails after exhausting retries.
///
/// # Example
Expand Down Expand Up @@ -130,7 +131,7 @@ pub async fn sync_address_balances<P: AddressProvider>(

// Step 2: Process trunk result
let mut tracker = KeyLeafTracker::new();
process_trunk_result(&trunk_result, provider, &mut result, &mut tracker);
process_trunk_result(&trunk_result, provider, &mut result, &mut tracker)?;

// Step 3: Iterative branch queries
let min_query_depth = platform_version
Expand Down Expand Up @@ -190,7 +191,7 @@ pub async fn sync_address_balances<P: AddressProvider>(
&key_to_index,
&mut result,
&mut tracker,
);
)?;
}

// Check if provider has extended pending addresses (gap limit behavior)
Expand Down Expand Up @@ -245,16 +246,16 @@ fn process_trunk_result<P: AddressProvider>(
provider: &mut P,
result: &mut AddressSyncResult,
tracker: &mut KeyLeafTracker,
) {
) -> Result<(), Error> {
// Get pending addresses
let pending: Vec<(AddressIndex, AddressKey)> = provider.pending_addresses();

for (index, key) in pending {
// Check if found in elements
if let Some(element) = trunk_result.elements.get(&key) {
let balance = extract_balance_from_element(element);
result.found.insert((index, key.clone()), balance);
provider.on_address_found(index, &key, balance);
let funds = AddressFunds::try_from(element)?;
result.found.insert((index, key.clone()), funds);
provider.on_address_found(index, &key, funds);
} else {
// Trace to leaf
if let Some((leaf_key, info)) = trunk_result.trace_key_to_leaf(&key) {
Expand All @@ -266,6 +267,8 @@ fn process_trunk_result<P: AddressProvider>(
}
}
}

Ok(())
}

/// Get privacy-adjusted leaves to query.
Expand Down Expand Up @@ -471,7 +474,7 @@ fn process_branch_result<P: AddressProvider>(
key_to_index: &HashMap<AddressKey, AddressIndex>,
result: &mut AddressSyncResult,
tracker: &mut KeyLeafTracker,
) {
) -> Result<(), Error> {
// Get all target keys that were in this leaf's subtree
let target_keys = tracker.keys_for_leaf(queried_leaf_key);

Expand All @@ -480,9 +483,9 @@ fn process_branch_result<P: AddressProvider>(

// Check if found in elements
if let Some(element) = branch_result.elements.get(&target_key) {
let balance = extract_balance_from_element(element);
result.found.insert((index, target_key.clone()), balance);
provider.on_address_found(index, &target_key, balance);
let funds = AddressFunds::try_from(element)?;
result.found.insert((index, target_key.clone()), funds);
provider.on_address_found(index, &target_key, funds);
tracker.key_found(&target_key);
} else {
// Try to trace to a deeper leaf
Expand All @@ -498,15 +501,32 @@ fn process_branch_result<P: AddressProvider>(
}

result.metrics.total_elements_seen += branch_result.elements.len();
Ok(())
}

/// Extract balance from a GroveDB Element.
///
/// The address funds tree stores balances as items with sum items.
fn extract_balance_from_element(element: &Element) -> u64 {
match element {
Element::ItemWithSumItem(_, value, _) => *value as u64,
_ => 0,
impl TryFrom<&Element> for AddressFunds {
type Error = Error;

/// Convert a GroveDB element into address funds (nonce and balance).
///
/// The address funds tree stores the nonce as the item value and the balance as the sum item.
fn try_from(element: &Element) -> Result<Self, Self::Error> {
if let Element::ItemWithSumItem(nonce_bytes, balance, _) = element {
let nonce_bytes: [u8; 4] = nonce_bytes.as_slice().try_into().map_err(|_| {
Error::InvalidProvedResponse(
"address funds nonce must be exactly 4 bytes".to_string(),
)
})?;
let nonce = AddressNonce::from_be_bytes(nonce_bytes);
let balance: u64 = (*balance).try_into().map_err(|_| {
Error::InvalidProvedResponse("address funds balance must fit into u64".to_string())
})?;
return Ok(AddressFunds { nonce, balance });
}

Err(Error::InvalidProvedResponse(
"unexpected element type for address funds".to_string(),
))
}
}

Expand All @@ -525,7 +545,7 @@ impl Sdk {
/// - `config`: Optional configuration; uses defaults if `None`.
///
/// # Returns
/// - `Ok(AddressSyncResult)`: Contains found addresses with balances and absent addresses.
/// - `Ok(AddressSyncResult)`: Contains found addresses with balances/nonces and absent addresses.
/// - `Err(Error)`: If the sync fails after exhausting retries.
///
/// # Example
Expand Down Expand Up @@ -564,11 +584,14 @@ mod tests {
use super::*;

#[test]
fn test_extract_balance() {
let item_with_sum_item = Element::ItemWithSumItem(vec![], 1000, None);
assert_eq!(extract_balance_from_element(&item_with_sum_item), 1000);
fn test_extract_funds_from_element() {
let item_with_sum_item = Element::ItemWithSumItem(vec![0, 0, 0, 5], 1000, None);
let funds = AddressFunds::try_from(&item_with_sum_item).expect("valid funds element");
assert_eq!(funds.balance, 1000);
assert_eq!(funds.nonce, 5);

let item = Element::Item(vec![1, 2, 3], None);
assert_eq!(extract_balance_from_element(&item), 0);
let err = AddressFunds::try_from(&item).unwrap_err();
assert!(matches!(err, Error::InvalidProvedResponse(_)));
}
}
Loading
Loading