Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: fix submission watcher test #5358

Merged
merged 2 commits into from
Oct 30, 2024
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
20 changes: 15 additions & 5 deletions engine/src/state_chain_observer/client/base_rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ use sc_rpc_api::{
system::{Health, SystemApiClient},
};

use futures::future::BoxFuture;
use futures::{future::BoxFuture, Stream};
use serde_json::value::RawValue;
use std::sync::Arc;
use std::{pin::Pin, sync::Arc};
use subxt::backend::rpc::RawRpcSubscription;

use super::RpcResult;
Expand Down Expand Up @@ -75,6 +75,14 @@ impl<
{
}

pub type WatchExtrinsicStream = Pin<
Box<
dyn Stream<
Item = Result<TransactionStatus<sp_core::H256, sp_core::H256>, serde_json::Error>,
> + Send,
>,
>;

/// Wraps the substrate client library methods. This trait allows us to mock a State Chain RPC.
/// It assumes that provided block_hash's are valid as we would have gotten them from the
/// RPC itself, and so it panics if a provided block_hash is invalid i.e. doesn't exist.
Expand All @@ -99,7 +107,7 @@ pub trait BaseRpcApi {
async fn submit_and_watch_extrinsic(
&self,
extrinsic: state_chain_runtime::UncheckedExtrinsic,
) -> RpcResult<Subscription<TransactionStatus<sp_core::H256, sp_core::H256>>>;
) -> RpcResult<WatchExtrinsicStream>;

