diff --git a/packages/rs-context-provider/src/provider.rs b/packages/rs-context-provider/src/provider.rs index d8843b48bee..5423f7d04d6 100644 --- a/packages/rs-context-provider/src/provider.rs +++ b/packages/rs-context-provider/src/provider.rs @@ -3,7 +3,7 @@ use dpp::data_contract::TokenConfiguration; use dpp::prelude::{CoreBlockHeight, DataContract, Identifier}; use dpp::version::PlatformVersion; use drive::{error::proof::ProofError, query::ContractLookupFn}; -use std::{ops::Deref, sync::Arc}; +use std::{future::Future, ops::Deref, pin::Pin, sync::Arc}; #[cfg(feature = "mocks")] use { @@ -62,7 +62,34 @@ pub trait ContextProvider: Send + Sync { token_id: &Identifier, ) -> Result, ContextProviderError>; - /// Fetches the public key for a specified quorum. + /// Fetches the public key for a specified quorum asynchronously. + /// + /// This is the primary method that should be implemented. The synchronous version + /// [get_quorum_public_key](ContextProvider::get_quorum_public_key) should typically + /// wrap this method using `dash_sdk::sync::block_on()`. + /// + /// # Arguments + /// + /// * `quorum_type`: The type of the quorum. + /// * `quorum_hash`: The hash of the quorum. This is used to determine which quorum's public key to fetch. + /// * `core_chain_locked_height`: Core chain locked height for which the quorum must be valid + /// + /// # Returns + /// + /// * `Ok([u8; 48])`: On success, returns a 48-byte array representing the public key of the quorum. + /// * `Err(Error)`: On failure, returns an error indicating why the operation failed. + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> Pin> + Send + 'static>>; + + /// Fetches the public key for a specified quorum synchronously. + /// + /// This method is typically implemented as a wrapper around + /// [get_quorum_public_key_async](ContextProvider::get_quorum_public_key_async) + /// using `dash_sdk::sync::block_on()`. /// /// # Arguments /// @@ -72,7 +99,7 @@ pub trait ContextProvider: Send + Sync { /// /// # Returns /// - /// * `Ok(Vec)`: On success, returns a byte vector representing the public key of the quorum. + /// * `Ok([u8; 48])`: On success, returns a 48-byte array representing the public key of the quorum. /// * `Err(Error)`: On failure, returns an error indicating why the operation failed. fn get_quorum_public_key( &self, @@ -106,6 +133,20 @@ impl + Send + Sync> ContextProvider for C { self.as_ref().get_token_configuration(token_id) } + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> Pin> + Send + 'static>> + { + self.as_ref().get_quorum_public_key_async( + quorum_type, + quorum_hash, + core_chain_locked_height, + ) + } + fn get_quorum_public_key( &self, quorum_type: u32, @@ -142,6 +183,19 @@ where lock.get_token_configuration(token_id) } + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> Pin> + Send + 'static>> + { + // Compute synchronously under the lock, then return a ready future. + // This avoids holding the MutexGuard across await points which would be unsound. + let result = self.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height); + Box::pin(async move { result }) + } + fn get_quorum_public_key( &self, quorum_type: u32, @@ -227,7 +281,22 @@ impl Default for MockContextProvider { #[cfg(feature = "mocks")] impl ContextProvider for MockContextProvider { - /// Mock implementation of [ContextProvider] that returns keys from files saved on disk. + /// Mock implementation of async [ContextProvider] that returns keys from files saved on disk. + /// + /// Use [dash_sdk::SdkBuilder::with_dump_dir()] to generate quorum keys files. + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> Pin> + Send + 'static>> + { + // For the mock implementation, we just wrap the sync version + let result = self.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height); + Box::pin(async move { result }) + } + + /// Mock implementation of sync [ContextProvider] that returns keys from files saved on disk. /// /// Use [dash_sdk::SdkBuilder::with_dump_dir()] to generate quorum keys files. fn get_quorum_public_key( diff --git a/packages/rs-sdk-ffi/src/context_callbacks.rs b/packages/rs-sdk-ffi/src/context_callbacks.rs index d0fe9bd6aa0..f8e9fa4169d 100644 --- a/packages/rs-sdk-ffi/src/context_callbacks.rs +++ b/packages/rs-sdk-ffi/src/context_callbacks.rs @@ -110,6 +110,23 @@ unsafe impl Send for CallbackContextProvider {} unsafe impl Sync for CallbackContextProvider {} impl ContextProvider for CallbackContextProvider { + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> std::pin::Pin< + Box< + dyn std::future::Future> + + Send + + 'static, + >, + > { + // Wrap sync version in async block + let result = self.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height); + Box::pin(async move { result }) + } + fn get_quorum_public_key( &self, quorum_type: u32, diff --git a/packages/rs-sdk-trusted-context-provider/examples/with_fallback.rs b/packages/rs-sdk-trusted-context-provider/examples/with_fallback.rs index c07a04c6aca..4516c5c74eb 100644 --- a/packages/rs-sdk-trusted-context-provider/examples/with_fallback.rs +++ b/packages/rs-sdk-trusted-context-provider/examples/with_fallback.rs @@ -13,6 +13,23 @@ use std::sync::Arc; struct MyFallbackProvider; impl ContextProvider for MyFallbackProvider { + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> std::pin::Pin< + Box< + dyn std::future::Future> + + Send + + 'static, + >, + > { + // Wrap the sync version in an async block + let result = self.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height); + Box::pin(async move { result }) + } + fn get_quorum_public_key( &self, _quorum_type: u32, diff --git a/packages/rs-sdk-trusted-context-provider/src/provider.rs b/packages/rs-sdk-trusted-context-provider/src/provider.rs index ce80912a389..0e64a6f712f 100644 --- a/packages/rs-sdk-trusted-context-provider/src/provider.rs +++ b/packages/rs-sdk-trusted-context-provider/src/provider.rs @@ -432,6 +432,24 @@ impl TrustedHttpContextProvider { } impl ContextProvider for TrustedHttpContextProvider { + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: QuorumHash, + core_chain_locked_height: CoreBlockHeight, + ) -> std::pin::Pin< + Box< + dyn std::future::Future> + + Send + + 'static, + >, + > { + // For now, wrap the sync version in an async block + // This could be made truly async in the future + let result = self.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height); + Box::pin(async move { result }) + } + fn get_quorum_public_key( &self, quorum_type: u32, diff --git a/packages/rs-sdk/src/mock/provider.rs b/packages/rs-sdk/src/mock/provider.rs index 88327ff5564..bc149dc4001 100644 --- a/packages/rs-sdk/src/mock/provider.rs +++ b/packages/rs-sdk/src/mock/provider.rs @@ -99,6 +99,31 @@ impl GrpcContextProvider { self.dump_dir = dump_dir; } + /// Internal implementation of get_quorum_public_key (used by both sync and async versions) + fn get_quorum_public_key_sync_impl( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> Result<[u8; 48], ContextProviderError> { + if let Some(key) = self + .quorum_public_keys_cache + .get(&(quorum_hash, quorum_type)) + { + return Ok(*key); + }; + + let key = self.core.get_quorum_public_key(quorum_type, quorum_hash)?; + + self.quorum_public_keys_cache + .put((quorum_hash, quorum_type), key); + + #[cfg(feature = "mocks")] + self.dump_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height, &key); + + Ok(key) + } + /// Save quorum public key to disk. /// /// Files are named: `quorum_pubkey--.json` @@ -162,28 +187,35 @@ impl GrpcContextProvider { } impl ContextProvider for GrpcContextProvider { + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> std::pin::Pin< + Box< + dyn std::future::Future> + + Send + + 'static, + >, + > { + // For now, we just wrap the synchronous version in an async block + // In the future, this can be made truly async if the core client supports it + let result = self.get_quorum_public_key_sync_impl( + quorum_type, + quorum_hash, + core_chain_locked_height, + ); + Box::pin(async move { result }) + } + fn get_quorum_public_key( &self, quorum_type: u32, quorum_hash: [u8; 32], // quorum hash is 32 bytes core_chain_locked_height: u32, ) -> Result<[u8; 48], ContextProviderError> { - if let Some(key) = self - .quorum_public_keys_cache - .get(&(quorum_hash, quorum_type)) - { - return Ok(*key); - }; - - let key = self.core.get_quorum_public_key(quorum_type, quorum_hash)?; - - self.quorum_public_keys_cache - .put((quorum_hash, quorum_type), key); - - #[cfg(feature = "mocks")] - self.dump_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height, &key); - - Ok(key) + self.get_quorum_public_key_sync_impl(quorum_type, quorum_hash, core_chain_locked_height) } fn get_data_contract( diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index aaea30430c8..153465e335d 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -19,6 +19,22 @@ pub struct WasmTrustedContext { } impl ContextProvider for WasmContext { + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> std::pin::Pin< + Box< + dyn std::future::Future> + + Send + + 'static, + >, + > { + let result = self.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height); + Box::pin(async move { result }) + } + fn get_quorum_public_key( &self, _quorum_type: u32, @@ -67,6 +83,23 @@ impl ContextProvider for WasmContext { } impl ContextProvider for WasmTrustedContext { + fn get_quorum_public_key_async( + &self, + quorum_type: u32, + quorum_hash: [u8; 32], + core_chain_locked_height: u32, + ) -> std::pin::Pin< + Box< + dyn std::future::Future> + + Send + + 'static, + >, + > { + // Delegate to the inner provider + self.inner + .get_quorum_public_key_async(quorum_type, quorum_hash, core_chain_locked_height) + } + fn get_quorum_public_key( &self, quorum_type: u32,