Files
pezkuwi-subxt/substrate/client/merkle-mountain-range/src/test_utils.rs
T
Bastian Köcher ca5f10567a sc-block-builder: Remove BlockBuilderProvider (#2099)
The `BlockBuilderProvider` was a trait that was defined in
`sc-block-builder`. The trait was implemented for `Client`. This
basically meant that you needed to import `sc-block-builder` any way to
have access to the block builder. So, this trait was not providing any
real value. This pull request is removing the said trait. Instead of the
trait it introduces a builder for creating a `BlockBuilder`. The builder
currently has the quite fabulous name `BlockBuilderBuilder` (I'm open to
any better name 😅). The rest of the pull request is about
replacing the old trait with the new builder.

# Downstream code changes

If you used `new_block` or `new_block_at` before you now need to switch
it over to the new `BlockBuilderBuilder` pattern:

```rust
// `new` requires a type that implements `CallApiAt`. 
let mut block_builder = BlockBuilderBuilder::new(client)
                // Then you need to specify the hash of the parent block the block will be build on top of
		.on_parent_block(at)
                // The block builder also needs the block number of the parent block. 
                // Here it is fetched from the given `client` using the `HeaderBackend`
                // However, there also exists `with_parent_block_number` for directly passing the number
		.fetch_parent_block_number(client)
		.unwrap()
                // Enable proof recording if required. This call is optional.
		.enable_proof_recording()
                // Pass the digests. This call is optional.
                .with_inherent_digests(digests)
		.build()
		.expect("Creates new block builder");
```

---------

Co-authored-by: Sebastian Kunert <skunert49@gmail.com>
Co-authored-by: command-bot <>
2023-11-03 19:06:31 +01:00

377 lines
11 KiB
Rust

// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::MmrGadget;
use parking_lot::Mutex;
use sc_block_builder::BlockBuilderBuilder;
use sc_client_api::{
Backend as BackendT, BlockchainEvents, FinalityNotifications, ImportNotifications,
StorageEventStream, StorageKey,
};
use sc_offchain::OffchainDb;
use sp_api::{ApiRef, ProvideRuntimeApi};
use sp_blockchain::{BlockStatus, CachedHeaderMetadata, HeaderBackend, HeaderMetadata, Info};
use sp_consensus::BlockOrigin;
use sp_core::{
offchain::{DbExternalities, StorageKind},
H256,
};
use sp_mmr_primitives as mmr;
use sp_mmr_primitives::{utils::NodesUtils, LeafIndex, NodeIndex};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT},
};
use std::{future::Future, sync::Arc, time::Duration};
use substrate_test_runtime_client::{
runtime::{Block, BlockNumber, Hash, Header},
Backend, BlockBuilderExt, Client, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt,
TestClientBuilder, TestClientBuilderExt,
};
use tokio::runtime::Runtime;
type MmrHash = H256;
pub(crate) struct MockRuntimeApiData {
pub(crate) num_blocks: BlockNumber,
}
#[derive(Clone)]
pub(crate) struct MockRuntimeApi {
pub(crate) data: Arc<Mutex<MockRuntimeApiData>>,
}
impl MockRuntimeApi {
pub(crate) const INDEXING_PREFIX: &'static [u8] = b"mmr_test";
}
#[derive(Clone, Debug)]
pub(crate) struct MmrBlock {
pub(crate) block: Block,
pub(crate) leaf_idx: Option<LeafIndex>,
pub(crate) leaf_data: Vec<u8>,
}
#[derive(Clone, Copy)]
pub enum OffchainKeyType {
Temp,
Canon,
}
impl MmrBlock {
pub fn hash(&self) -> Hash {
self.block.hash()
}
pub fn parent_hash(&self) -> Hash {
*self.block.header.parent_hash()
}
pub fn get_offchain_key(&self, node: NodeIndex, key_type: OffchainKeyType) -> Vec<u8> {
match key_type {
OffchainKeyType::Temp => NodesUtils::node_temp_offchain_key::<Header>(
MockRuntimeApi::INDEXING_PREFIX,
node,
self.parent_hash(),
),
OffchainKeyType::Canon =>
NodesUtils::node_canon_offchain_key(MockRuntimeApi::INDEXING_PREFIX, node),
}
}
}
pub(crate) struct MockClient {
pub(crate) client: Mutex<Client<Backend>>,
pub(crate) backend: Arc<Backend>,
pub(crate) runtime_api_params: Arc<Mutex<MockRuntimeApiData>>,
}
impl MockClient {
pub(crate) fn new() -> Self {
let client_builder = TestClientBuilder::new().enable_offchain_indexing_api();
let (client, backend) = client_builder.build_with_backend();
MockClient {
client: Mutex::new(client),
backend,
runtime_api_params: Arc::new(Mutex::new(MockRuntimeApiData { num_blocks: 0 })),
}
}
pub(crate) fn offchain_db(&self) -> OffchainDb<<Backend as BackendT<Block>>::OffchainStorage> {
OffchainDb::new(self.backend.offchain_storage().unwrap())
}
pub async fn import_block(
&self,
at: &BlockId<Block>,
name: &[u8],
maybe_leaf_idx: Option<LeafIndex>,
) -> MmrBlock {
let mut client = self.client.lock();
let hash = client.expect_block_hash_from_id(&at).unwrap();
let mut block_builder = BlockBuilderBuilder::new(&*client)
.on_parent_block(hash)
.fetch_parent_block_number(&*client)
.unwrap()
.build()
.unwrap();
// Make sure the block has a different hash than its siblings
block_builder
.push_storage_change(b"name".to_vec(), Some(name.to_vec()))
.unwrap();
let block = block_builder.build().unwrap().block;
client.import(BlockOrigin::Own, block.clone()).await.unwrap();
let parent_hash = *block.header.parent_hash();
// Simulate writing MMR nodes in offchain storage
if let Some(leaf_idx) = maybe_leaf_idx {
let mut offchain_db = self.offchain_db();
for node in NodesUtils::right_branch_ending_in_leaf(leaf_idx) {
let temp_key = NodesUtils::node_temp_offchain_key::<Header>(
MockRuntimeApi::INDEXING_PREFIX,
node,
parent_hash,
);
offchain_db.local_storage_set(
StorageKind::PERSISTENT,
&temp_key,
parent_hash.as_ref(),
)
}
}
MmrBlock { block, leaf_idx: maybe_leaf_idx, leaf_data: parent_hash.as_ref().to_vec() }
}
pub fn finalize_block(&self, hash: Hash, maybe_num_mmr_blocks: Option<BlockNumber>) {
let client = self.client.lock();
if let Some(num_mmr_blocks) = maybe_num_mmr_blocks {
self.runtime_api_params.lock().num_blocks = num_mmr_blocks;
}
client.finalize_block(hash, None).unwrap();
}
pub fn undo_block_canonicalization(&self, mmr_block: &MmrBlock) {
let mut offchain_db = self.offchain_db();
for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) {
let canon_key = mmr_block.get_offchain_key(node, OffchainKeyType::Canon);
let val = offchain_db.local_storage_get(StorageKind::PERSISTENT, &canon_key).unwrap();
offchain_db.local_storage_clear(StorageKind::PERSISTENT, &canon_key);
let temp_key = mmr_block.get_offchain_key(node, OffchainKeyType::Temp);
offchain_db.local_storage_set(StorageKind::PERSISTENT, &temp_key, &val);
}
}
pub fn check_offchain_storage<F>(
&self,
key_type: OffchainKeyType,
blocks: &[&MmrBlock],
mut f: F,
) where
F: FnMut(Option<Vec<u8>>, &MmrBlock),
{
let mut offchain_db = self.offchain_db();
for mmr_block in blocks {
for node in NodesUtils::right_branch_ending_in_leaf(mmr_block.leaf_idx.unwrap()) {
let temp_key = mmr_block.get_offchain_key(node, key_type);
let val = offchain_db.local_storage_get(StorageKind::PERSISTENT, &temp_key);
f(val, mmr_block);
}
}
}
pub fn assert_pruned(&self, blocks: &[&MmrBlock]) {
self.check_offchain_storage(OffchainKeyType::Temp, blocks, |val, _block| {
assert!(val.is_none());
})
}
pub fn assert_not_pruned(&self, blocks: &[&MmrBlock]) {
self.check_offchain_storage(OffchainKeyType::Temp, blocks, |val, block| {
assert_eq!(val.as_ref(), Some(&block.leaf_data));
})
}
pub fn assert_canonicalized(&self, blocks: &[&MmrBlock]) {
self.check_offchain_storage(OffchainKeyType::Canon, blocks, |val, block| {
assert_eq!(val.as_ref(), Some(&block.leaf_data));
});
self.assert_pruned(blocks);
}
pub fn assert_not_canonicalized(&self, blocks: &[&MmrBlock]) {
self.check_offchain_storage(OffchainKeyType::Canon, blocks, |val, _block| {
assert!(val.is_none());
});
self.assert_not_pruned(blocks);
}
}
impl HeaderMetadata<Block> for MockClient {
type Error = <Client<Backend> as HeaderMetadata<Block>>::Error;
fn header_metadata(&self, hash: Hash) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
self.client.lock().header_metadata(hash)
}
fn insert_header_metadata(&self, _hash: Hash, _header_metadata: CachedHeaderMetadata<Block>) {
todo!()
}
fn remove_header_metadata(&self, _hash: Hash) {
todo!()
}
}
impl HeaderBackend<Block> for MockClient {
fn header(&self, hash: Hash) -> sc_client_api::blockchain::Result<Option<Header>> {
self.client.lock().header(hash)
}
fn info(&self) -> Info<Block> {
self.client.lock().info()
}
fn status(&self, hash: Hash) -> sc_client_api::blockchain::Result<BlockStatus> {
self.client.lock().status(hash)
}
fn number(&self, hash: Hash) -> sc_client_api::blockchain::Result<Option<BlockNumber>> {
self.client.lock().number(hash)
}
fn hash(&self, number: BlockNumber) -> sc_client_api::blockchain::Result<Option<Hash>> {
self.client.lock().hash(number)
}
}
impl BlockchainEvents<Block> for MockClient {
fn import_notification_stream(&self) -> ImportNotifications<Block> {
unimplemented!()
}
fn every_import_notification_stream(&self) -> ImportNotifications<Block> {
unimplemented!()
}
fn finality_notification_stream(&self) -> FinalityNotifications<Block> {
self.client.lock().finality_notification_stream()
}
fn storage_changes_notification_stream(
&self,
_filter_keys: Option<&[StorageKey]>,
_child_filter_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
) -> sc_client_api::blockchain::Result<StorageEventStream<Hash>> {
unimplemented!()
}
}
impl ProvideRuntimeApi<Block> for MockClient {
type Api = MockRuntimeApi;
fn runtime_api(&self) -> ApiRef<'_, Self::Api> {
MockRuntimeApi { data: self.runtime_api_params.clone() }.into()
}
}
sp_api::mock_impl_runtime_apis! {
impl mmr::MmrApi<Block, MmrHash, BlockNumber> for MockRuntimeApi {
fn mmr_root() -> Result<MmrHash, mmr::Error> {
Err(mmr::Error::PalletNotIncluded)
}
fn mmr_leaf_count(&self) -> Result<LeafIndex, mmr::Error> {
Ok(self.data.lock().num_blocks)
}
fn generate_proof(
&self,
_block_numbers: Vec<u64>,
_best_known_block_number: Option<u64>,
) -> Result<(Vec<mmr::EncodableOpaqueLeaf>, mmr::Proof<MmrHash>), mmr::Error> {
Err(mmr::Error::PalletNotIncluded)
}
fn verify_proof(_leaves: Vec<mmr::EncodableOpaqueLeaf>, _proof: mmr::Proof<MmrHash>)
-> Result<(), mmr::Error>
{
Err(mmr::Error::PalletNotIncluded)
}
fn verify_proof_stateless(
_root: MmrHash,
_leaves: Vec<mmr::EncodableOpaqueLeaf>,
_proof: mmr::Proof<MmrHash>
) -> Result<(), mmr::Error> {
Err(mmr::Error::PalletNotIncluded)
}
}
}
pub(crate) fn run_test_with_mmr_gadget<F, Fut>(post_gadget: F)
where
F: FnOnce(Arc<MockClient>) -> Fut + 'static,
Fut: Future<Output = ()>,
{
run_test_with_mmr_gadget_pre_post(|_| async {}, post_gadget);
}
pub(crate) fn run_test_with_mmr_gadget_pre_post<F, G, RetF, RetG>(pre_gadget: F, post_gadget: G)
where
F: FnOnce(Arc<MockClient>) -> RetF + 'static,
G: FnOnce(Arc<MockClient>) -> RetG + 'static,
RetF: Future<Output = ()>,
RetG: Future<Output = ()>,
{
let client = Arc::new(MockClient::new());
run_test_with_mmr_gadget_pre_post_using_client(client, pre_gadget, post_gadget)
}
pub(crate) fn run_test_with_mmr_gadget_pre_post_using_client<F, G, RetF, RetG>(
client: Arc<MockClient>,
pre_gadget: F,
post_gadget: G,
) where
F: FnOnce(Arc<MockClient>) -> RetF + 'static,
G: FnOnce(Arc<MockClient>) -> RetG + 'static,
RetF: Future<Output = ()>,
RetG: Future<Output = ()>,
{
let client_clone = client.clone();
let runtime = Runtime::new().unwrap();
runtime.block_on(async move { pre_gadget(client_clone).await });
let client_clone = client.clone();
runtime.spawn(async move {
let backend = client_clone.backend.clone();
MmrGadget::start(client_clone, backend, MockRuntimeApi::INDEXING_PREFIX.to_vec()).await
});
runtime.block_on(async move {
tokio::time::sleep(Duration::from_millis(200)).await;
post_gadget(client).await
});
}