mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-09 21:07:31 +00:00
48baaa53ad
* rpc/chainhead: Test unpin for noncanonical prunned blocks Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/tests: Ensure fork is not reported by the Finalized event Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chainhead: Detect pruned forks to ignore from events Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/tests: Check unpin can be called on pruned hashes Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Fix clippy Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Handle race with memory blocks and notifications Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Add data config for the `follow` future Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Address feedback Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Move best block cache on the data config Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Send new events from the finalized stream Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chian_head: Report all pruned blocks Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Move `chainHead_follow` logic on dedicated file Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Delegate follow logic to `chain_head_follow` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Remove subscriptions on drop Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/tests: Ignore pruned blocks for a longer fork Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/tests: Check all pruned blocks are reported, not just stale heads Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/tests: Remove println debug and fix indentation Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Remove unnecessary trait bounds Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Add debug log for pruned forks Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Revert "rpc/chain_head: Add debug log for pruned forks" This reverts commit 425d6e7a8b60421bcece12add1941fe58524cf52. * Adjust blockID for testing Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * Update client/rpc-spec-v2/src/chain_head/chain_head_follow.rs Co-authored-by: Davide Galassi <davxy@datawok.net> * rpc/chain_head: Rename `ChainHeadFollow` to `ChainHeadFollower` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Remove subscriptions manually Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Improve log messages by adding subID and errors Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Ensure `follow` stops sending events on first error Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Use default constructor Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Add `StartupPoint` structure Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Rename `in_memory_blocks` Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Fix comment typo Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Keep unique blocks and remove itertools Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Make sure `bestBlocks` events are generated in order Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Maintain order of reported blocks Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Parent of finalized block could be unpinned Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * rpc/chain_head: Fix warning Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: Davide Galassi <davxy@datawok.net>
432 lines
12 KiB
Rust
432 lines
12 KiB
Rust
// 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 `chainHead`.
|
|
|
|
use crate::{
|
|
chain_head::{
|
|
api::ChainHeadApiServer,
|
|
chain_head_follow::ChainHeadFollower,
|
|
error::Error as ChainHeadRpcError,
|
|
event::{ChainHeadEvent, ChainHeadResult, ErrorEvent, FollowEvent, NetworkConfig},
|
|
subscription::SubscriptionManagement,
|
|
},
|
|
SubscriptionTaskExecutor,
|
|
};
|
|
use codec::Encode;
|
|
use futures::future::FutureExt;
|
|
use jsonrpsee::{
|
|
core::{async_trait, RpcResult},
|
|
types::{SubscriptionEmptyError, SubscriptionId, SubscriptionResult},
|
|
SubscriptionSink,
|
|
};
|
|
use log::debug;
|
|
use sc_client_api::{
|
|
Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey,
|
|
StorageProvider,
|
|
};
|
|
use sp_api::CallApiAt;
|
|
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
|
|
use sp_core::{hexdisplay::HexDisplay, storage::well_known_keys, traits::CallContext, Bytes};
|
|
use sp_runtime::traits::Block as BlockT;
|
|
use std::{marker::PhantomData, sync::Arc};
|
|
|
|
pub(crate) const LOG_TARGET: &str = "rpc-spec-v2";
|
|
|
|
/// An API for chain head RPC calls.
|
|
pub struct ChainHead<BE, Block: BlockT, Client> {
|
|
/// Substrate client.
|
|
client: Arc<Client>,
|
|
/// Backend of the chain.
|
|
backend: Arc<BE>,
|
|
/// Executor to spawn subscriptions.
|
|
executor: SubscriptionTaskExecutor,
|
|
/// Keep track of the pinned blocks for each subscription.
|
|
subscriptions: Arc<SubscriptionManagement<Block>>,
|
|
/// The hexadecimal encoded hash of the genesis block.
|
|
genesis_hash: String,
|
|
/// The maximum number of pinned blocks allowed per connection.
|
|
max_pinned_blocks: usize,
|
|
/// Phantom member to pin the block type.
|
|
_phantom: PhantomData<Block>,
|
|
}
|
|
|
|
impl<BE, Block: BlockT, Client> ChainHead<BE, Block, Client> {
|
|
/// Create a new [`ChainHead`].
|
|
pub fn new<GenesisHash: AsRef<[u8]>>(
|
|
client: Arc<Client>,
|
|
backend: Arc<BE>,
|
|
executor: SubscriptionTaskExecutor,
|
|
genesis_hash: GenesisHash,
|
|
max_pinned_blocks: usize,
|
|
) -> Self {
|
|
let genesis_hash = format!("0x{:?}", HexDisplay::from(&genesis_hash.as_ref()));
|
|
|
|
Self {
|
|
client,
|
|
backend,
|
|
executor,
|
|
subscriptions: Arc::new(SubscriptionManagement::new()),
|
|
genesis_hash,
|
|
max_pinned_blocks,
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
|
|
/// Accept the subscription and return the subscription ID on success.
|
|
fn accept_subscription(
|
|
&self,
|
|
sink: &mut SubscriptionSink,
|
|
) -> Result<String, SubscriptionEmptyError> {
|
|
// The subscription must be accepted before it can provide a valid subscription ID.
|
|
sink.accept()?;
|
|
|
|
let Some(sub_id) = sink.subscription_id() else {
|
|
// This can only happen if the subscription was not accepted.
|
|
return Err(SubscriptionEmptyError)
|
|
};
|
|
|
|
// Get the string representation for the subscription.
|
|
let sub_id = match sub_id {
|
|
SubscriptionId::Num(num) => num.to_string(),
|
|
SubscriptionId::Str(id) => id.into_owned().into(),
|
|
};
|
|
|
|
Ok(sub_id)
|
|
}
|
|
}
|
|
|
|
/// Parse hex-encoded string parameter as raw bytes.
|
|
///
|
|
/// If the parsing fails, the subscription is rejected.
|
|
fn parse_hex_param(
|
|
sink: &mut SubscriptionSink,
|
|
param: String,
|
|
) -> Result<Vec<u8>, SubscriptionEmptyError> {
|
|
// Methods can accept empty parameters.
|
|
if param.is_empty() {
|
|
return Ok(Default::default())
|
|
}
|
|
|
|
match array_bytes::hex2bytes(¶m) {
|
|
Ok(bytes) => Ok(bytes),
|
|
Err(_) => {
|
|
let _ = sink.reject(ChainHeadRpcError::InvalidParam(param));
|
|
Err(SubscriptionEmptyError)
|
|
},
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl<BE, Block, Client> ChainHeadApiServer<Block::Hash> for ChainHead<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 chain_head_unstable_follow(
|
|
&self,
|
|
mut sink: SubscriptionSink,
|
|
runtime_updates: bool,
|
|
) -> SubscriptionResult {
|
|
let sub_id = match self.accept_subscription(&mut sink) {
|
|
Ok(sub_id) => sub_id,
|
|
Err(err) => {
|
|
sink.close(ChainHeadRpcError::InvalidSubscriptionID);
|
|
return Err(err)
|
|
},
|
|
};
|
|
|
|
// Keep track of the subscription.
|
|
let Some((rx_stop, sub_handle)) = self.subscriptions.insert_subscription(sub_id.clone(), runtime_updates, self.max_pinned_blocks) else {
|
|
// Inserting the subscription can only fail if the JsonRPSee
|
|
// generated a duplicate subscription ID.
|
|
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription already accepted", sub_id);
|
|
let _ = sink.send(&FollowEvent::<Block::Hash>::Stop);
|
|
return Ok(())
|
|
};
|
|
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription accepted", sub_id);
|
|
|
|
let subscriptions = self.subscriptions.clone();
|
|
let backend = self.backend.clone();
|
|
let client = self.client.clone();
|
|
let fut = async move {
|
|
let mut chain_head_follow = ChainHeadFollower::new(
|
|
client,
|
|
backend,
|
|
sub_handle,
|
|
runtime_updates,
|
|
sub_id.clone(),
|
|
);
|
|
|
|
chain_head_follow.generate_events(sink, rx_stop).await;
|
|
|
|
subscriptions.remove_subscription(&sub_id);
|
|
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription removed", sub_id);
|
|
};
|
|
|
|
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
|
Ok(())
|
|
}
|
|
|
|
fn chain_head_unstable_body(
|
|
&self,
|
|
mut sink: SubscriptionSink,
|
|
follow_subscription: String,
|
|
hash: Block::Hash,
|
|
_network_config: Option<NetworkConfig>,
|
|
) -> SubscriptionResult {
|
|
let client = self.client.clone();
|
|
let subscriptions = self.subscriptions.clone();
|
|
|
|
let fut = async move {
|
|
let Some(handle) = subscriptions.get_subscription(&follow_subscription) else {
|
|
// Invalid invalid subscription ID.
|
|
let _ = sink.send(&ChainHeadEvent::<String>::Disjoint);
|
|
return
|
|
};
|
|
|
|
// Block is not part of the subscription.
|
|
if !handle.contains_block(&hash) {
|
|
let _ = sink.reject(ChainHeadRpcError::InvalidBlock);
|
|
return
|
|
}
|
|
|
|
let event = match client.block(hash) {
|
|
Ok(Some(signed_block)) => {
|
|
let extrinsics = signed_block.block.extrinsics();
|
|
let result = format!("0x{:?}", HexDisplay::from(&extrinsics.encode()));
|
|
ChainHeadEvent::Done(ChainHeadResult { result })
|
|
},
|
|
Ok(None) => {
|
|
// The block's body was pruned. This subscription ID has become invalid.
|
|
debug!(
|
|
target: LOG_TARGET,
|
|
"[body][id={:?}] Stopping subscription because hash={:?} was pruned",
|
|
follow_subscription,
|
|
hash
|
|
);
|
|
handle.stop();
|
|
ChainHeadEvent::<String>::Disjoint
|
|
},
|
|
Err(error) => ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }),
|
|
};
|
|
let _ = sink.send(&event);
|
|
};
|
|
|
|
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
|
Ok(())
|
|
}
|
|
|
|
fn chain_head_unstable_header(
|
|
&self,
|
|
follow_subscription: String,
|
|
hash: Block::Hash,
|
|
) -> RpcResult<Option<String>> {
|
|
let Some(handle) = self.subscriptions.get_subscription(&follow_subscription) else {
|
|
// Invalid invalid subscription ID.
|
|
return Ok(None)
|
|
};
|
|
|
|
// Block is not part of the subscription.
|
|
if !handle.contains_block(&hash) {
|
|
return Err(ChainHeadRpcError::InvalidBlock.into())
|
|
}
|
|
|
|
self.client
|
|
.header(hash)
|
|
.map(|opt_header| opt_header.map(|h| format!("0x{:?}", HexDisplay::from(&h.encode()))))
|
|
.map_err(ChainHeadRpcError::FetchBlockHeader)
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
fn chain_head_unstable_genesis_hash(&self) -> RpcResult<String> {
|
|
Ok(self.genesis_hash.clone())
|
|
}
|
|
|
|
fn chain_head_unstable_storage(
|
|
&self,
|
|
mut sink: SubscriptionSink,
|
|
follow_subscription: String,
|
|
hash: Block::Hash,
|
|
key: String,
|
|
child_key: Option<String>,
|
|
_network_config: Option<NetworkConfig>,
|
|
) -> SubscriptionResult {
|
|
let key = StorageKey(parse_hex_param(&mut sink, key)?);
|
|
|
|
let child_key = child_key
|
|
.map(|child_key| parse_hex_param(&mut sink, child_key))
|
|
.transpose()?
|
|
.map(ChildInfo::new_default_from_vec);
|
|
|
|
let client = self.client.clone();
|
|
let subscriptions = self.subscriptions.clone();
|
|
|
|
let fut = async move {
|
|
let Some(handle) = subscriptions.get_subscription(&follow_subscription) else {
|
|
// Invalid invalid subscription ID.
|
|
let _ = sink.send(&ChainHeadEvent::<String>::Disjoint);
|
|
return
|
|
};
|
|
|
|
// Block is not part of the subscription.
|
|
if !handle.contains_block(&hash) {
|
|
let _ = sink.reject(ChainHeadRpcError::InvalidBlock);
|
|
return
|
|
}
|
|
|
|
// The child key is provided, use the key to query the child trie.
|
|
if let Some(child_key) = child_key {
|
|
// The child key must not be prefixed with ":child_storage:" nor
|
|
// ":child_storage:default:".
|
|
if well_known_keys::is_default_child_storage_key(child_key.storage_key()) ||
|
|
well_known_keys::is_child_storage_key(child_key.storage_key())
|
|
{
|
|
let _ = sink
|
|
.send(&ChainHeadEvent::Done(ChainHeadResult { result: None::<String> }));
|
|
return
|
|
}
|
|
|
|
let res = client
|
|
.child_storage(hash, &child_key, &key)
|
|
.map(|result| {
|
|
let result =
|
|
result.map(|storage| format!("0x{:?}", HexDisplay::from(&storage.0)));
|
|
ChainHeadEvent::Done(ChainHeadResult { result })
|
|
})
|
|
.unwrap_or_else(|error| {
|
|
ChainHeadEvent::Error(ErrorEvent { error: error.to_string() })
|
|
});
|
|
let _ = sink.send(&res);
|
|
return
|
|
}
|
|
|
|
// The main key must not be prefixed with b":child_storage:" nor
|
|
// b":child_storage:default:".
|
|
if well_known_keys::is_default_child_storage_key(&key.0) ||
|
|
well_known_keys::is_child_storage_key(&key.0)
|
|
{
|
|
let _ =
|
|
sink.send(&ChainHeadEvent::Done(ChainHeadResult { result: None::<String> }));
|
|
return
|
|
}
|
|
|
|
// Main root trie storage query.
|
|
let res = client
|
|
.storage(hash, &key)
|
|
.map(|result| {
|
|
let result =
|
|
result.map(|storage| format!("0x{:?}", HexDisplay::from(&storage.0)));
|
|
ChainHeadEvent::Done(ChainHeadResult { result })
|
|
})
|
|
.unwrap_or_else(|error| {
|
|
ChainHeadEvent::Error(ErrorEvent { error: error.to_string() })
|
|
});
|
|
let _ = sink.send(&res);
|
|
};
|
|
|
|
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
|
Ok(())
|
|
}
|
|
|
|
fn chain_head_unstable_call(
|
|
&self,
|
|
mut sink: SubscriptionSink,
|
|
follow_subscription: String,
|
|
hash: Block::Hash,
|
|
function: String,
|
|
call_parameters: String,
|
|
_network_config: Option<NetworkConfig>,
|
|
) -> SubscriptionResult {
|
|
let call_parameters = Bytes::from(parse_hex_param(&mut sink, call_parameters)?);
|
|
|
|
let client = self.client.clone();
|
|
let subscriptions = self.subscriptions.clone();
|
|
|
|
let fut = async move {
|
|
let Some(handle) = subscriptions.get_subscription(&follow_subscription) else {
|
|
// Invalid invalid subscription ID.
|
|
let _ = sink.send(&ChainHeadEvent::<String>::Disjoint);
|
|
return
|
|
};
|
|
|
|
// Block is not part of the subscription.
|
|
if !handle.contains_block(&hash) {
|
|
let _ = sink.reject(ChainHeadRpcError::InvalidBlock);
|
|
return
|
|
}
|
|
|
|
// Reject subscription if runtime_updates is false.
|
|
if !handle.has_runtime_updates() {
|
|
let _ = sink.reject(ChainHeadRpcError::InvalidParam(
|
|
"The runtime updates flag must be set".into(),
|
|
));
|
|
return
|
|
}
|
|
|
|
let res = client
|
|
.executor()
|
|
.call(
|
|
hash,
|
|
&function,
|
|
&call_parameters,
|
|
client.execution_extensions().strategies().other,
|
|
CallContext::Offchain,
|
|
)
|
|
.map(|result| {
|
|
let result = format!("0x{:?}", HexDisplay::from(&result));
|
|
ChainHeadEvent::Done(ChainHeadResult { result })
|
|
})
|
|
.unwrap_or_else(|error| {
|
|
ChainHeadEvent::Error(ErrorEvent { error: error.to_string() })
|
|
});
|
|
|
|
let _ = sink.send(&res);
|
|
};
|
|
|
|
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
|
Ok(())
|
|
}
|
|
|
|
fn chain_head_unstable_unpin(
|
|
&self,
|
|
follow_subscription: String,
|
|
hash: Block::Hash,
|
|
) -> RpcResult<()> {
|
|
let Some(handle) = self.subscriptions.get_subscription(&follow_subscription) else {
|
|
// Invalid invalid subscription ID.
|
|
return Ok(())
|
|
};
|
|
|
|
if !handle.unpin_block(&hash) {
|
|
return Err(ChainHeadRpcError::InvalidBlock.into())
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|