Skip to content

Commit

Permalink
fix(sdk)!: mock sdk cannot find quorum keys in offline mode (#2061)
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek authored Aug 20, 2024
1 parent 0f0e301 commit 1ba00e0
Show file tree
Hide file tree
Showing 348 changed files with 162 additions and 129 deletions.
71 changes: 44 additions & 27 deletions packages/rs-sdk/src/mock/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
//! See [MockDashPlatformSdk] for more details.
use crate::{
platform::{types::identity::IdentityRequest, DocumentQuery, Fetch, FetchMany, Query},
Error,
Error, Sdk,
};
use arc_swap::ArcSwapOption;
use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
use dapi_grpc::{
mock::Mockable,
platform::v0::{self as proto},
};
use dpp::dashcore::Network;
use dpp::version::PlatformVersion;
use drive_proof_verifier::{error::ContextProviderError, FromProof, MockContextProvider};
use drive_proof_verifier::{error::ContextProviderError, ContextProvider, FromProof};
use rs_dapi_client::mock::MockError;
use rs_dapi_client::{
mock::{Key, MockDapiClient},
Expand All @@ -34,47 +35,55 @@ use super::MockResponse;
/// ## Panics
///
/// Can panic on errors.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct MockDashPlatformSdk {
from_proof_expectations: BTreeMap<Key, Vec<u8>>,
platform_version: &'static PlatformVersion,
dapi: Arc<Mutex<MockDapiClient>>,
prove: bool,
quorum_provider: Option<Arc<MockContextProvider>>,
sdk: ArcSwapOption<Sdk>,
}

impl MockDashPlatformSdk {
pub(crate) fn new(
version: &'static PlatformVersion,
dapi: Arc<Mutex<MockDapiClient>>,
prove: bool,
) -> Self {
/// Returns true when requests should use proofs.
///
/// ## Panics
///
/// Panics when sdk is not set during initialization.
pub fn prove(&self) -> bool {
if let Some(sdk) = self.sdk.load().as_ref() {
sdk.prove()
} else {
panic!("sdk must be set when creating mock ")
}
}

/// Create new mock SDK.
///
/// ## Note
///
/// You have to call [MockDashPlatformSdk::with_sdk()] to set sdk, otherwise Mock SDK will panic.
pub(crate) fn new(version: &'static PlatformVersion, dapi: Arc<Mutex<MockDapiClient>>) -> Self {
Self {
from_proof_expectations: Default::default(),
platform_version: version,
dapi,
prove,
quorum_provider: None,
sdk: ArcSwapOption::new(None),
}
}

pub(crate) fn version<'v>(&self) -> &'v PlatformVersion {
self.platform_version
pub(crate) fn set_sdk(&mut self, sdk: Sdk) {
self.sdk.store(Some(Arc::new(sdk)));
}
/// Define a directory where files containing quorum information, like quorum public keys, are stored.
///
/// This directory will be used to load quorum information from files.
/// You can use [SdkBuilder::with_dump_dir()](crate::SdkBuilder::with_dump_dir()) to generate these files.
pub fn quorum_info_dir<P: AsRef<std::path::Path>>(&mut self, dir: P) -> &mut Self {
let mut provider = MockContextProvider::new();
provider.quorum_keys_dir(Some(dir.as_ref().to_path_buf()));
self.quorum_provider = Some(Arc::new(provider));

self
pub(crate) fn version<'v>(&self) -> &'v PlatformVersion {
self.platform_version
}

/// Load all expectations from files in a directory.
///
///
/// By default, mock expectations are loaded when Sdk is built with [SdkBuilder::build()](crate::SdkBuilder::build()).
/// This function can be used to load expectations after the Sdk is created, or use alternative location.
/// Expectation files must be prefixed with [DapiClient::DUMP_FILE_PREFIX] and
/// have `.json` extension.
pub async fn load_expectations<P: AsRef<std::path::Path>>(
Expand Down Expand Up @@ -278,7 +287,7 @@ impl MockDashPlatformSdk {
where
<<O as Fetch>::Request as TransportRequest>::Response: Default,
{
let grpc_request = query.query(self.prove).expect("query must be correct");
let grpc_request = query.query(self.prove()).expect("query must be correct");
self.expect(grpc_request, object).await?;

Ok(self)
Expand Down Expand Up @@ -332,7 +341,7 @@ impl MockDashPlatformSdk {
Response = <<O as FetchMany<K, R>>::Request as TransportRequest>::Response,
> + Sync,
{
let grpc_request = query.query(self.prove).expect("query must be correct");
let grpc_request = query.query(self.prove()).expect("query must be correct");
self.expect(grpc_request, objects).await?;

Ok(self)
Expand Down Expand Up @@ -393,7 +402,7 @@ impl MockDashPlatformSdk {
),
None => {
let version = self.version();
let provider = self.quorum_provider.as_ref()
let provider = self.context_provider()
.ok_or(ContextProviderError::InvalidQuorum(
"expectation not found and quorum info provider not initialized with sdk.mock().quorum_info_dir()".to_string()
))?;
Expand All @@ -402,11 +411,19 @@ impl MockDashPlatformSdk {
response,
Network::Regtest,
version,
provider,
&provider,
)?
}
};

Ok(data)
}
/// Return context provider implementation defined for upstreeam Sdk object.
fn context_provider(&self) -> Option<impl ContextProvider> {
if let Some(sdk) = self.sdk.load_full() {
sdk.clone().context_provider()
} else {
None
}
}
}
28 changes: 22 additions & 6 deletions packages/rs-sdk/src/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,21 +796,37 @@ impl SdkBuilder {
None => {
let dapi =Arc::new(tokio::sync::Mutex::new( MockDapiClient::new()));
// We create mock context provider that will use the mock DAPI client to retrieve data contracts.
let context_provider = self.context_provider.unwrap_or(Box::new(MockContextProvider::new()));

Sdk {
let context_provider = self.context_provider.unwrap_or_else(||{
let mut cp=MockContextProvider::new();
if let Some(ref dump_dir) = self.dump_dir {
cp.quorum_keys_dir(Some(dump_dir.clone()));
}
Box::new(cp)
}
);
let mock_sdk = MockDashPlatformSdk::new(self.version, Arc::clone(&dapi));
let mock_sdk = Arc::new(Mutex::new(mock_sdk));
let sdk= Sdk {
network: self.network,
inner:SdkInstance::Mock {
mock:Arc::new(Mutex::new( MockDashPlatformSdk::new(self.version, Arc::clone(&dapi), self.proofs))),
mock:mock_sdk.clone(),
dapi,
version:self.version,

},
dump_dir: self.dump_dir,
dump_dir: self.dump_dir.clone(),
proofs:self.proofs,
internal_cache: Default::default(),
context_provider:Some(Arc::new(context_provider)),
cancel_token: self.cancel_token,
}
};
let mut guard = mock_sdk.try_lock().expect("mock sdk is in use by another thread and connot be reconfigured");
guard.set_sdk(sdk.clone());
if let Some(ref dump_dir) = self.dump_dir {
pollster::block_on( guard.load_expectations(dump_dir))?;
};

sdk
},
#[cfg(not(feature = "mocks"))]
None => return Err(Error::Config("Mock mode is not available. Please enable `mocks` feature or provide address list.".to_string())),
Expand Down
28 changes: 13 additions & 15 deletions packages/rs-sdk/tests/fetch/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub struct Config {
#[serde(default = "Config::default_document_id")]
pub existing_document_id: Identifier,
// Hex-encoded ProTxHash of the existing HP masternode
#[serde(default)]
#[serde(default = "Config::default_protxhash")]
pub masternode_owner_pro_reg_tx_hash: String,
}

Expand Down Expand Up @@ -208,18 +208,10 @@ impl Config {
// offline testing takes precedence over network testing
#[cfg(feature = "offline-testing")]
let sdk = {
let mut mock_sdk = dash_sdk::SdkBuilder::new_mock()
dash_sdk::SdkBuilder::new_mock()
.with_dump_dir(&dump_dir)
.build()
.expect("initialize api");

mock_sdk
.mock()
.quorum_info_dir(&dump_dir)
.load_expectations(&dump_dir)
.await
.expect("load expectations");

mock_sdk
.expect("initialize api")
};

sdk
Expand All @@ -231,11 +223,10 @@ impl Config {
// Next time we need to do it again and update this value :(. This is terrible.
// We should automate creation of identity for SDK tests when we have time.
Identifier::from_string(
"J2aTnrrc8eea3pQBY91QisM3QH5FM9JK11mQCVwxeMqj",
Encoding::Base58,
"a1534e47f60be71e823a9dbc9ceb6d3ea9f1ebde7a3773f03e49ef31c7d9c044",
Encoding::Hex,
)
.unwrap()
.into()
}

fn default_data_contract_id() -> Identifier {
Expand All @@ -255,6 +246,13 @@ impl Config {
.join("vectors")
}

/// Existing masternode proTxHash. Must be updated every time test vectors are regenerated.
///
/// See documentation of [contested_resource_identity_votes_ok](super::contested_resource_identity_votes::contested_resource_identity_votes_ok).
fn default_protxhash() -> String {
String::from("d10bf435af7c75f5b07b09486af1212469d69fdc787589548e315776bc1052a1")
}

/// Return ProTxHash of an existing evo node, or None if not set
pub fn existing_protxhash(&self) -> Result<ProTxHash, String> {
hex::decode(&self.masternode_owner_pro_reg_tx_hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ async fn contested_resource_identity_votes_not_found() {
/// 0. Ensure you have at least 1 contested DPNS name in the system.
/// See [check_mn_voting_prerequisities](super::contested_resource::check_mn_voting_prerequisities) for more details.
///
/// 1. Grep log output of `yarn setup` to find `ProRegTx transaction ID` and `Owner Private Key`.
/// Use `ProRegTx transaction ID` to set `DASH_SDK_MASTERNODE_OWNER_PRO_REG_TX_HASH` in `packages/rs-sdk/tests/.env`.
/// 1. Grep log output of `yarn setup` (see logs/setup.log) to find `ProRegTx transaction ID` and `Owner Private Key`:
/// ```bash
/// egrep '(ProRegTx transaction ID|Owner Private Key)' logs/setup.log|head -n2
/// ```
/// Hardcode `ProRegTx transaction ID` in [Config::default_protxhash].
///
/// 2. Load masternode identity into [rs-platform-explorer](https://github.com/dashpay/rs-platform-explorer/):
///
Expand All @@ -78,7 +81,7 @@ async fn contested_resource_identity_votes_not_found() {
ignore = "requires manual DPNS names setup for masternode voting tests; see docs of contested_resource_identity_votes_ok()"
)]
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn contested_resource_identity_votes_ok() {
pub(super) async fn contested_resource_identity_votes_ok() {
setup_logs();

let cfg = Config::new();
Expand Down
3 changes: 1 addition & 2 deletions packages/rs-sdk/tests/fetch/data_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,10 @@ async fn test_data_contracts_2_nx() {
not(feature = "offline-testing"),
ignore = "This test requires special procedure to regenerate test vectors"
)]
#[ignore = "todo: Lukazs to re-enable"]
async fn test_data_contract_history_read() {
let cfg = Config::new();
let id = Identifier::from_string(
"df73e40f62ab71d99254037226093f38f683b28cf552f3459d2bdc28614191e0",
"20d16030541c0494e84064e2e72b5ec620546305849a2f9d5893a5e65072364d",
Encoding::Hex,
)
.unwrap();
Expand Down
Loading

0 comments on commit 1ba00e0

Please sign in to comment.