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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -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(&param).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(&param).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);
}
}