Skip to content
Merged
34 changes: 34 additions & 0 deletions substrate/client/rpc-spec-v2/src/archive/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,38 @@ pub trait ArchiveApi<Hash> {
/// This method is unstable and subject to change in the future.
#[method(name = "archive_unstable_header")]
fn archive_unstable_header(&self, hash: Hash) -> RpcResult<Option<String>>;

/// 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_unstable_finalizedHeight")]
fn archive_unstable_finalized_height(&self) -> RpcResult<u32>;

/// 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_unstable_hashByHeight")]
fn archive_unstable_hash_by_height(&self, height: u32) -> RpcResult<Vec<String>>;

/// 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_unstable_call")]
fn archive_unstable_call(
&self,
hash: Hash,
function: String,
call_parameters: String,
) -> RpcResult<String>;
}
114 changes: 105 additions & 9 deletions substrate/client/rpc-spec-v2/src/archive/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,33 @@

//! API implementation for `archive`.

use super::ArchiveApiServer;
use crate::chain_head::hex_string;
use crate::{
archive::{error::Error as ArchiveError, ArchiveApiServer},
chain_head::hex_string,
};

use codec::Encode;
use jsonrpsee::core::{async_trait, RpcResult};
use sc_client_api::{Backend, BlockBackend, BlockchainEvents, ExecutorProvider, StorageProvider};
use sp_api::CallApiAt;
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
use sp_runtime::traits::Block as BlockT;
use std::{marker::PhantomData, sync::Arc};
use sc_client_api::{
Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, StorageProvider,
};
use sp_api::{CallApiAt, CallContext, NumberFor};
use sp_blockchain::{
Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
};
use sp_core::Bytes;
use sp_runtime::{
traits::{Block as BlockT, Header},
SaturatedConversion,
};
use std::{collections::HashSet, marker::PhantomData, sync::Arc};

/// An API for archive RPC calls.
pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> {
/// Substrate client.
client: Arc<Client>,
/// Backend of the chain.
backend: Arc<BE>,
/// The hexadecimal encoded hash of the genesis block.
genesis_hash: String,
/// Phantom member to pin the block type.
Expand All @@ -40,10 +53,26 @@ pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> {

impl<BE: Backend<Block>, Block: BlockT, Client> Archive<BE, Block, Client> {
/// Create a new [`Archive`].
pub fn new<GenesisHash: AsRef<[u8]>>(client: Arc<Client>, genesis_hash: GenesisHash) -> Self {
pub fn new<GenesisHash: AsRef<[u8]>>(
client: Arc<Client>,
backend: Arc<BE>,
genesis_hash: GenesisHash,
) -> Self {
let genesis_hash = hex_string(&genesis_hash.as_ref());
Self { client, genesis_hash, _phantom: PhantomData }
Self { client, backend, 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]
Expand Down Expand Up @@ -83,4 +112,71 @@ where

Ok(Some(hex_string(&header.encode())))
}

fn archive_unstable_finalized_height(&self) -> RpcResult<u32> {
Ok(self.client.info().finalized_number.saturated_into())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's makes sense to throw an error or something if the block number is bigger than u32::MAX instead of truncating....

It might be impossible not sure...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume it'd take a few hundred years to hit, but yeah I'd be inlined to at least panic rather than saturate since the latter is hiding some issue :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this to u64 to be extra safe here :D

}

fn archive_unstable_hash_by_height(&self, height: u32) -> RpcResult<Vec<String>> {
let height: NumberFor<Block> = height.into();
let finalized_num = self.client.info().finalized_number;

if finalized_num >= height {
let Ok(Some(hash)) = self.client.block_hash(height.into()) 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();
let Ok(Some(next_header)) = self.client.header(parent_hash) else { continue };

// Continue the iteration for unique hashes.
// Forks might intersect on a common chain that is not yet finalized.
if visited.insert(parent_hash) {
headers.push(next_header);
}
Comment thread
lexnv marked this conversation as resolved.
Outdated
}

Ok(result)
}

fn archive_unstable_call(
&self,
hash: Block::Hash,
function: String,
call_parameters: String,
) -> RpcResult<String> {
let call_parameters = Bytes::from(parse_hex_param(call_parameters)?);

self.client
.executor()
.call(hash, &function, &call_parameters, CallContext::Offchain)
.map(|result| hex_string(&result))
.map_err(|error| ArchiveError::RuntimeCall(error.to_string()).into())
}
}
66 changes: 66 additions & 0 deletions substrate/client/rpc-spec-v2/src/archive/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Error helpers for `archive` RPC module.

use jsonrpsee::{
core::Error as RpcError,
types::error::{CallError, 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()
}
}

impl From<Error> for RpcError {
fn from(e: Error) -> Self {
CallError::Custom(e.into()).into()
}
}
1 change: 1 addition & 0 deletions substrate/client/rpc-spec-v2/src/archive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ mod tests;

pub mod api;
pub mod archive;
pub mod error;

pub use api::ArchiveApiServer;
Loading