Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b3f5f17
feat(sdk): asset lock quorum verify against platform
lklimek Jul 25, 2024
9de013f
feat(sdk)!: params of dash networks - testnet, mainnet, etc
lklimek Aug 15, 2024
6362883
feat(sdk): network type, continued
lklimek Aug 15, 2024
f984ced
feat: asset lock verify
lklimek Aug 15, 2024
bff31d5
refactor(dapi-client): replace CanRetry.is_node_failure with !CanRetr…
lklimek Aug 15, 2024
e3a5538
feat(sdk): Error implements CanRetry
lklimek Aug 15, 2024
4df0c40
fix(dapi-grpc): GetEpochsInfoRequest not marked as versioned
lklimek Aug 15, 2024
f41f2bc
test(sdk): asset locks verify tests, WIP
lklimek Aug 15, 2024
3517779
fix(sdk): local devnet uses LlmqTest
lklimek Aug 15, 2024
d9cada9
test(sdk): asset_lock test vectors
lklimek Aug 15, 2024
0c04b42
Merge remote-tracking branch 'origin/v1.1-dev' into fix/sdk-invalid-q…
lklimek Aug 15, 2024
c9c4a21
fix(dapi-grpc): fix const
lklimek Aug 16, 2024
f57e455
fix(sdk): build error due to imports
lklimek Aug 16, 2024
b5dfc6a
chore(sdk): cargo fmt
lklimek Aug 19, 2024
1f1c957
Merge remote-tracking branch 'origin/v1.1-dev' into fix/sdk-invalid-q…
lklimek Aug 21, 2024
c4cc766
Merge branch 'master' into fix/sdk-invalid-quorum
lklimek Aug 30, 2024
5d33c31
Merge remote-tracking branch 'origin/v1.7-dev' into fix/sdk-invalid-q…
lklimek Dec 10, 2024
727cc20
chore: fix build
lklimek Dec 10, 2024
50a5267
Merge branch 'v1.8-dev' into fix/sdk-invalid-quorum
lklimek Dec 13, 2024
ee83d83
Merge remote-tracking branch 'origin/v2.0-dev' into fix/sdk-invalid-q…
lklimek Feb 24, 2025
bd6a4ff
feat: verify instant asset lock signature
lklimek Mar 3, 2025
9a67e22
Merge remote-tracking branch 'origin/v2.0-dev' into fix/sdk-invalid-q…
lklimek Mar 3, 2025
b2c6b13
refactor: use Network from dashcore
lklimek Mar 4, 2025
1264c18
refactor: further simplify Network type processing
lklimek Mar 4, 2025
521fdac
chore: remove verification of AssetLockProof::Instant
lklimek Mar 5, 2025
969fb0e
chore: minor cleanup
lklimek Mar 5, 2025
dd50797
chore: self-review
lklimek Mar 5, 2025
821bfd7
chore: fix warnings and failing build
lklimek Mar 5, 2025
a6910f2
test: remove unsupported test case
lklimek Mar 5, 2025
9ca5a7b
doc: fix some docs
lklimek Mar 5, 2025
de4de67
chore: use async_trait for StateTransitionBuilder
lklimek Mar 6, 2025
c22fa07
chore: remove duplicate async_trait
lklimek Mar 6, 2025
20a836b
chore(sdk): s/core_network()/network()/
lklimek Mar 6, 2025
ed981b6
Merge remote-tracking branch 'origin/v2.0-dev' into fix/sdk-invalid-q…
lklimek Mar 18, 2025
bb8dcff
build(dpp): remove unused ordered-float dependency
lklimek Mar 18, 2025
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
85 changes: 45 additions & 40 deletions packages/dapi-grpc/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,82 +63,87 @@ fn configure_platform(mut platform: MappingConfig) -> MappingConfig {
// Derive features for versioned messages
//
// "GetConsensusParamsRequest" is excluded as this message does not support proofs
const VERSIONED_REQUESTS: [&str; 34] = [
//
// Sort alphabetically to simplify maintenance.
const VERSIONED_REQUESTS: [&str; 35] = [
"GetContestedResourceIdentityVotesRequest",
"GetContestedResourcesRequest",
"GetContestedResourceVoteStateRequest",
"GetContestedResourceVotersForIdentityRequest",
"GetDataContractHistoryRequest",
"GetDataContractRequest",
"GetDataContractsRequest",
"GetDocumentsRequest",
"GetEpochsInfoRequest",
"GetEvonodesProposedEpochBlocksByIdsRequest",
"GetEvonodesProposedEpochBlocksByRangeRequest",
"GetIdentitiesBalancesRequest",
"GetIdentitiesByPublicKeyHashesRequest",
"GetIdentitiesContractKeysRequest",
"GetIdentitiesRequest",
"GetIdentitiesBalancesRequest",
"GetIdentityNonceRequest",
"GetIdentityContractNonceRequest",
"GetIdentitiesTokenBalancesRequest",
"GetIdentitiesTokenInfosRequest",
"GetIdentityBalanceAndRevisionRequest",
"GetIdentityBalanceRequest",
"GetIdentityByPublicKeyHashRequest",
"GetIdentityContractNonceRequest",
"GetIdentityKeysRequest",
"GetIdentityNonceRequest",
"GetIdentityRequest",
"GetIdentityTokenBalancesRequest",
"GetIdentityTokenInfosRequest",
"GetPathElementsRequest",
"GetPrefundedSpecializedBalanceRequest",
"GetProofsRequest",
"WaitForStateTransitionResultRequest",
"GetProtocolVersionUpgradeStateRequest",
"GetProtocolVersionUpgradeVoteStatusRequest",
"GetPathElementsRequest",
"GetIdentitiesContractKeysRequest",
"GetPrefundedSpecializedBalanceRequest",
"GetContestedResourcesRequest",
"GetContestedResourceVoteStateRequest",
"GetContestedResourceVotersForIdentityRequest",
"GetContestedResourceIdentityVotesRequest",
"GetVotePollsByEndDateRequest",
"GetTotalCreditsInPlatformRequest",
"GetEvonodesProposedEpochBlocksByIdsRequest",
"GetEvonodesProposedEpochBlocksByRangeRequest",
"GetStatusRequest",
"GetIdentityTokenBalancesRequest",
"GetIdentitiesTokenBalancesRequest",
"GetIdentityTokenInfosRequest",
"GetIdentitiesTokenInfosRequest",
"GetTotalCreditsInPlatformRequest",
"GetVotePollsByEndDateRequest",
"WaitForStateTransitionResultRequest",
];

// The following responses are excluded as they don't support proofs:
// - "GetConsensusParamsResponse"
// - "GetStatusResponse"
//
// "GetEvonodesProposedEpochBlocksResponse" is used for 2 Requests
//
// Sort alphabetically to simplify maintenance.
const VERSIONED_RESPONSES: [&str; 33] = [
"GetContestedResourceIdentityVotesResponse",
"GetContestedResourcesResponse",
"GetContestedResourceVoteStateResponse",
"GetContestedResourceVotersForIdentityResponse",
"GetDataContractHistoryResponse",
"GetDataContractResponse",
"GetDataContractsResponse",
"GetDocumentsResponse",
"GetIdentitiesByPublicKeyHashesResponse",
"GetIdentitiesResponse",
"GetIdentitiesBalancesResponse",
"GetEpochsInfoResponse",
"GetEvonodesProposedEpochBlocksResponse",
"GetIdentityBalanceAndRevisionResponse",
"GetIdentityBalanceResponse",
"GetIdentityNonceResponse",
"GetIdentityContractNonceResponse",
"GetIdentityByPublicKeyHashResponse",
"GetIdentityContractNonceResponse",
"GetIdentityKeysResponse",
"GetIdentityNonceResponse",
"GetIdentityResponse",
"GetIdentityTokenBalancesResponse",
"GetIdentityTokenInfosResponse",
"GetIdentitiesBalancesResponse",
"GetIdentitiesByPublicKeyHashesResponse",
"GetIdentitiesContractKeysResponse",
"GetIdentitiesResponse",
"GetIdentitiesTokenBalancesResponse",
"GetIdentitiesTokenInfosResponse",
"GetPathElementsResponse",
"GetPrefundedSpecializedBalanceResponse",
"GetProofsResponse",
"WaitForStateTransitionResultResponse",
"GetEpochsInfoResponse",
"GetProtocolVersionUpgradeStateResponse",
"GetProtocolVersionUpgradeVoteStatusResponse",
"GetPathElementsResponse",
"GetIdentitiesContractKeysResponse",
"GetPrefundedSpecializedBalanceResponse",
"GetContestedResourcesResponse",
"GetContestedResourceVoteStateResponse",
"GetContestedResourceVotersForIdentityResponse",
"GetContestedResourceIdentityVotesResponse",
"GetVotePollsByEndDateResponse",
"GetTotalCreditsInPlatformResponse",
"GetEvonodesProposedEpochBlocksResponse",
"GetIdentityTokenBalancesResponse",
"GetIdentitiesTokenBalancesResponse",
"GetIdentityTokenInfosResponse",
"GetIdentitiesTokenInfosResponse",
"GetVotePollsByEndDateResponse",
"WaitForStateTransitionResultResponse",
];

check_unique(&VERSIONED_REQUESTS).expect("VERSIONED_REQUESTS");
Expand Down
17 changes: 17 additions & 0 deletions packages/rs-dapi-client/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::transport::TransportRequest;
use crate::{Address, CanRetry, DapiClientError, RequestSettings};
use dapi_grpc::mock::Mockable;
use dapi_grpc::platform::VersionedGrpcResponse;
use dapi_grpc::tonic::async_trait;
use std::fmt::Debug;

Expand Down Expand Up @@ -127,6 +128,22 @@ where
}
}

