mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 12:17:58 +00:00
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:
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user