async fn storage(
&self,
Expand Down Expand Up @@ -215,8 +223,10 @@ impl<RawRpcClient: RawRpcApi + Send + Sync> BaseRpcApi for BaseRpcClient<RawRpcC
async fn submit_and_watch_extrinsic(
&self,
extrinsic: state_chain_runtime::UncheckedExtrinsic,
) -> RpcResult<Subscription<TransactionStatus<sp_core::H256, sp_core::H256>>> {
self.raw_rpc_client.watch_extrinsic(Bytes::from(extrinsic.encode())).await
) -> RpcResult<WatchExtrinsicStream> {
let subscription =
self.raw_rpc_client.watch_extrinsic(Bytes::from(extrinsic.encode())).await?;
Ok(Box::pin(subscription) as WatchExtrinsicStream)
}

async fn storage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::state_chain_observer::client::{
storage_api::{CheckBlockCompatibility, StorageApi},
SUBSTRATE_BEHAVIOUR,
};
use futures::StreamExt;
msgmaxim marked this conversation as resolved.
Show resolved Hide resolved
use jsonrpsee::{core::ClientError, types::ErrorObjectOwned};

use super::signer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,127 +1,118 @@
// TODO: In the latest jsonrpsee, Subscription::new is now private. This makes mocking
// harder to do. Also this is possibly the reason Mockall fails to create mocks
// because some internal types no longer support "Default" impl.
// We need to either re-think about this test, or use a different Mock mechanism to test this.

// use base_rpc_api::BaseRpcClient;
// use cf_chains::{dot, ChainState};
// use futures_util::FutureExt;
// use jsonrpsee::{
// core::client::{Subscription, SubscriptionKind},
// types::ErrorObject,
// };
// use cf_utilities::task_scope::task_scope;

// use crate::{
// constants::SIGNED_EXTRINSIC_LIFETIME,
// state_chain_observer::client::base_rpc_api::MockBaseRpcApi,
// };

// use super::*;

// const INITIAL_NONCE: state_chain_runtime::Nonce = 10;

// /// If the tx fails due to a bad proof, it should fetch the runtime version and retry.
// #[tokio::test]
// async fn should_update_version_on_bad_proof() {
// task_scope(|scope| {
// async {
// let mut mock_rpc_api = MockBaseRpcApi::new();

// mock_rpc_api.expect_next_account_nonce().return_once(move |_| Ok(1));
// mock_rpc_api.expect_submit_and_watch_extrinsic().times(1).returning(move |_| {
// Err(ErrorObject::owned(
// 1010,
// "Invalid Transaction",
// Some("Transaction has a bad signature"),
// ))
// });

// mock_rpc_api.expect_runtime_version().times(1).returning(move |_| {
// let new_runtime_version = sp_version::RuntimeVersion {
// spec_name: "test".into(),
// impl_name: "test".into(),
// authoring_version: 0,
// spec_version: 0,
// impl_version: 0,
// apis: vec![].into(),
// transaction_version: 0,
// state_version: 0,
// };
// assert_ne!(
// new_runtime_version,
// Default::default(),
// "The new runtime version must be different from the version that the watcher started with"
// );

// Ok(new_runtime_version)
// });

// // On the retry, return a success.
// mock_rpc_api.expect_next_account_nonce().return_once(move |_| Ok(1));

// expect_submit_and_watch_extrinsic().return_once(move |_| { Ok(Subscription::new(
// futures::channel::mpsc::channel(1).0,
// futures::channel::mpsc::channel(1).1,
// SubscriptionKind::Subscription(jsonrpsee::types::SubscriptionId::Num(0)),
// ))
// });

// let _watcher = new_watcher_and_submit_test_extrinsic(scope, mock_rpc_api).await;

// Ok(())
// }
// .boxed()
// })
// .await
// .unwrap();
// }

// /// Create a new watcher and submit a dummy extrinsic.
// async fn new_watcher_and_submit_test_extrinsic<'a, 'env>(
// scope: &'a Scope<'env, anyhow::Error>,
// mock_rpc_api: MockBaseRpcApi,
// ) -> SubmissionWatcher<'a, 'env, MockBaseRpcApi> {
// let (mut watcher, _requests) = SubmissionWatcher::new(
// scope,
// signer::PairSigner::new(sp_core::Pair::generate().0),
// INITIAL_NONCE,
// H256::default(),
// 0,
// Default::default(),
// H256::default(),
// SIGNED_EXTRINSIC_LIFETIME,
// Arc::new(mock_rpc_api),
// );

// // Just some dummy call to test with
// let call =
// state_chain_runtime::RuntimeCall::Witnesser(pallet_cf_witnesser::Call::witness_at_epoch {
// call: Box::new(state_chain_runtime::RuntimeCall::PolkadotChainTracking(
// pallet_cf_chain_tracking::Call::update_chain_state {
// new_chain_state: ChainState {
// block_height: 0,
// tracked_data: dot::PolkadotTrackedData {
// median_tip: 0,
// runtime_version: Default::default(),
// },
// },
// },
// )),
// epoch_index: 0,
// });
// let mut request = Request {
// id: 0,
// next_submission_id: 0,
// pending_submissions: Default::default(),
// strictly_one_submission: false,
// resubmit_window: ..=1,
// call,
// until_in_block_sender: Some(oneshot::channel().0),
// until_finalized_sender: oneshot::channel().0,
// };

// let _result = watcher.submit_extrinsic(&mut request).await;

// watcher
// }
use base_rpc_api::WatchExtrinsicStream;
use cf_chains::{dot, ChainState};
use cf_utilities::task_scope::task_scope;
use futures::stream;
use futures_util::FutureExt;
use jsonrpsee::types::ErrorObject;

use crate::{
constants::SIGNED_EXTRINSIC_LIFETIME,
state_chain_observer::client::base_rpc_api::MockBaseRpcApi,
};

use super::*;

const INITIAL_NONCE: state_chain_runtime::Nonce = 10;

/// If the tx fails due to a bad proof, it should fetch the runtime version and retry.
#[tokio::test]
async fn should_update_version_on_bad_proof() {
task_scope(|scope| {
async {
let mut mock_rpc_api = MockBaseRpcApi::new();

mock_rpc_api.expect_next_account_nonce().return_once(move |_| Ok(1));
mock_rpc_api.expect_submit_and_watch_extrinsic().times(1).returning(move |_| {
Err(ErrorObject::owned(
1010,
"Invalid Transaction",
Some("Transaction has a bad signature"),
)
.into())
});

mock_rpc_api.expect_runtime_version().times(1).returning(move |_| {
let new_runtime_version = sp_version::RuntimeVersion {
spec_name: "test".into(),
impl_name: "test".into(),
authoring_version: 0,
spec_version: 0,
impl_version: 0,
apis: vec![].into(),
transaction_version: 0,
state_version: 0,
};
assert_ne!(
new_runtime_version,
Default::default(),
"The new runtime version must be different from the version that the watcher started with"
);

Ok(new_runtime_version)
});

// On the retry, return a success.
mock_rpc_api.expect_next_account_nonce().return_once(move |_| Ok(1));

mock_rpc_api
.expect_submit_and_watch_extrinsic()
.return_once(move |_| Ok(Box::pin(stream::empty()) as WatchExtrinsicStream));

let _watcher = new_watcher_and_submit_test_extrinsic(scope, mock_rpc_api).await;

Ok(())
}
.boxed()
})
.await
.unwrap();
}

/// Create a new watcher and submit a dummy extrinsic.
async fn new_watcher_and_submit_test_extrinsic<'a, 'env>(
scope: &'a Scope<'env, anyhow::Error>,
mock_rpc_api: MockBaseRpcApi,
) -> SubmissionWatcher<'a, 'env, MockBaseRpcApi> {
let (mut watcher, _requests) = SubmissionWatcher::new(
scope,
signer::PairSigner::new(sp_core::Pair::generate().0),
INITIAL_NONCE,
H256::default(),
0,
Default::default(),
H256::default(),
SIGNED_EXTRINSIC_LIFETIME,
Arc::new(mock_rpc_api),
);

// Just some dummy call to test with
let call =
state_chain_runtime::RuntimeCall::Witnesser(pallet_cf_witnesser::Call::witness_at_epoch {
call: Box::new(state_chain_runtime::RuntimeCall::PolkadotChainTracking(
pallet_cf_chain_tracking::Call::update_chain_state {
new_chain_state: ChainState {
block_height: 0,
tracked_data: dot::PolkadotTrackedData {
median_tip: 0,
runtime_version: Default::default(),
},
},
},
)),
epoch_index: 0,
});
let mut request = Request {
id: 0,
next_submission_id: 0,
pending_submissions: Default::default(),
strictly_one_submission: false,
resubmit_window: ..=1,
call,
until_in_block_sender: Some(oneshot::channel().0),
until_finalized_sender: oneshot::channel().0,
};

let _result = watcher.submit_extrinsic(&mut request).await;

watcher
}
Loading