mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-15 10:21:05 +00:00
chainHead: Produce method responses on chainHead_follow (#14692)
* chainHead/api: Make storage/body/call pure RPC methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Add mpsc channel between RPC methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/subscriptions: Extract mpsc::Sender via BlockGuard Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/subscriptions: Generate and provide the method operation ID Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Generate `chainHead_body` response Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Generate `chainHead_call` response Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Generate `chainHead_storage` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Propagate responses of methods to chainHead_follow Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Adjust `chainHead_body` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Adjust `chainHead_call` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Adjust `chainHead_call` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Ensure unique operation IDs across methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/events: Remove old method events Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Return `InvalidBlock` error if pinning fails Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Wrap subscription IDs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Ensure separate operation IDs across subscriptions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::chain_head::{
|
||||
event::{ChainHeadStorageEvent, StorageQuery, StorageQueryType, StorageResultType},
|
||||
event::{MethodResponse, StorageQuery, StorageQueryType, StorageResultType},
|
||||
test_utils::ChainHeadMockClient,
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ use sp_core::{
|
||||
Blake2Hasher, Hasher,
|
||||
};
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
use substrate_test_runtime::Transfer;
|
||||
use substrate_test_runtime_client::{
|
||||
prelude::*, runtime, runtime::RuntimeApi, Backend, BlockBuilderExt, Client,
|
||||
@@ -330,29 +330,34 @@ async fn get_body() {
|
||||
let block_hash = format!("{:?}", block.header.hash());
|
||||
let invalid_hash = hex_string(&INVALID_HASH);
|
||||
|
||||
// Subscription ID is stale the disjoint event is emitted.
|
||||
let mut sub = api
|
||||
.subscribe("chainHead_unstable_body", ["invalid_sub_id", &invalid_hash])
|
||||
// Subscription ID is invalid.
|
||||
let response: MethodResponse = api
|
||||
.call("chainHead_unstable_body", ["invalid_sub_id", &invalid_hash])
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadEvent<String> = get_next_event(&mut sub).await;
|
||||
assert_eq!(event, ChainHeadEvent::<String>::Disjoint);
|
||||
assert_matches!(response, MethodResponse::LimitReached);
|
||||
|
||||
// Valid subscription ID with invalid block hash will error.
|
||||
// Block hash is invalid.
|
||||
let err = api
|
||||
.subscribe("chainHead_unstable_body", [&sub_id, &invalid_hash])
|
||||
.call::<_, serde_json::Value>("chainHead_unstable_body", [&sub_id, &invalid_hash])
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == 2001 && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Obtain valid the body (list of extrinsics).
|
||||
let mut sub = api.subscribe("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap();
|
||||
let event: ChainHeadEvent<String> = get_next_event(&mut sub).await;
|
||||
// Block contains no extrinsics.
|
||||
assert_matches!(event,
|
||||
ChainHeadEvent::Done(done) if done.result == "0x00"
|
||||
// Valid call.
|
||||
let response: MethodResponse =
|
||||
api.call("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
// Response propagated to `chainHead_follow`.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationBodyDone(done) if done.operation_id == operation_id && done.value.is_empty()
|
||||
);
|
||||
|
||||
// Import a block with extrinsics.
|
||||
@@ -378,35 +383,41 @@ async fn get_body() {
|
||||
FollowEvent::BestBlockChanged(_)
|
||||
);
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap();
|
||||
let event: ChainHeadEvent<String> = get_next_event(&mut sub).await;
|
||||
// Hex encoded scale encoded string for the vector of extrinsics.
|
||||
let expected = hex_string(&block.extrinsics.encode());
|
||||
assert_matches!(event,
|
||||
ChainHeadEvent::Done(done) if done.result == expected
|
||||
// Valid call to a block with extrinsics.
|
||||
let response: MethodResponse =
|
||||
api.call("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
// Response propagated to `chainHead_follow`.
|
||||
let expected_tx = hex_string(&block.extrinsics[0].encode());
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationBodyDone(done) if done.operation_id == operation_id && done.value == vec![expected_tx]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn call_runtime() {
|
||||
let (_client, api, _sub, sub_id, block) = setup_api().await;
|
||||
let (_client, api, mut block_sub, sub_id, block) = setup_api().await;
|
||||
let block_hash = format!("{:?}", block.header.hash());
|
||||
let invalid_hash = hex_string(&INVALID_HASH);
|
||||
|
||||
// Subscription ID is stale the disjoint event is emitted.
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
// Subscription ID is invalid.
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_call",
|
||||
["invalid_sub_id", &block_hash, "BabeApi_current_epoch", "0x00"],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadEvent<String> = get_next_event(&mut sub).await;
|
||||
assert_eq!(event, ChainHeadEvent::<String>::Disjoint);
|
||||
assert_matches!(response, MethodResponse::LimitReached);
|
||||
|
||||
// Valid subscription ID with invalid block hash will error.
|
||||
// Block hash is invalid.
|
||||
let err = api
|
||||
.subscribe(
|
||||
.call::<_, serde_json::Value>(
|
||||
"chainHead_unstable_call",
|
||||
[&sub_id, &invalid_hash, "BabeApi_current_epoch", "0x00"],
|
||||
)
|
||||
@@ -418,8 +429,9 @@ async fn call_runtime() {
|
||||
|
||||
// Pass an invalid parameters that cannot be decode.
|
||||
let err = api
|
||||
.subscribe(
|
||||
.call::<_, serde_json::Value>(
|
||||
"chainHead_unstable_call",
|
||||
// 0x0 is invalid.
|
||||
[&sub_id, &block_hash, "BabeApi_current_epoch", "0x0"],
|
||||
)
|
||||
.await
|
||||
@@ -428,34 +440,43 @@ async fn call_runtime() {
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == 2003 && err.message().contains("Invalid parameter")
|
||||
);
|
||||
|
||||
// Valid call.
|
||||
let alice_id = AccountKeyring::Alice.to_account_id();
|
||||
// Hex encoded scale encoded bytes representing the call parameters.
|
||||
let call_parameters = hex_string(&alice_id.encode());
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_call",
|
||||
[&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
// Response propagated to `chainHead_follow`.
|
||||
assert_matches!(
|
||||
get_next_event::<ChainHeadEvent<String>>(&mut sub).await,
|
||||
ChainHeadEvent::Done(done) if done.result == "0x0000000000000000"
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationCallDone(done) if done.operation_id == operation_id && done.output == "0x0000000000000000"
|
||||
);
|
||||
|
||||
// The `current_epoch` takes no parameters and not draining the input buffer
|
||||
// will cause the execution to fail.
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
"chainHead_unstable_call",
|
||||
[&sub_id, &block_hash, "BabeApi_current_epoch", "0x00"],
|
||||
)
|
||||
let response: MethodResponse = api
|
||||
.call("chainHead_unstable_call", [&sub_id, &block_hash, "BabeApi_current_epoch", "0x00"])
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
// Error propagated to `chainHead_follow`.
|
||||
assert_matches!(
|
||||
get_next_event::<ChainHeadEvent<String>>(&mut sub).await,
|
||||
ChainHeadEvent::Error(event) if event.error.contains("Execution failed")
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationError(error) if error.operation_id == operation_id && error.error.contains("Execution failed")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -501,7 +522,7 @@ async fn call_runtime_without_flag() {
|
||||
let alice_id = AccountKeyring::Alice.to_account_id();
|
||||
let call_parameters = hex_string(&alice_id.encode());
|
||||
let err = api
|
||||
.subscribe(
|
||||
.call::<_, serde_json::Value>(
|
||||
"chainHead_unstable_call",
|
||||
[&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters],
|
||||
)
|
||||
@@ -520,9 +541,9 @@ async fn get_storage_hash() {
|
||||
let invalid_hash = hex_string(&INVALID_HASH);
|
||||
let key = hex_string(&KEY);
|
||||
|
||||
// Subscription ID is stale the disjoint event is emitted.
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
// Subscription ID is invalid.
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
"invalid_sub_id",
|
||||
@@ -532,12 +553,11 @@ async fn get_storage_hash() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_eq!(event, ChainHeadStorageEvent::Disjoint);
|
||||
assert_matches!(response, MethodResponse::LimitReached);
|
||||
|
||||
// Valid subscription ID with invalid block hash will error.
|
||||
// Block hash is invalid.
|
||||
let err = api
|
||||
.subscribe(
|
||||
.call::<_, serde_json::Value>(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -552,8 +572,8 @@ async fn get_storage_hash() {
|
||||
);
|
||||
|
||||
// Valid call without storage at the key.
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -563,9 +583,15 @@ async fn get_storage_hash() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key does not have any value associated.
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Import a new block with storage changes.
|
||||
let mut builder = client.new_block(Default::default()).unwrap();
|
||||
@@ -585,9 +611,8 @@ async fn get_storage_hash() {
|
||||
);
|
||||
|
||||
// Valid call with storage at the key.
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&VALUE));
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -597,17 +622,30 @@ async fn get_storage_hash() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Items(res) if res.items.len() == 1 && res.items[0].key == key && res.items[0].result == StorageResultType::Hash(expected_hash));
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&VALUE));
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id &&
|
||||
res.items.len() == 1 &&
|
||||
res.items[0].key == key && res.items[0].result == StorageResultType::Hash(expected_hash)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Child value set in `setup_api`.
|
||||
let child_info = hex_string(&CHILD_STORAGE_KEY);
|
||||
let genesis_hash = format!("{:?}", client.genesis_hash());
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&CHILD_VALUE));
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
|
||||
// Valid call with storage at the key.
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -618,10 +656,22 @@ async fn get_storage_hash() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Items(res) if res.items.len() == 1 && res.items[0].key == key && res.items[0].result == StorageResultType::Hash(expected_hash));
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&CHILD_VALUE));
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id &&
|
||||
res.items.len() == 1 &&
|
||||
res.items[0].key == key && res.items[0].result == StorageResultType::Hash(expected_hash)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -647,10 +697,8 @@ async fn get_storage_multi_query_iter() {
|
||||
);
|
||||
|
||||
// Valid call with storage at the key.
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&VALUE));
|
||||
let expected_value = hex_string(&VALUE);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -669,22 +717,34 @@ async fn get_storage_multi_query_iter() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Items(res) if res.items.len() == 2 &&
|
||||
res.items[0].key == key &&
|
||||
res.items[1].key == key &&
|
||||
res.items[0].result == StorageResultType::Hash(expected_hash) &&
|
||||
res.items[1].result == StorageResultType::Value(expected_value));
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&VALUE));
|
||||
let expected_value = hex_string(&VALUE);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id &&
|
||||
res.items.len() == 2 &&
|
||||
res.items[0].key == key &&
|
||||
res.items[1].key == key &&
|
||||
res.items[0].result == StorageResultType::Hash(expected_hash) &&
|
||||
res.items[1].result == StorageResultType::Value(expected_value)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Child value set in `setup_api`.
|
||||
let child_info = hex_string(&CHILD_STORAGE_KEY);
|
||||
let genesis_hash = format!("{:?}", client.genesis_hash());
|
||||
let expected_hash = format!("{:?}", Blake2Hasher::hash(&CHILD_VALUE));
|
||||
let expected_value = hex_string(&CHILD_VALUE);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -704,14 +764,24 @@ async fn get_storage_multi_query_iter() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Items(res) if res.items.len() == 2 &&
|
||||
res.items[0].key == key &&
|
||||
res.items[1].key == key &&
|
||||
res.items[0].result == StorageResultType::Hash(expected_hash) &&
|
||||
res.items[1].result == StorageResultType::Value(expected_value));
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id &&
|
||||
res.items.len() == 2 &&
|
||||
res.items[0].key == key &&
|
||||
res.items[1].key == key &&
|
||||
res.items[0].result == StorageResultType::Hash(expected_hash) &&
|
||||
res.items[1].result == StorageResultType::Value(expected_value)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -721,9 +791,9 @@ async fn get_storage_value() {
|
||||
let invalid_hash = hex_string(&INVALID_HASH);
|
||||
let key = hex_string(&KEY);
|
||||
|
||||
// Subscription ID is stale the disjoint event is emitted.
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
// Subscription ID is invalid.
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
"invalid_sub_id",
|
||||
@@ -733,12 +803,11 @@ async fn get_storage_value() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_eq!(event, ChainHeadStorageEvent::Disjoint);
|
||||
assert_matches!(response, MethodResponse::LimitReached);
|
||||
|
||||
// Valid subscription ID with invalid block hash will error.
|
||||
// Block hash is invalid.
|
||||
let err = api
|
||||
.subscribe(
|
||||
.call::<_, serde_json::Value>(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -753,8 +822,8 @@ async fn get_storage_value() {
|
||||
);
|
||||
|
||||
// Valid call without storage at the key.
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -764,9 +833,15 @@ async fn get_storage_value() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key does not have any value associated.
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Import a new block with storage changes.
|
||||
let mut builder = client.new_block(Default::default()).unwrap();
|
||||
@@ -786,9 +861,8 @@ async fn get_storage_value() {
|
||||
);
|
||||
|
||||
// Valid call with storage at the key.
|
||||
let expected_value = hex_string(&VALUE);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -798,17 +872,29 @@ async fn get_storage_value() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Items(res) if res.items.len() == 1 && res.items[0].key == key && res.items[0].result == StorageResultType::Value(expected_value));
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
let expected_value = hex_string(&VALUE);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id &&
|
||||
res.items.len() == 1 &&
|
||||
res.items[0].key == key && res.items[0].result == StorageResultType::Value(expected_value)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Child value set in `setup_api`.
|
||||
let child_info = hex_string(b"child");
|
||||
let child_info = hex_string(&CHILD_STORAGE_KEY);
|
||||
let genesis_hash = format!("{:?}", client.genesis_hash());
|
||||
let expected_value = hex_string(&CHILD_VALUE);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -819,15 +905,28 @@ async fn get_storage_value() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Items(res) if res.items.len() == 1 && res.items[0].key == key && res.items[0].result == StorageResultType::Value(expected_value));
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
|
||||
let expected_value = hex_string(&CHILD_VALUE);
|
||||
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id &&
|
||||
res.items.len() == 1 &&
|
||||
res.items[0].key == key && res.items[0].result == StorageResultType::Value(expected_value)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_storage_wrong_key() {
|
||||
let (mut _client, api, mut _block_sub, sub_id, block) = setup_api().await;
|
||||
async fn get_storage_non_queryable_key() {
|
||||
let (mut _client, api, mut block_sub, sub_id, block) = setup_api().await;
|
||||
let block_hash = format!("{:?}", block.header.hash());
|
||||
let key = hex_string(&KEY);
|
||||
|
||||
@@ -835,8 +934,9 @@ async fn get_storage_wrong_key() {
|
||||
let mut prefixed_key = well_known_keys::CHILD_STORAGE_KEY_PREFIX.to_vec();
|
||||
prefixed_key.extend_from_slice(&KEY);
|
||||
let prefixed_key = hex_string(&prefixed_key);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -846,15 +946,22 @@ async fn get_storage_wrong_key() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key is not queryable.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Key is prefixed by DEFAULT_CHILD_STORAGE_KEY_PREFIX.
|
||||
let mut prefixed_key = well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec();
|
||||
prefixed_key.extend_from_slice(&KEY);
|
||||
let prefixed_key = hex_string(&prefixed_key);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -864,15 +971,22 @@ async fn get_storage_wrong_key() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key is not queryable.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Child key is prefixed by CHILD_STORAGE_KEY_PREFIX.
|
||||
let mut prefixed_key = well_known_keys::CHILD_STORAGE_KEY_PREFIX.to_vec();
|
||||
prefixed_key.extend_from_slice(b"child");
|
||||
prefixed_key.extend_from_slice(CHILD_STORAGE_KEY);
|
||||
let prefixed_key = hex_string(&prefixed_key);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -883,15 +997,22 @@ async fn get_storage_wrong_key() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key is not queryable.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
|
||||
// Child key is prefixed by DEFAULT_CHILD_STORAGE_KEY_PREFIX.
|
||||
let mut prefixed_key = well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.to_vec();
|
||||
prefixed_key.extend_from_slice(b"child");
|
||||
prefixed_key.extend_from_slice(CHILD_STORAGE_KEY);
|
||||
let prefixed_key = hex_string(&prefixed_key);
|
||||
let mut sub = api
|
||||
.subscribe(
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
@@ -902,8 +1023,164 @@ async fn get_storage_wrong_key() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let event: ChainHeadStorageEvent = get_next_event(&mut sub).await;
|
||||
assert_matches!(event, ChainHeadStorageEvent::Done);
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key is not queryable.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unique_operation_ids() {
|
||||
let (mut _client, api, mut block_sub, sub_id, block) = setup_api().await;
|
||||
let block_hash = format!("{:?}", block.header.hash());
|
||||
|
||||
let mut op_ids = HashSet::new();
|
||||
|
||||
// Ensure that operation IDs are unique for multiple method calls.
|
||||
for _ in 0..5 {
|
||||
// Valid `chainHead_unstable_body` call.
|
||||
let response: MethodResponse =
|
||||
api.call("chainHead_unstable_body", [&sub_id, &block_hash]).await.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationBodyDone(done) if done.operation_id == operation_id && done.value.is_empty()
|
||||
);
|
||||
// Ensure uniqueness.
|
||||
assert!(op_ids.insert(operation_id));
|
||||
|
||||
// Valid `chainHead_unstable_storage` call.
|
||||
let key = hex_string(&KEY);
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_storage",
|
||||
rpc_params![
|
||||
&sub_id,
|
||||
&block_hash,
|
||||
vec![StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }]
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The `Done` event is generated directly since the key does not have any value associated.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
|
||||
);
|
||||
// Ensure uniqueness.
|
||||
assert!(op_ids.insert(operation_id));
|
||||
|
||||
// Valid `chainHead_unstable_call` call.
|
||||
let alice_id = AccountKeyring::Alice.to_account_id();
|
||||
let call_parameters = hex_string(&alice_id.encode());
|
||||
let response: MethodResponse = api
|
||||
.call(
|
||||
"chainHead_unstable_call",
|
||||
[&sub_id, &block_hash, "AccountNonceApi_account_nonce", &call_parameters],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// Response propagated to `chainHead_follow`.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut block_sub).await,
|
||||
FollowEvent::OperationCallDone(done) if done.operation_id == operation_id && done.output == "0x0000000000000000"
|
||||
);
|
||||
// Ensure uniqueness.
|
||||
assert!(op_ids.insert(operation_id));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn separate_operation_ids_for_subscriptions() {
|
||||
let builder = TestClientBuilder::new();
|
||||
let backend = builder.backend();
|
||||
let mut client = Arc::new(builder.build());
|
||||
|
||||
let api = ChainHead::new(
|
||||
client.clone(),
|
||||
backend,
|
||||
Arc::new(TaskExecutor::default()),
|
||||
CHAIN_GENESIS,
|
||||
MAX_PINNED_BLOCKS,
|
||||
Duration::from_secs(MAX_PINNED_SECS),
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
// Create two separate subscriptions.
|
||||
let mut sub_first = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id_first = sub_first.subscription_id();
|
||||
let sub_id_first = serde_json::to_string(&sub_id_first).unwrap();
|
||||
|
||||
let mut sub_second = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id_second = sub_second.subscription_id();
|
||||
let sub_id_second = serde_json::to_string(&sub_id_second).unwrap();
|
||||
|
||||
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
|
||||
client.import(BlockOrigin::Own, block.clone()).await.unwrap();
|
||||
let block_hash = format!("{:?}", block.header.hash());
|
||||
|
||||
// Ensure the imported block is propagated and pinned.
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut sub_first).await,
|
||||
FollowEvent::Initialized(_)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut sub_first).await,
|
||||
FollowEvent::NewBlock(_)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut sub_first).await,
|
||||
FollowEvent::BestBlockChanged(_)
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut sub_second).await,
|
||||
FollowEvent::Initialized(_)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut sub_second).await,
|
||||
FollowEvent::NewBlock(_)
|
||||
);
|
||||
assert_matches!(
|
||||
get_next_event::<FollowEvent<String>>(&mut sub_second).await,
|
||||
FollowEvent::BestBlockChanged(_)
|
||||
);
|
||||
|
||||
// Each `chainHead_follow` subscription receives a separate operation ID.
|
||||
let response: MethodResponse =
|
||||
api.call("chainHead_unstable_body", [&sub_id_first, &block_hash]).await.unwrap();
|
||||
let operation_id: String = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
assert_eq!(operation_id, "0");
|
||||
|
||||
let response: MethodResponse = api
|
||||
.call("chainHead_unstable_body", [&sub_id_second, &block_hash])
|
||||
.await
|
||||
.unwrap();
|
||||
let operation_id_second: String = match response {
|
||||
MethodResponse::Started(started) => started.operation_id,
|
||||
MethodResponse::LimitReached => panic!("Expected started response"),
|
||||
};
|
||||
// The second subscription does not increment the operation ID of the first one.
|
||||
assert_eq!(operation_id_second, "0");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user