mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 19:05:41 +00:00
chainHead: Produce method responses on chainHead_follow (#14692)
* chainHead/api: Make storage/body/call pure RPC methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Add mpsc channel between RPC methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/subscriptions: Extract mpsc::Sender via BlockGuard Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/subscriptions: Generate and provide the method operation ID Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Generate `chainHead_body` response Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Generate `chainHead_call` response Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Generate `chainHead_storage` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Propagate responses of methods to chainHead_follow Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Adjust `chainHead_body` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Adjust `chainHead_call` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Adjust `chainHead_call` responses Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Ensure unique operation IDs across methods Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/events: Remove old method events Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Return `InvalidBlock` error if pinning fails Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead: Wrap subscription IDs Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> * chainHead/tests: Ensure separate operation IDs across subscriptions Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -18,12 +18,16 @@
|
||||
|
||||
//! API implementation for `chainHead`.
|
||||
|
||||
use super::{
|
||||
chain_head_storage::ChainHeadStorage,
|
||||
event::{MethodResponseStarted, OperationBodyDone, OperationCallDone},
|
||||
};
|
||||
use crate::{
|
||||
chain_head::{
|
||||
api::ChainHeadApiServer,
|
||||
chain_head_follow::ChainHeadFollower,
|
||||
error::Error as ChainHeadRpcError,
|
||||
event::{ChainHeadEvent, ChainHeadResult, ErrorEvent, FollowEvent},
|
||||
event::{FollowEvent, MethodResponse, OperationError, StorageQuery, StorageQueryType},
|
||||
hex_string,
|
||||
subscription::{SubscriptionManagement, SubscriptionManagementError},
|
||||
},
|
||||
@@ -47,11 +51,6 @@ use sp_core::{traits::CallContext, Bytes};
|
||||
use sp_runtime::traits::Block as BlockT;
|
||||
use std::{marker::PhantomData, sync::Arc, time::Duration};
|
||||
|
||||
use super::{
|
||||
chain_head_storage::ChainHeadStorage,
|
||||
event::{ChainHeadStorageEvent, StorageQuery, StorageQueryType},
|
||||
};
|
||||
|
||||
pub(crate) const LOG_TARGET: &str = "rpc-spec-v2";
|
||||
|
||||
/// An API for chain head RPC calls.
|
||||
@@ -81,7 +80,6 @@ impl<BE: Backend<Block>, Block: BlockT, Client> ChainHead<BE, Block, Client> {
|
||||
max_pinned_duration: Duration,
|
||||
) -> Self {
|
||||
let genesis_hash = hex_string(&genesis_hash.as_ref());
|
||||
|
||||
Self {
|
||||
client,
|
||||
backend: backend.clone(),
|
||||
@@ -121,11 +119,8 @@ impl<BE: Backend<Block>, Block: BlockT, Client> ChainHead<BE, Block, Client> {
|
||||
|
||||
/// 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> {
|
||||
/// If the parsing fails, returns an error propagated to the RPC method.
|
||||
fn parse_hex_param(param: String) -> Result<Vec<u8>, ChainHeadRpcError> {
|
||||
// Methods can accept empty parameters.
|
||||
if param.is_empty() {
|
||||
return Ok(Default::default())
|
||||
@@ -133,10 +128,7 @@ fn parse_hex_param(
|
||||
|
||||
match array_bytes::hex2bytes(¶m) {
|
||||
Ok(bytes) => Ok(bytes),
|
||||
Err(_) => {
|
||||
let _ = sink.reject(ChainHeadRpcError::InvalidParam(param));
|
||||
Err(SubscriptionEmptyError)
|
||||
},
|
||||
Err(_) => Err(ChainHeadRpcError::InvalidParam(param)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +160,7 @@ where
|
||||
},
|
||||
};
|
||||
// Keep track of the subscription.
|
||||
let Some(rx_stop) = self.subscriptions.insert_subscription(sub_id.clone(), with_runtime)
|
||||
let Some(sub_data) = self.subscriptions.insert_subscription(sub_id.clone(), with_runtime)
|
||||
else {
|
||||
// Inserting the subscription can only fail if the JsonRPSee
|
||||
// generated a duplicate subscription ID.
|
||||
@@ -190,7 +182,7 @@ where
|
||||
sub_id.clone(),
|
||||
);
|
||||
|
||||
chain_head_follow.generate_events(sink, rx_stop).await;
|
||||
chain_head_follow.generate_events(sink, sub_data).await;
|
||||
|
||||
subscriptions.remove_subscription(&sub_id);
|
||||
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription removed", sub_id);
|
||||
@@ -202,59 +194,57 @@ where
|
||||
|
||||
fn chain_head_unstable_body(
|
||||
&self,
|
||||
mut sink: SubscriptionSink,
|
||||
follow_subscription: String,
|
||||
hash: Block::Hash,
|
||||
) -> SubscriptionResult {
|
||||
let client = self.client.clone();
|
||||
let subscriptions = self.subscriptions.clone();
|
||||
|
||||
let block_guard = match subscriptions.lock_block(&follow_subscription, hash) {
|
||||
) -> RpcResult<MethodResponse> {
|
||||
let block_guard = match self.subscriptions.lock_block(&follow_subscription, hash) {
|
||||
Ok(block) => block,
|
||||
Err(SubscriptionManagementError::SubscriptionAbsent) => {
|
||||
// Invalid invalid subscription ID.
|
||||
let _ = sink.send(&ChainHeadEvent::<String>::Disjoint);
|
||||
return Ok(())
|
||||
return Ok(MethodResponse::LimitReached)
|
||||
},
|
||||
Err(SubscriptionManagementError::BlockHashAbsent) => {
|
||||
// Block is not part of the subscription.
|
||||
let _ = sink.reject(ChainHeadRpcError::InvalidBlock);
|
||||
return Ok(())
|
||||
},
|
||||
Err(error) => {
|
||||
let _ = sink.send(&ChainHeadEvent::<String>::Error(ErrorEvent {
|
||||
error: error.to_string(),
|
||||
}));
|
||||
return Ok(())
|
||||
return Err(ChainHeadRpcError::InvalidBlock.into())
|
||||
},
|
||||
Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()),
|
||||
};
|
||||
|
||||
let fut = async move {
|
||||
let _block_guard = block_guard;
|
||||
let event = match client.block(hash) {
|
||||
Ok(Some(signed_block)) => {
|
||||
let extrinsics = signed_block.block.extrinsics();
|
||||
let result = hex_string(&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
|
||||
);
|
||||
subscriptions.remove_subscription(&follow_subscription);
|
||||
ChainHeadEvent::<String>::Disjoint
|
||||
},
|
||||
Err(error) => ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }),
|
||||
};
|
||||
let _ = sink.send(&event);
|
||||
let event = match self.client.block(hash) {
|
||||
Ok(Some(signed_block)) => {
|
||||
let extrinsics = signed_block
|
||||
.block
|
||||
.extrinsics()
|
||||
.iter()
|
||||
.map(|extrinsic| hex_string(&extrinsic.encode()))
|
||||
.collect();
|
||||
FollowEvent::<Block::Hash>::OperationBodyDone(OperationBodyDone {
|
||||
operation_id: block_guard.operation_id(),
|
||||
value: extrinsics,
|
||||
})
|
||||
},
|
||||
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
|
||||
);
|
||||
self.subscriptions.remove_subscription(&follow_subscription);
|
||||
return Err(ChainHeadRpcError::InvalidBlock.into())
|
||||
},
|
||||
Err(error) => FollowEvent::<Block::Hash>::OperationError(OperationError {
|
||||
operation_id: block_guard.operation_id(),
|
||||
error: error.to_string(),
|
||||
}),
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
let _ = block_guard.response_sender().unbounded_send(event);
|
||||
Ok(MethodResponse::Started(MethodResponseStarted {
|
||||
operation_id: block_guard.operation_id(),
|
||||
discarded_items: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn chain_head_unstable_header(
|
||||
@@ -288,128 +278,113 @@ where
|
||||
|
||||
fn chain_head_unstable_storage(
|
||||
&self,
|
||||
mut sink: SubscriptionSink,
|
||||
follow_subscription: String,
|
||||
hash: Block::Hash,
|
||||
items: Vec<StorageQuery<String>>,
|
||||
child_trie: Option<String>,
|
||||
) -> SubscriptionResult {
|
||||
) -> RpcResult<MethodResponse> {
|
||||
// Gain control over parameter parsing and returned error.
|
||||
let items = items
|
||||
.into_iter()
|
||||
.map(|query| {
|
||||
if query.query_type == StorageQueryType::ClosestDescendantMerkleValue {
|
||||
// Note: remove this once all types are implemented.
|
||||
let _ = sink.reject(ChainHeadRpcError::InvalidParam(
|
||||
return Err(ChainHeadRpcError::InvalidParam(
|
||||
"Storage query type not supported".into(),
|
||||
));
|
||||
return Err(SubscriptionEmptyError)
|
||||
))
|
||||
}
|
||||
|
||||
Ok(StorageQuery {
|
||||
key: StorageKey(parse_hex_param(&mut sink, query.key)?),
|
||||
key: StorageKey(parse_hex_param(query.key)?),
|
||||
query_type: query.query_type,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let child_trie = child_trie
|
||||
.map(|child_trie| parse_hex_param(&mut sink, child_trie))
|
||||
.map(|child_trie| parse_hex_param(child_trie))
|
||||
.transpose()?
|
||||
.map(ChildInfo::new_default_from_vec);
|
||||
|
||||
let client = self.client.clone();
|
||||
let subscriptions = self.subscriptions.clone();
|
||||
|
||||
let block_guard = match subscriptions.lock_block(&follow_subscription, hash) {
|
||||
let block_guard = match self.subscriptions.lock_block(&follow_subscription, hash) {
|
||||
Ok(block) => block,
|
||||
Err(SubscriptionManagementError::SubscriptionAbsent) => {
|
||||
// Invalid invalid subscription ID.
|
||||
let _ = sink.send(&ChainHeadStorageEvent::Disjoint);
|
||||
return Ok(())
|
||||
return Ok(MethodResponse::LimitReached)
|
||||
},
|
||||
Err(SubscriptionManagementError::BlockHashAbsent) => {
|
||||
// Block is not part of the subscription.
|
||||
let _ = sink.reject(ChainHeadRpcError::InvalidBlock);
|
||||
return Ok(())
|
||||
},
|
||||
Err(error) => {
|
||||
let _ = sink
|
||||
.send(&ChainHeadStorageEvent::Error(ErrorEvent { error: error.to_string() }));
|
||||
return Ok(())
|
||||
return Err(ChainHeadRpcError::InvalidBlock.into())
|
||||
},
|
||||
Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()),
|
||||
};
|
||||
|
||||
let storage_client = ChainHeadStorage::<Client, Block, BE>::new(client);
|
||||
|
||||
let storage_client = ChainHeadStorage::<Client, Block, BE>::new(self.client.clone());
|
||||
let operation_id = block_guard.operation_id();
|
||||
let fut = async move {
|
||||
let _block_guard = block_guard;
|
||||
|
||||
storage_client.generate_events(sink, hash, items, child_trie);
|
||||
storage_client.generate_events(block_guard, hash, items, child_trie);
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
self.executor
|
||||
.spawn_blocking("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(MethodResponse::Started(MethodResponseStarted {
|
||||
operation_id,
|
||||
discarded_items: Some(0),
|
||||
}))
|
||||
}
|
||||
|
||||
fn chain_head_unstable_call(
|
||||
&self,
|
||||
mut sink: SubscriptionSink,
|
||||
follow_subscription: String,
|
||||
hash: Block::Hash,
|
||||
function: String,
|
||||
call_parameters: String,
|
||||
) -> SubscriptionResult {
|
||||
let call_parameters = Bytes::from(parse_hex_param(&mut sink, call_parameters)?);
|
||||
) -> RpcResult<MethodResponse> {
|
||||
let call_parameters = Bytes::from(parse_hex_param(call_parameters)?);
|
||||
|
||||
let client = self.client.clone();
|
||||
let subscriptions = self.subscriptions.clone();
|
||||
|
||||
let block_guard = match subscriptions.lock_block(&follow_subscription, hash) {
|
||||
let block_guard = match self.subscriptions.lock_block(&follow_subscription, hash) {
|
||||
Ok(block) => block,
|
||||
Err(SubscriptionManagementError::SubscriptionAbsent) => {
|
||||
// Invalid invalid subscription ID.
|
||||
let _ = sink.send(&ChainHeadEvent::<String>::Disjoint);
|
||||
return Ok(())
|
||||
return Ok(MethodResponse::LimitReached)
|
||||
},
|
||||
Err(SubscriptionManagementError::BlockHashAbsent) => {
|
||||
// Block is not part of the subscription.
|
||||
let _ = sink.reject(ChainHeadRpcError::InvalidBlock);
|
||||
return Ok(())
|
||||
},
|
||||
Err(error) => {
|
||||
let _ = sink.send(&ChainHeadEvent::<String>::Error(ErrorEvent {
|
||||
error: error.to_string(),
|
||||
}));
|
||||
return Ok(())
|
||||
return Err(ChainHeadRpcError::InvalidBlock.into())
|
||||
},
|
||||
Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()),
|
||||
};
|
||||
|
||||
let fut = async move {
|
||||
// Reject subscription if with_runtime is false.
|
||||
if !block_guard.has_runtime() {
|
||||
let _ = sink.reject(ChainHeadRpcError::InvalidParam(
|
||||
"The runtime updates flag must be set".into(),
|
||||
));
|
||||
return
|
||||
}
|
||||
// Reject subscription if with_runtime is false.
|
||||
if !block_guard.has_runtime() {
|
||||
return Err(ChainHeadRpcError::InvalidParam(
|
||||
"The runtime updates flag must be set".to_string(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
let res = client
|
||||
.executor()
|
||||
.call(hash, &function, &call_parameters, CallContext::Offchain)
|
||||
.map(|result| {
|
||||
let result = hex_string(&result);
|
||||
ChainHeadEvent::Done(ChainHeadResult { result })
|
||||
let event = self
|
||||
.client
|
||||
.executor()
|
||||
.call(hash, &function, &call_parameters, CallContext::Offchain)
|
||||
.map(|result| {
|
||||
FollowEvent::<Block::Hash>::OperationCallDone(OperationCallDone {
|
||||
operation_id: block_guard.operation_id(),
|
||||
output: hex_string(&result),
|
||||
})
|
||||
.unwrap_or_else(|error| {
|
||||
ChainHeadEvent::Error(ErrorEvent { error: error.to_string() })
|
||||
});
|
||||
})
|
||||
.unwrap_or_else(|error| {
|
||||
FollowEvent::<Block::Hash>::OperationError(OperationError {
|
||||
operation_id: block_guard.operation_id(),
|
||||
error: error.to_string(),
|
||||
})
|
||||
});
|
||||
|
||||
let _ = sink.send(&res);
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
let _ = block_guard.response_sender().unbounded_send(event);
|
||||
Ok(MethodResponse::Started(MethodResponseStarted {
|
||||
operation_id: block_guard.operation_id(),
|
||||
discarded_items: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn chain_head_unstable_unpin(
|
||||
|
||||
Reference in New Issue
Block a user