// This file is part of Substrate.
// Copyright (C) 2019-2022 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 .
//! State API backend for full nodes.
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use super::{
client_err,
error::{Error, Result},
ChildStateBackend, StateBackend,
};
use crate::SubscriptionTaskExecutor;
use futures::{future, stream, FutureExt, StreamExt};
use jsonrpsee::{core::Error as JsonRpseeError, PendingSubscription};
use sc_client_api::{
Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider,
StorageProvider,
};
use sc_rpc_api::state::ReadProof;
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
use sp_blockchain::{
CachedHeaderMetadata, Error as ClientError, HeaderBackend, HeaderMetadata,
Result as ClientResult,
};
use sp_core::{
storage::{
ChildInfo, ChildType, PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey,
},
Bytes,
};
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_version::RuntimeVersion;
/// Ranges to query in state_queryStorage.
struct QueryStorageRange {
/// Hashes of all the blocks in the range.
pub hashes: Vec,
}
/// State API backend for full nodes.
pub struct FullState {
client: Arc,
executor: SubscriptionTaskExecutor,
_phantom: PhantomData<(BE, Block)>,
rpc_max_payload: Option,
}
impl FullState
where
BE: Backend,
Client: StorageProvider
+ HeaderBackend
+ BlockBackend
+ HeaderMetadata,
Block: BlockT + 'static,
{
/// Create new state API backend for full nodes.
pub fn new(
client: Arc,
executor: SubscriptionTaskExecutor,
rpc_max_payload: Option,
) -> Self {
Self { client, executor, _phantom: PhantomData, rpc_max_payload }
}
/// Returns given block hash or best block hash if None is passed.
fn block_or_best(&self, hash: Option) -> ClientResult {
Ok(hash.unwrap_or_else(|| self.client.info().best_hash))
}
/// Validates block range.
fn query_storage_range(
&self,
from: Block::Hash,
to: Option,
) -> Result> {
let to = self
.block_or_best(to)
.map_err(|e| invalid_block::(from, to, e.to_string()))?;
let invalid_block_err =
|e: ClientError| invalid_block::(from, Some(to), e.to_string());
let from_meta = self.client.header_metadata(from).map_err(invalid_block_err)?;
let to_meta = self.client.header_metadata(to).map_err(invalid_block_err)?;
if from_meta.number > to_meta.number {
return Err(invalid_block_range(
&from_meta,
&to_meta,
"from number > to number".to_owned(),
))
}
// check if we can get from `to` to `from` by going through parent_hashes.
let from_number = from_meta.number;
let hashes = {
let mut hashes = vec![to_meta.hash];
let mut last = to_meta.clone();
while last.number > from_number {
let header_metadata = self
.client
.header_metadata(last.parent)
.map_err(|e| invalid_block_range::(&last, &to_meta, e.to_string()))?;
hashes.push(header_metadata.hash);
last = header_metadata;
}
if last.hash != from_meta.hash {
return Err(invalid_block_range(
&from_meta,
&to_meta,
"from and to are on different forks".to_owned(),
))
}
hashes.reverse();
hashes
};
Ok(QueryStorageRange { hashes })
}
/// Iterates through range.unfiltered_range and check each block for changes of keys' values.
fn query_storage_unfiltered(
&self,
range: &QueryStorageRange,
keys: &[StorageKey],
last_values: &mut HashMap>,
changes: &mut Vec>,
) -> Result<()> {
for block_hash in &range.hashes {
let mut block_changes = StorageChangeSet { block: *block_hash, changes: Vec::new() };
let id = BlockId::hash(*block_hash);
for key in keys {
let (has_changed, data) = {
let curr_data = self.client.storage(&id, key).map_err(client_err)?;
match last_values.get(key) {
Some(prev_data) => (curr_data != *prev_data, curr_data),
None => (true, curr_data),
}
};
if has_changed {
block_changes.changes.push((key.clone(), data.clone()));
}
last_values.insert(key.clone(), data);
}
if !block_changes.changes.is_empty() {
changes.push(block_changes);
}
}
Ok(())
}
}
impl StateBackend for FullState
where
Block: BlockT + 'static,
Block::Hash: Unpin,
BE: Backend + 'static,
Client: ExecutorProvider
+ StorageProvider
+ ProofProvider
+ HeaderBackend
+ HeaderMetadata
+ BlockchainEvents
+ CallApiAt
+ ProvideRuntimeApi
+ BlockBackend
+ Send
+ Sync
+ 'static,
Client::Api: Metadata,
{
fn call(
&self,
block: Option,
method: String,
call_data: Bytes,
) -> std::result::Result {
self.block_or_best(block)
.and_then(|block| {
self.client
.executor()
.call(
&BlockId::Hash(block),
&method,
&*call_data,
self.client.execution_extensions().strategies().other,
None,
)
.map(Into::into)
})
.map_err(client_err)
}
fn storage_keys(
&self,
block: Option,
prefix: StorageKey,
) -> std::result::Result, Error> {
self.block_or_best(block)
.and_then(|block| self.client.storage_keys(&BlockId::Hash(block), &prefix))
.map_err(client_err)
}
fn storage_pairs(
&self,
block: Option,
prefix: StorageKey,
) -> std::result::Result, Error> {
self.block_or_best(block)
.and_then(|block| self.client.storage_pairs(&BlockId::Hash(block), &prefix))
.map_err(client_err)
}
fn storage_keys_paged(
&self,
block: Option,
prefix: Option,
count: u32,
start_key: Option,
) -> std::result::Result, Error> {
self.block_or_best(block)
.and_then(|block| {
self.client.storage_keys_iter(
&BlockId::Hash(block),
prefix.as_ref(),
start_key.as_ref(),
)
})
.map(|iter| iter.take(count as usize).collect())
.map_err(client_err)
}
fn storage(
&self,
block: Option,
key: StorageKey,
) -> std::result::Result