impl<T: VersionedGrpcResponse> VersionedGrpcResponse for ExecutionResponse<T> {
type Error = T::Error;

fn metadata(&self) -> Result<&dapi_grpc::platform::v0::ResponseMetadata, Self::Error> {
self.inner.metadata()
}

fn proof(&self) -> Result<&dapi_grpc::platform::v0::Proof, Self::Error> {
self.inner.proof()
}

fn proof_owned(self) -> Result<dapi_grpc::platform::v0::Proof, Self::Error> {
self.inner.proof_owned()
}
}

/// Result of request execution
pub type ExecutionResult<R, E> = Result<ExecutionResponse<R>, ExecutionError<E>>;

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-dpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ authors = [
[dependencies]
anyhow = { version = "1.0.81" }
async-trait = { version = "0.1.79" }
ordered-float = { version = "4.6.0", features = ["serde"]}
ordered-float = { version = "4.6.0", features = ["serde"] }
base64 = "0.22.1"
bs58 = "0.5"
byteorder = { version = "1.4" }
Expand Down
2 changes: 1 addition & 1 deletion packages/rs-dpp/src/core_types/validator_set/v0/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ mod tests {
let node_ip = "192.168.1.1".to_string();
let node_id = PubkeyHash::from_slice(&[4; 20]).unwrap();
let validator = ValidatorV0 {
pro_tx_hash: pro_tx_hash.clone(),
pro_tx_hash,
public_key,
node_ip,
node_id,
Expand Down
41 changes: 39 additions & 2 deletions packages/rs-sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,37 @@ pub enum Error {
/// Epoch not found; we must have at least one epoch
#[error("No epoch found on Platform; it should never happen")]
EpochNotFound,
/// Quorum not found; try again later
#[error(
"Quorum {quorum_hash_hex} of type {quorum_type} at height {core_chain_locked_height}: {e}"
)]
QuorumNotFound {
quorum_hash_hex: String,
quorum_type: u32,
core_chain_locked_height: u32,
e: ContextProviderError,
},

/// Asset lock not found; try again later.
///
/// ## Parameters
///
/// - core locked height in asset lock
/// - current core locked height on the platform
#[error("Asset lock for core locked height {core_locked_height_in_asset_lock} not available yet, max avaiable locked core height is {core_locked_height_on_platform}; try again later")]
CoreLockedHeightNotYetAvailable {
core_locked_height_in_asset_lock: u32,
core_locked_height_on_platform: u32,
},

/// Provided asset lock is invalid
///
/// ## Parameters
///
/// - 0 - detailed error message
#[error("Invalid asset lock: {0}")]
InvalidAssetLock(String),

/// SDK operation timeout reached error
#[error("SDK operation timeout {} secs reached: {1}", .0.as_secs())]
TimeoutReached(Duration, String),
Expand Down Expand Up @@ -100,7 +131,7 @@ impl TryFrom<StateTransitionBroadcastErrorProto> for StateTransitionBroadcastErr
type Error = Error;

fn try_from(value: StateTransitionBroadcastErrorProto) -> Result<Self, Self::Error> {
let cause = if value.data.len() > 0 {
let cause = if !value.data.is_empty() {
let consensus_error =
ConsensusError::deserialize_from_bytes(&value.data).map_err(|e| {
tracing::debug!("Failed to deserialize consensus error: {}", e);
Expand Down Expand Up @@ -178,7 +209,13 @@ where

impl CanRetry for Error {
fn can_retry(&self) -> bool {
matches!(self, Error::StaleNode(..) | Error::TimeoutReached(_, _))
matches!(
self,
Error::StaleNode(..)
| Error::TimeoutReached(_, _)
| Error::CoreLockedHeightNotYetAvailable { .. }
| Error::QuorumNotFound { .. }
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/src/platform/fetch_unproved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ where
Self::maybe_from_unproved_with_metadata(
request.clone(),
response,
sdk.network,
sdk.core_network(),
sdk.version(),
)
.map_err(|e| ExecutionError {
Expand Down
1 change: 1 addition & 0 deletions packages/rs-sdk/src/platform/transition.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! State transitions used to put changed objects to the Dash Platform.
pub mod asset_lock;
pub mod broadcast;
pub(crate) mod broadcast_identity;
pub mod broadcast_request;
Expand Down
92 changes: 92 additions & 0 deletions packages/rs-sdk/src/platform/transition/asset_lock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! [AssetLockProof] utilities

use crate::{Error, Sdk};
use dapi_grpc::platform::v0::get_epochs_info_request::{self, GetEpochsInfoRequestV0};
use dapi_grpc::platform::v0::GetEpochsInfoRequest;
use dapi_grpc::platform::VersionedGrpcResponse;
use dpp::prelude::AssetLockProof;
use rs_dapi_client::{DapiRequestExecutor, RequestSettings};

#[async_trait::async_trait]
pub trait AssetLockProofVerifier {
/// Verifies the asset lock proof against the platform.
///
/// This function verifies some assertions that are necessary for the asset lock proof to be used by Dash Platform,
/// and errors if any of them are not met.
///
/// Verification involves fetching some information from DAPI and comparing it with the provided asset lock proof.
///
/// Note that positive verification result does not imply that the asset lock proof is valid. Dash Platform can
/// still reject the asset lock.
///
/// # Limitations
///
/// Only [AssetLockProof::Chain] is supported.
///
/// # Errors
///
/// - [Error::CoreLockedHeightNotYetAvailable] if the core locked height in the proof is higher than the
/// current core locked height on the platform. Try again later.
/// - [Error::QuorumNotFound] if the quorum public key is not yet available on the platform, what implies that
/// the quorum is not (yet) available. Try again later.
/// - [Error::InvalidSignature] if the signature in the proof is invalid.
/// - other errors when something goes wrong.
///
/// # Unstable
///
/// This function is unstable and may change in the future.
async fn verify(&self, sdk: &Sdk) -> Result<(), Error>;
}

#[async_trait::async_trait]
impl AssetLockProofVerifier for AssetLockProof {
async fn verify(&self, sdk: &Sdk) -> Result<(), Error> {
match self {
AssetLockProof::Chain(asset_lock) => {
let platform_core_chain_locked_height = fetch_platform_locked_height(sdk).await?;
if asset_lock.core_chain_locked_height > platform_core_chain_locked_height {
Err(Error::CoreLockedHeightNotYetAvailable {
core_locked_height_in_asset_lock: asset_lock.core_chain_locked_height,
core_locked_height_on_platform: platform_core_chain_locked_height,
})
} else {
Ok(())
}
}
AssetLockProof::Instant(instant_asset_lock_proof) => {
instant_asset_lock_proof.validate_structure(sdk.version())?;

Check failure on line 57 in packages/rs-sdk/src/platform/transition/asset_lock.rs

View workflow job for this annotation

GitHub Actions / Rust packages (dash-sdk) / Linting

no method named `validate_structure` found for reference `&dpp::identity::state_transition::asset_lock_proof::InstantAssetLockProof` in the current scope

error[E0599]: no method named `validate_structure` found for reference `&dpp::identity::state_transition::asset_lock_proof::InstantAssetLockProof` in the current scope --> packages/rs-sdk/src/platform/transition/asset_lock.rs:57:42 | 57 | instant_asset_lock_proof.validate_structure(sdk.version())?; | ^^^^^^^^^^^^^^^^^^ method not found in `&InstantAssetLockProof`
// To verify instant asset lock, we need to:
//
// 1. Determine quorum hash used to sign it.
// 2. Detch quorum public key for this hash.
// 3. Verify instant asset lock signature.
//
// Unfortunately, determining quorum used to sign the instant asset lock is not straightforward,
// as it requires processing of SML which is not implemented in the SDK yet.
//
// So we just accept the instant asset lock as valid for now.

Ok(())
}
}
}
}

/// Fetches the current core chain locked height from the platform.
async fn fetch_platform_locked_height(sdk: &Sdk) -> Result<u32, Error> {
// Retrieve current core chain lock info from the platform
// TODO: implement some caching mechanism to avoid fetching the same data multiple times
let request = GetEpochsInfoRequest {
version: Some(get_epochs_info_request::Version::V0(
GetEpochsInfoRequestV0 {
ascending: false,
count: 1,
prove: true,
start_epoch: None,
},
)),
};
let response = sdk.execute(request, RequestSettings::default()).await?;

Ok(response.metadata()?.core_chain_locked_height)
}
1 change: 1 addition & 0 deletions packages/rs-sdk/src/platform/transition/put_identity.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::platform::block_info_from_metadata::block_info_from_metadata;
use crate::platform::transition::broadcast_identity::BroadcastRequestForNewIdentity;
use crate::{Error, Sdk};

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/src/platform/types/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
Error, Sdk,
};

/// Epoch type used in the SDK.
/// Epoch information
pub type Epoch = ExtendedEpochInfo;

#[async_trait]
Expand Down
Loading
Loading