// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezcumulus.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// Pezcumulus 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.
// Pezcumulus 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 Pezcumulus. If not, see .
use futures::channel::{
mpsc::{Receiver, Sender},
oneshot::Sender as OneshotSender,
};
use jsonrpsee::{
core::{params::ArrayParams, ClientError as JsonRpseeError},
rpc_params,
};
use prometheus::Registry;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value as JsonValue;
use std::collections::{btree_map::BTreeMap, VecDeque};
use tokio::sync::mpsc::Sender as TokioSender;
use codec::{Decode, Encode};
use pezcumulus_primitives_core::{
relay_chain::{
async_backing::{AsyncBackingParams, BackingState, Constraints},
slashing, ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateEvent,
CandidateHash, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreIndex,
CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash as RelayHash,
Header as RelayHeader, InboundHrmpMessage, NodeFeatures, OccupiedCoreAssumption,
PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode,
ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
},
InboundDownwardMessage, ParaId, PersistedValidationData,
};
use pezcumulus_relay_chain_interface::{RelayChainError, RelayChainResult};
use pezsc_client_api::StorageData;
use pezsc_rpc_api::{state::ReadProof, system::Health};
use pezsc_service::TaskManager;
use pezsp_consensus_babe::Epoch;
use pezsp_storage::StorageKey;
use pezsp_version::RuntimeVersion;
use crate::{metrics::RelaychainRpcMetrics, reconnecting_ws_client::ReconnectingWebsocketWorker};
pub use url::Url;
const LOG_TARGET: &str = "relay-chain-rpc-client";
const NOTIFICATION_CHANNEL_SIZE_LIMIT: usize = 20;
/// Messages for communication between [`RelayChainRpcClient`] and the RPC workers.
#[derive(Debug)]
pub enum RpcDispatcherMessage {
/// Register new listener for the best headers stream. Contains a sender which will be used
/// to send incoming headers.
RegisterBestHeadListener(Sender),
/// Register new listener for the import headers stream. Contains a sender which will be used
/// to send incoming headers.
RegisterImportListener(Sender),
/// Register new listener for the finalized headers stream. Contains a sender which will be
/// used to send incoming headers.
RegisterFinalizationListener(Sender),
/// Register new listener for the finalized headers stream.
/// Contains the following:
/// - [`String`] representing the RPC method to be called
/// - [`ArrayParams`] for the parameters to the RPC call
/// - [`OneshotSender`] for the return value of the request
Request(String, ArrayParams, OneshotSender>),
}
/// Entry point to create [`RelayChainRpcClient`] and start a worker that communicates
/// to JsonRPC servers over the network.
pub async fn create_client_and_start_worker(
urls: Vec,
task_manager: &mut TaskManager,
prometheus_registry: Option<&Registry>,
) -> RelayChainResult {
let (worker, sender) = ReconnectingWebsocketWorker::new(urls).await;
task_manager
.spawn_essential_handle()
.spawn("relay-chain-rpc-worker", None, worker.run());
let client = RelayChainRpcClient::new(sender, prometheus_registry);
Ok(client)
}
#[derive(Serialize)]
struct PayloadToHex<'a>(#[serde(with = "pezsp_core::bytes")] &'a [u8]);
/// Client that maps RPC methods and deserializes results
#[derive(Clone)]
pub struct RelayChainRpcClient {
/// Sender to send messages to the worker.
worker_channel: TokioSender,
metrics: Option,
}
impl RelayChainRpcClient {
/// Initialize new RPC Client.
///
/// This client expects a channel connected to a worker that processes
/// requests sent via this channel.
pub(crate) fn new(
worker_channel: TokioSender,
prometheus_registry: Option<&Registry>,
) -> Self {
RelayChainRpcClient {
worker_channel,
metrics: prometheus_registry
.and_then(|inner| RelaychainRpcMetrics::register(inner).map_err(|err| {
tracing::warn!(target: LOG_TARGET, error = %err, "Unable to instantiate the RPC client metrics, continuing w/o metrics setup.");
}).ok()),
}
}
/// Same as `call_remote_runtime_function` but work on encoded data
pub async fn call_remote_runtime_function_encoded(
&self,
method_name: &str,
hash: RelayHash,
payload: &[u8],
) -> RelayChainResult {
let payload = PayloadToHex(payload);
let params = rpc_params! {
method_name,
payload,
hash
};
self.request_tracing::("state_call", params, |err| {
tracing::trace!(
target: LOG_TARGET,
%method_name,
%hash,
error = %err,
"Error during call to 'state_call'.",
);
})
.await
}
/// Call a call to `state_call` rpc method.
pub async fn call_remote_runtime_function(
&self,
method_name: &str,
hash: RelayHash,
payload: Option,
) -> RelayChainResult {
let payload_bytes =
payload.map_or(pezsp_core::Bytes(Vec::new()), |v| pezsp_core::Bytes(v.encode()));
let res = self
.call_remote_runtime_function_encoded(method_name, hash, &payload_bytes)
.await?;
Decode::decode(&mut &*res.0).map_err(Into::into)
}
/// Perform RPC request
async fn request<'a, R>(
&self,
method: &'a str,
params: ArrayParams,
) -> Result
where
R: DeserializeOwned + std::fmt::Debug,
{
self.request_tracing(
method,
params,
|e| tracing::trace!(target:LOG_TARGET, error = %e, %method, "Unable to complete RPC request"),
)
.await
}
/// Perform RPC request
async fn request_tracing<'a, R, OR>(
&self,
method: &'a str,
params: ArrayParams,
trace_error: OR,
) -> Result
where
R: DeserializeOwned + std::fmt::Debug,
OR: Fn(&RelayChainError),
{
let _timer = self.metrics.as_ref().map(|inner| inner.start_request_timer(method));
let (tx, rx) = futures::channel::oneshot::channel();
let message = RpcDispatcherMessage::Request(method.into(), params, tx);
self.worker_channel.send(message).await.map_err(|err| {
RelayChainError::WorkerCommunicationError(format!(
"Unable to send message to RPC worker: {}",
err
))
})?;
let value = rx.await.map_err(|err| {
RelayChainError::WorkerCommunicationError(format!(
"RPC worker channel closed. This can hint and connectivity issues with the supplied RPC endpoints. Message: {}",
err
))
})??;
serde_json::from_value(value).map_err(|_| {
trace_error(&RelayChainError::GenericError("Unable to deserialize value".to_string()));
RelayChainError::RpcCallError(method.to_string())
})
}
/// Returns information regarding the current epoch.
pub async fn babe_api_current_epoch(&self, at: RelayHash) -> Result {
self.call_remote_runtime_function("BabeApi_current_epoch", at, None::<()>).await
}
/// Scrape dispute relevant from on-chain, backing votes and resolved disputes.
pub async fn teyrchain_host_on_chain_votes(
&self,
at: RelayHash,
) -> Result