1#![warn(missing_docs)]
21
22pub mod client_ext;
23
24pub use self::client_ext::{BlockOrigin, ClientBlockImportExt, ClientExt};
25pub use sc_client_api::{execution_extensions::ExecutionExtensions, BadBlocks, ForkBlocks};
26pub use sc_client_db::{self, Backend, BlocksPruning};
27pub use sc_executor::{self, WasmExecutionMethod, WasmExecutor};
28pub use sc_service::{client, RpcHandlers};
29pub use sp_consensus;
30pub use sp_keyring::{Ed25519Keyring, Sr25519Keyring};
31pub use sp_keystore::{Keystore, KeystorePtr};
32pub use sp_runtime::{Storage, StorageChild};
33
34use futures::{future::Future, stream::StreamExt};
35use sc_client_api::BlockchainEvents;
36use sc_service::client::{ClientConfig, LocalCallExecutor};
37use serde::Deserialize;
38use sp_core::{storage::ChildInfo, testing::TaskExecutor};
39use sp_runtime::{
40 codec::Encode,
41 traits::{Block as BlockT, Header},
42 OpaqueExtrinsic,
43};
44use std::{
45 collections::{HashMap, HashSet},
46 pin::Pin,
47 sync::Arc,
48};
49
50pub trait GenesisInit: Default {
52 fn genesis_storage(&self) -> Storage;
54}
55
56impl GenesisInit for () {
57 fn genesis_storage(&self) -> Storage {
58 Default::default()
59 }
60}
61
62pub struct TestClientBuilder<Block: BlockT, ExecutorDispatch, Backend: 'static, G: GenesisInit> {
64 genesis_init: G,
65 child_storage_extension: HashMap<Vec<u8>, StorageChild>,
68 backend: Arc<Backend>,
69 _executor: std::marker::PhantomData<ExecutorDispatch>,
70 fork_blocks: ForkBlocks<Block>,
71 bad_blocks: BadBlocks<Block>,
72 enable_offchain_indexing_api: bool,
73 enable_import_proof_recording: bool,
74 no_genesis: bool,
75}
76
77impl<Block: BlockT, ExecutorDispatch, G: GenesisInit> Default
78 for TestClientBuilder<Block, ExecutorDispatch, Backend<Block>, G>
79{
80 fn default() -> Self {
81 Self::with_default_backend()
82 }
83}
84
85impl<Block: BlockT, ExecutorDispatch, G: GenesisInit>
86 TestClientBuilder<Block, ExecutorDispatch, Backend<Block>, G>
87{
88 pub fn with_default_backend() -> Self {
90 let backend = Arc::new(Backend::new_test(std::u32::MAX, std::u64::MAX));
91 Self::with_backend(backend)
92 }
93
94 pub fn with_pruning_window(blocks_pruning: u32) -> Self {
96 let backend = Arc::new(Backend::new_test(blocks_pruning, 0));
97 Self::with_backend(backend)
98 }
99
100 pub fn with_tx_storage(blocks_pruning: u32) -> Self {
102 let backend =
103 Arc::new(Backend::new_test_with_tx_storage(BlocksPruning::Some(blocks_pruning), 0));
104 Self::with_backend(backend)
105 }
106}
107
108impl<Block: BlockT, ExecutorDispatch, Backend, G: GenesisInit>
109 TestClientBuilder<Block, ExecutorDispatch, Backend, G>
110{
111 pub fn with_backend(backend: Arc<Backend>) -> Self {
113 TestClientBuilder {
114 backend,
115 child_storage_extension: Default::default(),
116 genesis_init: Default::default(),
117 _executor: Default::default(),
118 fork_blocks: None,
119 bad_blocks: None,
120 enable_offchain_indexing_api: false,
121 no_genesis: false,
122 enable_import_proof_recording: false,
123 }
124 }
125
126 pub fn genesis_init_mut(&mut self) -> &mut G {
128 &mut self.genesis_init
129 }
130
131 pub fn backend(&self) -> Arc<Backend> {
133 self.backend.clone()
134 }
135
136 pub fn add_child_storage(
138 mut self,
139 child_info: &ChildInfo,
140 key: impl AsRef<[u8]>,
141 value: impl AsRef<[u8]>,
142 ) -> Self {
143 let storage_key = child_info.storage_key();
144 let entry = self.child_storage_extension.entry(storage_key.to_vec()).or_insert_with(|| {
145 StorageChild { data: Default::default(), child_info: child_info.clone() }
146 });
147 entry.data.insert(key.as_ref().to_vec(), value.as_ref().to_vec());
148 self
149 }
150
151 pub fn set_block_rules(
153 mut self,
154 fork_blocks: ForkBlocks<Block>,
155 bad_blocks: BadBlocks<Block>,
156 ) -> Self {
157 self.fork_blocks = fork_blocks;
158 self.bad_blocks = bad_blocks;
159 self
160 }
161
162 pub fn enable_offchain_indexing_api(mut self) -> Self {
164 self.enable_offchain_indexing_api = true;
165 self
166 }
167
168 pub fn enable_import_proof_recording(mut self) -> Self {
170 self.enable_import_proof_recording = true;
171 self
172 }
173
174 pub fn set_no_genesis(mut self) -> Self {
176 self.no_genesis = true;
177 self
178 }
179
180 pub fn build_with_executor<RuntimeApi>(
182 self,
183 executor: ExecutorDispatch,
184 ) -> (
185 client::Client<Backend, ExecutorDispatch, Block, RuntimeApi>,
186 sc_consensus::LongestChain<Backend, Block>,
187 )
188 where
189 ExecutorDispatch:
190 sc_client_api::CallExecutor<Block> + sc_executor::RuntimeVersionOf + Clone + 'static,
191 Backend: sc_client_api::backend::Backend<Block>,
192 <Backend as sc_client_api::backend::Backend<Block>>::OffchainStorage: 'static,
193 {
194 let storage = {
195 let mut storage = self.genesis_init.genesis_storage();
196 for (key, child_content) in self.child_storage_extension {
198 storage.children_default.insert(
199 key,
200 StorageChild {
201 data: child_content.data.into_iter().collect(),
202 child_info: child_content.child_info,
203 },
204 );
205 }
206
207 storage
208 };
209
210 let client_config = ClientConfig {
211 enable_import_proof_recording: self.enable_import_proof_recording,
212 offchain_indexing_api: self.enable_offchain_indexing_api,
213 no_genesis: self.no_genesis,
214 ..Default::default()
215 };
216
217 let genesis_block_builder = sc_service::GenesisBlockBuilder::new(
218 &storage,
219 !client_config.no_genesis,
220 self.backend.clone(),
221 executor.clone(),
222 )
223 .expect("Creates genesis block builder");
224
225 let spawn_handle = Box::new(TaskExecutor::new());
226
227 let client = client::Client::new(
228 self.backend.clone(),
229 executor,
230 spawn_handle,
231 genesis_block_builder,
232 self.fork_blocks,
233 self.bad_blocks,
234 None,
235 None,
236 client_config,
237 )
238 .expect("Creates new client");
239
240 let longest_chain = sc_consensus::LongestChain::new(self.backend);
241
242 (client, longest_chain)
243 }
244}
245
246impl<Block: BlockT, H, Backend, G: GenesisInit>
247 TestClientBuilder<Block, client::LocalCallExecutor<Block, Backend, WasmExecutor<H>>, Backend, G>
248{
249 pub fn build_with_native_executor<RuntimeApi, I>(
251 self,
252 executor: I,
253 ) -> (
254 client::Client<
255 Backend,
256 client::LocalCallExecutor<Block, Backend, WasmExecutor<H>>,
257 Block,
258 RuntimeApi,
259 >,
260 sc_consensus::LongestChain<Backend, Block>,
261 )
262 where
263 I: Into<Option<WasmExecutor<H>>>,
264 Backend: sc_client_api::backend::Backend<Block> + 'static,
265 H: sc_executor::HostFunctions,
266 {
267 let executor = executor.into().unwrap_or_else(|| WasmExecutor::<H>::builder().build());
268 let executor = LocalCallExecutor::new(
269 self.backend.clone(),
270 executor.clone(),
271 Default::default(),
272 ExecutionExtensions::new(None, Arc::new(executor)),
273 )
274 .expect("Creates LocalCallExecutor");
275
276 self.build_with_executor(executor)
277 }
278}
279
280pub struct RpcTransactionOutput {
282 pub result: String,
284 pub receiver: tokio::sync::mpsc::Receiver<String>,
286}
287
288impl std::fmt::Debug for RpcTransactionOutput {
289 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
290 write!(f, "RpcTransactionOutput {{ result: {:?}, receiver }}", self.result)
291 }
292}
293
294#[derive(Deserialize, Debug)]
296pub struct RpcTransactionError {
297 pub code: i64,
299 pub message: String,
301 pub data: Option<serde_json::Value>,
303}
304
305impl std::fmt::Display for RpcTransactionError {
306 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
307 std::fmt::Debug::fmt(self, f)
308 }
309}
310
311#[async_trait::async_trait]
313pub trait RpcHandlersExt {
314 async fn send_transaction(
316 &self,
317 extrinsic: OpaqueExtrinsic,
318 ) -> Result<RpcTransactionOutput, RpcTransactionError>;
319}
320
321#[async_trait::async_trait]
322impl RpcHandlersExt for RpcHandlers {
323 async fn send_transaction(
324 &self,
325 extrinsic: OpaqueExtrinsic,
326 ) -> Result<RpcTransactionOutput, RpcTransactionError> {
327 let (result, rx) = self
328 .rpc_query(&format!(
329 r#"{{
330 "jsonrpc": "2.0",
331 "method": "author_submitExtrinsic",
332 "params": ["0x{}"],
333 "id": 0
334 }}"#,
335 array_bytes::bytes2hex("", &extrinsic.encode())
336 ))
337 .await
338 .expect("valid JSON-RPC request object; qed");
339 parse_rpc_result(result, rx)
340 }
341}
342
343pub(crate) fn parse_rpc_result(
344 result: String,
345 receiver: tokio::sync::mpsc::Receiver<String>,
346) -> Result<RpcTransactionOutput, RpcTransactionError> {
347 let json: serde_json::Value =
348 serde_json::from_str(&result).expect("the result can only be a JSONRPC string; qed");
349 let error = json.as_object().expect("JSON result is always an object; qed").get("error");
350
351 if let Some(error) = error {
352 return Err(serde_json::from_value(error.clone())
353 .expect("the JSONRPC result's error is always valid; qed"))
354 }
355
356 Ok(RpcTransactionOutput { result, receiver })
357}
358
359pub trait BlockchainEventsExt<C, B>
361where
362 C: BlockchainEvents<B>,
363 B: BlockT,
364{
365 fn wait_for_blocks(&self, count: usize) -> Pin<Box<dyn Future<Output = ()> + Send>>;
369}
370
371impl<C, B> BlockchainEventsExt<C, B> for C
372where
373 C: BlockchainEvents<B>,
374 B: BlockT,
375{
376 fn wait_for_blocks(&self, count: usize) -> Pin<Box<dyn Future<Output = ()> + Send>> {
377 assert!(count > 0, "'count' argument must be greater than 0");
378
379 let mut import_notification_stream = self.import_notification_stream();
380 let mut blocks = HashSet::new();
381
382 Box::pin(async move {
383 while let Some(notification) = import_notification_stream.next().await {
384 if notification.is_new_best {
385 blocks.insert(*notification.header.number());
386 if blocks.len() == count {
387 break
388 }
389 }
390 }
391 })
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 #[test]
398 fn parses_error_properly() {
399 let (_, rx) = tokio::sync::mpsc::channel(1);
400 assert!(super::parse_rpc_result(
401 r#"{
402 "jsonrpc": "2.0",
403 "result": 19,
404 "id": 1
405 }"#
406 .to_string(),
407 rx
408 )
409 .is_ok());
410
411 let (_, rx) = tokio::sync::mpsc::channel(1);
412 let error = super::parse_rpc_result(
413 r#"{
414 "jsonrpc": "2.0",
415 "error": {
416 "code": -32601,
417 "message": "Method not found"
418 },
419 "id": 1
420 }"#
421 .to_string(),
422 rx,
423 )
424 .unwrap_err();
425 assert_eq!(error.code, -32601);
426 assert_eq!(error.message, "Method not found");
427 assert!(error.data.is_none());
428
429 let (_, rx) = tokio::sync::mpsc::channel(1);
430 let error = super::parse_rpc_result(
431 r#"{
432 "jsonrpc": "2.0",
433 "error": {
434 "code": -32601,
435 "message": "Method not found",
436 "data": 42
437 },
438 "id": 1
439 }"#
440 .to_string(),
441 rx,
442 )
443 .unwrap_err();
444 assert_eq!(error.code, -32601);
445 assert_eq!(error.message, "Method not found");
446 assert!(error.data.is_some());
447 }
448}