Merge commit '392447f5c8f986ded2559a78457f4cd87942f393' into update-bridges-subtree-r/w

This commit is contained in:
antonio-dropulic
2021-12-01 09:46:14 +01:00
321 changed files with 28385 additions and 10466 deletions
@@ -8,17 +8,18 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
async-std = { version = "1.6.5", features = ["attributes"] }
async-trait = "0.1.40"
codec = { package = "parity-scale-codec", version = "2.0.0" }
jsonrpsee-proc-macros = "=0.2.0-alpha.6"
jsonrpsee-ws-client = "=0.2.0-alpha.6"
codec = { package = "parity-scale-codec", version = "2.2.0" }
jsonrpsee-proc-macros = "0.3.1"
jsonrpsee-ws-client = "0.3.1"
log = "0.4.11"
num-traits = "0.2"
rand = "0.7"
tokio = "1.8"
thiserror = "1.0.26"
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain" }
bp-messages = { path = "../../primitives/messages" }
bp-runtime = { path = "../../primitives/runtime" }
finality-relay = { path = "../finality" }
headers-relay = { path = "../headers" }
@@ -29,12 +30,15 @@ relay-utils = { path = "../utils" }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-rpc-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-transaction-pool-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-rpc = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-storage = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
@@ -14,16 +14,16 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use bp_runtime::Chain as ChainBase;
use frame_support::Parameter;
use jsonrpsee_ws_client::{DeserializeOwned, Serialize};
use num_traits::{CheckedSub, SaturatingAdd, Zero};
use bp_runtime::{Chain as ChainBase, HashOf, TransactionEraOf};
use codec::{Codec, Encode};
use frame_support::weights::WeightToFeePolynomial;
use jsonrpsee_ws_client::types::{DeserializeOwned, Serialize};
use num_traits::Zero;
use sc_transaction_pool_api::TransactionStatus;
use sp_core::{storage::StorageKey, Pair};
use sp_runtime::{
generic::SignedBlock,
traits::{
AtLeast32Bit, Block as BlockT, Dispatchable, MaybeDisplay, MaybeSerialize, MaybeSerializeDeserialize, Member,
},
traits::{Block as BlockT, Dispatchable, Member},
EncodedJustification,
};
use std::{fmt::Debug, time::Duration};
@@ -37,30 +37,27 @@ pub trait Chain: ChainBase + Clone {
/// How often blocks are produced on that chain. It's suggested to set this value
/// to match the block time of the chain.
const AVERAGE_BLOCK_INTERVAL: Duration;
/// Maximal expected storage proof overhead (in bytes).
const STORAGE_PROOF_OVERHEAD: u32;
/// Maximal size (in bytes) of SCALE-encoded account id on this chain.
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32;
/// The user account identifier type for the runtime.
type AccountId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + Default;
/// Index of a transaction used by the chain.
type Index: Parameter
+ Member
+ MaybeSerialize
+ Debug
+ Default
+ MaybeDisplay
+ DeserializeOwned
+ AtLeast32Bit
+ Copy;
/// Block type.
type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification<Self::Header>;
/// The aggregated `Call` type.
type Call: Dispatchable + Debug;
/// Balance of an account in native tokens.
///
/// The chain may support multiple tokens, but this particular type is for token that is used
/// to pay for transaction dispatch, to reward different relayers (headers, messages), etc.
type Balance: Parameter + Member + DeserializeOwned + Clone + Copy + CheckedSub + PartialOrd + SaturatingAdd + Zero;
type Call: Clone + Dispatchable + Debug;
/// Type that is used by the chain, to convert from weight to fee.
type WeightToFee: WeightToFeePolynomial<Balance = Self::Balance>;
}
/// Call type used by the chain.
pub type CallOf<C> = <C as Chain>::Call;
/// Weight-to-Fee type used by the chain.
pub type WeightToFeeOf<C> = <C as Chain>::WeightToFee;
/// Transaction status of the chain.
pub type TransactionStatusOf<C> = TransactionStatus<HashOf<C>, HashOf<C>>;
/// Substrate-based chain with `frame_system::Config::AccountData` set to
/// the `pallet_balances::AccountData<Balance>`.
pub trait ChainWithBalances: Chain {
@@ -68,14 +65,43 @@ pub trait ChainWithBalances: Chain {
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey;
}
/// SCALE-encoded extrinsic.
pub type EncodedExtrinsic = Vec<u8>;
/// Block with justification.
pub trait BlockWithJustification<Header> {
/// Return block header.
fn header(&self) -> Header;
/// Return encoded block extrinsics.
fn extrinsics(&self) -> Vec<EncodedExtrinsic>;
/// Return block justification, if known.
fn justification(&self) -> Option<&EncodedJustification>;
}
/// Transaction before it is signed.
#[derive(Clone, Debug)]
pub struct UnsignedTransaction<C: Chain> {
/// Runtime call of this transaction.
pub call: C::Call,
/// Transaction nonce.
pub nonce: C::Index,
/// Tip included into transaction.
pub tip: C::Balance,
}
impl<C: Chain> UnsignedTransaction<C> {
/// Create new unsigned transaction with given call, nonce and zero tip.
pub fn new(call: C::Call, nonce: C::Index) -> Self {
Self { call, nonce, tip: Zero::zero() }
}
/// Set transaction tip.
pub fn tip(mut self, tip: C::Balance) -> Self {
self.tip = tip;
self
}
}
/// Substrate-based chain transactions signing scheme.
pub trait TransactionSignScheme {
/// Chain that this scheme is to be used.
@@ -83,15 +109,26 @@ pub trait TransactionSignScheme {
/// Type of key pairs used to sign transactions.
type AccountKeyPair: Pair;
/// Signed transaction.
type SignedTransaction;
type SignedTransaction: Clone + Debug + Codec + Send + 'static;
/// Create transaction for given runtime call, signed by given account.
fn sign_transaction(
genesis_hash: <Self::Chain as ChainBase>::Hash,
signer: &Self::AccountKeyPair,
signer_nonce: <Self::Chain as Chain>::Index,
call: <Self::Chain as Chain>::Call,
era: TransactionEraOf<Self::Chain>,
unsigned: UnsignedTransaction<Self::Chain>,
) -> Self::SignedTransaction;
/// Returns true if transaction is signed.
fn is_signed(tx: &Self::SignedTransaction) -> bool;
/// Returns true if transaction is signed by given signer.
fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool;
/// Parse signed transaction into its unsigned part.
///
/// Returns `None` if signed transaction has unsupported format.
fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>>;
}
impl<Block: BlockT> BlockWithJustification<Block::Header> for SignedBlock<Block> {
@@ -99,6 +136,10 @@ impl<Block: BlockT> BlockWithJustification<Block::Header> for SignedBlock<Block>
self.block.header().clone()
}
fn extrinsics(&self) -> Vec<EncodedExtrinsic> {
self.block.extrinsics().iter().map(Encode::encode).collect()
}
fn justification(&self) -> Option<&EncodedJustification> {
self.justifications
.as_ref()
@@ -16,27 +16,46 @@
//! Substrate node client.
use crate::chain::{Chain, ChainWithBalances};
use crate::rpc::Substrate;
use crate::{ConnectionParams, Error, Result};
use crate::{
chain::{Chain, ChainWithBalances, TransactionStatusOf},
rpc::Substrate,
ConnectionParams, Error, HashOf, HeaderIdOf, Result,
};
use async_std::sync::{Arc, Mutex};
use codec::Decode;
use async_trait::async_trait;
use codec::{Decode, Encode};
use frame_system::AccountInfo;
use jsonrpsee_ws_client::{traits::SubscriptionClient, v2::params::JsonRpcParams, DeserializeOwned};
use jsonrpsee_ws_client::{Subscription, WsClient as RpcClient, WsClientBuilder as RpcClientBuilder};
use num_traits::Zero;
use futures::{SinkExt, StreamExt};
use jsonrpsee_ws_client::{
types::{
self as jsonrpsee_types, traits::SubscriptionClient, v2::params::JsonRpcParams,
DeserializeOwned,
},
WsClient as RpcClient, WsClientBuilder as RpcClientBuilder,
};
use num_traits::{Bounded, Zero};
use pallet_balances::AccountData;
use relay_utils::relay_loop::RECONNECT_DELAY;
use sp_core::{storage::StorageKey, Bytes};
use pallet_transaction_payment::InclusionFee;
use relay_utils::{relay_loop::RECONNECT_DELAY, HeaderId};
use sp_core::{
storage::{StorageData, StorageKey},
Bytes, Hasher,
};
use sp_runtime::{
traits::Header as HeaderT,
transaction_validity::{TransactionSource, TransactionValidity},
};
use sp_trie::StorageProof;
use sp_version::RuntimeVersion;
use std::{convert::TryFrom, future::Future};
const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities";
const SUB_API_TXPOOL_VALIDATE_TRANSACTION: &str = "TaggedTransactionQueue_validate_transaction";
const MAX_SUBSCRIPTION_CAPACITY: usize = 4096;
/// Opaque justifications subscription type.
pub type JustificationsSubscription = Subscription<Bytes>;
pub struct Subscription<T>(Mutex<futures::channel::mpsc::Receiver<Option<T>>>);
/// Opaque GRANDPA authorities set.
pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
@@ -45,21 +64,37 @@ pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
///
/// Cloning `Client` is a cheap operation.
pub struct Client<C: Chain> {
/// Tokio runtime handle.
tokio: Arc<tokio::runtime::Runtime>,
/// Client connection params.
params: ConnectionParams,
/// Substrate RPC client.
client: Arc<RpcClient>,
/// Genesis block hash.
genesis_hash: C::Hash,
/// If several tasks are submitting their transactions simultaneously using `submit_signed_extrinsic`
/// method, they may get the same transaction nonce. So one of transactions will be rejected
/// from the pool. This lock is here to prevent situations like that.
genesis_hash: HashOf<C>,
/// If several tasks are submitting their transactions simultaneously using
/// `submit_signed_extrinsic` method, they may get the same transaction nonce. So one of
/// transactions will be rejected from the pool. This lock is here to prevent situations like
/// that.
submit_signed_extrinsic_lock: Arc<Mutex<()>>,
}
#[async_trait]
impl<C: Chain> relay_utils::relay_loop::Client for Client<C> {
type Error = Error;
async fn reconnect(&mut self) -> Result<()> {
let (tokio, client) = Self::build_client(self.params.clone()).await?;
self.tokio = tokio;
self.client = client;
Ok(())
}
}
impl<C: Chain> Clone for Client<C> {
fn clone(&self) -> Self {
Client {
tokio: self.tokio.clone(),
params: self.params.clone(),
client: self.client.clone(),
genesis_hash: self.genesis_hash,
@@ -70,9 +105,7 @@ impl<C: Chain> Clone for Client<C> {
impl<C: Chain> std::fmt::Debug for Client<C> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Client")
.field("genesis_hash", &self.genesis_hash)
.finish()
fmt.debug_struct("Client").field("genesis_hash", &self.genesis_hash).finish()
}
}
@@ -101,12 +134,18 @@ impl<C: Chain> Client<C> {
/// Try to connect to Substrate node over websocket. Returns Substrate RPC client if connection
/// has been established or error otherwise.
pub async fn try_connect(params: ConnectionParams) -> Result<Self> {
let client = Self::build_client(params.clone()).await?;
let (tokio, client) = Self::build_client(params.clone()).await?;
let number: C::BlockNumber = Zero::zero();
let genesis_hash = Substrate::<C>::chain_get_block_hash(&*client, number).await?;
let genesis_hash_client = client.clone();
let genesis_hash = tokio
.spawn(async move {
Substrate::<C>::chain_get_block_hash(&*genesis_hash_client, number).await
})
.await??;
Ok(Self {
tokio,
params,
client,
genesis_hash,
@@ -114,39 +153,43 @@ impl<C: Chain> Client<C> {
})
}
/// Reopen client connection.
pub async fn reconnect(&mut self) -> Result<()> {
self.client = Self::build_client(self.params.clone()).await?;
Ok(())
}
/// Build client to use in connection.
async fn build_client(params: ConnectionParams) -> Result<Arc<RpcClient>> {
async fn build_client(
params: ConnectionParams,
) -> Result<(Arc<tokio::runtime::Runtime>, Arc<RpcClient>)> {
let tokio = tokio::runtime::Runtime::new()?;
let uri = format!(
"{}://{}:{}",
if params.secure { "wss" } else { "ws" },
params.host,
params.port,
);
let client = RpcClientBuilder::default()
.max_notifs_per_subscription(MAX_SUBSCRIPTION_CAPACITY)
.build(&uri)
.await?;
let client = tokio
.spawn(async move {
RpcClientBuilder::default()
.max_notifs_per_subscription(MAX_SUBSCRIPTION_CAPACITY)
.build(&uri)
.await
})
.await??;
Ok(Arc::new(client))
Ok((Arc::new(tokio), Arc::new(client)))
}
}
impl<C: Chain> Client<C> {
/// Returns true if client is connected to at least one peer and is in synced state.
pub async fn ensure_synced(&self) -> Result<()> {
let health = Substrate::<C>::system_health(&*self.client).await?;
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
if is_synced {
Ok(())
} else {
Err(Error::ClientNotSynced(health))
}
self.jsonrpsee_execute(|client| async move {
let health = Substrate::<C>::system_health(&*client).await?;
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
if is_synced {
Ok(())
} else {
Err(Error::ClientNotSynced(health))
}
})
.await
}
/// Return hash of the genesis block.
@@ -156,7 +199,15 @@ impl<C: Chain> Client<C> {
/// Return hash of the best finalized block.
pub async fn best_finalized_header_hash(&self) -> Result<C::Hash> {
Ok(Substrate::<C>::chain_get_finalized_head(&*self.client).await?)
self.jsonrpsee_execute(|client| async move {
Ok(Substrate::<C>::chain_get_finalized_head(&*client).await?)
})
.await
}
/// Return number of the best finalized block.
pub async fn best_finalized_header_number(&self) -> Result<C::BlockNumber> {
Ok(*self.header_by_hash(self.best_finalized_header_hash().await?).await?.number())
}
/// Returns the best Substrate header.
@@ -164,12 +215,18 @@ impl<C: Chain> Client<C> {
where
C::Header: DeserializeOwned,
{
Ok(Substrate::<C>::chain_get_header(&*self.client, None).await?)
self.jsonrpsee_execute(|client| async move {
Ok(Substrate::<C>::chain_get_header(&*client, None).await?)
})
.await
}
/// Get a Substrate block from its hash.
pub async fn get_block(&self, block_hash: Option<C::Hash>) -> Result<C::SignedBlock> {
Ok(Substrate::<C>::chain_get_block(&*self.client, block_hash).await?)
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::chain_get_block(&*client, block_hash).await?)
})
.await
}
/// Get a Substrate header by its hash.
@@ -177,12 +234,18 @@ impl<C: Chain> Client<C> {
where
C::Header: DeserializeOwned,
{
Ok(Substrate::<C>::chain_get_header(&*self.client, block_hash).await?)
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::chain_get_header(&*client, block_hash).await?)
})
.await
}
/// Get a Substrate block hash by its number.
pub async fn block_hash_by_number(&self, number: C::BlockNumber) -> Result<C::Hash> {
Ok(Substrate::<C>::chain_get_block_hash(&*self.client, number).await?)
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::chain_get_block_hash(&*client, number).await?)
})
.await
}
/// Get a Substrate header by its number.
@@ -191,51 +254,84 @@ impl<C: Chain> Client<C> {
C::Header: DeserializeOwned,
{
let block_hash = Self::block_hash_by_number(self, block_number).await?;
Ok(Self::header_by_hash(self, block_hash).await?)
let header_by_hash = Self::header_by_hash(self, block_hash).await?;
Ok(header_by_hash)
}
/// Return runtime version.
pub async fn runtime_version(&self) -> Result<RuntimeVersion> {
Ok(Substrate::<C>::state_runtime_version(&*self.client).await?)
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::state_runtime_version(&*client).await?)
})
.await
}
/// Read value from runtime storage.
pub async fn storage_value<T: Decode>(&self, storage_key: StorageKey) -> Result<Option<T>> {
Substrate::<C>::state_get_storage(&*self.client, storage_key)
pub async fn storage_value<T: Send + Decode + 'static>(
&self,
storage_key: StorageKey,
block_hash: Option<C::Hash>,
) -> Result<Option<T>> {
self.raw_storage_value(storage_key, block_hash)
.await?
.map(|encoded_value| T::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed))
.map(|encoded_value| {
T::decode(&mut &encoded_value.0[..]).map_err(Error::ResponseParseFailed)
})
.transpose()
}
/// Read raw value from runtime storage.
pub async fn raw_storage_value(
&self,
storage_key: StorageKey,
block_hash: Option<C::Hash>,
) -> Result<Option<StorageData>> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::state_get_storage(&*client, storage_key, block_hash).await?)
})
.await
}
/// Return native tokens balance of the account.
pub async fn free_native_balance(&self, account: C::AccountId) -> Result<C::Balance>
where
C: ChainWithBalances,
{
let storage_key = C::account_info_storage_key(&account);
let encoded_account_data = Substrate::<C>::state_get_storage(&*self.client, storage_key)
.await?
.ok_or(Error::AccountDoesNotExist)?;
let decoded_account_data =
AccountInfo::<C::Index, AccountData<C::Balance>>::decode(&mut &encoded_account_data.0[..])
.map_err(Error::ResponseParseFailed)?;
Ok(decoded_account_data.data.free)
self.jsonrpsee_execute(move |client| async move {
let storage_key = C::account_info_storage_key(&account);
let encoded_account_data =
Substrate::<C>::state_get_storage(&*client, storage_key, None)
.await?
.ok_or(Error::AccountDoesNotExist)?;
let decoded_account_data = AccountInfo::<C::Index, AccountData<C::Balance>>::decode(
&mut &encoded_account_data.0[..],
)
.map_err(Error::ResponseParseFailed)?;
Ok(decoded_account_data.data.free)
})
.await
}
/// Get the nonce of the given Substrate account.
///
/// Note: It's the caller's responsibility to make sure `account` is a valid SS58 address.
pub async fn next_account_index(&self, account: C::AccountId) -> Result<C::Index> {
Ok(Substrate::<C>::system_account_next_index(&*self.client, account).await?)
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::system_account_next_index(&*client, account).await?)
})
.await
}
/// Submit unsigned extrinsic for inclusion in a block.
///
/// Note: The given transaction needs to be SCALE encoded beforehand.
pub async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<C::Hash> {
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*self.client, transaction).await?;
log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
Ok(tx_hash)
self.jsonrpsee_execute(move |client| async move {
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*client, transaction).await?;
log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
Ok(tx_hash)
})
.await
}
/// Submit an extrinsic signed by given account.
@@ -248,51 +344,242 @@ impl<C: Chain> Client<C> {
pub async fn submit_signed_extrinsic(
&self,
extrinsic_signer: C::AccountId,
prepare_extrinsic: impl FnOnce(C::Index) -> Bytes,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Bytes + Send + 'static,
) -> Result<C::Hash> {
let _guard = self.submit_signed_extrinsic_lock.lock().await;
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
let extrinsic = prepare_extrinsic(transaction_nonce);
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*self.client, extrinsic).await?;
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
Ok(tx_hash)
let best_header = self.best_header().await?;
let best_header_id = HeaderId(*best_header.number(), best_header.hash());
self.jsonrpsee_execute(move |client| async move {
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce);
let tx_hash = Substrate::<C>::author_submit_extrinsic(&*client, extrinsic).await?;
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
Ok(tx_hash)
})
.await
}
/// Does exactly the same as `submit_signed_extrinsic`, but keeps watching for extrinsic status
/// after submission.
pub async fn submit_and_watch_signed_extrinsic(
&self,
extrinsic_signer: C::AccountId,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, C::Index) -> Bytes + Send + 'static,
) -> Result<Subscription<TransactionStatusOf<C>>> {
let _guard = self.submit_signed_extrinsic_lock.lock().await;
let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
let best_header = self.best_header().await?;
let best_header_id = HeaderId(*best_header.number(), best_header.hash());
let subscription = self
.jsonrpsee_execute(move |client| async move {
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce);
let tx_hash = C::Hasher::hash(&extrinsic.0);
let subscription = client
.subscribe(
"author_submitAndWatchExtrinsic",
JsonRpcParams::Array(vec![jsonrpsee_types::to_json_value(extrinsic)
.map_err(|e| Error::RpcError(e.into()))?]),
"author_unwatchExtrinsic",
)
.await?;
log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
Ok(subscription)
})
.await?;
let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY);
self.tokio.spawn(Subscription::background_worker(
C::NAME.into(),
"extrinsic".into(),
subscription,
sender,
));
Ok(Subscription(Mutex::new(receiver)))
}
/// Returns pending extrinsics from transaction pool.
pub async fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
self.jsonrpsee_execute(move |client| async move {
Ok(Substrate::<C>::author_pending_extrinsics(&*client).await?)
})
.await
}
/// Validate transaction at given block state.
pub async fn validate_transaction<SignedTransaction: Encode + Send + 'static>(
&self,
at_block: C::Hash,
transaction: SignedTransaction,
) -> Result<TransactionValidity> {
self.jsonrpsee_execute(move |client| async move {
let call = SUB_API_TXPOOL_VALIDATE_TRANSACTION.to_string();
let data = Bytes((TransactionSource::External, transaction, at_block).encode());
let encoded_response =
Substrate::<C>::state_call(&*client, call, data, Some(at_block)).await?;
let validity = TransactionValidity::decode(&mut &encoded_response.0[..])
.map_err(Error::ResponseParseFailed)?;
Ok(validity)
})
.await
}
/// Estimate fee that will be spent on given extrinsic.
pub async fn estimate_extrinsic_fee(
&self,
transaction: Bytes,
) -> Result<InclusionFee<C::Balance>> {
self.jsonrpsee_execute(move |client| async move {
let fee_details =
Substrate::<C>::payment_query_fee_details(&*client, transaction, None).await?;
let inclusion_fee = fee_details
.inclusion_fee
.map(|inclusion_fee| InclusionFee {
base_fee: C::Balance::try_from(inclusion_fee.base_fee.into_u256())
.unwrap_or_else(|_| C::Balance::max_value()),
len_fee: C::Balance::try_from(inclusion_fee.len_fee.into_u256())
.unwrap_or_else(|_| C::Balance::max_value()),
adjusted_weight_fee: C::Balance::try_from(
inclusion_fee.adjusted_weight_fee.into_u256(),
)
.unwrap_or_else(|_| C::Balance::max_value()),
})
.unwrap_or_else(|| InclusionFee {
base_fee: Zero::zero(),
len_fee: Zero::zero(),
adjusted_weight_fee: Zero::zero(),
});
Ok(inclusion_fee)
})
.await
}
/// Get the GRANDPA authority set at given block.
pub async fn grandpa_authorities_set(&self, block: C::Hash) -> Result<OpaqueGrandpaAuthoritiesSet> {
let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
let data = Bytes(Vec::new());
pub async fn grandpa_authorities_set(
&self,
block: C::Hash,
) -> Result<OpaqueGrandpaAuthoritiesSet> {
self.jsonrpsee_execute(move |client| async move {
let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
let data = Bytes(Vec::new());
let encoded_response = Substrate::<C>::state_call(&*self.client, call, data, Some(block)).await?;
let authority_list = encoded_response.0;
let encoded_response =
Substrate::<C>::state_call(&*client, call, data, Some(block)).await?;
let authority_list = encoded_response.0;
Ok(authority_list)
Ok(authority_list)
})
.await
}
/// Execute runtime call at given block.
pub async fn state_call(&self, method: String, data: Bytes, at_block: Option<C::Hash>) -> Result<Bytes> {
Substrate::<C>::state_call(&*self.client, method, data, at_block)
.await
.map_err(Into::into)
pub async fn state_call(
&self,
method: String,
data: Bytes,
at_block: Option<C::Hash>,
) -> Result<Bytes> {
self.jsonrpsee_execute(move |client| async move {
Substrate::<C>::state_call(&*client, method, data, at_block)
.await
.map_err(Into::into)
})
.await
}
/// Returns storage proof of given storage keys.
pub async fn prove_storage(&self, keys: Vec<StorageKey>, at_block: C::Hash) -> Result<StorageProof> {
Substrate::<C>::state_prove_storage(&*self.client, keys, Some(at_block))
.await
.map(|proof| StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect()))
.map_err(Into::into)
pub async fn prove_storage(
&self,
keys: Vec<StorageKey>,
at_block: C::Hash,
) -> Result<StorageProof> {
self.jsonrpsee_execute(move |client| async move {
Substrate::<C>::state_prove_storage(&*client, keys, Some(at_block))
.await
.map(|proof| StorageProof::new(proof.proof.into_iter().map(|b| b.0).collect()))
.map_err(Into::into)
})
.await
}
/// Return new justifications stream.
pub async fn subscribe_justifications(&self) -> Result<JustificationsSubscription> {
Ok(self
.client
.subscribe(
"grandpa_subscribeJustifications",
JsonRpcParams::NoParams,
"grandpa_unsubscribeJustifications",
)
.await?)
pub async fn subscribe_justifications(&self) -> Result<Subscription<Bytes>> {
let subscription = self
.jsonrpsee_execute(move |client| async move {
Ok(client
.subscribe(
"grandpa_subscribeJustifications",
JsonRpcParams::NoParams,
"grandpa_unsubscribeJustifications",
)
.await?)
})
.await?;
let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY);
self.tokio.spawn(Subscription::background_worker(
C::NAME.into(),
"justification".into(),
subscription,
sender,
));
Ok(Subscription(Mutex::new(receiver)))
}
/// Execute jsonrpsee future in tokio context.
async fn jsonrpsee_execute<MF, F, T>(&self, make_jsonrpsee_future: MF) -> Result<T>
where
MF: FnOnce(Arc<RpcClient>) -> F + Send + 'static,
F: Future<Output = Result<T>> + Send,
T: Send + 'static,
{
let client = self.client.clone();
self.tokio.spawn(async move { make_jsonrpsee_future(client).await }).await?
}
}
impl<T: DeserializeOwned> Subscription<T> {
/// Return next item from the subscription.
pub async fn next(&self) -> Result<Option<T>> {
let mut receiver = self.0.lock().await;
let item = receiver.next().await;
Ok(item.unwrap_or(None))
}
/// Background worker that is executed in tokio context as `jsonrpsee` requires.
async fn background_worker(
chain_name: String,
item_type: String,
mut subscription: jsonrpsee_types::Subscription<T>,
mut sender: futures::channel::mpsc::Sender<Option<T>>,
) {
loop {
match subscription.next().await {
Ok(Some(item)) =>
if sender.send(Some(item)).await.is_err() {
break
},
Ok(None) => {
log::trace!(
target: "bridge",
"{} {} subscription stream has returned None. Stream needs to be restarted.",
chain_name,
item_type,
);
let _ = sender.send(None).await;
break
},
Err(e) => {
log::trace!(
target: "bridge",
"{} {} subscription stream has returned '{:?}'. Stream needs to be restarted.",
chain_name,
item_type,
e,
);
let _ = sender.send(None).await;
break
},
}
}
}
}
@@ -16,54 +16,55 @@
//! Substrate node RPC errors.
use jsonrpsee_ws_client::Error as RpcError;
use jsonrpsee_ws_client::types::Error as RpcError;
use relay_utils::MaybeConnectionError;
use sc_rpc_api::system::Health;
use sp_runtime::transaction_validity::TransactionValidityError;
use thiserror::Error;
/// Result type used by Substrate client.
pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with
/// a Substrate node through RPC.
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum Error {
/// IO error.
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
/// An error that can occur when making a request to
/// an JSON-RPC server.
RpcError(RpcError),
#[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// The response from the server could not be SCALE decoded.
ResponseParseFailed(codec::Error),
#[error("Response parse failed: {0}")]
ResponseParseFailed(#[from] codec::Error),
/// The Substrate bridge pallet has not yet been initialized.
#[error("The Substrate bridge pallet has not been initialized yet.")]
UninitializedBridgePallet,
/// Account does not exist on the chain.
#[error("Account does not exist on the chain.")]
AccountDoesNotExist,
/// Runtime storage is missing mandatory ":code:" entry.
#[error("Mandatory :code: entry is missing from runtime storage.")]
MissingMandatoryCodeEntry,
/// The client we're connected to is not synced, so we can't rely on its state.
#[error("Substrate client is not synced {0}.")]
ClientNotSynced(Health),
/// An error has happened when we have tried to parse storage proof.
#[error("Error when parsing storage proof: {0:?}.")]
StorageProofError(bp_runtime::StorageProofError),
/// The Substrate transaction is invalid.
#[error("Substrate transaction is invalid: {0:?}")]
TransactionInvalid(#[from] TransactionValidityError),
/// Custom logic error.
#[error("{0}")]
Custom(String),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::RpcError(ref e) => Some(e),
Self::ResponseParseFailed(ref e) => Some(e),
Self::UninitializedBridgePallet => None,
Self::AccountDoesNotExist => None,
Self::MissingMandatoryCodeEntry => None,
Self::ClientNotSynced(_) => None,
Self::StorageProofError(_) => None,
Self::Custom(_) => None,
}
}
}
impl From<RpcError> for Error {
fn from(error: RpcError) -> Self {
Error::RpcError(error)
impl From<tokio::task::JoinError> for Error {
fn from(error: tokio::task::JoinError) -> Self {
Error::Custom(format!("Failed to wait tokio task: {}", error))
}
}
@@ -71,7 +72,7 @@ impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool {
matches!(
*self,
Error::RpcError(RpcError::TransportError(_))
Error::RpcError(RpcError::Transport(_))
// right now if connection to the ws server is dropped (after it is already established),
// we're getting this error
| Error::RpcError(RpcError::Internal(_))
@@ -80,26 +81,3 @@ impl MaybeConnectionError for Error {
)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let s = match self {
Self::RpcError(e) => e.to_string(),
Self::ResponseParseFailed(e) => e.to_string(),
Self::UninitializedBridgePallet => "The Substrate bridge pallet has not been initialized yet.".into(),
Self::AccountDoesNotExist => "Account does not exist on the chain".into(),
Self::MissingMandatoryCodeEntry => "Mandatory :code: entry is missing from runtime storage".into(),
Self::StorageProofError(e) => format!("Error when parsing storage proof: {:?}", e),
Self::ClientNotSynced(health) => format!("Substrate client is not synced: {}", health),
Self::Custom(e) => e.clone(),
};
write!(f, "{}", s)
}
}
impl From<Error> for String {
fn from(error: Error) -> String {
error.to_string()
}
}
@@ -16,10 +16,12 @@
//! Default generic implementation of finality source for basic Substrate client.
use crate::chain::{BlockWithJustification, Chain};
use crate::client::Client;
use crate::error::Error;
use crate::sync_header::SyncHeader;
use crate::{
chain::{BlockWithJustification, Chain},
client::Client,
error::Error,
sync_header::SyncHeader,
};
use async_std::sync::{Arc, Mutex};
use async_trait::async_trait;
@@ -43,12 +45,11 @@ pub struct FinalitySource<C: Chain, P> {
impl<C: Chain, P> FinalitySource<C, P> {
/// Create new headers source using given client.
pub fn new(client: Client<C>, maximal_header_number: Option<RequiredHeaderNumberRef<C>>) -> Self {
FinalitySource {
client,
maximal_header_number,
_phantom: Default::default(),
}
pub fn new(
client: Client<C>,
maximal_header_number: Option<RequiredHeaderNumberRef<C>>,
) -> Self {
FinalitySource { client, maximal_header_number, _phantom: Default::default() }
}
/// Returns reference to the underlying RPC client.
@@ -122,7 +123,9 @@ where
let justification = signed_block
.justification()
.map(|raw_justification| GrandpaJustification::<C::Header>::decode(&mut raw_justification.as_slice()))
.map(|raw_justification| {
GrandpaJustification::<C::Header>::decode(&mut raw_justification.as_slice())
})
.transpose()
.map_err(Error::ResponseParseFailed)?;
@@ -132,27 +135,35 @@ where
async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
Ok(unfold(
self.client.clone().subscribe_justifications().await?,
move |mut subscription| async move {
move |subscription| async move {
loop {
let next_justification = subscription.next().await?;
let log_error = |err| {
log::error!(
target: "bridge",
"Failed to read justification target from the {} justifications stream: {:?}",
P::SOURCE_NAME,
err,
);
};
let next_justification = subscription
.next()
.await
.map_err(|err| log_error(err.to_string()))
.ok()??;
let decoded_justification =
GrandpaJustification::<C::Header>::decode(&mut &next_justification.0[..]);
GrandpaJustification::<C::Header>::decode(&mut &next_justification[..]);
let justification = match decoded_justification {
Ok(j) => j,
Err(err) => {
log::error!(
target: "bridge",
"Failed to decode justification target from the {} justifications stream: {:?}",
P::SOURCE_NAME,
err,
);
continue;
}
log_error(format!("decode failed with error {:?}", err));
continue
},
};
return Some((justification, subscription));
return Some((justification, subscription))
}
},
)
@@ -17,32 +17,41 @@
//! Pallet provides a set of guard functions that are running in background threads
//! and are aborting process if some condition fails.
use crate::{Chain, ChainWithBalances, Client};
use crate::{error::Error, Chain, ChainWithBalances, Client};
use async_trait::async_trait;
use num_traits::CheckedSub;
use sp_version::RuntimeVersion;
use std::{
collections::VecDeque,
fmt::Display,
time::{Duration, Instant},
};
/// Guards environment.
#[async_trait]
pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
/// Error type.
type Error: Display + Send + Sync + 'static;
/// Return current runtime version.
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String>;
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error>;
/// Return free native balance of the account on the chain.
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String>;
async fn free_native_balance(
&mut self,
account: C::AccountId,
) -> Result<C::Balance, Self::Error>;
/// Return current time.
fn now(&self) -> Instant {
Instant::now()
}
/// Sleep given amount of time.
async fn sleep(&mut self, duration: Duration) {
async_std::task::sleep(duration).await
}
/// Abort current process. Called when guard condition check fails.
async fn abort(&mut self) {
std::process::abort();
@@ -50,7 +59,10 @@ pub trait Environment<C: ChainWithBalances>: Send + Sync + 'static {
}
/// Abort when runtime spec version is different from specified.
pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environment<C>, expected_spec_version: u32) {
pub fn abort_on_spec_version_change<C: ChainWithBalances>(
mut env: impl Environment<C>,
expected_spec_version: u32,
) {
async_std::task::spawn(async move {
loop {
let actual_spec_version = env.runtime_version().await;
@@ -66,10 +78,10 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environm
);
env.abort().await;
}
},
Err(error) => log::warn!(
target: "bridge-guard",
"Failed to read {} runtime version: {:?}. Relay may need to be stopped manually",
"Failed to read {} runtime version: {}. Relay may need to be stopped manually",
C::NAME,
error,
),
@@ -80,8 +92,9 @@ pub fn abort_on_spec_version_change<C: ChainWithBalances>(mut env: impl Environm
});
}
/// Abort if, during a 24 hours, free balance of given account is decreased at least by given value.
/// Other components may increase (or decrease) balance of account and it WILL affect logic of the guard.
/// Abort if, during 24 hours, free balance of given account is decreased at least by given value.
/// Other components may increase (or decrease) balance of account and it WILL affect logic of the
/// guard.
pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
mut env: impl Environment<C>,
account_id: C::AccountId,
@@ -127,16 +140,16 @@ pub fn abort_when_account_balance_decreased<C: ChainWithBalances>(
env.abort().await;
}
}
},
Err(error) => {
log::warn!(
target: "bridge-guard",
"Failed to read {} account {:?} balance: {:?}. Relay may need to be stopped manually",
"Failed to read {} account {:?} balance: {}. Relay may need to be stopped manually",
C::NAME,
account_id,
error,
);
}
},
};
env.sleep(conditions_check_delay::<C>()).await;
@@ -151,20 +164,24 @@ fn conditions_check_delay<C: Chain>() -> Duration {
#[async_trait]
impl<C: ChainWithBalances> Environment<C> for Client<C> {
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> {
Client::<C>::runtime_version(self).await.map_err(|e| e.to_string())
type Error = Error;
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
Client::<C>::runtime_version(self).await
}
async fn free_native_balance(&mut self, account: C::AccountId) -> Result<C::Balance, String> {
Client::<C>::free_native_balance(self, account)
.await
.map_err(|e| e.to_string())
async fn free_native_balance(
&mut self,
account: C::AccountId,
) -> Result<C::Balance, Self::Error> {
Client::<C>::free_native_balance(self, account).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::weights::IdentityFee;
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future::FutureExt,
@@ -180,18 +197,24 @@ mod tests {
type Hash = sp_core::H256;
type Hasher = sp_runtime::traits::BlakeTwo256;
type Header = sp_runtime::generic::Header<u32, sp_runtime::traits::BlakeTwo256>;
type AccountId = u32;
type Balance = u32;
type Index = u32;
type Signature = sp_runtime::testing::TestSignature;
}
impl Chain for TestChain {
const NAME: &'static str = "Test";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(1);
const STORAGE_PROOF_OVERHEAD: u32 = 0;
const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = 0;
type AccountId = u32;
type Index = u32;
type SignedBlock =
sp_runtime::generic::SignedBlock<sp_runtime::generic::Block<Self::Header, sp_runtime::OpaqueExtrinsic>>;
type SignedBlock = sp_runtime::generic::SignedBlock<
sp_runtime::generic::Block<Self::Header, sp_runtime::OpaqueExtrinsic>,
>;
type Call = ();
type Balance = u32;
type WeightToFee = IdentityFee<u32>;
}
impl ChainWithBalances for TestChain {
@@ -209,11 +232,13 @@ mod tests {
#[async_trait]
impl Environment<TestChain> for TestEnvironment {
async fn runtime_version(&mut self) -> Result<RuntimeVersion, String> {
type Error = Error;
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
Ok(self.runtime_version_rx.next().await.unwrap_or_default())
}
async fn free_native_balance(&mut self, _account: u32) -> Result<u32, String> {
async fn free_native_balance(&mut self, _account: u32) -> Result<u32, Self::Error> {
Ok(self.free_native_balance_rx.next().await.unwrap_or_default())
}
@@ -249,10 +274,7 @@ mod tests {
// client responds with wrong version
runtime_version_tx
.send(RuntimeVersion {
spec_version: 42,
..Default::default()
})
.send(RuntimeVersion { spec_version: 42, ..Default::default() })
.await
.unwrap();
@@ -284,10 +306,7 @@ mod tests {
// client responds with the same version
runtime_version_tx
.send(RuntimeVersion {
spec_version: 42,
..Default::default()
})
.send(RuntimeVersion { spec_version: 42, ..Default::default() })
.await
.unwrap();
@@ -16,9 +16,11 @@
//! Default generic implementation of headers source for basic Substrate client.
use crate::chain::{BlockWithJustification, Chain};
use crate::client::Client;
use crate::error::Error;
use crate::{
chain::{BlockWithJustification, Chain},
client::Client,
error::Error,
};
use async_trait::async_trait;
use headers_relay::{
@@ -38,19 +40,13 @@ pub struct HeadersSource<C: Chain, P> {
impl<C: Chain, P> HeadersSource<C, P> {
/// Create new headers source using given client.
pub fn new(client: Client<C>) -> Self {
HeadersSource {
client,
_phantom: Default::default(),
}
HeadersSource { client, _phantom: Default::default() }
}
}
impl<C: Chain, P> Clone for HeadersSource<C, P> {
fn clone(&self) -> Self {
HeadersSource {
client: self.client.clone(),
_phantom: Default::default(),
}
HeadersSource { client: self.client.clone(), _phantom: Default::default() }
}
}
@@ -69,7 +65,12 @@ where
C: Chain,
C::BlockNumber: relay_utils::BlockNumberBase,
C::Header: Into<P::Header>,
P: HeadersSyncPipeline<Extra = (), Completion = EncodedJustification, Hash = C::Hash, Number = C::BlockNumber>,
P: HeadersSyncPipeline<
Extra = (),
Completion = EncodedJustification,
Hash = C::Hash,
Number = C::BlockNumber,
>,
P::Header: SourceHeader<C::Hash, C::BlockNumber>,
{
async fn best_block_number(&self) -> Result<P::Number, Error> {
@@ -79,22 +80,17 @@ where
}
async fn header_by_hash(&self, hash: P::Hash) -> Result<P::Header, Error> {
self.client
.header_by_hash(hash)
.await
.map(Into::into)
.map_err(Into::into)
self.client.header_by_hash(hash).await.map(Into::into).map_err(Into::into)
}
async fn header_by_number(&self, number: P::Number) -> Result<P::Header, Error> {
self.client
.header_by_number(number)
.await
.map(Into::into)
.map_err(Into::into)
self.client.header_by_number(number).await.map(Into::into).map_err(Into::into)
}
async fn header_completion(&self, id: HeaderIdOf<P>) -> Result<(HeaderIdOf<P>, Option<P::Completion>), Error> {
async fn header_completion(
&self,
id: HeaderIdOf<P>,
) -> Result<(HeaderIdOf<P>, Option<P::Completion>), Error> {
let hash = id.1;
let signed_block = self.client.get_block(Some(hash)).await?;
let grandpa_justification = signed_block.justification().cloned();
@@ -102,7 +98,11 @@ where
Ok((id, grandpa_justification))
}
async fn header_extra(&self, id: HeaderIdOf<P>, _header: QueuedHeader<P>) -> Result<(HeaderIdOf<P>, ()), Error> {
async fn header_extra(
&self,
id: HeaderIdOf<P>,
_header: QueuedHeader<P>,
) -> Result<(HeaderIdOf<P>, ()), Error> {
Ok((id, ()))
}
}
@@ -29,11 +29,21 @@ pub mod guard;
pub mod headers_source;
pub mod metrics;
pub use crate::chain::{BlockWithJustification, Chain, ChainWithBalances, TransactionSignScheme};
pub use crate::client::{Client, JustificationsSubscription, OpaqueGrandpaAuthoritiesSet};
pub use crate::error::{Error, Result};
pub use crate::sync_header::SyncHeader;
pub use bp_runtime::{BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf};
use std::time::Duration;
pub use crate::{
chain::{
BlockWithJustification, CallOf, Chain, ChainWithBalances, TransactionSignScheme,
TransactionStatusOf, UnsignedTransaction, WeightToFeeOf,
},
client::{Client, OpaqueGrandpaAuthoritiesSet, Subscription},
error::{Error, Result},
sync_header::SyncHeader,
};
pub use bp_runtime::{
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf,
IndexOf, SignatureOf, TransactionEra, TransactionEraOf,
};
/// Header id used by the chain.
pub type HeaderIdOf<C> = relay_utils::HeaderId<HashOf<C>, BlockNumberOf<C>>;
@@ -41,7 +51,7 @@ pub type HeaderIdOf<C> = relay_utils::HeaderId<HashOf<C>, BlockNumberOf<C>>;
/// Substrate-over-websocket connection params.
#[derive(Debug, Clone)]
pub struct ConnectionParams {
/// Websocket server hostname.
/// Websocket server host name.
pub host: String,
/// Websocket server TCP port.
pub port: u16,
@@ -51,10 +61,48 @@ pub struct ConnectionParams {
impl Default for ConnectionParams {
fn default() -> Self {
ConnectionParams {
host: "localhost".into(),
port: 9944,
secure: false,
}
ConnectionParams { host: "localhost".into(), port: 9944, secure: false }
}
}
/// Returns stall timeout for relay loop.
///
/// Relay considers himself stalled if he has submitted transaction to the node, but it has not
/// been mined for this period.
pub fn transaction_stall_timeout(
mortality_period: Option<u32>,
average_block_interval: Duration,
default_stall_timeout: Duration,
) -> Duration {
// 1 extra block for transaction to reach the pool && 1 for relayer to awake after it is mined
mortality_period
.map(|mortality_period| average_block_interval.saturating_mul(mortality_period + 1 + 1))
.unwrap_or(default_stall_timeout)
}
/// Returns stall timeout for relay loop that submit transactions to two chains.
///
/// Bidirectional relay may have two active transactions. Even if one of them has been spoiled, we
/// can't just restart the loop - the other transaction may still be alive and we'll be submitting
/// duplicate transaction, which may result in funds loss. So we'll be selecting maximal mortality
/// for choosing loop stall timeout.
pub fn bidirectional_transaction_stall_timeout(
left_mortality_period: Option<u32>,
right_mortality_period: Option<u32>,
left_average_block_interval: Duration,
right_average_block_interval: Duration,
default_stall_timeout: Duration,
) -> Duration {
std::cmp::max(
transaction_stall_timeout(
left_mortality_period,
left_average_block_interval,
default_stall_timeout,
),
transaction_stall_timeout(
right_mortality_period,
right_average_block_interval,
default_stall_timeout,
),
)
}
@@ -14,12 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::chain::Chain;
use crate::client::Client;
use crate::{chain::Chain, client::Client};
use async_std::sync::{Arc, RwLock};
use async_trait::async_trait;
use codec::Decode;
use relay_utils::metrics::{metric_name, register, Gauge, PrometheusError, Registry, StandaloneMetrics, F64};
use relay_utils::metrics::{
metric_name, register, F64SharedRef, Gauge, PrometheusError, Registry, StandaloneMetrics, F64,
};
use sp_core::storage::StorageKey;
use sp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber};
use std::time::Duration;
@@ -34,6 +36,7 @@ pub struct FloatStorageValueMetric<C: Chain, T: Clone> {
storage_key: StorageKey,
maybe_default_value: Option<T>,
metric: Gauge<F64>,
shared_value_ref: F64SharedRef,
}
impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
@@ -47,13 +50,20 @@ impl<C: Chain, T: Decode + FixedPointNumber> FloatStorageValueMetric<C, T> {
name: String,
help: String,
) -> Result<Self, PrometheusError> {
let shared_value_ref = Arc::new(RwLock::new(None));
Ok(FloatStorageValueMetric {
client,
storage_key,
maybe_default_value,
metric: register(Gauge::new(metric_name(prefix, &name), help)?, registry)?,
shared_value_ref,
})
}
/// Get shared reference to metric value.
pub fn shared_value_ref(&self) -> F64SharedRef {
self.shared_value_ref.clone()
}
}
#[async_trait]
@@ -66,17 +76,18 @@ where
}
async fn update(&self) {
relay_utils::metrics::set_gauge_value(
&self.metric,
self.client
.storage_value::<T>(self.storage_key.clone())
.await
.map(|maybe_storage_value| {
maybe_storage_value.or(self.maybe_default_value).map(|storage_value| {
storage_value.into_inner().unique_saturated_into() as f64
/ T::DIV.unique_saturated_into() as f64
})
}),
);
let value = self
.client
.storage_value::<T>(self.storage_key.clone(), None)
.await
.map(|maybe_storage_value| {
maybe_storage_value.or(self.maybe_default_value).map(|storage_value| {
storage_value.into_inner().unique_saturated_into() as f64 /
T::DIV.unique_saturated_into() as f64
})
})
.map_err(drop);
relay_utils::metrics::set_gauge_value(&self.metric, value);
*self.shared_value_ref.write().await = value.ok().and_then(|x| x);
}
}
@@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::chain::Chain;
use crate::client::Client;
use crate::error::Error;
use crate::{chain::Chain, client::Client, error::Error};
use async_trait::async_trait;
use relay_utils::metrics::{metric_name, register, Gauge, PrometheusError, Registry, StandaloneMetrics, U64};
use relay_utils::metrics::{
metric_name, register, Gauge, PrometheusError, Registry, StandaloneMetrics, U64,
};
use sp_core::storage::StorageKey;
use sp_runtime::traits::Header as HeaderT;
use sp_storage::well_known_keys::CODE;
@@ -40,10 +40,7 @@ pub struct StorageProofOverheadMetric<C: Chain> {
impl<C: Chain> Clone for StorageProofOverheadMetric<C> {
fn clone(&self) -> Self {
StorageProofOverheadMetric {
client: self.client.clone(),
metric: self.metric.clone(),
}
StorageProofOverheadMetric { client: self.client.clone(), metric: self.metric.clone() }
}
}
@@ -73,15 +70,15 @@ impl<C: Chain> StorageProofOverheadMetric<C> {
.await?;
let storage_proof_size: usize = storage_proof.clone().iter_nodes().map(|n| n.len()).sum();
let storage_value_reader =
bp_runtime::StorageProofChecker::<C::Hasher>::new(*best_header.state_root(), storage_proof)
.map_err(Error::StorageProofError)?;
let maybe_encoded_storage_value = storage_value_reader
.read_value(CODE)
.map_err(Error::StorageProofError)?;
let encoded_storage_value_size = maybe_encoded_storage_value
.ok_or(Error::MissingMandatoryCodeEntry)?
.len();
let storage_value_reader = bp_runtime::StorageProofChecker::<C::Hasher>::new(
*best_header.state_root(),
storage_proof,
)
.map_err(Error::StorageProofError)?;
let maybe_encoded_storage_value =
storage_value_reader.read_value(CODE).map_err(Error::StorageProofError)?;
let encoded_storage_value_size =
maybe_encoded_storage_value.ok_or(Error::MissingMandatoryCodeEntry)?.len();
Ok(storage_proof_size - encoded_storage_value_size)
}
@@ -18,11 +18,13 @@
use crate::chain::Chain;
use pallet_transaction_payment_rpc_runtime_api::FeeDetails;
use sc_rpc_api::{state::ReadProof, system::Health};
use sp_core::{
storage::{StorageData, StorageKey},
Bytes,
};
use sp_rpc::number::NumberOrHex;
use sp_version::RuntimeVersion;
jsonrpsee_proc_macros::rpc_client_api! {
@@ -41,13 +43,17 @@ jsonrpsee_proc_macros::rpc_client_api! {
fn system_account_next_index(account_id: C::AccountId) -> C::Index;
#[rpc(method = "author_submitExtrinsic", positional_params)]
fn author_submit_extrinsic(extrinsic: Bytes) -> C::Hash;
#[rpc(method = "author_pendingExtrinsics", positional_params)]
fn author_pending_extrinsics() -> Vec<Bytes>;
#[rpc(method = "state_call", positional_params)]
fn state_call(method: String, data: Bytes, at_block: Option<C::Hash>) -> Bytes;
#[rpc(method = "state_getStorage", positional_params)]
fn state_get_storage(key: StorageKey) -> Option<StorageData>;
fn state_get_storage(key: StorageKey, at_block: Option<C::Hash>) -> Option<StorageData>;
#[rpc(method = "state_getReadProof", positional_params)]
fn state_prove_storage(keys: Vec<StorageKey>, hash: Option<C::Hash>) -> ReadProof<C::Hash>;
#[rpc(method = "state_getRuntimeVersion", positional_params)]
fn state_runtime_version() -> RuntimeVersion;
#[rpc(method = "payment_queryFeeDetails", positional_params)]
fn payment_query_fee_details(extrinsic: Bytes, at_block: Option<C::Hash>) -> FeeDetails<NumberOrHex>;
}
}