chainHead: Add support for storage closest merkle descendant #14818 (#1153)

This PR adds support for fetching the closest merkle value of some key.


Builds on top of
- https://github.com/paritytech/trie/pull/199

Migrates https://github.com/paritytech/substrate/pull/14818 to the
monorepo.
Closes: https://github.com/paritytech/substrate/issues/14550
Closes: https://github.com/paritytech/polkadot-sdk/issues/1506

// @paritytech/subxt-team

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
This commit is contained in:
Alexandru Vasile
2023-09-18 13:54:44 +03:00
committed by GitHub
parent 20052e1675
commit 5d346643ca
15 changed files with 449 additions and 28 deletions
@@ -27,7 +27,7 @@ use crate::{
api::ChainHeadApiServer,
chain_head_follow::ChainHeadFollower,
error::Error as ChainHeadRpcError,
event::{FollowEvent, MethodResponse, OperationError, StorageQuery, StorageQueryType},
event::{FollowEvent, MethodResponse, OperationError, StorageQuery},
hex_string,
subscription::{SubscriptionManagement, SubscriptionManagementError},
},
@@ -329,19 +329,10 @@ where
let items = items
.into_iter()
.map(|query| {
if query.query_type == StorageQueryType::ClosestDescendantMerkleValue {
// Note: remove this once all types are implemented.
return Err(ChainHeadRpcError::InvalidParam(
"Storage query type not supported".into(),
))
}
Ok(StorageQuery {
key: StorageKey(parse_hex_param(query.key)?),
query_type: query.query_type,
})
let key = StorageKey(parse_hex_param(query.key)?);
Ok(StorageQuery { key, query_type: query.query_type })
})
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, ChainHeadRpcError>>()?;
let child_trie = child_trie
.map(|child_trie| parse_hex_param(child_trie))
@@ -145,6 +145,36 @@ where
.unwrap_or_else(|error| QueryResult::Err(error.to_string()))
}
/// Fetch the closest merkle value.
fn query_storage_merkle_value(
&self,
hash: Block::Hash,
key: &StorageKey,
child_key: Option<&ChildInfo>,
) -> QueryResult {
let result = if let Some(child_key) = child_key {
self.client.child_closest_merkle_value(hash, child_key, key)
} else {
self.client.closest_merkle_value(hash, key)
};
result
.map(|opt| {
QueryResult::Ok(opt.map(|storage_data| {
let result = match &storage_data {
sc_client_api::MerkleValue::Node(data) => hex_string(&data.as_slice()),
sc_client_api::MerkleValue::Hash(hash) => hex_string(&hash.as_ref()),
};
StorageResult {
key: hex_string(&key.0),
result: StorageResultType::ClosestDescendantMerkleValue(result),
}
}))
})
.unwrap_or_else(|error| QueryResult::Err(error.to_string()))
}
/// Iterate over at most `operation_max_storage_items` keys.
///
/// Returns the storage result with a potential next key to resume iteration.
@@ -286,13 +316,21 @@ where
return
},
},
StorageQueryType::ClosestDescendantMerkleValue =>
match self.query_storage_merkle_value(hash, &item.key, child_key.as_ref()) {
Ok(Some(value)) => storage_results.push(value),
Ok(None) => continue,
Err(error) => {
send_error::<Block>(&sender, operation.operation_id(), error);
return
},
},
StorageQueryType::DescendantsValues => self
.iter_operations
.push_back(QueryIter { next_key: item.key, ty: IterQueryType::Value }),
StorageQueryType::DescendantsHashes => self
.iter_operations
.push_back(QueryIter { next_key: item.key, ty: IterQueryType::Hash }),
_ => continue,
};
}
@@ -20,8 +20,8 @@ use parking_lot::Mutex;
use sc_client_api::{
execution_extensions::ExecutionExtensions, BlockBackend, BlockImportNotification,
BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, FinalityNotification,
FinalityNotifications, FinalizeSummary, ImportNotifications, KeysIter, PairsIter, StorageData,
StorageEventStream, StorageKey, StorageProvider,
FinalityNotifications, FinalizeSummary, ImportNotifications, KeysIter, MerkleValue, PairsIter,
StorageData, StorageEventStream, StorageKey, StorageProvider,
};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
use sp_api::{CallApiAt, CallApiAtParams, NumberFor, RuntimeVersion};
@@ -198,6 +198,23 @@ impl<
) -> sp_blockchain::Result<Option<Block::Hash>> {
self.client.child_storage_hash(hash, child_info, key)
}
fn closest_merkle_value(
&self,
hash: Block::Hash,
key: &StorageKey,
) -> sp_blockchain::Result<Option<MerkleValue<Block::Hash>>> {
self.client.closest_merkle_value(hash, key)
}
fn child_closest_merkle_value(
&self,
hash: Block::Hash,
child_info: &ChildInfo,
key: &StorageKey,
) -> sp_blockchain::Result<Option<MerkleValue<Block::Hash>>> {
self.client.child_closest_merkle_value(hash, child_info, key)
}
}
impl<Block: BlockT, Client: CallApiAt<Block>> CallApiAt<Block> for ChainHeadMockClient<Client> {
@@ -43,7 +43,12 @@ use sp_core::{
Blake2Hasher, Hasher,
};
use sp_version::RuntimeVersion;
use std::{collections::HashSet, fmt::Debug, sync::Arc, time::Duration};
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
sync::Arc,
time::Duration,
};
use substrate_test_runtime::Transfer;
use substrate_test_runtime_client::{
prelude::*, runtime, runtime::RuntimeApi, Backend, BlockBuilderExt, Client,
@@ -2583,3 +2588,186 @@ async fn stop_storage_operation() {
)
.await;
}
#[tokio::test]
async fn storage_closest_merkle_value() {
let (mut client, api, mut sub, sub_id, _) = setup_api().await;
/// The core of this test.
///
/// Checks keys that are exact match, keys with descedant and keys that should not return
/// values.
///
/// Returns (key, merkle value) pairs.
async fn expect_merkle_request(
api: &RpcModule<ChainHead<Backend, Block, Client<Backend>>>,
mut sub: &mut RpcSubscription,
sub_id: String,
block_hash: String,
) -> HashMap<String, String> {
// Valid call with storage at the keys.
let response: MethodResponse = api
.call(
"chainHead_unstable_storage",
rpc_params![
&sub_id,
&block_hash,
vec![
StorageQuery {
key: hex_string(b":AAAA"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
StorageQuery {
key: hex_string(b":AAAB"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
// Key with descedent.
StorageQuery {
key: hex_string(b":A"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
StorageQuery {
key: hex_string(b":AA"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
// Keys below this comment do not produce a result.
// Key that exceed the keyspace of the trie.
StorageQuery {
key: hex_string(b":AAAAX"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
StorageQuery {
key: hex_string(b":AAABX"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
// Key that are not part of the trie.
StorageQuery {
key: hex_string(b":AAX"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
StorageQuery {
key: hex_string(b":AAAX"),
query_type: StorageQueryType::ClosestDescendantMerkleValue
},
]
],
)
.await
.unwrap();
let operation_id = match response {
MethodResponse::Started(started) => started.operation_id,
MethodResponse::LimitReached => panic!("Expected started response"),
};
let event = get_next_event::<FollowEvent<String>>(&mut sub).await;
let merkle_values: HashMap<_, _> = match event {
FollowEvent::OperationStorageItems(res) => {
assert_eq!(res.operation_id, operation_id);
res.items
.into_iter()
.map(|res| {
let value = match res.result {
StorageResultType::ClosestDescendantMerkleValue(value) => value,
_ => panic!("Unexpected StorageResultType"),
};
(res.key, value)
})
.collect()
},
_ => panic!("Expected OperationStorageItems event"),
};
// Finished.
assert_matches!(
get_next_event::<FollowEvent<String>>(&mut sub).await,
FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id
);
// Response for AAAA, AAAB, A and AA.
assert_eq!(merkle_values.len(), 4);
// While checking for expected merkle values to align,
// the following will check that the returned keys are
// expected.
// Values for AAAA and AAAB are different.
assert_ne!(
merkle_values.get(&hex_string(b":AAAA")).unwrap(),
merkle_values.get(&hex_string(b":AAAB")).unwrap()
);
// Values for A and AA should be on the same branch node.
assert_eq!(
merkle_values.get(&hex_string(b":A")).unwrap(),
merkle_values.get(&hex_string(b":AA")).unwrap()
);
// The branch node value must be different than the leaf of either
// AAAA and AAAB.
assert_ne!(
merkle_values.get(&hex_string(b":A")).unwrap(),
merkle_values.get(&hex_string(b":AAAA")).unwrap()
);
assert_ne!(
merkle_values.get(&hex_string(b":A")).unwrap(),
merkle_values.get(&hex_string(b":AAAB")).unwrap()
);
merkle_values
}
// Import a new block with storage changes.
let mut builder = client.new_block(Default::default()).unwrap();
builder.push_storage_change(b":AAAA".to_vec(), Some(vec![1; 64])).unwrap();
builder.push_storage_change(b":AAAB".to_vec(), Some(vec![2; 64])).unwrap();
let block = builder.build().unwrap().block;
let block_hash = format!("{:?}", block.header.hash());
client.import(BlockOrigin::Own, block.clone()).await.unwrap();
// Ensure the imported block is propagated and pinned for this subscription.
assert_matches!(
get_next_event::<FollowEvent<String>>(&mut sub).await,
FollowEvent::NewBlock(_)
);
assert_matches!(
get_next_event::<FollowEvent<String>>(&mut sub).await,
FollowEvent::BestBlockChanged(_)
);
let merkle_values_lhs = expect_merkle_request(&api, &mut sub, sub_id.clone(), block_hash).await;
// Import a new block with and change AAAB value.
let mut builder = client.new_block(Default::default()).unwrap();
builder.push_storage_change(b":AAAA".to_vec(), Some(vec![1; 64])).unwrap();
builder.push_storage_change(b":AAAB".to_vec(), Some(vec![3; 64])).unwrap();
let block = builder.build().unwrap().block;
let block_hash = format!("{:?}", block.header.hash());
client.import(BlockOrigin::Own, block.clone()).await.unwrap();
// Ensure the imported block is propagated and pinned for this subscription.
assert_matches!(
get_next_event::<FollowEvent<String>>(&mut sub).await,
FollowEvent::NewBlock(_)
);
assert_matches!(
get_next_event::<FollowEvent<String>>(&mut sub).await,
FollowEvent::BestBlockChanged(_)
);
let merkle_values_rhs = expect_merkle_request(&api, &mut sub, sub_id.clone(), block_hash).await;
// Change propagated to the root.
assert_ne!(
merkle_values_lhs.get(&hex_string(b":A")).unwrap(),
merkle_values_rhs.get(&hex_string(b":A")).unwrap()
);
assert_ne!(
merkle_values_lhs.get(&hex_string(b":AAAB")).unwrap(),
merkle_values_rhs.get(&hex_string(b":AAAB")).unwrap()
);
// However the AAAA branch leaf remains unchanged.
assert_eq!(
merkle_values_lhs.get(&hex_string(b":AAAA")).unwrap(),
merkle_values_rhs.get(&hex_string(b":AAAA")).unwrap()
);
}