Introduce Backend trait to allow different RPC (or other) backends to be implemented (#1126)

* WIP backend trait

* WIP converting higher level stuff to using Backend impl

* more implementing new backend trait, mainly storage focused

* Get core code compiling with new backend bits

* subxt crate checks passing

* fix tests

* cargo fmt

* clippy/fixes

* merging and other fixes

* fix test

* fix lightclient code

* Fix some broken doc links

* another book link fix

* fix broken test when moving default_rpc_client

* fix dry_run test

* fix more tests; lightclient and wasm

* fix wasm tests

* fix some doc examples

* use next() instead of next_item()

* missing next_item() -> next()s

* move legacy RPc methods to LegacyRpcMethods type to host generic param instead of RpcClient

* standardise on all RpcClient types prefixed with Rpc, and 'raw' trait types prefixed with RawRpc so it's less ocnfusing which is which

* rename fixes

* doc fixes

* Add back system_dryRun RPC method and rename tx.dry_run() to tx.validate(), to signal that the calls are different

* Add a test that we return the correct extrinsic hash from submit()

* add TransactionValid details back, and protect against out of range bytes

* add test for decoding transaction validation from empty bytes

* fix clippy warning
This commit is contained in:
James Wilson
2023-08-22 12:32:22 +01:00
committed by GitHub
parent 7e15e96e52
commit d7124b56f7
61 changed files with 2627 additions and 3150 deletions
+532
View File
@@ -0,0 +1,532 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
//! An interface to call the raw legacy RPC methods.
use crate::backend::rpc::{rpc_params, RpcClient, RpcSubscription};
use crate::metadata::Metadata;
use crate::{Config, Error};
use codec::Decode;
use primitive_types::U256;
use serde::{Deserialize, Serialize};
/// An interface to call the legacy RPC methods. This interface is instantiated with
/// some `T: Config` trait which determines some of the types that the RPC methods will
/// take or hand back.
pub struct LegacyRpcMethods<T> {
client: RpcClient,
_marker: std::marker::PhantomData<T>,
}
impl<T> Clone for LegacyRpcMethods<T> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
_marker: self._marker,
}
}
}
impl<T> std::fmt::Debug for LegacyRpcMethods<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LegacyRpcMethods")
.field("client", &self.client)
.field("_marker", &self._marker)
.finish()
}
}
impl<T: Config> LegacyRpcMethods<T> {
/// Instantiate the legacy RPC method interface.
pub fn new(client: RpcClient) -> Self {
LegacyRpcMethods {
client,
_marker: std::marker::PhantomData,
}
}
/// Fetch the raw bytes for a given storage key
pub async fn state_get_storage(
&self,
key: &[u8],
hash: Option<T::Hash>,
) -> Result<Option<StorageKey>, Error> {
let params = rpc_params![to_hex(key), hash];
let data: Option<Bytes> = self.client.request("state_getStorage", params).await?;
Ok(data.map(|b| b.0))
}
/// Returns the keys with prefix with pagination support.
/// Up to `count` keys will be returned.
/// If `start_key` is passed, return next keys in storage in lexicographic order.
pub async fn state_get_keys_paged(
&self,
key: &[u8],
count: u32,
start_key: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<Vec<StorageData>, Error> {
let start_key = start_key.map(to_hex);
let params = rpc_params![to_hex(key), count, start_key, at];
let data: Vec<Bytes> = self.client.request("state_getKeysPaged", params).await?;
Ok(data.into_iter().map(|b| b.0).collect())
}
/// Fetch the genesis hash
pub async fn genesis_hash(&self) -> Result<T::Hash, Error> {
let block_zero = 0u32;
let params = rpc_params![block_zero];
let genesis_hash: Option<T::Hash> =
self.client.request("chain_getBlockHash", params).await?;
genesis_hash.ok_or_else(|| "Genesis hash not found".into())
}
/// Fetch the metadata via the legacy `state_getMetadata` RPC method.
pub async fn state_get_metadata(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
let bytes: Bytes = self
.client
.request("state_getMetadata", rpc_params![at])
.await?;
let metadata = Metadata::decode(&mut &bytes[..])?;
Ok(metadata)
}
/// Fetch system health
pub async fn system_health(&self) -> Result<SystemHealth, Error> {
self.client.request("system_health", rpc_params![]).await
}
/// Fetch system chain
pub async fn system_chain(&self) -> Result<String, Error> {
self.client.request("system_chain", rpc_params![]).await
}
/// Fetch system name
pub async fn system_name(&self) -> Result<String, Error> {
self.client.request("system_name", rpc_params![]).await
}
/// Fetch system version
pub async fn system_version(&self) -> Result<String, Error> {
self.client.request("system_version", rpc_params![]).await
}
/// Fetch system properties
pub async fn system_properties(&self) -> Result<SystemProperties, Error> {
self.client
.request("system_properties", rpc_params![])
.await
}
/// Get a header
pub async fn chain_get_header(
&self,
hash: Option<T::Hash>,
) -> Result<Option<T::Header>, Error> {
let params = rpc_params![hash];
let header = self.client.request("chain_getHeader", params).await?;
Ok(header)
}
/// Get a block hash, returns hash of latest _best_ block by default.
pub async fn chain_get_block_hash(
&self,
block_number: Option<BlockNumber>,
) -> Result<Option<T::Hash>, Error> {
let params = rpc_params![block_number];
let block_hash = self.client.request("chain_getBlockHash", params).await?;
Ok(block_hash)
}
/// Get a block hash of the latest finalized block
pub async fn chain_get_finalized_head(&self) -> Result<T::Hash, Error> {
let hash = self
.client
.request("chain_getFinalizedHead", rpc_params![])
.await?;
Ok(hash)
}
/// Get a Block
pub async fn chain_get_block(
&self,
hash: Option<T::Hash>,
) -> Result<Option<BlockDetails<T>>, Error> {
let params = rpc_params![hash];
let block = self.client.request("chain_getBlock", params).await?;
Ok(block)
}
/// Fetch the runtime version
pub async fn state_get_runtime_version(
&self,
at: Option<T::Hash>,
) -> Result<RuntimeVersion, Error> {
let params = rpc_params![at];
let version = self
.client
.request("state_getRuntimeVersion", params)
.await?;
Ok(version)
}
/// Subscribe to all new best block headers.
pub async fn chain_subscribe_new_heads(&self) -> Result<RpcSubscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
// Despite the name, this returns a stream of all new blocks
// imported by the node that happen to be added to the current best chain
// (ie all best blocks).
"chain_subscribeNewHeads",
rpc_params![],
"chain_unsubscribeNewHeads",
)
.await?;
Ok(subscription)
}
/// Subscribe to all new block headers.
pub async fn chain_subscribe_all_heads(&self) -> Result<RpcSubscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
// Despite the name, this returns a stream of all new blocks
// imported by the node that happen to be added to the current best chain
// (ie all best blocks).
"chain_subscribeAllHeads",
rpc_params![],
"chain_unsubscribeAllHeads",
)
.await?;
Ok(subscription)
}
/// Subscribe to finalized block headers.
///
/// Note: this may not produce _every_ block in the finalized chain;
/// sometimes multiple blocks are finalized at once, and in this case only the
/// latest one is returned. the higher level APIs that use this "fill in" the
/// gaps for us.
pub async fn chain_subscribe_finalized_heads(
&self,
) -> Result<RpcSubscription<T::Header>, Error> {
let subscription = self
.client
.subscribe(
"chain_subscribeFinalizedHeads",
rpc_params![],
"chain_unsubscribeFinalizedHeads",
)
.await?;
Ok(subscription)
}
/// Subscribe to runtime version updates that produce changes in the metadata.
/// The first item emitted by the stream is the current runtime version.
pub async fn state_subscribe_runtime_version(
&self,
) -> Result<RpcSubscription<RuntimeVersion>, Error> {
let subscription = self
.client
.subscribe(
"state_subscribeRuntimeVersion",
rpc_params![],
"state_unsubscribeRuntimeVersion",
)
.await?;
Ok(subscription)
}
/// Create and submit an extrinsic and return corresponding Hash if successful
pub async fn author_submit_extrinsic(&self, extrinsic: &[u8]) -> Result<T::Hash, Error> {
let params = rpc_params![to_hex(extrinsic)];
let xt_hash = self
.client
.request("author_submitExtrinsic", params)
.await?;
Ok(xt_hash)
}
/// Create and submit an extrinsic and return a subscription to the events triggered.
pub async fn author_submit_and_watch_extrinsic(
&self,
extrinsic: &[u8],
) -> Result<RpcSubscription<TransactionStatus<T::Hash>>, Error> {
let params = rpc_params![to_hex(extrinsic)];
let subscription = self
.client
.subscribe(
"author_submitAndWatchExtrinsic",
params,
"author_unwatchExtrinsic",
)
.await?;
Ok(subscription)
}
/// Execute a runtime API call via `state_call` RPC method.
pub async fn state_call(
&self,
function: &str,
call_parameters: Option<&[u8]>,
at: Option<T::Hash>,
) -> Result<Vec<u8>, Error> {
let call_parameters = call_parameters.unwrap_or_default();
let bytes: Bytes = self
.client
.request(
"state_call",
rpc_params![function, to_hex(call_parameters), at],
)
.await?;
Ok(bytes.0)
}
/// Submits the extrinsic to the dry_run RPC, to test if it would succeed.
///
/// Returns a [`DryRunResult`], which is the result of performing the dry run.
pub async fn dry_run(
&self,
encoded_signed: &[u8],
at: Option<T::Hash>,
) -> Result<DryRunResultBytes, Error> {
let params = rpc_params![to_hex(encoded_signed), at];
let result_bytes: Bytes = self.client.request("system_dryRun", params).await?;
Ok(DryRunResultBytes(result_bytes.0))
}
}
/// Storage key.
pub type StorageKey = Vec<u8>;
/// Storage data.
pub type StorageData = Vec<u8>;
/// Health struct returned by the RPC
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SystemHealth {
/// Number of connected peers
pub peers: usize,
/// Is the node syncing
pub is_syncing: bool,
/// Should this node have any peers
///
/// Might be false for local chains or when running without discovery.
pub should_have_peers: bool,
}
/// System properties; an arbitrary JSON object.
pub type SystemProperties = serde_json::Map<String, serde_json::Value>;
/// A block number
pub type BlockNumber = NumberOrHex;
/// The response from `chain_getBlock`
#[derive(Debug, Deserialize)]
#[serde(bound = "T: Config")]
pub struct BlockDetails<T: Config> {
/// The block itself.
pub block: Block<T>,
/// Block justification.
pub justifications: Option<Vec<BlockJustification>>,
}
/// Block details in the [`BlockDetails`].
#[derive(Debug, Deserialize)]
pub struct Block<T: Config> {
/// The block header.
pub header: T::Header,
/// The accompanying extrinsics.
pub extrinsics: Vec<Bytes>,
}
/// An abstraction over justification for a block's validity under a consensus algorithm.
pub type BlockJustification = (ConsensusEngineId, EncodedJustification);
/// Consensus engine unique ID.
pub type ConsensusEngineId = [u8; 4];
/// The encoded justification specific to a consensus engine.
pub type EncodedJustification = Vec<u8>;
/// This contains the runtime version information necessary to make transactions, as obtained from
/// the RPC call `state_getRuntimeVersion`,
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeVersion {
/// Version of the runtime specification. A full-node will not attempt to use its native
/// runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`,
/// `spec_version` and `authoring_version` are the same between Wasm and native.
pub spec_version: u32,
/// All existing dispatches are fully compatible when this number doesn't change. If this
/// number changes, then `spec_version` must change, also.
///
/// This number must change when an existing dispatchable (module ID, dispatch ID) is changed,
/// either through an alteration in its user-level semantics, a parameter
/// added/removed/changed, a dispatchable being removed, a module being removed, or a
/// dispatchable/module changing its index.
///
/// It need *not* change when a new module is added or when a dispatchable is added.
pub transaction_version: u32,
/// Fields unnecessary to Subxt are written out to this map.
#[serde(flatten)]
pub other: std::collections::HashMap<String, serde_json::Value>,
}
/// Possible transaction status events.
///
/// # Note
///
/// This is copied from `sp-transaction-pool` to avoid a dependency on that crate. Therefore it
/// must be kept compatible with that type from the target substrate version.
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TransactionStatus<Hash> {
/// Transaction is part of the future queue.
Future,
/// Transaction is part of the ready queue.
Ready,
/// The transaction has been broadcast to the given peers.
Broadcast(Vec<String>),
/// Transaction has been included in block with given hash.
InBlock(Hash),
/// The block this transaction was included in has been retracted.
Retracted(Hash),
/// Maximum number of finality watchers has been reached,
/// old watchers are being removed.
FinalityTimeout(Hash),
/// Transaction has been finalized by a finality-gadget, e.g GRANDPA
Finalized(Hash),
/// Transaction has been replaced in the pool, by another transaction
/// that provides the same tags. (e.g. same (sender, nonce)).
Usurped(Hash),
/// Transaction has been dropped from the pool because of the limit.
Dropped,
/// Transaction is no longer valid in the current state.
Invalid,
}
/// Hex-serialized shim for `Vec<u8>`.
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord, Debug)]
pub struct Bytes(#[serde(with = "impl_serde::serialize")] pub Vec<u8>);
impl std::ops::Deref for Bytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0[..]
}
}
impl From<Vec<u8>> for Bytes {
fn from(s: Vec<u8>) -> Self {
Bytes(s)
}
}
/// The decoded result returned from calling `system_dryRun` on some extrinsic.
#[derive(Debug, PartialEq, Eq)]
pub enum DryRunResult {
/// The transaction could be included in the block and executed.
Success,
/// The transaction could be included in the block, but the call failed to dispatch.
DispatchError(crate::error::DispatchError),
/// The transaction could not be included in the block.
TransactionValidityError,
}
/// The bytes representing an error dry running an extrinsic. call [`DryRunResultBytes::into_dry_run_result`]
/// to attempt to decode this into something more meaningful.
pub struct DryRunResultBytes(pub Vec<u8>);
impl DryRunResultBytes {
/// Attempt to decode the error bytes into a [`DryRunResult`] using the provided [`Metadata`].
pub fn into_dry_run_result(
self,
metadata: &crate::metadata::Metadata,
) -> Result<DryRunResult, crate::Error> {
// dryRun returns an ApplyExtrinsicResult, which is basically a
// `Result<Result<(), DispatchError>, TransactionValidityError>`.
let bytes = self.0;
if bytes[0] == 0 && bytes[1] == 0 {
// Ok(Ok(())); transaction is valid and executed ok
Ok(DryRunResult::Success)
} else if bytes[0] == 0 && bytes[1] == 1 {
// Ok(Err(dispatch_error)); transaction is valid but execution failed
let dispatch_error =
crate::error::DispatchError::decode_from(&bytes[2..], metadata.clone())?;
Ok(DryRunResult::DispatchError(dispatch_error))
} else if bytes[0] == 1 {
// Err(transaction_error); some transaction validity error (we ignore the details at the moment)
Ok(DryRunResult::TransactionValidityError)
} else {
// unable to decode the bytes; they aren't what we expect.
Err(crate::Error::Unknown(bytes))
}
}
}
/// A number type that can be serialized both as a number or a string that encodes a number in a
/// string.
///
/// We allow two representations of the block number as input. Either we deserialize to the type
/// that is specified in the block type or we attempt to parse given hex value.
///
/// The primary motivation for having this type is to avoid overflows when using big integers in
/// JavaScript (which we consider as an important RPC API consumer).
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum NumberOrHex {
/// The number represented directly.
Number(u64),
/// Hex representation of the number.
Hex(U256),
}
impl NumberOrHex {
/// Converts this number into an U256.
pub fn into_u256(self) -> U256 {
match self {
NumberOrHex::Number(n) => n.into(),
NumberOrHex::Hex(h) => h,
}
}
}
impl From<NumberOrHex> for U256 {
fn from(num_or_hex: NumberOrHex) -> U256 {
num_or_hex.into_u256()
}
}
macro_rules! into_number_or_hex {
($($t: ty)+) => {
$(
impl From<$t> for NumberOrHex {
fn from(x: $t) -> Self {
NumberOrHex::Number(x.into())
}
}
)+
}
}
into_number_or_hex!(u8 u16 u32 u64);
impl From<u128> for NumberOrHex {
fn from(n: u128) -> Self {
NumberOrHex::Hex(n.into())
}
}
impl From<U256> for NumberOrHex {
fn from(n: U256) -> Self {
NumberOrHex::Hex(n)
}
}
/// A quick helper to encode some bytes to hex.
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}