Skip to content

Commit

Permalink
archive: Fetch body, genesisHash and header (#1560)
Browse files Browse the repository at this point in the history
This PR lays the foundation for implementing the archive RPC methods.

The methods implemented by this PR:
- archive_unstable_body: Fetch the block's body (a vector of hex-encoded
scale-encoded extrinsics) from a given block hash
- archive_unstable_genesisHash: Fetch the genesis hash
- archive_unstable_header: Fetch the header from a given block hash

Added unit tests for the methods.

This PR is implementing the methods without exposing them to the RPC
layer; which are to be exposed by a follow-up PR.

Closes: #1509 
Closes: #1514

@paritytech/subxt-team

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
  • Loading branch information
lexnv authored Sep 19, 2023
1 parent 6079b6d commit f3061c1
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 0 deletions.
56 changes: 56 additions & 0 deletions substrate/client/rpc-spec-v2/src/archive/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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/>.

//! API trait of the archive methods.

use jsonrpsee::{core::RpcResult, 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_unstable_body")]
fn archive_unstable_body(&self, hash: Hash) -> RpcResult<Option<Vec<String>>>;

/// 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_unstable_genesisHash")]
fn archive_unstable_genesis_hash(&self) -> RpcResult<String>;

/// 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_unstable_header")]
fn archive_unstable_header(&self, hash: Hash) -> RpcResult<Option<String>>;
}
86 changes: 86 additions & 0 deletions substrate/client/rpc-spec-v2/src/archive/archive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 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/>.

//! API implementation for `archive`.

use super::ArchiveApiServer;
use crate::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};

/// An API for archive RPC calls.
pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> {
/// Substrate client.
client: Arc<Client>,
/// The hexadecimal encoded hash of the genesis block.
genesis_hash: String,
/// Phantom member to pin the block type.
_phantom: PhantomData<(Block, BE)>,
}

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 {
let genesis_hash = hex_string(&genesis_hash.as_ref());
Self { client, genesis_hash, _phantom: PhantomData }
}
}

#[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_unstable_body(&self, hash: Block::Hash) -> RpcResult<Option<Vec<String>>> {
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_unstable_genesis_hash(&self) -> RpcResult<String> {
Ok(self.genesis_hash.clone())
}

fn archive_unstable_header(&self, hash: Block::Hash) -> RpcResult<Option<String>> {
let Ok(Some(header)) = self.client.header(hash) else { return Ok(None) };

Ok(Some(hex_string(&header.encode())))
}
}
31 changes: 31 additions & 0 deletions substrate/client/rpc-spec-v2/src/archive/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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/>.

//! Substrate archive API.
//!
//! # Note
//!
//! Methods are prefixed by `archive`.

#[cfg(test)]
mod tests;

pub mod api;
pub mod archive;

pub use api::ArchiveApiServer;
113 changes: 113 additions & 0 deletions substrate/client/rpc-spec-v2/src/archive/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// This file is part of Substrate.

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

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

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

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

use crate::chain_head::hex_string;

use super::{archive::Archive, *};

use codec::{Decode, Encode};
use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule};
use sc_block_builder::BlockBuilderProvider;

use sp_consensus::BlockOrigin;
use std::sync::Arc;
use substrate_test_runtime_client::{
prelude::*, runtime, Backend, BlockBuilderExt, Client, ClientBlockImportExt,
};

const CHAIN_GENESIS: [u8; 32] = [0; 32];
const INVALID_HASH: [u8; 32] = [1; 32];

type Header = substrate_test_runtime_client::runtime::Header;
type Block = substrate_test_runtime_client::runtime::Block;

fn setup_api() -> (Arc<Client<Backend>>, RpcModule<Archive<Backend, Block, Client<Backend>>>) {
let builder = TestClientBuilder::new();
let client = Arc::new(builder.build());

let api = Archive::new(client.clone(), CHAIN_GENESIS).into_rpc();

(client, api)
}

#[tokio::test]
async fn archive_genesis() {
let (_client, api) = setup_api();

let genesis: String =
api.call("archive_unstable_genesisHash", EmptyParams::new()).await.unwrap();
assert_eq!(genesis, hex_string(&CHAIN_GENESIS));
}

#[tokio::test]
async fn archive_body() {
let (mut client, api) = setup_api();

// Invalid block hash.
let invalid_hash = hex_string(&INVALID_HASH);
let res: Option<Vec<String>> = api.call("archive_unstable_body", [invalid_hash]).await.unwrap();
assert!(res.is_none());

// Import a new block with an extrinsic.
let mut builder = client.new_block(Default::default()).unwrap();
builder
.push_transfer(runtime::Transfer {
from: AccountKeyring::Alice.into(),
to: AccountKeyring::Ferdie.into(),
amount: 42,
nonce: 0,
})
.unwrap();
let block = builder.build().unwrap().block;
let block_hash = format!("{:?}", block.header.hash());
client.import(BlockOrigin::Own, block.clone()).await.unwrap();

let expected_tx = hex_string(&block.extrinsics[0].encode());

let body: Vec<String> = api.call("archive_unstable_body", [block_hash]).await.unwrap();
assert_eq!(vec![expected_tx], body);
}

#[tokio::test]
async fn archive_header() {
let (mut client, api) = setup_api();

// Invalid block hash.
let invalid_hash = hex_string(&INVALID_HASH);
let res: Option<String> = api.call("archive_unstable_header", [invalid_hash]).await.unwrap();
assert!(res.is_none());

// Import a new block with an extrinsic.
let mut builder = client.new_block(Default::default()).unwrap();
builder
.push_transfer(runtime::Transfer {
from: AccountKeyring::Alice.into(),
to: AccountKeyring::Ferdie.into(),
amount: 42,
nonce: 0,
})
.unwrap();
let block = builder.build().unwrap().block;
let block_hash = format!("{:?}", block.header.hash());
client.import(BlockOrigin::Own, block.clone()).await.unwrap();

let header: String = api.call("archive_unstable_header", [block_hash]).await.unwrap();
let bytes = array_bytes::hex2bytes(&header).unwrap();
let header: Header = Decode::decode(&mut &bytes[..]).unwrap();
assert_eq!(header, block.header);
}
1 change: 1 addition & 0 deletions substrate/client/rpc-spec-v2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#![warn(missing_docs)]
#![deny(unused_crate_dependencies)]

pub mod archive;
pub mod chain_head;
pub mod chain_spec;
pub mod transaction;
Expand Down

0 comments on commit f3061c1

Please sign in to comment.