feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! API trait of the archive methods.
|
||||
|
||||
use crate::{
|
||||
archive::{
|
||||
error::{Error, Infallible},
|
||||
types::MethodResult,
|
||||
},
|
||||
common::events::{
|
||||
ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageEvent, StorageQuery,
|
||||
},
|
||||
};
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
|
||||
#[rpc(client, server)]
|
||||
pub trait ArchiveApi<Hash> {
|
||||
/// Retrieves the body (list of transactions) of a given block hash.
|
||||
///
|
||||
/// Returns an array of strings containing the hexadecimal-encoded SCALE-codec-encoded
|
||||
/// transactions in that block. If no block with that hash is found, null.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[method(name = "archive_v1_body")]
|
||||
fn archive_v1_body(&self, hash: Hash) -> Result<Option<Vec<String>>, Infallible>;
|
||||
|
||||
/// Get the chain's genesis hash.
|
||||
///
|
||||
/// Returns a string containing the hexadecimal-encoded hash of the genesis block of the chain.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[method(name = "archive_v1_genesisHash")]
|
||||
fn archive_v1_genesis_hash(&self) -> Result<String, Infallible>;
|
||||
|
||||
/// Get the block's header.
|
||||
///
|
||||
/// Returns a string containing the hexadecimal-encoded SCALE-codec encoding header of the
|
||||
/// block.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[method(name = "archive_v1_header")]
|
||||
fn archive_v1_header(&self, hash: Hash) -> Result<Option<String>, Infallible>;
|
||||
|
||||
/// Get the height of the current finalized block.
|
||||
///
|
||||
/// Returns an integer height of the current finalized block of the chain.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[method(name = "archive_v1_finalizedHeight")]
|
||||
fn archive_v1_finalized_height(&self) -> Result<u64, Infallible>;
|
||||
|
||||
/// Get the hashes of blocks from the given height.
|
||||
///
|
||||
/// Returns an array (possibly empty) of strings containing an hexadecimal-encoded hash of a
|
||||
/// block header.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[method(name = "archive_v1_hashByHeight")]
|
||||
fn archive_v1_hash_by_height(&self, height: u64) -> Result<Vec<String>, Error>;
|
||||
|
||||
/// Call into the Runtime API at a specified block's state.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[method(name = "archive_v1_call")]
|
||||
fn archive_v1_call(
|
||||
&self,
|
||||
hash: Hash,
|
||||
function: String,
|
||||
call_parameters: String,
|
||||
) -> Result<MethodResult, Error>;
|
||||
|
||||
/// Returns storage entries at a specific block's state.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and subject to change in the future.
|
||||
#[subscription(
|
||||
name = "archive_v1_storage" => "archive_v1_storageEvent",
|
||||
unsubscribe = "archive_v1_stopStorage",
|
||||
item = ArchiveStorageEvent,
|
||||
)]
|
||||
fn archive_v1_storage(
|
||||
&self,
|
||||
hash: Hash,
|
||||
items: Vec<StorageQuery<String>>,
|
||||
child_trie: Option<String>,
|
||||
);
|
||||
|
||||
/// Returns the storage difference between two blocks.
|
||||
///
|
||||
/// # Unstable
|
||||
///
|
||||
/// This method is unstable and can change in minor or patch releases.
|
||||
#[subscription(
|
||||
name = "archive_v1_storageDiff" => "archive_v1_storageDiffEvent",
|
||||
unsubscribe = "archive_v1_storageDiff_stopStorageDiff",
|
||||
item = ArchiveStorageDiffEvent,
|
||||
)]
|
||||
fn archive_v1_storage_diff(
|
||||
&self,
|
||||
hash: Hash,
|
||||
items: Vec<ArchiveStorageDiffItem<String>>,
|
||||
previous_hash: Option<Hash>,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! API implementation for `archive`.
|
||||
|
||||
use crate::{
|
||||
archive::{
|
||||
archive_storage::ArchiveStorageDiff,
|
||||
error::{Error as ArchiveError, Infallible},
|
||||
types::MethodResult,
|
||||
ArchiveApiServer,
|
||||
},
|
||||
common::{
|
||||
events::{
|
||||
ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageEvent, StorageQuery,
|
||||
},
|
||||
storage::{QueryResult, StorageSubscriptionClient},
|
||||
},
|
||||
hex_string, SubscriptionTaskExecutor,
|
||||
};
|
||||
|
||||
use codec::Encode;
|
||||
use futures::FutureExt;
|
||||
use jsonrpsee::{core::async_trait, PendingSubscriptionSink};
|
||||
use pezsc_client_api::{
|
||||
Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey,
|
||||
StorageProvider,
|
||||
};
|
||||
use pezsc_rpc::utils::Subscription;
|
||||
use pezsp_api::{CallApiAt, CallContext};
|
||||
use pezsp_blockchain::{
|
||||
Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
|
||||
};
|
||||
use pezsp_core::{Bytes, U256};
|
||||
use pezsp_runtime::{
|
||||
traits::{Block as BlockT, Header as HeaderT, NumberFor},
|
||||
SaturatedConversion,
|
||||
};
|
||||
use std::{collections::HashSet, marker::PhantomData, sync::Arc};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub(crate) const LOG_TARGET: &str = "rpc-spec-v2::archive";
|
||||
|
||||
/// The buffer capacity for each storage query.
|
||||
///
|
||||
/// This is small because the underlying JSON-RPC server has
|
||||
/// its down buffer capacity per connection as well.
|
||||
const STORAGE_QUERY_BUF: usize = 16;
|
||||
|
||||
/// An API for archive RPC calls.
|
||||
pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> {
|
||||
/// Bizinikiwi client.
|
||||
client: Arc<Client>,
|
||||
/// Backend of the chain.
|
||||
backend: Arc<BE>,
|
||||
/// Executor to spawn subscriptions.
|
||||
executor: SubscriptionTaskExecutor,
|
||||
/// The hexadecimal encoded hash of the genesis block.
|
||||
genesis_hash: String,
|
||||
/// Phantom member to pin the block type.
|
||||
_phantom: PhantomData<Block>,
|
||||
}
|
||||
|
||||
impl<BE: Backend<Block>, Block: BlockT, Client> Archive<BE, Block, Client> {
|
||||
/// Create a new [`Archive`].
|
||||
pub fn new<GenesisHash: AsRef<[u8]>>(
|
||||
client: Arc<Client>,
|
||||
backend: Arc<BE>,
|
||||
genesis_hash: GenesisHash,
|
||||
executor: SubscriptionTaskExecutor,
|
||||
) -> Self {
|
||||
let genesis_hash = hex_string(&genesis_hash.as_ref());
|
||||
Self { client, backend, executor, genesis_hash, _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse hex-encoded string parameter as raw bytes.
|
||||
///
|
||||
/// If the parsing fails, returns an error propagated to the RPC method.
|
||||
fn parse_hex_param(param: String) -> Result<Vec<u8>, ArchiveError> {
|
||||
// Methods can accept empty parameters.
|
||||
if param.is_empty() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
array_bytes::hex2bytes(¶m).map_err(|_| ArchiveError::InvalidParam(param))
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<BE, Block, Client> ArchiveApiServer<Block::Hash> for Archive<BE, Block, Client>
|
||||
where
|
||||
Block: BlockT + 'static,
|
||||
Block::Header: Unpin,
|
||||
BE: Backend<Block> + 'static,
|
||||
Client: BlockBackend<Block>
|
||||
+ ExecutorProvider<Block>
|
||||
+ HeaderBackend<Block>
|
||||
+ HeaderMetadata<Block, Error = BlockChainError>
|
||||
+ BlockchainEvents<Block>
|
||||
+ CallApiAt<Block>
|
||||
+ StorageProvider<Block, BE>
|
||||
+ 'static,
|
||||
{
|
||||
fn archive_v1_body(&self, hash: Block::Hash) -> Result<Option<Vec<String>>, Infallible> {
|
||||
let Ok(Some(signed_block)) = self.client.block(hash) else { return Ok(None) };
|
||||
|
||||
let extrinsics = signed_block
|
||||
.block
|
||||
.extrinsics()
|
||||
.iter()
|
||||
.map(|extrinsic| hex_string(&extrinsic.encode()))
|
||||
.collect();
|
||||
|
||||
Ok(Some(extrinsics))
|
||||
}
|
||||
|
||||
fn archive_v1_genesis_hash(&self) -> Result<String, Infallible> {
|
||||
Ok(self.genesis_hash.clone())
|
||||
}
|
||||
|
||||
fn archive_v1_header(&self, hash: Block::Hash) -> Result<Option<String>, Infallible> {
|
||||
let Ok(Some(header)) = self.client.header(hash) else { return Ok(None) };
|
||||
|
||||
Ok(Some(hex_string(&header.encode())))
|
||||
}
|
||||
|
||||
fn archive_v1_finalized_height(&self) -> Result<u64, Infallible> {
|
||||
Ok(self.client.info().finalized_number.saturated_into())
|
||||
}
|
||||
|
||||
fn archive_v1_hash_by_height(&self, height: u64) -> Result<Vec<String>, ArchiveError> {
|
||||
let height: NumberFor<Block> = U256::from(height)
|
||||
.try_into()
|
||||
.map_err(|_| ArchiveError::InvalidParam(format!("Invalid block height: {}", height)))?;
|
||||
|
||||
let finalized_num = self.client.info().finalized_number;
|
||||
|
||||
if finalized_num >= height {
|
||||
let Ok(Some(hash)) = self.client.block_hash(height) else { return Ok(vec![]) };
|
||||
return Ok(vec![hex_string(&hash.as_ref())]);
|
||||
}
|
||||
|
||||
let blockchain = self.backend.blockchain();
|
||||
// Fetch all the leaves of the blockchain that are on a higher or equal height.
|
||||
let mut headers: Vec<_> = blockchain
|
||||
.leaves()
|
||||
.map_err(|error| ArchiveError::FetchLeaves(error.to_string()))?
|
||||
.into_iter()
|
||||
.filter_map(|hash| {
|
||||
let Ok(Some(header)) = self.client.header(hash) else { return None };
|
||||
|
||||
if header.number() < &height {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(header)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
while let Some(header) = headers.pop() {
|
||||
if header.number() == &height {
|
||||
result.push(hex_string(&header.hash().as_ref()));
|
||||
continue;
|
||||
}
|
||||
|
||||
let parent_hash = *header.parent_hash();
|
||||
|
||||
// Continue the iteration for unique hashes.
|
||||
// Forks might intersect on a common chain that is not yet finalized.
|
||||
if visited.insert(parent_hash) {
|
||||
let Ok(Some(next_header)) = self.client.header(parent_hash) else { continue };
|
||||
headers.push(next_header);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn archive_v1_call(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
function: String,
|
||||
call_parameters: String,
|
||||
) -> Result<MethodResult, ArchiveError> {
|
||||
let call_parameters = Bytes::from(parse_hex_param(call_parameters)?);
|
||||
|
||||
let result =
|
||||
self.client
|
||||
.executor()
|
||||
.call(hash, &function, &call_parameters, CallContext::Offchain);
|
||||
|
||||
Ok(match result {
|
||||
Ok(result) => MethodResult::ok(hex_string(&result)),
|
||||
Err(error) => MethodResult::err(error.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn archive_v1_storage(
|
||||
&self,
|
||||
pending: PendingSubscriptionSink,
|
||||
hash: Block::Hash,
|
||||
items: Vec<StorageQuery<String>>,
|
||||
child_trie: Option<String>,
|
||||
) {
|
||||
let mut storage_client =
|
||||
StorageSubscriptionClient::<Client, Block, BE>::new(self.client.clone());
|
||||
|
||||
let fut = async move {
|
||||
let Ok(mut sink) = pending.accept().await.map(Subscription::from) else { return };
|
||||
|
||||
let items = match items
|
||||
.into_iter()
|
||||
.map(|query| {
|
||||
let key = StorageKey(parse_hex_param(query.key)?);
|
||||
|
||||
// Validate that paginationStartKey is only used with descendant queries
|
||||
if query.pagination_start_key.is_some() &&
|
||||
!query.query_type.is_descendant_query()
|
||||
{
|
||||
return Err(ArchiveError::InvalidParam(
|
||||
"paginationStartKey is only valid for descendantsValues and descendantsHashes query types"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let pagination_start_key = query
|
||||
.pagination_start_key
|
||||
.map(|key| parse_hex_param(key).map(StorageKey))
|
||||
.transpose()?;
|
||||
|
||||
Ok(StorageQuery { key, query_type: query.query_type, pagination_start_key })
|
||||
})
|
||||
.collect::<Result<Vec<_>, ArchiveError>>()
|
||||
{
|
||||
Ok(items) => items,
|
||||
Err(error) => {
|
||||
let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await;
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose();
|
||||
let child_trie = match child_trie {
|
||||
Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec),
|
||||
Err(error) => {
|
||||
let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())).await;
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF);
|
||||
let storage_fut = storage_client.generate_events(hash, items, child_trie, tx);
|
||||
|
||||
// We don't care about the return value of this join:
|
||||
// - process_events might encounter an error (if the client disconnected)
|
||||
// - storage_fut might encounter an error while processing a trie queries and
|
||||
// the error is propagated via the sink.
|
||||
let _ = futures::future::join(storage_fut, process_storage_events(&mut rx, &mut sink))
|
||||
.await;
|
||||
};
|
||||
|
||||
self.executor.spawn("bizinikiwi-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
}
|
||||
|
||||
fn archive_v1_storage_diff(
|
||||
&self,
|
||||
pending: PendingSubscriptionSink,
|
||||
hash: Block::Hash,
|
||||
items: Vec<ArchiveStorageDiffItem<String>>,
|
||||
previous_hash: Option<Block::Hash>,
|
||||
) {
|
||||
let storage_client = ArchiveStorageDiff::new(self.client.clone());
|
||||
let client = self.client.clone();
|
||||
|
||||
log::trace!(target: LOG_TARGET, "Storage diff subscription started");
|
||||
|
||||
let fut = async move {
|
||||
let Ok(mut sink) = pending.accept().await.map(Subscription::from) else { return };
|
||||
|
||||
let previous_hash = if let Some(previous_hash) = previous_hash {
|
||||
previous_hash
|
||||
} else {
|
||||
let Ok(Some(current_header)) = client.header(hash) else {
|
||||
let message = format!("Block header is not present: {hash}");
|
||||
let _ = sink.send(&ArchiveStorageDiffEvent::err(message)).await;
|
||||
return;
|
||||
};
|
||||
*current_header.parent_hash()
|
||||
};
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF);
|
||||
let storage_fut =
|
||||
storage_client.handle_trie_queries(hash, items, previous_hash, tx.clone());
|
||||
|
||||
// We don't care about the return value of this join:
|
||||
// - process_events might encounter an error (if the client disconnected)
|
||||
// - storage_fut might encounter an error while processing a trie queries and
|
||||
// the error is propagated via the sink.
|
||||
let _ =
|
||||
futures::future::join(storage_fut, process_storage_diff_events(&mut rx, &mut sink))
|
||||
.await;
|
||||
};
|
||||
|
||||
self.executor.spawn("bizinikiwi-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends all the events of the storage_diff method to the sink.
|
||||
async fn process_storage_diff_events(
|
||||
rx: &mut mpsc::Receiver<ArchiveStorageDiffEvent>,
|
||||
sink: &mut Subscription,
|
||||
) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sink.closed() => {
|
||||
return
|
||||
},
|
||||
|
||||
maybe_event = rx.recv() => {
|
||||
let Some(event) = maybe_event else {
|
||||
break;
|
||||
};
|
||||
|
||||
if event.is_done() {
|
||||
log::debug!(target: LOG_TARGET, "Finished processing partial trie query");
|
||||
} else if event.is_err() {
|
||||
log::debug!(target: LOG_TARGET, "Error encountered while processing partial trie query");
|
||||
}
|
||||
|
||||
if sink.send(&event).await.is_err() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends all the events of the storage method to the sink.
|
||||
async fn process_storage_events(rx: &mut mpsc::Receiver<QueryResult>, sink: &mut Subscription) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = sink.closed() => {
|
||||
break
|
||||
}
|
||||
|
||||
maybe_storage = rx.recv() => {
|
||||
let Some(event) = maybe_storage else {
|
||||
break;
|
||||
};
|
||||
|
||||
match event {
|
||||
Ok(None) => continue,
|
||||
|
||||
Ok(Some(event)) =>
|
||||
if sink.send(&ArchiveStorageEvent::result(event)).await.is_err() {
|
||||
return
|
||||
},
|
||||
|
||||
Err(error) => {
|
||||
let _ = sink.send(&ArchiveStorageEvent::err(error)).await;
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = sink.send(&ArchiveStorageEvent::StorageDone).await;
|
||||
}
|
||||
@@ -0,0 +1,850 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! Implementation of the `archive_storage` method.
|
||||
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use pezsc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider};
|
||||
use pezsp_runtime::traits::Block as BlockT;
|
||||
|
||||
use super::error::Error as ArchiveError;
|
||||
use crate::{
|
||||
archive::archive::LOG_TARGET,
|
||||
common::{
|
||||
events::{
|
||||
ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType,
|
||||
ArchiveStorageDiffResult, ArchiveStorageDiffType, StorageResult,
|
||||
},
|
||||
storage::Storage,
|
||||
},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Parse hex-encoded string parameter as raw bytes.
|
||||
///
|
||||
/// If the parsing fails, returns an error propagated to the RPC method.
|
||||
pub fn parse_hex_param(param: String) -> Result<Vec<u8>, ArchiveError> {
|
||||
// Methods can accept empty parameters.
|
||||
if param.is_empty() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
array_bytes::hex2bytes(¶m).map_err(|_| ArchiveError::InvalidParam(param))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct DiffDetails {
|
||||
key: StorageKey,
|
||||
return_type: ArchiveStorageDiffType,
|
||||
child_trie_key: Option<ChildInfo>,
|
||||
child_trie_key_string: Option<String>,
|
||||
}
|
||||
|
||||
/// The type of storage query.
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
enum FetchStorageType {
|
||||
/// Only fetch the value.
|
||||
Value,
|
||||
/// Only fetch the hash.
|
||||
Hash,
|
||||
/// Fetch both the value and the hash.
|
||||
Both,
|
||||
}
|
||||
|
||||
/// The return value of the `fetch_storage` method.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum FetchedStorage {
|
||||
/// Storage value under a key.
|
||||
Value(StorageResult),
|
||||
/// Storage hash under a key.
|
||||
Hash(StorageResult),
|
||||
/// Both storage value and hash under a key.
|
||||
Both { value: StorageResult, hash: StorageResult },
|
||||
}
|
||||
|
||||
pub struct ArchiveStorageDiff<Client, Block, BE> {
|
||||
client: Storage<Client, Block, BE>,
|
||||
}
|
||||
|
||||
impl<Client, Block, BE> ArchiveStorageDiff<Client, Block, BE> {
|
||||
pub fn new(client: Arc<Client>) -> Self {
|
||||
Self { client: Storage::new(client) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, Block, BE> ArchiveStorageDiff<Client, Block, BE>
|
||||
where
|
||||
Block: BlockT + 'static,
|
||||
BE: Backend<Block> + 'static,
|
||||
Client: StorageProvider<Block, BE> + Send + Sync + 'static,
|
||||
{
|
||||
/// Fetch the storage from the given key.
|
||||
fn fetch_storage(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
key: StorageKey,
|
||||
maybe_child_trie: Option<ChildInfo>,
|
||||
ty: FetchStorageType,
|
||||
) -> Result<Option<FetchedStorage>, String> {
|
||||
match ty {
|
||||
FetchStorageType::Value => {
|
||||
let result = self.client.query_value(hash, &key, maybe_child_trie.as_ref())?;
|
||||
|
||||
Ok(result.map(FetchedStorage::Value))
|
||||
},
|
||||
|
||||
FetchStorageType::Hash => {
|
||||
let result = self.client.query_hash(hash, &key, maybe_child_trie.as_ref())?;
|
||||
|
||||
Ok(result.map(FetchedStorage::Hash))
|
||||
},
|
||||
|
||||
FetchStorageType::Both => {
|
||||
let Some(value) = self.client.query_value(hash, &key, maybe_child_trie.as_ref())?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(hash) = self.client.query_hash(hash, &key, maybe_child_trie.as_ref())?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(FetchedStorage::Both { value, hash }))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the key belongs to the provided query items.
|
||||
///
|
||||
/// A key belongs to the query items when:
|
||||
/// - the provided key is a prefix of the key in the query items.
|
||||
/// - the query items are empty.
|
||||
///
|
||||
/// Returns an optional `FetchStorageType` based on the query items.
|
||||
/// If the key does not belong to the query items, returns `None`.
|
||||
fn belongs_to_query(key: &StorageKey, items: &[DiffDetails]) -> Option<FetchStorageType> {
|
||||
// User has requested all keys, by default this fallbacks to fetching the value.
|
||||
if items.is_empty() {
|
||||
return Some(FetchStorageType::Value);
|
||||
}
|
||||
|
||||
let mut value = false;
|
||||
let mut hash = false;
|
||||
|
||||
for item in items {
|
||||
if key.as_ref().starts_with(&item.key.as_ref()) {
|
||||
match item.return_type {
|
||||
ArchiveStorageDiffType::Value => value = true,
|
||||
ArchiveStorageDiffType::Hash => hash = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match (value, hash) {
|
||||
(true, true) => Some(FetchStorageType::Both),
|
||||
(true, false) => Some(FetchStorageType::Value),
|
||||
(false, true) => Some(FetchStorageType::Hash),
|
||||
(false, false) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the provided result to the `tx` sender.
|
||||
///
|
||||
/// Returns `false` if the sender has been closed.
|
||||
fn send_result(
|
||||
tx: &mpsc::Sender<ArchiveStorageDiffEvent>,
|
||||
result: FetchedStorage,
|
||||
operation_type: ArchiveStorageDiffOperationType,
|
||||
child_trie_key: Option<String>,
|
||||
) -> bool {
|
||||
let items = match result {
|
||||
FetchedStorage::Value(storage_result) | FetchedStorage::Hash(storage_result) => {
|
||||
vec![storage_result]
|
||||
},
|
||||
FetchedStorage::Both { value, hash } => vec![value, hash],
|
||||
};
|
||||
|
||||
for item in items {
|
||||
let res = ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult {
|
||||
key: item.key,
|
||||
result: item.result,
|
||||
operation_type,
|
||||
child_trie_key: child_trie_key.clone(),
|
||||
});
|
||||
if tx.blocking_send(res).is_err() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_trie_queries_inner(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
previous_hash: Block::Hash,
|
||||
items: Vec<DiffDetails>,
|
||||
tx: &mpsc::Sender<ArchiveStorageDiffEvent>,
|
||||
) -> Result<(), String> {
|
||||
// Parse the child trie key as `ChildInfo` and `String`.
|
||||
let maybe_child_trie = items.first().and_then(|item| item.child_trie_key.clone());
|
||||
let maybe_child_trie_str =
|
||||
items.first().and_then(|item| item.child_trie_key_string.clone());
|
||||
|
||||
// Iterator over the current block and previous block
|
||||
// at the same time to compare the keys. This approach effectively
|
||||
// leverages backpressure to avoid memory consumption.
|
||||
let keys_iter = self.client.raw_keys_iter(hash, maybe_child_trie.clone())?;
|
||||
let previous_keys_iter =
|
||||
self.client.raw_keys_iter(previous_hash, maybe_child_trie.clone())?;
|
||||
|
||||
let mut diff_iter = lexicographic_diff(keys_iter, previous_keys_iter);
|
||||
|
||||
while let Some(item) = diff_iter.next() {
|
||||
let (operation_type, key) = match item {
|
||||
Diff::Added(key) => (ArchiveStorageDiffOperationType::Added, key),
|
||||
Diff::Deleted(key) => (ArchiveStorageDiffOperationType::Deleted, key),
|
||||
Diff::Equal(key) => (ArchiveStorageDiffOperationType::Modified, key),
|
||||
};
|
||||
|
||||
let Some(fetch_type) = Self::belongs_to_query(&key, &items) else {
|
||||
// The key does not belong the the query items.
|
||||
continue;
|
||||
};
|
||||
|
||||
let maybe_result = match operation_type {
|
||||
ArchiveStorageDiffOperationType::Added =>
|
||||
self.fetch_storage(hash, key.clone(), maybe_child_trie.clone(), fetch_type)?,
|
||||
ArchiveStorageDiffOperationType::Deleted => self.fetch_storage(
|
||||
previous_hash,
|
||||
key.clone(),
|
||||
maybe_child_trie.clone(),
|
||||
fetch_type,
|
||||
)?,
|
||||
ArchiveStorageDiffOperationType::Modified => {
|
||||
let Some(storage_result) = self.fetch_storage(
|
||||
hash,
|
||||
key.clone(),
|
||||
maybe_child_trie.clone(),
|
||||
fetch_type,
|
||||
)?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(previous_storage_result) = self.fetch_storage(
|
||||
previous_hash,
|
||||
key.clone(),
|
||||
maybe_child_trie.clone(),
|
||||
fetch_type,
|
||||
)?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// For modified records we need to check the actual storage values.
|
||||
if storage_result == previous_storage_result {
|
||||
continue;
|
||||
}
|
||||
|
||||
Some(storage_result)
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(storage_result) = maybe_result {
|
||||
if !Self::send_result(
|
||||
&tx,
|
||||
storage_result,
|
||||
operation_type,
|
||||
maybe_child_trie_str.clone(),
|
||||
) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This method will iterate over the keys of the main trie or a child trie and fetch the
|
||||
/// given keys. The fetched keys will be sent to the provided `tx` sender to leverage
|
||||
/// the backpressure mechanism.
|
||||
pub async fn handle_trie_queries(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
items: Vec<ArchiveStorageDiffItem<String>>,
|
||||
previous_hash: Block::Hash,
|
||||
tx: mpsc::Sender<ArchiveStorageDiffEvent>,
|
||||
) -> Result<(), tokio::task::JoinError> {
|
||||
let this = ArchiveStorageDiff { client: self.client.clone() };
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
// Deduplicate the items.
|
||||
let mut trie_items = match deduplicate_storage_diff_items(items) {
|
||||
Ok(items) => items,
|
||||
Err(error) => {
|
||||
let _ = tx.blocking_send(ArchiveStorageDiffEvent::err(error.to_string()));
|
||||
return;
|
||||
},
|
||||
};
|
||||
// Default to using the main storage trie if no items are provided.
|
||||
if trie_items.is_empty() {
|
||||
trie_items.push(Vec::new());
|
||||
}
|
||||
log::trace!(target: LOG_TARGET, "Storage diff deduplicated items: {:?}", trie_items);
|
||||
|
||||
for items in trie_items {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"handle_trie_queries: hash={:?}, previous_hash={:?}, items={:?}",
|
||||
hash,
|
||||
previous_hash,
|
||||
items
|
||||
);
|
||||
|
||||
let result = this.handle_trie_queries_inner(hash, previous_hash, items, &tx);
|
||||
|
||||
if let Err(error) = result {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"handle_trie_queries: sending error={:?}",
|
||||
error,
|
||||
);
|
||||
|
||||
let _ = tx.blocking_send(ArchiveStorageDiffEvent::err(error));
|
||||
|
||||
return;
|
||||
} else {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"handle_trie_queries: sending storage diff done",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tx.blocking_send(ArchiveStorageDiffEvent::StorageDiffDone);
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of the `lexicographic_diff` method.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Diff<T> {
|
||||
Added(T),
|
||||
Deleted(T),
|
||||
Equal(T),
|
||||
}
|
||||
|
||||
/// Compare two iterators lexicographically and return the differences.
|
||||
fn lexicographic_diff<T, LeftIter, RightIter>(
|
||||
mut left: LeftIter,
|
||||
mut right: RightIter,
|
||||
) -> impl Iterator<Item = Diff<T>>
|
||||
where
|
||||
T: Ord,
|
||||
LeftIter: Iterator<Item = T>,
|
||||
RightIter: Iterator<Item = T>,
|
||||
{
|
||||
let mut a = left.next();
|
||||
let mut b = right.next();
|
||||
|
||||
core::iter::from_fn(move || match (a.take(), b.take()) {
|
||||
(Some(a_value), Some(b_value)) =>
|
||||
if a_value < b_value {
|
||||
b = Some(b_value);
|
||||
a = left.next();
|
||||
|
||||
Some(Diff::Added(a_value))
|
||||
} else if a_value > b_value {
|
||||
a = Some(a_value);
|
||||
b = right.next();
|
||||
|
||||
Some(Diff::Deleted(b_value))
|
||||
} else {
|
||||
a = left.next();
|
||||
b = right.next();
|
||||
|
||||
Some(Diff::Equal(a_value))
|
||||
},
|
||||
(Some(a_value), None) => {
|
||||
a = left.next();
|
||||
Some(Diff::Added(a_value))
|
||||
},
|
||||
(None, Some(b_value)) => {
|
||||
b = right.next();
|
||||
Some(Diff::Deleted(b_value))
|
||||
},
|
||||
(None, None) => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Deduplicate the provided items and return a list of `DiffDetails`.
|
||||
///
|
||||
/// Each list corresponds to a single child trie or the main trie.
|
||||
fn deduplicate_storage_diff_items(
|
||||
items: Vec<ArchiveStorageDiffItem<String>>,
|
||||
) -> Result<Vec<Vec<DiffDetails>>, ArchiveError> {
|
||||
let mut deduplicated: HashMap<Option<ChildInfo>, Vec<DiffDetails>> = HashMap::new();
|
||||
|
||||
for diff_item in items {
|
||||
// Ensure the provided hex keys are valid before deduplication.
|
||||
let key = StorageKey(parse_hex_param(diff_item.key)?);
|
||||
let child_trie_key_string = diff_item.child_trie_key.clone();
|
||||
let child_trie_key = diff_item
|
||||
.child_trie_key
|
||||
.map(|child_trie_key| parse_hex_param(child_trie_key))
|
||||
.transpose()?
|
||||
.map(ChildInfo::new_default_from_vec);
|
||||
|
||||
let diff_item = DiffDetails {
|
||||
key,
|
||||
return_type: diff_item.return_type,
|
||||
child_trie_key: child_trie_key.clone(),
|
||||
child_trie_key_string,
|
||||
};
|
||||
|
||||
match deduplicated.entry(child_trie_key.clone()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let mut should_insert = true;
|
||||
|
||||
for existing in entry.get() {
|
||||
// This points to a different return type.
|
||||
if existing.return_type != diff_item.return_type {
|
||||
continue;
|
||||
}
|
||||
// Keys and return types are identical.
|
||||
if existing.key == diff_item.key {
|
||||
should_insert = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// The following two conditions ensure that we keep the shortest key.
|
||||
|
||||
// The current key is a longer prefix of the existing key.
|
||||
if diff_item.key.as_ref().starts_with(&existing.key.as_ref()) {
|
||||
should_insert = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// The existing key is a longer prefix of the current key.
|
||||
// We need to keep the current key and remove the existing one.
|
||||
if existing.key.as_ref().starts_with(&diff_item.key.as_ref()) {
|
||||
let to_remove = existing.clone();
|
||||
entry.get_mut().retain(|item| item != &to_remove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if should_insert {
|
||||
entry.get_mut().push(diff_item);
|
||||
}
|
||||
},
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(vec![diff_item]);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deduplicated
|
||||
.into_iter()
|
||||
.sorted_by_key(|(child_trie_key, _)| child_trie_key.clone())
|
||||
.map(|(_, values)| values)
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dedup_empty() {
|
||||
let items = vec![];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_single() {
|
||||
let items = vec![ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
}];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 1);
|
||||
|
||||
let expected = DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
};
|
||||
assert_eq!(result[0][0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_different_keys() {
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x02".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 2);
|
||||
|
||||
let expected = vec![
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
},
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![2]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(result[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_same_keys() {
|
||||
// Identical keys.
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 1);
|
||||
|
||||
let expected = vec![DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
}];
|
||||
assert_eq!(result[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_same_prefix() {
|
||||
// Identical keys.
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01ff".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 1);
|
||||
|
||||
let expected = vec![DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
}];
|
||||
assert_eq!(result[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_different_return_types() {
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Hash,
|
||||
child_trie_key: None,
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 2);
|
||||
|
||||
let expected = vec![
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
},
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Hash,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(result[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_different_child_tries() {
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x01".into()),
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x02".into()),
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 2);
|
||||
assert_eq!(result[0].len(), 1);
|
||||
assert_eq!(result[1].len(), 1);
|
||||
|
||||
let expected = vec![
|
||||
vec![DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])),
|
||||
child_trie_key_string: Some("0x01".into()),
|
||||
}],
|
||||
vec![DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])),
|
||||
child_trie_key_string: Some("0x02".into()),
|
||||
}],
|
||||
];
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_same_child_tries() {
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x01".into()),
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x01".into()),
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 1);
|
||||
|
||||
let expected = vec![DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])),
|
||||
child_trie_key_string: Some("0x01".into()),
|
||||
}];
|
||||
assert_eq!(result[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_with_shorter_key_reverse_order() {
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01ff".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
];
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].len(), 1);
|
||||
|
||||
let expected = vec![DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
}];
|
||||
assert_eq!(result[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dedup_multiple_child_tries() {
|
||||
let items = vec![
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x02".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x01".into()),
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x02".into(),
|
||||
return_type: ArchiveStorageDiffType::Hash,
|
||||
child_trie_key: Some("0x01".into()),
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x02".into()),
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01".into(),
|
||||
return_type: ArchiveStorageDiffType::Hash,
|
||||
child_trie_key: Some("0x02".into()),
|
||||
},
|
||||
ArchiveStorageDiffItem {
|
||||
key: "0x01ff".into(),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some("0x02".into()),
|
||||
},
|
||||
];
|
||||
|
||||
let result = deduplicate_storage_diff_items(items).unwrap();
|
||||
|
||||
let expected = vec![
|
||||
vec![DiffDetails {
|
||||
key: StorageKey(vec![2]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: None,
|
||||
child_trie_key_string: None,
|
||||
}],
|
||||
vec![
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])),
|
||||
child_trie_key_string: Some("0x01".into()),
|
||||
},
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![2]),
|
||||
return_type: ArchiveStorageDiffType::Hash,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])),
|
||||
child_trie_key_string: Some("0x01".into()),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Value,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])),
|
||||
child_trie_key_string: Some("0x02".into()),
|
||||
},
|
||||
DiffDetails {
|
||||
key: StorageKey(vec![1]),
|
||||
return_type: ArchiveStorageDiffType::Hash,
|
||||
child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])),
|
||||
child_trie_key_string: Some("0x02".into()),
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lexicographic_diff() {
|
||||
let left = vec![1, 2, 3, 4, 5];
|
||||
let right = vec![2, 3, 4, 5, 6];
|
||||
|
||||
let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::<Vec<_>>();
|
||||
let expected = vec![
|
||||
Diff::Added(1),
|
||||
Diff::Equal(2),
|
||||
Diff::Equal(3),
|
||||
Diff::Equal(4),
|
||||
Diff::Equal(5),
|
||||
Diff::Deleted(6),
|
||||
];
|
||||
assert_eq!(diff, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lexicographic_diff_one_side_empty() {
|
||||
let left = vec![];
|
||||
let right = vec![1, 2, 3, 4, 5, 6];
|
||||
|
||||
let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::<Vec<_>>();
|
||||
let expected = vec![
|
||||
Diff::Deleted(1),
|
||||
Diff::Deleted(2),
|
||||
Diff::Deleted(3),
|
||||
Diff::Deleted(4),
|
||||
Diff::Deleted(5),
|
||||
Diff::Deleted(6),
|
||||
];
|
||||
assert_eq!(diff, expected);
|
||||
|
||||
let left = vec![1, 2, 3, 4, 5, 6];
|
||||
let right = vec![];
|
||||
|
||||
let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::<Vec<_>>();
|
||||
let expected = vec![
|
||||
Diff::Added(1),
|
||||
Diff::Added(2),
|
||||
Diff::Added(3),
|
||||
Diff::Added(4),
|
||||
Diff::Added(5),
|
||||
Diff::Added(6),
|
||||
];
|
||||
assert_eq!(diff, expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! Error helpers for `archive` RPC module.
|
||||
|
||||
use jsonrpsee::types::error::ErrorObject;
|
||||
|
||||
/// ChainHead RPC errors.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Invalid parameter provided to the RPC method.
|
||||
#[error("Invalid parameter: {0}")]
|
||||
InvalidParam(String),
|
||||
/// Runtime call failed.
|
||||
#[error("Runtime call: {0}")]
|
||||
RuntimeCall(String),
|
||||
/// Failed to fetch leaves.
|
||||
#[error("Failed to fetch leaves of the chain: {0}")]
|
||||
FetchLeaves(String),
|
||||
}
|
||||
|
||||
// Base code for all `archive` errors.
|
||||
const BASE_ERROR: i32 = 3000;
|
||||
/// Invalid parameter error.
|
||||
const INVALID_PARAM_ERROR: i32 = BASE_ERROR + 1;
|
||||
/// Runtime call error.
|
||||
const RUNTIME_CALL_ERROR: i32 = BASE_ERROR + 2;
|
||||
/// Failed to fetch leaves.
|
||||
const FETCH_LEAVES_ERROR: i32 = BASE_ERROR + 3;
|
||||
|
||||
impl From<Error> for ErrorObject<'static> {
|
||||
fn from(e: Error) -> Self {
|
||||
let msg = e.to_string();
|
||||
|
||||
match e {
|
||||
Error::InvalidParam(_) => ErrorObject::owned(INVALID_PARAM_ERROR, msg, None::<()>),
|
||||
Error::RuntimeCall(_) => ErrorObject::owned(RUNTIME_CALL_ERROR, msg, None::<()>),
|
||||
Error::FetchLeaves(_) => ErrorObject::owned(FETCH_LEAVES_ERROR, msg, None::<()>),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type for errors that can never happen.
|
||||
//
|
||||
// NOTE: Can't use std::convert::Infallible because of the orphan-rule
|
||||
pub enum Infallible {}
|
||||
|
||||
impl From<Infallible> for ErrorObject<'static> {
|
||||
fn from(e: Infallible) -> Self {
|
||||
match e {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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/>.
|
||||
|
||||
//! Bizinikiwi archive API.
|
||||
//!
|
||||
//! # Note
|
||||
//!
|
||||
//! Methods are prefixed by `archive`.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod archive_storage;
|
||||
mod types;
|
||||
|
||||
pub mod api;
|
||||
pub mod archive;
|
||||
pub mod error;
|
||||
|
||||
pub use api::ArchiveApiServer;
|
||||
pub use archive::Archive;
|
||||
pub use types::{MethodResult, MethodResultErr, MethodResultOk};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
/// The result of an RPC method.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum MethodResult {
|
||||
/// Method generated a result.
|
||||
Ok(MethodResultOk),
|
||||
/// Method encountered an error.
|
||||
Err(MethodResultErr),
|
||||
}
|
||||
|
||||
impl MethodResult {
|
||||
/// Constructs a successful result.
|
||||
pub fn ok(result: impl Into<String>) -> MethodResult {
|
||||
MethodResult::Ok(MethodResultOk { success: true, value: result.into() })
|
||||
}
|
||||
|
||||
/// Constructs an error result.
|
||||
pub fn err(error: impl Into<String>) -> MethodResult {
|
||||
MethodResult::Err(MethodResultErr { success: false, error: error.into() })
|
||||
}
|
||||
}
|
||||
|
||||
/// The successful result of an RPC method.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MethodResultOk {
|
||||
/// Method was successful.
|
||||
pub success: bool,
|
||||
/// The result of the method.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// The error result of an RPC method.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MethodResultErr {
|
||||
/// Method encountered an error.
|
||||
pub success: bool,
|
||||
/// The error of the method.
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn method_result_ok() {
|
||||
let ok = MethodResult::ok("hello");
|
||||
|
||||
let ser = serde_json::to_string(&ok).unwrap();
|
||||
let exp = r#"{"success":true,"value":"hello"}"#;
|
||||
assert_eq!(ser, exp);
|
||||
|
||||
let ok_dec: MethodResult = serde_json::from_str(exp).unwrap();
|
||||
assert_eq!(ok_dec, ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_result_error() {
|
||||
let ok = MethodResult::err("hello");
|
||||
|
||||
let ser = serde_json::to_string(&ok).unwrap();
|
||||
let exp = r#"{"success":false,"error":"hello"}"#;
|
||||
assert_eq!(ser, exp);
|
||||
|
||||
let ok_dec: MethodResult = serde_json::from_str(exp).unwrap();
|
||||
assert_eq!(ok_dec, ok);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user