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:
Alexandru Vasile
2023-08-08 21:13:52 +03:00
committed by GitHub
parent 7afff714e4
commit 4849b6e865
10 changed files with 663 additions and 494 deletions
@@ -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]