From 35f19f37c33b889025e1ad70ce6d76a19d9cc9c6 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 27 Nov 2025 10:40:20 +0700 Subject: [PATCH] fix: non proved `get_token_perpetual_distribution_last_claim` --- packages/wasm-sdk/QUERY_REVIEW.md | 18 ++ packages/wasm-sdk/src/context_provider.rs | 6 +- packages/wasm-sdk/src/queries/token.rs | 240 ++++++++-------------- 3 files changed, 105 insertions(+), 159 deletions(-) create mode 100644 packages/wasm-sdk/QUERY_REVIEW.md diff --git a/packages/wasm-sdk/QUERY_REVIEW.md b/packages/wasm-sdk/QUERY_REVIEW.md new file mode 100644 index 00000000000..5268a7f11d0 --- /dev/null +++ b/packages/wasm-sdk/QUERY_REVIEW.md @@ -0,0 +1,18 @@ +# wasm-sdk query review (fetch/fetch_many, proofs, efficiency) + +Context: review of query implementations to verify we use fetch/fetch_many correctly, avoid inefficient patterns, and handle proofs. + +## Findings + +- `src/queries/token.rs:getTokenPerpetualDistributionLastClaim` + - Uses a direct gRPC call with `prove: false` to avoid context/provider issues. Result is unverified and may diverge from the proof-backed variant. Should either default to the proofable path or document that this endpoint is best-effort/non-verified. +- `src/queries/protocol.rs:getProtocolVersionUpgradeVoteStatus` (and proof variant TODO) + - Uses `fetch_votes` without a proof-capable path; proof variant remains unimplemented. Protocol vote status cannot currently be verified. +- `src/queries/epoch.rs:getEvonodesProposedEpochBlocksBy*WithProofInfo` + - Proof-supporting variants are stubbed (return errors). Evonode proposed block counts cannot be fetched with proofs yet. + +## Suggested follow-ups + +1) Add/enable batch identity key queries (or parallelize) and return combined proofs for `getIdentitiesContractKeys*`. +2) Decide whether `getTokenPerpetualDistributionLastClaim` should default to the proof-backed path or be clearly marked as best-effort. +3) Implement proof-capable variants for protocol vote status and evonode proposed block counts once underlying fetch/proof traits land. diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index aaea30430c8..924caa7e567 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -106,7 +106,8 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - .with_refetch_if_not_found(false); // Disable refetch since we'll pre-fetch + // Enable refetch so token configs (and other context) are pulled on demand, same as rs-sdk + .with_refetch_if_not_found(true); Ok(Self { inner: std::sync::Arc::new(inner), @@ -120,7 +121,8 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - .with_refetch_if_not_found(false); // Disable refetch since we'll pre-fetch + // Enable refetch so token configs (and other context) are pulled on demand, same as rs-sdk + .with_refetch_if_not_found(true); Ok(Self { inner: std::sync::Arc::new(inner), diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index 3c06aac02a2..96ef55f1cfb 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -4,10 +4,12 @@ use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; use dash_sdk::dpp::balances::credits::TokenAmount; use dash_sdk::dpp::tokens::calculate_token_id; +use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment; use dash_sdk::dpp::tokens::info::IdentityTokenInfo; use dash_sdk::dpp::tokens::status::TokenStatus; use dash_sdk::dpp::tokens::token_pricing_schedule::TokenPricingSchedule; -use dash_sdk::platform::{FetchMany, Identifier}; +use dash_sdk::platform::query::TokenLastClaimQuery; +use dash_sdk::platform::{Fetch, FetchMany, Identifier}; use js_sys::{BigInt, Map}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -480,172 +482,96 @@ impl WasmSdk { let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; let token_identifier = identifier_from_js(&token_id, "token ID")?; - // Use direct gRPC request instead of high-level SDK fetch to avoid proof verification issues - use dapi_grpc::platform::v0::{ - get_token_perpetual_distribution_last_claim_request::{ - GetTokenPerpetualDistributionLastClaimRequestV0, Version, - }, - GetTokenPerpetualDistributionLastClaimRequest, - }; - use rs_dapi_client::DapiRequestExecutor; - - // Create direct gRPC Request without proofs to avoid context provider issues - let request = GetTokenPerpetualDistributionLastClaimRequest { - version: Some(Version::V0( - GetTokenPerpetualDistributionLastClaimRequestV0 { - token_id: token_identifier.to_vec(), - identity_id: identity_identifier.to_vec(), - contract_info: None, // Not needed for this query - prove: false, // Use prove: false to avoid proof verification and context provider dependency - }, - )), + // Create query and fetch via SDK (proofless variant) + let query = TokenLastClaimQuery { + token_id: token_identifier, + identity_id: identity_identifier, }; - // Execute the gRPC request - let response = self - .inner_sdk() - .execute(request, rs_dapi_client::RequestSettings::default()) - .await - .map_err(|e| { - WasmSdkError::generic(format!( - "Failed to fetch token perpetual distribution last claim: {}", - e - )) - })?; - - // Extract result from response and convert to our expected format - let claim_result = match response.inner.version { - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::Version::V0( - v0, - ), - ) => { - match v0.result { - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::LastClaim( - claim, - ), - ) => { - match claim.paid_at { - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::TimestampMs( - timestamp, - ), - ) => Some((timestamp, 0)), - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::BlockHeight( - height, - ), - ) => Some((0, height)), - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::Epoch( - epoch, - ), - ) => Some((0, epoch as u64)), + // Try proof-backed fetch first; fall back to direct gRPC with prove=false if context lacks token config + let claim_result = match RewardDistributionMoment::fetch(self.as_ref(), query.clone()).await + { + Ok(res) => res, + Err(err) => { + // If proof verification failed due to missing token distribution type, retry without proofs + if err + .to_string() + .contains("Token distribution type not found with get_token_distribution") + { + use dapi_grpc::platform::v0::{ + get_token_perpetual_distribution_last_claim_request::{ + GetTokenPerpetualDistributionLastClaimRequestV0, Version, + }, + get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt, + GetTokenPerpetualDistributionLastClaimRequest, + }; + use rs_dapi_client::DapiRequestExecutor; + + let request = GetTokenPerpetualDistributionLastClaimRequest { + version: Some(Version::V0( + GetTokenPerpetualDistributionLastClaimRequestV0 { + token_id: query.token_id.to_vec(), + identity_id: query.identity_id.to_vec(), + contract_info: None, + prove: false, + }, + )), + }; + + let response = self + .inner_sdk() + .execute(request, rs_dapi_client::RequestSettings::default()) + .await + .map_err(|e| { + WasmSdkError::generic(format!( + "Failed to fetch token perpetual distribution last claim (fallback): {}", + e + )) + })?; + + let claim = match response.inner.version { + Some( + dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::Version::V0( + v0, + ), + ) => match v0.result { Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::RawBytes( - bytes, + dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::LastClaim( + claim, ), - ) => { - // Raw bytes format specification (confirmed via server trace logs): - // - Total length: 8 bytes (big-endian encoding) - // - Bytes 0-3: Timestamp as u32 (seconds since Unix epoch, 0 = no timestamp recorded) - // - Bytes 4-7: Block height as u32 (Dash blockchain block number) - // - // Validation ranges: - // - Timestamp: 0 (unset) or >= 1609459200 (Jan 1, 2021 00:00:00 UTC, before Dash Platform mainnet) - // - Block height: 0 (invalid) or >= 1 (valid blockchain height) - if bytes.len() >= 8 { - let timestamp = u32::from_be_bytes([ - bytes[0], - bytes[1], - bytes[2], - bytes[3], - ]) as u64; - let block_height = u32::from_be_bytes([ - bytes[4], - bytes[5], - bytes[6], - bytes[7], - ]) as u64; - - // Validate timestamp: must be 0 (unset) or a reasonable Unix timestamp - let validated_timestamp = if timestamp != 0 - && timestamp < 1609459200 - { - tracing::warn!( - target = "wasm_sdk", timestamp, - "Invalid timestamp in raw bytes (too early)" - ); - 0 // Use 0 for invalid timestamps - } else { - timestamp - }; - - // Validate block height: must be a positive value - let validated_block_height = if block_height == 0 { - tracing::warn!( - target = "wasm_sdk", - "Invalid block height in raw bytes: 0 (genesis block not expected)" - ); - 1 // Use minimum valid block height - } else { - block_height - }; - - Some((validated_timestamp * 1000, validated_block_height)) - } else if bytes.len() >= 4 { - // Fallback: decode only the last 4 bytes as block height - let block_height = u32::from_be_bytes([ - bytes[bytes.len() - 4], - bytes[bytes.len() - 3], - bytes[bytes.len() - 2], - bytes[bytes.len() - 1], - ]) as u64; - - // Validate block height - let validated_block_height = if block_height == 0 { - tracing::warn!( - target = "wasm_sdk", - "Invalid block height in fallback parsing: 0" - ); - 1 // Use minimum valid block height - } else { - block_height - }; - - Some((0, validated_block_height)) - } else { - tracing::warn!( - target = "wasm_sdk", len = bytes.len(), - "Insufficient raw bytes length (expected 8 or 4)" - ); - Some((0, 0)) - } - } - None => None, // No paid_at info + ) => claim.paid_at, + _ => None, + }, + _ => None, + }; + + match claim { + Some(PaidAt::TimestampMs(ts)) => Some(RewardDistributionMoment::TimeBasedMoment(ts)), + Some(PaidAt::BlockHeight(height)) => { + Some(RewardDistributionMoment::BlockBasedMoment(height)) } + Some(PaidAt::Epoch(epoch)) => { + Some(RewardDistributionMoment::EpochBasedMoment(epoch as u64)) + } + _ => None, } - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::Proof( - _, - ), - ) => { - return Err( - WasmSdkError::generic( - "Received proof instead of data - this should not happen with prove: false", - ), - ); - } - None => None, // No claim found + } else { + return Err(err.into()); } } - None => return Err(WasmSdkError::generic("Invalid response version")), }; - Ok(claim_result.map(|(timestamp_ms, block_height)| { - TokenLastClaimWasm::new(timestamp_ms, block_height) - })) + let data = claim_result.map(|moment| { + let (last_claim_timestamp_ms, last_claim_block_height) = match moment { + RewardDistributionMoment::BlockBasedMoment(height) => (0, height), + RewardDistributionMoment::TimeBasedMoment(timestamp) => (timestamp, 0), + RewardDistributionMoment::EpochBasedMoment(epoch) => (0, epoch as u64), + }; + + TokenLastClaimWasm::new(last_claim_timestamp_ms, last_claim_block_height) + }); + + Ok(data) } #[wasm_bindgen(js_name = "getTokenTotalSupply")]