From 01ac54db29ae054cac46740c72dcecf441ceaa44 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Tue, 23 Jan 2024 18:22:56 +0200 Subject: [PATCH] rpc-v2: Enable the `archive` class of methods (#3017) The [archive](https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/archive.md) API is unstable and subject to change. This PR enables the `archive` class of the RPC-V2 spec to substrate based chains. The `archive` API is enabled for archive nodes: - the state of the blocks is in archive mode - the block's bodies are in archive mode While at it, this PR extends the `BlocksPrunning` enum with an `is_archive` helper to check if the pruning mode keeps the block's bodies for long enough. Defaults used for the `archive` API: - a maximum of 5 responses are provided for descendants queries (this is similar to chainHead) - a maximum of 8 item queries are accepted at a time Before stabilizing the API we should look into these defaults and adjust after collecting some data. --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --- substrate/client/db/src/lib.rs | 10 ++++ .../client/rpc-spec-v2/src/archive/archive.rs | 52 +++++++++++++++---- .../src/archive/archive_storage.rs | 18 ++++--- .../client/rpc-spec-v2/src/archive/mod.rs | 1 + .../client/rpc-spec-v2/src/archive/tests.rs | 17 ++++-- substrate/client/service/src/builder.rs | 24 ++++++++- 6 files changed, 99 insertions(+), 23 deletions(-) diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 194bec8a88e..2d8622d5f12 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -320,6 +320,16 @@ pub enum BlocksPruning { Some(u32), } +impl BlocksPruning { + /// True if this is an archive pruning mode (either KeepAll or KeepFinalized). + pub fn is_archive(&self) -> bool { + match *self { + BlocksPruning::KeepAll | BlocksPruning::KeepFinalized => true, + BlocksPruning::Some(_) => false, + } + } +} + /// Where to find the database.. #[derive(Debug, Clone)] pub enum DatabaseSource { diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index c01afb5d779..82c6b2cacc2 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -34,7 +34,7 @@ use sp_api::{CallApiAt, CallContext}; use sp_blockchain::{ Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, }; -use sp_core::Bytes; +use sp_core::{Bytes, U256}; use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT, NumberFor}, SaturatedConversion, @@ -43,6 +43,36 @@ use std::{collections::HashSet, marker::PhantomData, sync::Arc}; use super::archive_storage::ArchiveStorage; +/// The configuration of [`Archive`]. +pub struct ArchiveConfig { + /// The maximum number of items the `archive_storage` can return for a descendant query before + /// pagination is required. + pub max_descendant_responses: usize, + /// The maximum number of queried items allowed for the `archive_storage` at a time. + pub max_queried_items: usize, +} + +/// The maximum number of items the `archive_storage` can return for a descendant query before +/// pagination is required. +/// +/// Note: this is identical to the `chainHead` value. +const MAX_DESCENDANT_RESPONSES: usize = 5; + +/// The maximum number of queried items allowed for the `archive_storage` at a time. +/// +/// Note: A queried item can also be a descendant query which can return up to +/// `MAX_DESCENDANT_RESPONSES`. +const MAX_QUERIED_ITEMS: usize = 8; + +impl Default for ArchiveConfig { + fn default() -> Self { + Self { + max_descendant_responses: MAX_DESCENDANT_RESPONSES, + max_queried_items: MAX_QUERIED_ITEMS, + } + } +} + /// An API for archive RPC calls. pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> { /// Substrate client. @@ -51,8 +81,9 @@ pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> { backend: Arc<BE>, /// The hexadecimal encoded hash of the genesis block. genesis_hash: String, - /// The maximum number of reported items by the `archive_storage` at a time. - storage_max_reported_items: usize, + /// The maximum number of items the `archive_storage` can return for a descendant query before + /// pagination is required. + storage_max_descendant_responses: usize, /// The maximum number of queried items allowed for the `archive_storage` at a time. storage_max_queried_items: usize, /// Phantom member to pin the block type. @@ -65,16 +96,15 @@ impl<BE: Backend<Block>, Block: BlockT, Client> Archive<BE, Block, Client> { client: Arc<Client>, backend: Arc<BE>, genesis_hash: GenesisHash, - storage_max_reported_items: usize, - storage_max_queried_items: usize, + config: ArchiveConfig, ) -> Self { let genesis_hash = hex_string(&genesis_hash.as_ref()); Self { client, backend, genesis_hash, - storage_max_reported_items, - storage_max_queried_items, + storage_max_descendant_responses: config.max_descendant_responses, + storage_max_queried_items: config.max_queried_items, _phantom: PhantomData, } } @@ -97,7 +127,6 @@ impl<BE, Block, Client> ArchiveApiServer<Block::Hash> for Archive<BE, Block, Cli where Block: BlockT + 'static, Block::Header: Unpin, - <<Block as BlockT>::Header as HeaderT>::Number: From<u64>, BE: Backend<Block> + 'static, Client: BlockBackend<Block> + ExecutorProvider<Block> @@ -136,7 +165,10 @@ where } fn archive_unstable_hash_by_height(&self, height: u64) -> RpcResult<Vec<String>> { - let height: NumberFor<Block> = height.into(); + 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 { @@ -240,7 +272,7 @@ where let storage_client = ArchiveStorage::new( self.client.clone(), - self.storage_max_reported_items, + self.storage_max_descendant_responses, self.storage_max_queried_items, ); Ok(storage_client.handle_query(hash, items, child_trie)) diff --git a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs index 09415af1ca1..26e7c299de4 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs @@ -28,12 +28,12 @@ use crate::common::{ storage::{IterQueryType, QueryIter, Storage}, }; -/// Generates the events of the `chainHead_storage` method. +/// Generates the events of the `archive_storage` method. pub struct ArchiveStorage<Client, Block, BE> { /// Storage client. client: Storage<Client, Block, BE>, - /// The maximum number of reported items by the `archive_storage` at a time. - storage_max_reported_items: usize, + /// The maximum number of responses the API can return for a descendant query at a time. + storage_max_descendant_responses: usize, /// The maximum number of queried items allowed for the `archive_storage` at a time. storage_max_queried_items: usize, } @@ -42,10 +42,14 @@ impl<Client, Block, BE> ArchiveStorage<Client, Block, BE> { /// Constructs a new [`ArchiveStorage`]. pub fn new( client: Arc<Client>, - storage_max_reported_items: usize, + storage_max_descendant_responses: usize, storage_max_queried_items: usize, ) -> Self { - Self { client: Storage::new(client), storage_max_reported_items, storage_max_queried_items } + Self { + client: Storage::new(client), + storage_max_descendant_responses, + storage_max_queried_items, + } } } @@ -96,7 +100,7 @@ where }, hash, child_key.as_ref(), - self.storage_max_reported_items, + self.storage_max_descendant_responses, ) { Ok((results, _)) => storage_results.extend(results), Err(error) => return ArchiveStorageResult::err(error), @@ -111,7 +115,7 @@ where }, hash, child_key.as_ref(), - self.storage_max_reported_items, + self.storage_max_descendant_responses, ) { Ok((results, _)) => storage_results.extend(results), Err(error) => return ArchiveStorageResult::err(error), diff --git a/substrate/client/rpc-spec-v2/src/archive/mod.rs b/substrate/client/rpc-spec-v2/src/archive/mod.rs index e1f45e19a62..5f020c203ea 100644 --- a/substrate/client/rpc-spec-v2/src/archive/mod.rs +++ b/substrate/client/rpc-spec-v2/src/archive/mod.rs @@ -32,3 +32,4 @@ pub mod archive; pub mod error; pub use api::ArchiveApiServer; +pub use archive::{Archive, ArchiveConfig}; diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 108395eb91a..09b2410eac6 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -24,7 +24,10 @@ use crate::{ hex_string, MethodResult, }; -use super::{archive::Archive, *}; +use super::{ + archive::{Archive, ArchiveConfig}, + *, +}; use assert_matches::assert_matches; use codec::{Decode, Encode}; @@ -60,7 +63,7 @@ type Header = substrate_test_runtime_client::runtime::Header; type Block = substrate_test_runtime_client::runtime::Block; fn setup_api( - max_returned_items: usize, + max_descendant_responses: usize, max_queried_items: usize, ) -> (Arc<Client<Backend>>, RpcModule<Archive<Backend, Block, Client<Backend>>>) { let child_info = ChildInfo::new_default(CHILD_STORAGE_KEY); @@ -72,9 +75,13 @@ fn setup_api( let backend = builder.backend(); let client = Arc::new(builder.build()); - let api = - Archive::new(client.clone(), backend, CHAIN_GENESIS, max_returned_items, max_queried_items) - .into_rpc(); + let api = Archive::new( + client.clone(), + backend, + CHAIN_GENESIS, + ArchiveConfig { max_descendant_responses, max_queried_items }, + ) + .into_rpc(); (client, api) } diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 0b8c86be92b..bc848e8d4b2 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -63,7 +63,9 @@ use sc_rpc::{ system::SystemApiServer, DenyUnsafe, SubscriptionTaskExecutor, }; -use sc_rpc_spec_v2::{chain_head::ChainHeadApiServer, transaction::TransactionApiServer}; +use sc_rpc_spec_v2::{ + archive::ArchiveApiServer, chain_head::ChainHeadApiServer, transaction::TransactionApiServer, +}; use sc_telemetry::{telemetry, ConnectionMessage, Telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; @@ -664,6 +666,26 @@ where ) .into_rpc(); + // Part of the RPC v2 spec. + // An archive node that can respond to the `archive` RPC-v2 queries is a node with: + // - state pruning in archive mode: The storage of blocks is kept around + // - block pruning in archive mode: The block's body is kept around + let is_archive_node = config.state_pruning.as_ref().map(|sp| sp.is_archive()).unwrap_or(false) && + config.blocks_pruning.is_archive(); + if is_archive_node { + let genesis_hash = + client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed"); + let archive_v2 = sc_rpc_spec_v2::archive::Archive::new( + client.clone(), + backend.clone(), + genesis_hash, + // Defaults to sensible limits for the `Archive`. + sc_rpc_spec_v2::archive::ArchiveConfig::default(), + ) + .into_rpc(); + rpc_api.merge(archive_v2).map_err(|e| Error::Application(e.into()))?; + } + let author = sc_rpc::author::Author::new( client.clone(), transaction_pool, -- GitLab