fix: Complete snowbridge pezpallet rebrand and critical bug fixes

- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs)
- pallet/ directories → pezpallet/ (4 locations)
- Fixed pezpallet.rs self-include recursion bug
- Fixed sc-chain-spec hardcoded crate name in derive macro
- Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API)
- Added BizinikiwiConfig type alias for zombienet tests
- Deleted obsolete session state files

Verified: pezsnowbridge-pezpallet-*, pezpallet-staking,
pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
This commit is contained in:
2025-12-16 09:57:23 +03:00
parent eea003e14d
commit 3139ffa25e
3022 changed files with 42157 additions and 23579 deletions
@@ -0,0 +1,59 @@
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Basic runtime calls.
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use pezsp_std::{boxed::Box, vec::Vec};
use xcm::{VersionedLocation, VersionedXcm};
/// A minimized version of `pezframe-system::Call` that can be used without a runtime.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum SystemCall {
/// `pezframe-system::Call::remark`
#[codec(index = 1)]
remark(Vec<u8>),
}
/// A minimized version of `pezpallet-utility::Call` that can be used without a runtime.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum UtilityCall<Call> {
/// `pezpallet-utility::Call::batch_all`
#[codec(index = 2)]
batch_all(Vec<Call>),
}
/// A minimized version of `pezpallet-sudo::Call` that can be used without a runtime.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum SudoCall<Call> {
/// `pezpallet-sudo::Call::sudo`
#[codec(index = 0)]
sudo(Box<Call>),
}
/// A minimized version of `pezpallet-xcm::Call`, that can be used without a runtime.
#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
#[allow(non_camel_case_types)]
pub enum XcmCall {
/// `pezpallet-xcm::Call::send`
#[codec(index = 0)]
send(Box<VersionedLocation>, Box<VersionedXcm<()>>),
}
@@ -0,0 +1,305 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::calls::UtilityCall;
use crate::SimpleRuntimeVersion;
use bp_header_pez_chain::ChainWithGrandpa as ChainWithGrandpaBase;
use bp_messages::ChainWithMessages as ChainWithMessagesBase;
use pezbp_runtime::{
Chain as ChainBase, EncodedOrDecodedCall, HashOf, Teyrchain as TeyrchainBase, TransactionEra,
TransactionEraOf, UnderlyingChainProvider,
};
use codec::{Codec, Decode, Encode, MaxEncodedLen};
use pezframe_support::Parameter;
use jsonrpsee::core::{DeserializeOwned, Serialize};
use num_traits::Zero;
use pezsc_transaction_pool_api::TransactionStatus;
use scale_info::TypeInfo;
use pezsp_core::{storage::StorageKey, Pair};
use pezsp_runtime::{
generic::SignedBlock,
traits::{AtLeast32BitUnsigned, Block as BlockT, Member},
ConsensusEngineId, EncodedJustification,
};
use std::{fmt::Debug, time::Duration};
/// Signed block type of given chain.
pub type SignedBlockOf<C> = <C as Chain>::SignedBlock;
/// Bizinikiwi-based chain from minimal relay-client point of view.
pub trait Chain: ChainBase + Clone {
/// Chain name.
const NAME: &'static str;
/// Name of the runtime API method that is returning best known finalized header number
/// and hash (as tuple).
///
/// Keep in mind that this method is normally provided by the other chain, which is
/// bridged with this chain.
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str;
/// Name of the runtime API method that is returning interval between source chain
/// headers that may be submitted for free to the target chain.
///
/// Keep in mind that this method is normally provided by the other chain, which is
/// bridged with this chain.
const FREE_HEADERS_INTERVAL_METHOD: &'static str;
/// Average block interval.
///
/// 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;
/// Block type.
type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification<Self::Header>;
/// The aggregated `Call` type.
type Call: Clone + Codec + Debug + Send + Sync;
}
/// Bridge-supported network definition.
///
/// Used to abstract away CLI commands.
pub trait ChainWithRuntimeVersion: Chain {
/// Current version of the chain runtime, known to relay.
///
/// can be `None` if relay is not going to submit transactions to that chain.
const RUNTIME_VERSION: Option<SimpleRuntimeVersion>;
}
/// Bizinikiwi-based relay chain that supports teyrchains.
///
/// We assume that the teyrchains are supported using `runtime_teyrchains::paras` pezpallet.
pub trait RelayChain: Chain {
/// Name of the `runtime_teyrchains::paras` pezpallet in the runtime of this chain.
const PARAS_PALLET_NAME: &'static str;
/// Name of the `pezpallet-bridge-teyrchains`, deployed at the **bridged** chain to sync
/// teyrchains of **this** chain.
const WITH_CHAIN_BRIDGE_TEYRCHAINS_PALLET_NAME: &'static str;
}
/// Bizinikiwi-based chain that is using direct GRANDPA finality from minimal relay-client point of
/// view.
///
/// Keep in mind that teyrchains are relying on relay chain GRANDPA, so they should not implement
/// this trait.
pub trait ChainWithGrandpa: Chain + ChainWithGrandpaBase {
/// Name of the runtime API method that is returning the GRANDPA info associated with the
/// headers accepted by the `submit_finality_proofs` extrinsic in the queried block.
///
/// Keep in mind that this method is normally provided by the other chain, which is
/// bridged with this chain.
const SYNCED_HEADERS_GRANDPA_INFO_METHOD: &'static str;
/// The type of the key owner proof used by the grandpa engine.
type KeyOwnerProof: Decode + TypeInfo + Send;
}
/// Bizinikiwi-based teyrchain from minimal relay-client point of view.
pub trait Teyrchain: Chain + TeyrchainBase {}
impl<T> Teyrchain for T where T: UnderlyingChainProvider + Chain + TeyrchainBase {}
/// Bizinikiwi-based chain with messaging support from minimal relay-client point of view.
pub trait ChainWithMessages: Chain + ChainWithMessagesBase {
/// Name of the `To<ChainWithMessages>OutboundLaneApi::message_details` runtime API method.
/// The method is provided by the runtime that is bridged with this `ChainWithMessages`.
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str;
/// Name of the `From<ChainWithMessages>InboundLaneApi::message_details` runtime API method.
/// The method is provided by the runtime that is bridged with this `ChainWithMessages`.
const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str;
}
/// Call type used by the chain.
pub type CallOf<C> = <C as Chain>::Call;
/// Transaction status of the chain.
pub type TransactionStatusOf<C> = TransactionStatus<HashOf<C>, HashOf<C>>;
/// Bizinikiwi-based chain with `AccountData` generic argument of `pezframe_system::AccountInfo` set to
/// the `pezpallet_balances::AccountData<Balance>`.
pub trait ChainWithBalances: Chain {
/// Return runtime storage key for getting `pezframe_system::AccountInfo` of given account.
fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey;
}
/// Bizinikiwi-based chain with bridge relayers pezpallet as a reward ledger.
pub trait ChainWithRewards: Chain {
/// Name of the bridge relayers pezpallet (used in `construct_runtime` macro call).
const WITH_CHAIN_RELAYERS_PALLET_NAME: Option<&'static str>;
/// Type of relayer reward balance.
type RewardBalance: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen;
/// Reward discriminator type.
type Reward: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
/// Return runtime storage key for getting `reward_kind` of given account.
fn account_reward_storage_key(
account_id: &Self::AccountId,
reward: impl Into<Self::Reward>,
) -> 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, engine_id: ConsensusEngineId) -> Option<&EncodedJustification>;
}
/// Transaction before it is signed.
#[derive(Clone, Debug, PartialEq)]
pub struct UnsignedTransaction<C: Chain> {
/// Runtime call of this transaction.
pub call: EncodedOrDecodedCall<C::Call>,
/// Transaction nonce.
pub nonce: C::Nonce,
/// Tip included into transaction.
pub tip: C::Balance,
/// Transaction era used by the chain.
pub era: TransactionEraOf<C>,
}
impl<C: Chain> UnsignedTransaction<C> {
/// Create new unsigned transaction with given call, nonce, era and zero tip.
pub fn new(call: EncodedOrDecodedCall<C::Call>, nonce: C::Nonce) -> Self {
Self { call, nonce, era: TransactionEra::Immortal, tip: Zero::zero() }
}
/// Convert to the transaction of the other compatible chain.
pub fn switch_chain<Other>(self) -> UnsignedTransaction<Other>
where
Other: Chain<
Nonce = C::Nonce,
Balance = C::Balance,
BlockNumber = C::BlockNumber,
Hash = C::Hash,
>,
{
UnsignedTransaction {
call: EncodedOrDecodedCall::Encoded(self.call.into_encoded()),
nonce: self.nonce,
tip: self.tip,
era: self.era,
}
}
/// Set transaction tip.
#[must_use]
pub fn tip(mut self, tip: C::Balance) -> Self {
self.tip = tip;
self
}
/// Set transaction era.
#[must_use]
pub fn era(mut self, era: TransactionEraOf<C>) -> Self {
self.era = era;
self
}
}
/// Account key pair used by transactions signing scheme.
pub type AccountKeyPairOf<S> = <S as ChainWithTransactions>::AccountKeyPair;
/// Bizinikiwi-based chain transactions signing scheme.
pub trait ChainWithTransactions: Chain {
/// Type of key pairs used to sign transactions.
type AccountKeyPair: Pair + Clone + Send + Sync;
/// Signed transaction.
type SignedTransaction: Clone + Debug + Encode + Send + 'static;
/// Create transaction for given runtime call, signed by given account.
fn sign_transaction(
param: SignParam<Self>,
unsigned: UnsignedTransaction<Self>,
) -> Result<Self::SignedTransaction, crate::Error>
where
Self: Sized;
}
/// Sign transaction parameters
pub struct SignParam<C: ChainWithTransactions> {
/// Version of the runtime specification.
pub spec_version: u32,
/// Transaction version
pub transaction_version: u32,
/// Hash of the genesis block.
pub genesis_hash: HashOf<C>,
/// Signer account
pub signer: AccountKeyPairOf<C>,
}
impl<Block: BlockT> BlockWithJustification<Block::Header> for SignedBlock<Block> {
fn header(&self) -> Block::Header {
self.block.header().clone()
}
fn extrinsics(&self) -> Vec<EncodedExtrinsic> {
self.block.extrinsics().iter().map(Encode::encode).collect()
}
fn justification(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification> {
self.justifications.as_ref().and_then(|j| j.get(engine_id))
}
}
/// Trait that provides functionality defined inside `pezpallet-utility`
pub trait UtilityPallet<C: Chain> {
/// Create batch call from given calls vector.
fn build_batch_call(calls: Vec<C::Call>) -> C::Call;
}
/// Structure that implements `UtilityPalletProvider` based on a full runtime.
pub struct FullRuntimeUtilityPallet<R> {
_phantom: std::marker::PhantomData<R>,
}
impl<C, R> UtilityPallet<C> for FullRuntimeUtilityPallet<R>
where
C: Chain,
R: pezpallet_utility::Config<RuntimeCall = C::Call>,
<R as pezpallet_utility::Config>::RuntimeCall: From<pezpallet_utility::Call<R>>,
{
fn build_batch_call(calls: Vec<C::Call>) -> C::Call {
pezpallet_utility::Call::batch_all { calls }.into()
}
}
/// Structure that implements `UtilityPalletProvider` based on a call conversion.
pub struct MockedRuntimeUtilityPallet<Call> {
_phantom: std::marker::PhantomData<Call>,
}
impl<C, Call> UtilityPallet<C> for MockedRuntimeUtilityPallet<Call>
where
C: Chain,
C::Call: From<UtilityCall<C::Call>>,
{
fn build_batch_call(calls: Vec<C::Call>) -> C::Call {
UtilityCall::batch_all(calls).into()
}
}
/// Bizinikiwi-based chain that uses `pezpallet-utility`.
pub trait ChainWithUtilityPallet: Chain {
/// The utility pezpallet provider.
type UtilityPallet: UtilityPallet<Self>;
}
@@ -0,0 +1,472 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Client implementation that is caching (whenever possible) results of its backend
//! method calls.
use crate::{
client::{Client, SubscriptionBroadcaster},
error::{Error, Result},
AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, ChainWithGrandpa, ChainWithTransactions,
HashOf, HeaderIdOf, HeaderOf, NonceOf, SignedBlockOf, SimpleRuntimeVersion, Subscription,
TransactionTracker, UnsignedTransaction, ANCIENT_BLOCK_THRESHOLD,
};
use std::{cmp::Ordering, future::Future, task::Poll};
use async_std::{
sync::{Arc, Mutex, RwLock},
task::JoinHandle,
};
use async_trait::async_trait;
use codec::Encode;
use pezframe_support::weights::Weight;
use futures::{FutureExt, StreamExt};
use quick_cache::unsync::Cache;
use pezsp_consensus_grandpa::{AuthorityId, OpaqueKeyOwnershipProof, SetId};
use pezsp_core::{
storage::{StorageData, StorageKey},
Bytes, Pair,
};
use pezsp_runtime::{traits::Header as _, transaction_validity::TransactionValidity};
use pezsp_trie::StorageProof;
use pezsp_version::RuntimeVersion;
/// `quick_cache::unsync::Cache` wrapped in async-aware synchronization primitives.
type SyncCache<K, V> = Arc<RwLock<Cache<K, V>>>;
/// Client implementation that is caching (whenever possible) results of its backend
/// method calls. Apart from caching call results, it also supports some (at the
/// moment: justifications) subscription sharing, meaning that the single server
/// subscription may be shared by multiple subscribers at the client side.
#[derive(Clone)]
pub struct CachingClient<C: Chain, B: Client<C>> {
backend: B,
data: Arc<ClientData<C>>,
}
/// Client data, shared by all `CachingClient` clones.
struct ClientData<C: Chain> {
grandpa_justifications: Arc<Mutex<Option<SubscriptionBroadcaster<Bytes>>>>,
beefy_justifications: Arc<Mutex<Option<SubscriptionBroadcaster<Bytes>>>>,
background_task_handle: Arc<Mutex<JoinHandle<Result<()>>>>,
best_header: Arc<RwLock<Option<HeaderOf<C>>>>,
best_finalized_header: Arc<RwLock<Option<HeaderOf<C>>>>,
// `quick_cache::sync::Cache` has the `get_or_insert_async` method, which fits our needs,
// but it uses synchronization primitives that are not aware of async execution. They
// can block the executor threads and cause deadlocks => let's use primitives from
// `async_std` crate around `quick_cache::unsync::Cache`
header_hash_by_number_cache: SyncCache<BlockNumberOf<C>, HashOf<C>>,
header_by_hash_cache: SyncCache<HashOf<C>, HeaderOf<C>>,
block_by_hash_cache: SyncCache<HashOf<C>, SignedBlockOf<C>>,
raw_storage_value_cache: SyncCache<(HashOf<C>, StorageKey), Option<StorageData>>,
state_call_cache: SyncCache<(HashOf<C>, String, Bytes), Bytes>,
}
impl<C: Chain, B: Client<C>> CachingClient<C, B> {
/// Creates new `CachingClient` on top of given `backend`.
pub async fn new(backend: B) -> Self {
// most of relayer operations will never touch more than `ANCIENT_BLOCK_THRESHOLD`
// headers, so we'll use this as a cache capacity for all chain-related caches
let chain_state_capacity = ANCIENT_BLOCK_THRESHOLD as usize;
let best_header = Arc::new(RwLock::new(None));
let best_finalized_header = Arc::new(RwLock::new(None));
let header_by_hash_cache = Arc::new(RwLock::new(Cache::new(chain_state_capacity)));
let background_task_handle = Self::start_background_task(
backend.clone(),
best_header.clone(),
best_finalized_header.clone(),
header_by_hash_cache.clone(),
)
.await;
CachingClient {
backend,
data: Arc::new(ClientData {
grandpa_justifications: Arc::new(Mutex::new(None)),
beefy_justifications: Arc::new(Mutex::new(None)),
background_task_handle: Arc::new(Mutex::new(background_task_handle)),
best_header,
best_finalized_header,
header_hash_by_number_cache: Arc::new(RwLock::new(Cache::new(
chain_state_capacity,
))),
header_by_hash_cache,
block_by_hash_cache: Arc::new(RwLock::new(Cache::new(chain_state_capacity))),
raw_storage_value_cache: Arc::new(RwLock::new(Cache::new(1_024))),
state_call_cache: Arc::new(RwLock::new(Cache::new(1_024))),
}),
}
}
/// Try to get value from the cache, or compute and insert it using given future.
async fn get_or_insert_async<K: Clone + std::fmt::Debug + Eq + std::hash::Hash, V: Clone>(
&self,
cache: &Arc<RwLock<Cache<K, V>>>,
key: &K,
with: impl std::future::Future<Output = Result<V>>,
) -> Result<V> {
// try to get cached value first using read lock
{
let cache = cache.read().await;
if let Some(value) = cache.get(key) {
return Ok(value.clone());
}
}
// let's compute the value without holding any locks - it may cause additional misses and
// double insertions, but that's better than holding a lock for a while
let value = with.await?;
// insert/update the value in the cache
cache.write().await.insert(key.clone(), value.clone());
Ok(value)
}
/// Subscribe to finality justifications, trying to reuse existing subscription.
async fn subscribe_finality_justifications<'a>(
&'a self,
maybe_broadcaster: &Mutex<Option<SubscriptionBroadcaster<Bytes>>>,
do_subscribe: impl Future<Output = Result<Subscription<Bytes>>> + 'a,
) -> Result<Subscription<Bytes>> {
let mut maybe_broadcaster = maybe_broadcaster.lock().await;
let broadcaster = match maybe_broadcaster.as_ref() {
Some(justifications) => justifications,
None => {
let broadcaster = match SubscriptionBroadcaster::new(do_subscribe.await?) {
Ok(broadcaster) => broadcaster,
Err(subscription) => return Ok(subscription),
};
maybe_broadcaster.get_or_insert(broadcaster)
},
};
broadcaster.subscribe().await
}
/// Start background task that reads best (and best finalized) headers from subscriptions.
async fn start_background_task(
backend: B,
best_header: Arc<RwLock<Option<HeaderOf<C>>>>,
best_finalized_header: Arc<RwLock<Option<HeaderOf<C>>>>,
header_by_hash_cache: SyncCache<HashOf<C>, HeaderOf<C>>,
) -> JoinHandle<Result<()>> {
async_std::task::spawn(async move {
// initialize by reading headers directly from backend to avoid doing that in the
// high-level code
let mut last_finalized_header =
backend.header_by_hash(backend.best_finalized_header_hash().await?).await?;
*best_header.write().await = Some(backend.best_header().await?);
*best_finalized_header.write().await = Some(last_finalized_header.clone());
// ...and then continue with subscriptions
let mut best_headers = backend.subscribe_best_headers().await?;
let mut finalized_headers = backend.subscribe_finalized_headers().await?;
loop {
futures::select! {
new_best_header = best_headers.next().fuse() => {
// we assume that the best header is always the actual best header, even if its
// number is lower than the number of previous-best-header (chain may use its own
// best header selection algorithms)
let new_best_header = new_best_header
.ok_or_else(|| Error::ChannelError(format!("Mandatory best headers subscription for {} has finished", C::NAME)))?;
let new_best_header_hash = new_best_header.hash();
header_by_hash_cache.write().await.insert(new_best_header_hash, new_best_header.clone());
*best_header.write().await = Some(new_best_header);
},
new_finalized_header = finalized_headers.next().fuse() => {
// in theory we'll always get finalized headers in order, but let's double check
let new_finalized_header = new_finalized_header.
ok_or_else(|| Error::ChannelError(format!("Finalized headers subscription for {} has finished", C::NAME)))?;
let new_finalized_header_number = *new_finalized_header.number();
let last_finalized_header_number = *last_finalized_header.number();
match new_finalized_header_number.cmp(&last_finalized_header_number) {
Ordering::Greater => {
let new_finalized_header_hash = new_finalized_header.hash();
header_by_hash_cache.write().await.insert(new_finalized_header_hash, new_finalized_header.clone());
*best_finalized_header.write().await = Some(new_finalized_header.clone());
last_finalized_header = new_finalized_header;
},
Ordering::Less => {
return Err(Error::unordered_finalized_headers::<C>(
new_finalized_header_number,
last_finalized_header_number,
));
},
_ => (),
}
},
}
}
})
}
/// Ensure that the background task is active.
async fn ensure_background_task_active(&self) -> Result<()> {
let mut background_task_handle = self.data.background_task_handle.lock().await;
if let Poll::Ready(result) = futures::poll!(&mut *background_task_handle) {
return Err(Error::ChannelError(format!(
"Background task of {} client has exited with result: {:?}",
C::NAME,
result
)));
}
Ok(())
}
/// Try to get header, read elsewhere by background task through subscription.
async fn read_header_from_background<'a>(
&'a self,
header: &Arc<RwLock<Option<HeaderOf<C>>>>,
read_header_from_backend: impl Future<Output = Result<HeaderOf<C>>> + 'a,
) -> Result<HeaderOf<C>> {
// ensure that the background task is active
self.ensure_background_task_active().await?;
// now we know that the background task is active, so we could trust that the
// `header` has the most recent updates from it
match header.read().await.clone() {
Some(header) => Ok(header),
None => {
// header has not yet been read from the subscription, which means that
// we are just starting - let's read header directly from backend this time
read_header_from_backend.await
},
}
}
}
impl<C: Chain, B: Client<C>> std::fmt::Debug for CachingClient<C, B> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_fmt(format_args!("CachingClient<{:?}>", self.backend))
}
}
#[async_trait]
impl<C: Chain, B: Client<C>> Client<C> for CachingClient<C, B> {
async fn ensure_synced(&self) -> Result<()> {
self.backend.ensure_synced().await
}
async fn reconnect(&self) -> Result<()> {
self.backend.reconnect().await?;
// since we have new underlying client, we need to restart subscriptions too
*self.data.grandpa_justifications.lock().await = None;
*self.data.beefy_justifications.lock().await = None;
// also restart background task too
*self.data.best_header.write().await = None;
*self.data.best_finalized_header.write().await = None;
*self.data.background_task_handle.lock().await = Self::start_background_task(
self.backend.clone(),
self.data.best_header.clone(),
self.data.best_finalized_header.clone(),
self.data.header_by_hash_cache.clone(),
)
.await;
Ok(())
}
fn genesis_hash(&self) -> HashOf<C> {
self.backend.genesis_hash()
}
async fn header_hash_by_number(&self, number: BlockNumberOf<C>) -> Result<HashOf<C>> {
self.get_or_insert_async(
&self.data.header_hash_by_number_cache,
&number,
self.backend.header_hash_by_number(number),
)
.await
}
async fn header_by_hash(&self, hash: HashOf<C>) -> Result<HeaderOf<C>> {
self.get_or_insert_async(
&self.data.header_by_hash_cache,
&hash,
self.backend.header_by_hash(hash),
)
.await
}
async fn block_by_hash(&self, hash: HashOf<C>) -> Result<SignedBlockOf<C>> {
self.get_or_insert_async(
&self.data.block_by_hash_cache,
&hash,
self.backend.block_by_hash(hash),
)
.await
}
async fn best_finalized_header_hash(&self) -> Result<HashOf<C>> {
self.read_header_from_background(
&self.data.best_finalized_header,
self.backend.best_finalized_header(),
)
.await
.map(|h| h.hash())
}
async fn best_header(&self) -> Result<HeaderOf<C>> {
self.read_header_from_background(&self.data.best_header, self.backend.best_header())
.await
}
async fn subscribe_best_headers(&self) -> Result<Subscription<HeaderOf<C>>> {
// we may share the sunbscription here, but atm there's no callers of this method
self.backend.subscribe_best_headers().await
}
async fn subscribe_finalized_headers(&self) -> Result<Subscription<HeaderOf<C>>> {
// we may share the sunbscription here, but atm there's no callers of this method
self.backend.subscribe_finalized_headers().await
}
async fn subscribe_grandpa_finality_justifications(&self) -> Result<Subscription<Bytes>>
where
C: ChainWithGrandpa,
{
self.subscribe_finality_justifications(
&self.data.grandpa_justifications,
self.backend.subscribe_grandpa_finality_justifications(),
)
.await
}
async fn generate_grandpa_key_ownership_proof(
&self,
at: HashOf<C>,
set_id: SetId,
authority_id: AuthorityId,
) -> Result<Option<OpaqueKeyOwnershipProof>> {
self.backend
.generate_grandpa_key_ownership_proof(at, set_id, authority_id)
.await
}
async fn subscribe_beefy_finality_justifications(&self) -> Result<Subscription<Bytes>> {
self.subscribe_finality_justifications(
&self.data.beefy_justifications,
self.backend.subscribe_beefy_finality_justifications(),
)
.await
}
async fn token_decimals(&self) -> Result<Option<u64>> {
self.backend.token_decimals().await
}
async fn runtime_version(&self) -> Result<RuntimeVersion> {
self.backend.runtime_version().await
}
async fn simple_runtime_version(&self) -> Result<SimpleRuntimeVersion> {
self.backend.simple_runtime_version().await
}
fn can_start_version_guard(&self) -> bool {
self.backend.can_start_version_guard()
}
async fn raw_storage_value(
&self,
at: HashOf<C>,
storage_key: StorageKey,
) -> Result<Option<StorageData>> {
self.get_or_insert_async(
&self.data.raw_storage_value_cache,
&(at, storage_key.clone()),
self.backend.raw_storage_value(at, storage_key),
)
.await
}
async fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
self.backend.pending_extrinsics().await
}
async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<HashOf<C>> {
self.backend.submit_unsigned_extrinsic(transaction).await
}
async fn submit_signed_extrinsic(
&self,
signer: &AccountKeyPairOf<C>,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, NonceOf<C>) -> Result<UnsignedTransaction<C>>
+ Send
+ 'static,
) -> Result<HashOf<C>>
where
C: ChainWithTransactions,
AccountIdOf<C>: From<<AccountKeyPairOf<C> as Pair>::Public>,
{
self.backend.submit_signed_extrinsic(signer, prepare_extrinsic).await
}
async fn submit_and_watch_signed_extrinsic(
&self,
signer: &AccountKeyPairOf<C>,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, NonceOf<C>) -> Result<UnsignedTransaction<C>>
+ Send
+ 'static,
) -> Result<TransactionTracker<C, Self>>
where
C: ChainWithTransactions,
AccountIdOf<C>: From<<AccountKeyPairOf<C> as Pair>::Public>,
{
self.backend
.submit_and_watch_signed_extrinsic(signer, prepare_extrinsic)
.await
.map(|t| t.switch_environment(self.clone()))
}
async fn validate_transaction<SignedTransaction: Encode + Send + 'static>(
&self,
at: HashOf<C>,
transaction: SignedTransaction,
) -> Result<TransactionValidity> {
self.backend.validate_transaction(at, transaction).await
}
async fn estimate_extrinsic_weight<SignedTransaction: Encode + Send + 'static>(
&self,
at: HashOf<C>,
transaction: SignedTransaction,
) -> Result<Weight> {
self.backend.estimate_extrinsic_weight(at, transaction).await
}
async fn raw_state_call<Args: Encode + Send>(
&self,
at: HashOf<C>,
method: String,
arguments: Args,
) -> Result<Bytes> {
let encoded_arguments = Bytes(arguments.encode());
self.get_or_insert_async(
&self.data.state_call_cache,
&(at, method.clone(), encoded_arguments),
self.backend.raw_state_call(at, method, arguments),
)
.await
}
async fn prove_storage(
&self,
at: HashOf<C>,
keys: Vec<StorageKey>,
) -> Result<(StorageProof, HashOf<C>)> {
self.backend.prove_storage(at, keys).await
}
}
@@ -0,0 +1,91 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Layered Bizinikiwi client implementation.
use crate::{Chain, ConnectionParams};
use caching::CachingClient;
use num_traits::Saturating;
use rpc::RpcClient;
use pezsp_version::RuntimeVersion;
pub mod caching;
pub mod rpc;
mod rpc_api;
mod subscription;
mod traits;
pub use subscription::{StreamDescription, Subscription, SubscriptionBroadcaster};
pub use traits::Client;
/// Type of RPC client with caching support.
pub type RpcWithCachingClient<C> = CachingClient<C, RpcClient<C>>;
/// Creates new RPC client with caching support.
pub async fn rpc_with_caching<C: Chain>(params: ConnectionParams) -> RpcWithCachingClient<C> {
let rpc = rpc::RpcClient::<C>::new(params).await;
caching::CachingClient::new(rpc).await
}
/// The difference between best block number and number of its ancestor, that is enough
/// for us to consider that ancestor an "ancient" block with dropped state.
///
/// The relay does not assume that it is connected to the archive node, so it always tries
/// to use the best available chain state. But sometimes it still may use state of some
/// old block. If the state of that block is already dropped, relay will see errors when
/// e.g. it tries to prove something.
///
/// By default Bizinikiwi-based nodes are storing state for last 256 blocks. We'll use
/// half of this value.
pub const ANCIENT_BLOCK_THRESHOLD: u32 = 128;
/// Returns `true` if we think that the state is already discarded for given block.
pub fn is_ancient_block<N: From<u32> + PartialOrd + Saturating>(block: N, best: N) -> bool {
best.saturating_sub(block) >= N::from(ANCIENT_BLOCK_THRESHOLD)
}
/// Opaque GRANDPA authorities set.
pub type OpaqueGrandpaAuthoritiesSet = Vec<u8>;
/// A simple runtime version. It only includes the `spec_version` and `transaction_version`.
#[derive(Copy, Clone, Debug)]
pub struct SimpleRuntimeVersion {
/// Version of the runtime specification.
pub spec_version: u32,
/// All existing dispatches are fully compatible when this number doesn't change.
pub transaction_version: u32,
}
impl SimpleRuntimeVersion {
/// Create a new instance of `SimpleRuntimeVersion` from a `RuntimeVersion`.
pub const fn from_runtime_version(runtime_version: &RuntimeVersion) -> Self {
Self {
spec_version: runtime_version.spec_version,
transaction_version: runtime_version.transaction_version,
}
}
}
/// Chain runtime version in client
#[derive(Copy, Clone, Debug)]
pub enum ChainRuntimeVersion {
/// Auto query from chain.
Auto,
/// Custom runtime version, defined by user.
Custom(SimpleRuntimeVersion),
}
@@ -0,0 +1,743 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Client implementation that connects to the Bizinikiwi node over `ws`/`wss` connection
//! and is using RPC methods to get required data and submit transactions.
use crate::{
client::{
rpc_api::{
BizinikiwiAuthorClient, BizinikiwiBeefyClient, BizinikiwiChainClient,
BizinikiwiFrameSystemClient, BizinikiwiGrandpaClient, BizinikiwiStateClient,
BizinikiwiSystemClient,
},
subscription::{StreamDescription, Subscription},
Client,
},
error::{Error, Result},
guard::Environment,
transaction_stall_timeout, AccountIdOf, AccountKeyPairOf, BalanceOf, BlockNumberOf, Chain,
ChainRuntimeVersion, ChainWithGrandpa, ChainWithTransactions, ConnectionParams, HashOf,
HeaderIdOf, HeaderOf, NonceOf, SignParam, SignedBlockOf, SimpleRuntimeVersion,
TransactionTracker, UnsignedTransaction,
};
use async_std::sync::{Arc, Mutex, RwLock};
use async_trait::async_trait;
use pezbp_runtime::HeaderIdProvider;
use codec::Encode;
use pezframe_support::weights::Weight;
use futures::TryFutureExt;
use jsonrpsee::{
core::{client::Subscription as RpcSubscription, ClientError},
ws_client::{WsClient, WsClientBuilder},
};
use num_traits::Zero;
use pezpallet_transaction_payment::RuntimeDispatchInfo;
use relay_utils::{relay_loop::RECONNECT_DELAY, STALL_TIMEOUT};
use pezsp_core::{
storage::{StorageData, StorageKey},
Bytes, Hasher, Pair,
};
use pezsp_runtime::{
traits::Header,
transaction_validity::{TransactionSource, TransactionValidity},
};
use pezsp_trie::StorageProof;
use pezsp_version::RuntimeVersion;
use std::{cmp::Ordering, future::Future, marker::PhantomData};
const MAX_SUBSCRIPTION_CAPACITY: usize = 4096;
const SUB_API_TXPOOL_VALIDATE_TRANSACTION: &str = "TaggedTransactionQueue_validate_transaction";
const SUB_API_TX_PAYMENT_QUERY_INFO: &str = "TransactionPaymentApi_query_info";
const SUB_API_GRANDPA_GENERATE_KEY_OWNERSHIP_PROOF: &str =
"GrandpaApi_generate_key_ownership_proof";
/// Client implementation that connects to the Bizinikiwi node over `ws`/`wss` connection
/// and is using RPC methods to get required data and submit transactions.
pub struct RpcClient<C: Chain> {
// Lock order: `submit_signed_extrinsic_lock`, `data`
/// Client connection params.
params: Arc<ConnectionParams>,
/// 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<()>>,
/// Genesis block hash.
genesis_hash: HashOf<C>,
/// Shared dynamic data.
data: Arc<RwLock<ClientData>>,
/// Generic arguments dump.
_phantom: PhantomData<C>,
}
/// Client data, shared by all `RpcClient` clones.
struct ClientData {
/// Tokio runtime handle.
tokio: Arc<tokio::runtime::Runtime>,
/// Bizinikiwi RPC client.
client: Arc<WsClient>,
}
/// Already encoded value.
struct PreEncoded(Vec<u8>);
impl Encode for PreEncoded {
fn encode(&self) -> Vec<u8> {
self.0.clone()
}
}
impl<C: Chain> std::fmt::Debug for RpcClient<C> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.write_fmt(format_args!("RpcClient<{}>", C::NAME))
}
}
impl<C: Chain> RpcClient<C> {
/// Returns client that is able to call RPCs on Bizinikiwi node over websocket connection.
///
/// This function will keep connecting to given Bizinikiwi node until connection is established
/// and is functional. If attempt fail, it will wait for `RECONNECT_DELAY` and retry again.
pub async fn new(params: ConnectionParams) -> Self {
let params = Arc::new(params);
loop {
match Self::try_connect(params.clone()).await {
Ok(client) => return client,
Err(error) => tracing::error!(
target: "bridge",
?error,
node=%C::NAME,
retry_as_secs=%RECONNECT_DELAY.as_secs(),
"Failed to connect. Going to retry"
),
}
async_std::task::sleep(RECONNECT_DELAY).await;
}
}
/// Try to connect to Bizinikiwi node over websocket. Returns Bizinikiwi RPC client if connection
/// has been established or error otherwise.
async fn try_connect(params: Arc<ConnectionParams>) -> Result<Self> {
let (tokio, client) = Self::build_client(&params).await?;
let genesis_hash_client = client.clone();
let genesis_hash = tokio
.spawn(async move {
BizinikiwiChainClient::<C>::block_hash(&*genesis_hash_client, Some(Zero::zero()))
.await
})
.await??;
let chain_runtime_version = params.chain_runtime_version;
let mut client = Self {
params,
submit_signed_extrinsic_lock: Arc::new(Mutex::new(())),
genesis_hash,
data: Arc::new(RwLock::new(ClientData { tokio, client })),
_phantom: PhantomData,
};
Self::ensure_correct_runtime_version(&mut client, chain_runtime_version).await?;
Ok(client)
}
// Check runtime version to understand if we need are connected to expected version, or we
// need to wait for upgrade, we need to abort immediately.
async fn ensure_correct_runtime_version<E: Environment<C, Error = Error>>(
env: &mut E,
expected: ChainRuntimeVersion,
) -> Result<()> {
// we are only interested if version mode is bundled or passed using CLI
let expected = match expected {
ChainRuntimeVersion::Auto => return Ok(()),
ChainRuntimeVersion::Custom(expected) => expected,
};
// we need to wait if actual version is < than expected, we are OK of versions are the
// same and we need to abort if actual version is > than expected
let actual = SimpleRuntimeVersion::from_runtime_version(&env.runtime_version().await?);
match actual.spec_version.cmp(&expected.spec_version) {
Ordering::Less =>
Err(Error::WaitingForRuntimeUpgrade { chain: C::NAME.into(), expected, actual }),
Ordering::Equal => Ok(()),
Ordering::Greater => {
tracing::error!(
target: "bridge",
node=%C::NAME,
?expected,
?actual,
"The client is configured to use runtime version, which is different from the \
actual version. Aborting",
);
env.abort().await;
Err(Error::Custom("Aborted".into()))
},
}
}
/// Build client to use in connection.
async fn build_client(
params: &ConnectionParams,
) -> Result<(Arc<tokio::runtime::Runtime>, Arc<WsClient>)> {
let tokio = tokio::runtime::Runtime::new()?;
let uri = params.uri.clone();
tracing::info!(target: "bridge", node=%C::NAME, %uri, "Connecting");
let client = tokio
.spawn(async move {
WsClientBuilder::default()
.max_buffer_capacity_per_subscription(MAX_SUBSCRIPTION_CAPACITY)
.build(&uri)
.await
})
.await??;
Ok((Arc::new(tokio), Arc::new(client)))
}
/// Execute jsonrpsee future in tokio context.
async fn jsonrpsee_execute<MF, F, T>(&self, make_jsonrpsee_future: MF) -> Result<T>
where
MF: FnOnce(Arc<WsClient>) -> F + Send + 'static,
F: Future<Output = Result<T>> + Send + 'static,
T: Send + 'static,
{
let data = self.data.read().await;
let client = data.client.clone();
data.tokio.spawn(make_jsonrpsee_future(client)).await?
}
/// Prepare parameters used to sign chain transactions.
async fn build_sign_params(&self, signer: AccountKeyPairOf<C>) -> Result<SignParam<C>>
where
C: ChainWithTransactions,
{
let runtime_version = self.simple_runtime_version().await?;
Ok(SignParam::<C> {
spec_version: runtime_version.spec_version,
transaction_version: runtime_version.transaction_version,
genesis_hash: self.genesis_hash,
signer,
})
}
/// Get the nonce of the given Bizinikiwi account.
pub async fn next_account_index(&self, account: AccountIdOf<C>) -> Result<NonceOf<C>> {
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiFrameSystemClient::<C>::account_next_index(&*client, account).await?)
})
.await
}
/// Subscribe to finality justifications.
async fn subscribe_finality_justifications<Fut>(
&self,
gadget_name: &str,
do_subscribe: impl FnOnce(Arc<WsClient>) -> Fut + Send + 'static,
) -> Result<Subscription<Bytes>>
where
Fut: Future<Output = std::result::Result<RpcSubscription<Bytes>, ClientError>> + Send,
{
let subscription = self
.jsonrpsee_execute(move |client| async move { Ok(do_subscribe(client).await?) })
.map_err(|e| Error::failed_to_subscribe_justification::<C>(e))
.await?;
Ok(Subscription::new_forwarded(
StreamDescription::new(format!("{} justifications", gadget_name), C::NAME.into()),
subscription,
))
}
/// Subscribe to headers stream.
async fn subscribe_headers<Fut>(
&self,
stream_name: &str,
do_subscribe: impl FnOnce(Arc<WsClient>) -> Fut + Send + 'static,
map_err: impl FnOnce(Error) -> Error,
) -> Result<Subscription<HeaderOf<C>>>
where
Fut: Future<Output = std::result::Result<RpcSubscription<HeaderOf<C>>, ClientError>> + Send,
{
let subscription = self
.jsonrpsee_execute(move |client| async move { Ok(do_subscribe(client).await?) })
.map_err(map_err)
.await?;
Ok(Subscription::new_forwarded(
StreamDescription::new(format!("{} headers", stream_name), C::NAME.into()),
subscription,
))
}
}
impl<C: Chain> Clone for RpcClient<C> {
fn clone(&self) -> Self {
RpcClient {
params: self.params.clone(),
submit_signed_extrinsic_lock: self.submit_signed_extrinsic_lock.clone(),
genesis_hash: self.genesis_hash,
data: self.data.clone(),
_phantom: PhantomData,
}
}
}
#[async_trait]
impl<C: Chain> Client<C> for RpcClient<C> {
async fn ensure_synced(&self) -> Result<()> {
let health = self
.jsonrpsee_execute(|client| async move {
Ok(BizinikiwiSystemClient::<C>::health(&*client).await?)
})
.await
.map_err(|e| Error::failed_to_get_system_health::<C>(e))?;
let is_synced = !health.is_syncing && (!health.should_have_peers || health.peers > 0);
if is_synced {
Ok(())
} else {
Err(Error::ClientNotSynced(health))
}
}
async fn reconnect(&self) -> Result<()> {
let mut data = self.data.write().await;
let (tokio, client) = Self::build_client(&self.params).await?;
data.tokio = tokio;
data.client = client;
Ok(())
}
fn genesis_hash(&self) -> HashOf<C> {
self.genesis_hash
}
async fn header_hash_by_number(&self, number: BlockNumberOf<C>) -> Result<HashOf<C>> {
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiChainClient::<C>::block_hash(&*client, Some(number)).await?)
})
.await
.map_err(|e| Error::failed_to_read_header_hash_by_number::<C>(number, e))
}
async fn header_by_hash(&self, hash: HashOf<C>) -> Result<HeaderOf<C>> {
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiChainClient::<C>::header(&*client, Some(hash)).await?)
})
.await
.map_err(|e| Error::failed_to_read_header_by_hash::<C>(hash, e))
}
async fn block_by_hash(&self, hash: HashOf<C>) -> Result<SignedBlockOf<C>> {
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiChainClient::<C>::block(&*client, Some(hash)).await?)
})
.await
.map_err(|e| Error::failed_to_read_block_by_hash::<C>(hash, e))
}
async fn best_finalized_header_hash(&self) -> Result<HashOf<C>> {
self.jsonrpsee_execute(|client| async move {
Ok(BizinikiwiChainClient::<C>::finalized_head(&*client).await?)
})
.await
.map_err(|e| Error::failed_to_read_best_finalized_header_hash::<C>(e))
}
async fn best_header(&self) -> Result<HeaderOf<C>> {
self.jsonrpsee_execute(|client| async move {
Ok(BizinikiwiChainClient::<C>::header(&*client, None).await?)
})
.await
.map_err(|e| Error::failed_to_read_best_header::<C>(e))
}
async fn subscribe_best_headers(&self) -> Result<Subscription<HeaderOf<C>>> {
self.subscribe_headers(
"best headers",
move |client| async move { BizinikiwiChainClient::<C>::subscribe_new_heads(&*client).await },
|e| Error::failed_to_subscribe_best_headers::<C>(e),
)
.await
}
async fn subscribe_finalized_headers(&self) -> Result<Subscription<HeaderOf<C>>> {
self.subscribe_headers(
"best finalized headers",
move |client| async move {
BizinikiwiChainClient::<C>::subscribe_finalized_heads(&*client).await
},
|e| Error::failed_to_subscribe_finalized_headers::<C>(e),
)
.await
}
async fn subscribe_grandpa_finality_justifications(&self) -> Result<Subscription<Bytes>>
where
C: ChainWithGrandpa,
{
self.subscribe_finality_justifications("GRANDPA", move |client| async move {
BizinikiwiGrandpaClient::<C>::subscribe_justifications(&*client).await
})
.await
}
async fn generate_grandpa_key_ownership_proof(
&self,
at: HashOf<C>,
set_id: pezsp_consensus_grandpa::SetId,
authority_id: pezsp_consensus_grandpa::AuthorityId,
) -> Result<Option<pezsp_consensus_grandpa::OpaqueKeyOwnershipProof>> {
self.state_call(
at,
SUB_API_GRANDPA_GENERATE_KEY_OWNERSHIP_PROOF.into(),
(set_id, authority_id),
)
.await
}
async fn subscribe_beefy_finality_justifications(&self) -> Result<Subscription<Bytes>> {
self.subscribe_finality_justifications("BEEFY", move |client| async move {
BizinikiwiBeefyClient::<C>::subscribe_justifications(&*client).await
})
.await
}
async fn token_decimals(&self) -> Result<Option<u64>> {
self.jsonrpsee_execute(move |client| async move {
let system_properties = BizinikiwiSystemClient::<C>::properties(&*client).await?;
Ok(system_properties.get("tokenDecimals").and_then(|v| v.as_u64()))
})
.await
}
async fn runtime_version(&self) -> Result<RuntimeVersion> {
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiStateClient::<C>::runtime_version(&*client).await?)
})
.await
.map_err(|e| Error::failed_to_read_runtime_version::<C>(e))
}
async fn simple_runtime_version(&self) -> Result<SimpleRuntimeVersion> {
Ok(match self.params.chain_runtime_version {
ChainRuntimeVersion::Auto => {
let runtime_version = self.runtime_version().await?;
SimpleRuntimeVersion::from_runtime_version(&runtime_version)
},
ChainRuntimeVersion::Custom(ref version) => *version,
})
}
fn can_start_version_guard(&self) -> bool {
!matches!(self.params.chain_runtime_version, ChainRuntimeVersion::Auto)
}
async fn raw_storage_value(
&self,
at: HashOf<C>,
storage_key: StorageKey,
) -> Result<Option<StorageData>> {
let cloned_storage_key = storage_key.clone();
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiStateClient::<C>::storage(&*client, cloned_storage_key, Some(at)).await?)
})
.await
.map_err(|e| Error::failed_to_read_storage_value::<C>(at, storage_key, e))
}
async fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
self.jsonrpsee_execute(move |client| async move {
Ok(BizinikiwiAuthorClient::<C>::pending_extrinsics(&*client).await?)
})
.await
.map_err(|e| Error::failed_to_get_pending_extrinsics::<C>(e))
}
async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<HashOf<C>> {
// one last check that the transaction is valid. Most of checks happen in the relay loop and
// it is the "final" check before submission.
let best_header_hash = self.best_header_hash().await?;
self.validate_transaction(best_header_hash, PreEncoded(transaction.0.clone()))
.await
.map_err(|e| Error::failed_to_submit_transaction::<C>(e))?
.map_err(|e| Error::failed_to_submit_transaction::<C>(Error::TransactionInvalid(e)))?;
self.jsonrpsee_execute(move |client| async move {
let tx_hash = BizinikiwiAuthorClient::<C>::submit_extrinsic(&*client, transaction)
.await
.map_err(|e| {
tracing::error!(target: "bridge", error=?e, node=%C::NAME, "Failed to send transaction");
e
})?;
tracing::trace!(target: "bridge", node=%C::NAME, ?tx_hash, "Sent transaction");
Ok(tx_hash)
})
.await
.map_err(|e| Error::failed_to_submit_transaction::<C>(e))
}
async fn submit_signed_extrinsic(
&self,
signer: &AccountKeyPairOf<C>,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, NonceOf<C>) -> Result<UnsignedTransaction<C>>
+ Send
+ 'static,
) -> Result<HashOf<C>>
where
C: ChainWithTransactions,
AccountIdOf<C>: From<<AccountKeyPairOf<C> as Pair>::Public>,
{
let _guard = self.submit_signed_extrinsic_lock.lock().await;
let transaction_nonce = self.next_account_index(signer.public().into()).await?;
let best_header = self.best_header().await?;
let signing_data = self.build_sign_params(signer.clone()).await?;
// By using parent of best block here, we are protecting again best-block reorganizations.
// E.g. transaction may have been submitted when the best block was `A[num=100]`. Then it
// has been changed to `B[num=100]`. Hash of `A` has been included into transaction
// signature payload. So when signature will be checked, the check will fail and transaction
// will be dropped from the pool.
let best_header_id = best_header.parent_id().unwrap_or_else(|| best_header.id());
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?;
let signed_extrinsic = C::sign_transaction(signing_data, extrinsic)?.encode();
self.submit_unsigned_extrinsic(Bytes(signed_extrinsic)).await
}
async fn submit_and_watch_signed_extrinsic(
&self,
signer: &AccountKeyPairOf<C>,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, NonceOf<C>) -> Result<UnsignedTransaction<C>>
+ Send
+ 'static,
) -> Result<TransactionTracker<C, Self>>
where
C: ChainWithTransactions,
AccountIdOf<C>: From<<AccountKeyPairOf<C> as Pair>::Public>,
{
let self_clone = self.clone();
let signing_data = self.build_sign_params(signer.clone()).await?;
let _guard = self.submit_signed_extrinsic_lock.lock().await;
let transaction_nonce = self.next_account_index(signer.public().into()).await?;
let best_header = self.best_header().await?;
let best_header_id = best_header.id();
let extrinsic = prepare_extrinsic(best_header_id, transaction_nonce)?;
let stall_timeout = transaction_stall_timeout(
extrinsic.era.mortality_period(),
C::AVERAGE_BLOCK_INTERVAL,
STALL_TIMEOUT,
);
let signed_extrinsic = C::sign_transaction(signing_data, extrinsic)?.encode();
// one last check that the transaction is valid. Most of checks happen in the relay loop and
// it is the "final" check before submission.
self.validate_transaction(best_header_id.hash(), PreEncoded(signed_extrinsic.clone()))
.await
.map_err(|e| Error::failed_to_submit_transaction::<C>(e))?
.map_err(|e| Error::failed_to_submit_transaction::<C>(Error::TransactionInvalid(e)))?;
self.jsonrpsee_execute(move |client| async move {
let tx_hash = C::Hasher::hash(&signed_extrinsic);
let subscription: jsonrpsee::core::client::Subscription<_> =
BizinikiwiAuthorClient::<C>::submit_and_watch_extrinsic(
&*client,
Bytes(signed_extrinsic),
)
.await
.map_err(|e| {
tracing::error!(target: "bridge", error=?e, node=%C::NAME, "Failed to send transaction");
e
})?;
tracing::trace!(target: "bridge", node=%C::NAME, ?tx_hash, "Sent transaction");
Ok(TransactionTracker::new(
self_clone,
stall_timeout,
tx_hash,
Subscription::new_forwarded(
StreamDescription::new("transaction events".into(), C::NAME.into()),
subscription,
),
))
})
.await
.map_err(|e| Error::failed_to_submit_transaction::<C>(e))
}
async fn validate_transaction<SignedTransaction: Encode + Send + 'static>(
&self,
at: HashOf<C>,
transaction: SignedTransaction,
) -> Result<TransactionValidity> {
self.state_call(
at,
SUB_API_TXPOOL_VALIDATE_TRANSACTION.into(),
(TransactionSource::External, transaction, at),
)
.await
}
async fn estimate_extrinsic_weight<SignedTransaction: Encode + Send + 'static>(
&self,
at: HashOf<C>,
transaction: SignedTransaction,
) -> Result<Weight> {
let transaction_len = transaction.encoded_size() as u32;
let dispatch_info: RuntimeDispatchInfo<BalanceOf<C>> = self
.state_call(at, SUB_API_TX_PAYMENT_QUERY_INFO.into(), (transaction, transaction_len))
.await?;
Ok(dispatch_info.weight)
}
async fn raw_state_call<Args: Encode + Send>(
&self,
at: HashOf<C>,
method: String,
arguments: Args,
) -> Result<Bytes> {
let arguments = Bytes(arguments.encode());
let arguments_clone = arguments.clone();
let method_clone = method.clone();
self.jsonrpsee_execute(move |client| async move {
BizinikiwiStateClient::<C>::call(&*client, method, arguments, Some(at))
.await
.map_err(Into::into)
})
.await
.map_err(|e| Error::failed_state_call::<C>(at, method_clone, arguments_clone, e))
}
async fn prove_storage(
&self,
at: HashOf<C>,
keys: Vec<StorageKey>,
) -> Result<(StorageProof, HashOf<C>)> {
let state_root = *self.header_by_hash(at).await?.state_root();
let keys_clone = keys.clone();
let read_proof = self
.jsonrpsee_execute(move |client| async move {
BizinikiwiStateClient::<C>::prove_storage(&*client, keys_clone, Some(at))
.await
.map(|proof| StorageProof::new(proof.proof.into_iter().map(|b| b.0)))
.map_err(Into::into)
})
.await
.map_err(|e| Error::failed_to_prove_storage::<C>(at, keys.clone(), e))?;
Ok((read_proof, state_root))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{guard::tests::TestEnvironment, test_chain::TestChain};
use futures::{channel::mpsc::unbounded, FutureExt, SinkExt, StreamExt};
async fn run_ensure_correct_runtime_version(
expected: ChainRuntimeVersion,
actual: RuntimeVersion,
) -> Result<()> {
let (
(mut runtime_version_tx, runtime_version_rx),
(slept_tx, _slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded());
runtime_version_tx.send(actual).await.unwrap();
let mut env = TestEnvironment { runtime_version_rx, slept_tx, aborted_tx };
let ensure_correct_runtime_version =
RpcClient::<TestChain>::ensure_correct_runtime_version(&mut env, expected).boxed();
let aborted = aborted_rx.next().map(|_| Err(Error::Custom("".into()))).boxed();
futures::pin_mut!(ensure_correct_runtime_version, aborted);
futures::future::select(ensure_correct_runtime_version, aborted)
.await
.into_inner()
.0
}
#[async_std::test]
async fn ensure_correct_runtime_version_works() {
// when we are configured to use auto version
assert!(matches!(
run_ensure_correct_runtime_version(
ChainRuntimeVersion::Auto,
RuntimeVersion {
spec_version: 100,
transaction_version: 100,
..Default::default()
},
)
.await,
Ok(()),
));
// when actual == expected
assert!(matches!(
run_ensure_correct_runtime_version(
ChainRuntimeVersion::Custom(SimpleRuntimeVersion {
spec_version: 100,
transaction_version: 100
}),
RuntimeVersion {
spec_version: 100,
transaction_version: 100,
..Default::default()
},
)
.await,
Ok(()),
));
// when actual spec version < expected spec version
assert!(matches!(
run_ensure_correct_runtime_version(
ChainRuntimeVersion::Custom(SimpleRuntimeVersion {
spec_version: 100,
transaction_version: 100
}),
RuntimeVersion { spec_version: 99, transaction_version: 100, ..Default::default() },
)
.await,
Err(Error::WaitingForRuntimeUpgrade {
expected: SimpleRuntimeVersion { spec_version: 100, transaction_version: 100 },
actual: SimpleRuntimeVersion { spec_version: 99, transaction_version: 100 },
..
}),
));
// when actual spec version > expected spec version
assert!(matches!(
run_ensure_correct_runtime_version(
ChainRuntimeVersion::Custom(SimpleRuntimeVersion {
spec_version: 100,
transaction_version: 100
}),
RuntimeVersion {
spec_version: 101,
transaction_version: 100,
..Default::default()
},
)
.await,
Err(Error::Custom(_)),
));
}
}
@@ -0,0 +1,152 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! The most generic Bizinikiwi node RPC interface.
use crate::{Chain, ChainWithGrandpa, TransactionStatusOf};
use jsonrpsee::proc_macros::rpc;
use pezpallet_transaction_payment_rpc_runtime_api::FeeDetails;
use pezsc_rpc_api::{state::ReadProof, system::Health};
use pezsp_core::{
storage::{StorageData, StorageKey},
Bytes,
};
use pezsp_rpc::number::NumberOrHex;
use pezsp_version::RuntimeVersion;
/// RPC methods of Bizinikiwi `system` namespace, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "system")]
pub(crate) trait BizinikiwiSystem<C> {
/// Return node health.
#[method(name = "health")]
async fn health(&self) -> RpcResult<Health>;
/// Return system properties.
#[method(name = "properties")]
async fn properties(&self) -> RpcResult<pezsc_chain_spec::Properties>;
}
/// RPC methods of Bizinikiwi `chain` namespace, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "chain")]
pub(crate) trait BizinikiwiChain<C> {
/// Get block hash by its number.
#[method(name = "getBlockHash")]
async fn block_hash(&self, block_number: Option<C::BlockNumber>) -> RpcResult<C::Hash>;
/// Return block header by its hash.
#[method(name = "getHeader")]
async fn header(&self, block_hash: Option<C::Hash>) -> RpcResult<C::Header>;
/// Return best finalized block hash.
#[method(name = "getFinalizedHead")]
async fn finalized_head(&self) -> RpcResult<C::Hash>;
/// Return signed block (with justifications) by its hash.
#[method(name = "getBlock")]
async fn block(&self, block_hash: Option<C::Hash>) -> RpcResult<C::SignedBlock>;
/// Subscribe to best headers.
#[subscription(
name = "subscribeNewHeads" => "newHead",
unsubscribe = "unsubscribeNewHeads",
item = C::Header
)]
async fn subscribe_new_heads(&self);
/// Subscribe to finalized headers.
#[subscription(
name = "subscribeFinalizedHeads" => "finalizedHead",
unsubscribe = "unsubscribeFinalizedHeads",
item = C::Header
)]
async fn subscribe_finalized_heads(&self);
}
/// RPC methods of Bizinikiwi `author` namespace, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "author")]
pub(crate) trait BizinikiwiAuthor<C> {
/// Submit extrinsic to the transaction pool.
#[method(name = "submitExtrinsic")]
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<C::Hash>;
/// Return vector of pending extrinsics from the transaction pool.
#[method(name = "pendingExtrinsics")]
async fn pending_extrinsics(&self) -> RpcResult<Vec<Bytes>>;
/// Submit and watch for extrinsic state.
#[subscription(name = "submitAndWatchExtrinsic", unsubscribe = "unwatchExtrinsic", item = TransactionStatusOf<C>)]
async fn submit_and_watch_extrinsic(&self, extrinsic: Bytes);
}
/// RPC methods of Bizinikiwi `state` namespace, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "state")]
pub(crate) trait BizinikiwiState<C> {
/// Get current runtime version.
#[method(name = "getRuntimeVersion")]
async fn runtime_version(&self) -> RpcResult<RuntimeVersion>;
/// Call given runtime method.
#[method(name = "call")]
async fn call(
&self,
method: String,
data: Bytes,
at_block: Option<C::Hash>,
) -> RpcResult<Bytes>;
/// Get value of the runtime storage.
#[method(name = "getStorage")]
async fn storage(
&self,
key: StorageKey,
at_block: Option<C::Hash>,
) -> RpcResult<Option<StorageData>>;
/// Get proof of the runtime storage value.
#[method(name = "getReadProof")]
async fn prove_storage(
&self,
keys: Vec<StorageKey>,
hash: Option<C::Hash>,
) -> RpcResult<ReadProof<C::Hash>>;
}
/// RPC methods of Bizinikiwi `grandpa` namespace, that we are using.
#[rpc(client, client_bounds(C: ChainWithGrandpa), namespace = "grandpa")]
pub(crate) trait BizinikiwiGrandpa<C> {
/// Subscribe to GRANDPA justifications.
#[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)]
async fn subscribe_justifications(&self);
}
// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged
/// RPC methods of Bizinikiwi `beefy` namespace, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "beefy")]
pub(crate) trait BizinikiwiBeefy<C> {
/// Subscribe to BEEFY justifications.
#[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)]
async fn subscribe_justifications(&self);
}
/// RPC methods of Bizinikiwi `system` frame pezpallet, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "system")]
pub(crate) trait BizinikiwiFrameSystem<C> {
/// Return index of next account transaction.
#[method(name = "accountNextIndex")]
async fn account_next_index(&self, account_id: C::AccountId) -> RpcResult<C::Nonce>;
}
/// RPC methods of Bizinikiwi `pezpallet_transaction_payment` frame pezpallet, that we are using.
#[rpc(client, client_bounds(C: Chain), namespace = "payment")]
pub(crate) trait BizinikiwiTransactionPayment<C> {
/// Query transaction fee details.
#[method(name = "queryFeeDetails")]
async fn fee_details(
&self,
extrinsic: Bytes,
at_block: Option<C::Hash>,
) -> RpcResult<FeeDetails<NumberOrHex>>;
}
@@ -0,0 +1,238 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::error::Result as ClientResult;
use async_std::{
channel::{bounded, Receiver, Sender},
stream::StreamExt,
};
use futures::{FutureExt, Stream};
use pezsp_runtime::DeserializeOwned;
use std::{
fmt::Debug,
pin::Pin,
result::Result as StdResult,
task::{Context, Poll},
};
/// Once channel reaches this capacity, the subscription breaks.
const CHANNEL_CAPACITY: usize = 128;
/// Structure describing a stream.
#[derive(Clone)]
pub struct StreamDescription {
stream_name: String,
chain_name: String,
}
impl StreamDescription {
/// Create a new instance of `StreamDescription`.
pub fn new(stream_name: String, chain_name: String) -> Self {
Self { stream_name, chain_name }
}
/// Get a stream description.
fn get(&self) -> String {
format!("{} stream of {}", self.stream_name, self.chain_name)
}
}
/// Chainable stream that transforms items of type `Result<T, E>` to items of type `T`.
///
/// If it encounters an item of type `Err`, it returns `Poll::Ready(None)`
/// and terminates the underlying stream.
struct Unwrap<S: Stream<Item = StdResult<T, E>>, T, E> {
desc: StreamDescription,
stream: Option<S>,
}
impl<S: Stream<Item = StdResult<T, E>>, T, E> Unwrap<S, T, E> {
/// Create a new instance of `Unwrap`.
pub fn new(desc: StreamDescription, stream: S) -> Self {
Self { desc, stream: Some(stream) }
}
}
impl<S: Stream<Item = StdResult<T, E>> + Unpin, T: DeserializeOwned, E: Debug> Stream
for Unwrap<S, T, E>
{
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Poll::Ready(match self.stream.as_mut() {
Some(subscription) => match futures::ready!(Pin::new(subscription).poll_next(cx)) {
Some(Ok(item)) => Some(item),
Some(Err(e)) => {
self.stream.take();
tracing::debug!(
target: "bridge",
error=?e,
desc=%self.desc.get(),
"Returned with error. It may need to be restarted"
);
None
},
None => {
self.stream.take();
tracing::debug!(
target: "bridge",
desc=%self.desc.get(),
"Returned `None`. It may need to be restarted"
);
None
},
},
None => None,
})
}
}
/// Subscription factory that produces subscriptions, sharing the same background thread.
#[derive(Clone)]
pub struct SubscriptionBroadcaster<T> {
desc: StreamDescription,
subscribers_sender: Sender<Sender<T>>,
}
impl<T: 'static + Clone + DeserializeOwned + Send> SubscriptionBroadcaster<T> {
/// Create new subscription factory.
pub fn new(subscription: Subscription<T>) -> StdResult<Self, Subscription<T>> {
// It doesn't make sense to further broadcast a broadcasted subscription.
if subscription.is_broadcasted {
return Err(subscription);
}
let desc = subscription.desc().clone();
let (subscribers_sender, subscribers_receiver) = bounded(CHANNEL_CAPACITY);
async_std::task::spawn(background_worker(subscription, subscribers_receiver));
Ok(Self { desc, subscribers_sender })
}
/// Produce new subscription.
pub async fn subscribe(&self) -> ClientResult<Subscription<T>> {
let (items_sender, items_receiver) = bounded(CHANNEL_CAPACITY);
self.subscribers_sender.try_send(items_sender)?;
Ok(Subscription::new_broadcasted(self.desc.clone(), items_receiver))
}
}
/// Subscription to some chain events.
pub struct Subscription<T> {
desc: StreamDescription,
subscription: Box<dyn Stream<Item = T> + Unpin + Send>,
is_broadcasted: bool,
}
impl<T: 'static + Clone + DeserializeOwned + Send> Subscription<T> {
/// Create new forwarded subscription.
pub fn new_forwarded(
desc: StreamDescription,
subscription: impl Stream<Item = StdResult<T, serde_json::Error>> + Unpin + Send + 'static,
) -> Self {
Self {
desc: desc.clone(),
subscription: Box::new(Unwrap::new(desc, subscription)),
is_broadcasted: false,
}
}
/// Create new broadcasted subscription.
pub fn new_broadcasted(
desc: StreamDescription,
subscription: impl Stream<Item = T> + Unpin + Send + 'static,
) -> Self {
Self { desc, subscription: Box::new(subscription), is_broadcasted: true }
}
/// Get the description of the underlying stream
pub fn desc(&self) -> &StreamDescription {
&self.desc
}
}
impl<T> Stream for Subscription<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Poll::Ready(futures::ready!(Pin::new(&mut self.subscription).poll_next(cx)))
}
}
/// Background worker that is executed in tokio context as `jsonrpsee` requires.
///
/// This task may exit under some circumstances. It'll send the correspondent
/// message (`Err` or `None`) to all known listeners. Also, when it stops, all
/// subsequent reads and new subscribers will get the connection error (`ChannelError`).
async fn background_worker<T: 'static + Clone + DeserializeOwned + Send>(
mut subscription: Subscription<T>,
mut subscribers_receiver: Receiver<Sender<T>>,
) {
fn log_task_exit(desc: &StreamDescription, reason: &str) {
tracing::debug!(
target: "bridge",
desc=%desc.get(),
%reason,
"Background task of subscription broadcaster has stopped"
);
}
// wait for first subscriber until actually starting subscription
let subscriber = match subscribers_receiver.next().await {
Some(subscriber) => subscriber,
None => {
// it means that the last subscriber/factory has been dropped, so we need to
// exit too
return log_task_exit(subscription.desc(), "client has stopped");
},
};
// actually subscribe
let mut subscribers = vec![subscriber];
// start listening for new items and receivers
loop {
futures::select! {
subscriber = subscribers_receiver.next().fuse() => {
match subscriber {
Some(subscriber) => subscribers.push(subscriber),
None => {
// it means that the last subscriber/factory has been dropped, so we need to
// exit too
return log_task_exit(subscription.desc(), "client has stopped")
},
}
},
maybe_item = subscription.subscription.next().fuse() => {
match maybe_item {
Some(item) => {
// notify subscribers
subscribers.retain(|subscriber| {
let send_result = subscriber.try_send(item.clone());
send_result.is_ok()
});
}
None => {
// The underlying client has dropped, so we can't do anything here
// and need to stop the task.
return log_task_exit(subscription.desc(), "stream has finished");
}
}
},
}
}
}
@@ -0,0 +1,234 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{
error::{Error, Result},
AccountIdOf, AccountKeyPairOf, BlockNumberOf, Chain, ChainWithGrandpa, ChainWithTransactions,
HashOf, HeaderIdOf, HeaderOf, NonceOf, SignedBlockOf, SimpleRuntimeVersion, Subscription,
TransactionTracker, UnsignedTransaction,
};
use async_trait::async_trait;
use pezbp_runtime::{StorageDoubleMapKeyProvider, StorageMapKeyProvider};
use codec::{Decode, Encode};
use pezframe_support::weights::Weight;
use pezsp_core::{
storage::{StorageData, StorageKey},
Bytes, Pair,
};
use pezsp_runtime::{traits::Header as _, transaction_validity::TransactionValidity};
use pezsp_trie::StorageProof;
use pezsp_version::RuntimeVersion;
use std::fmt::Debug;
/// Relay uses the `Client` to communicate with the node, connected to Bizinikiwi
/// chain `C`.
#[async_trait]
pub trait Client<C: Chain>: 'static + Send + Sync + Clone + Debug {
/// Returns error if client has no connected peers or it believes it is far
/// behind the chain tip.
async fn ensure_synced(&self) -> Result<()>;
/// Reconnects the client.
async fn reconnect(&self) -> Result<()>;
/// Return hash of the genesis block.
fn genesis_hash(&self) -> HashOf<C>;
/// Get header hash by number.
async fn header_hash_by_number(&self, number: BlockNumberOf<C>) -> Result<HashOf<C>>;
/// Get header by hash.
async fn header_by_hash(&self, hash: HashOf<C>) -> Result<HeaderOf<C>>;
/// Get header by number.
async fn header_by_number(&self, number: BlockNumberOf<C>) -> Result<HeaderOf<C>> {
self.header_by_hash(self.header_hash_by_number(number).await?).await
}
/// Get block by hash.
async fn block_by_hash(&self, hash: HashOf<C>) -> Result<SignedBlockOf<C>>;
/// Get best finalized header hash.
async fn best_finalized_header_hash(&self) -> Result<HashOf<C>>;
/// Get best finalized header number.
async fn best_finalized_header_number(&self) -> Result<BlockNumberOf<C>> {
Ok(*self.best_finalized_header().await?.number())
}
/// Get best finalized header.
async fn best_finalized_header(&self) -> Result<HeaderOf<C>> {
self.header_by_hash(self.best_finalized_header_hash().await?).await
}
/// Get best header.
async fn best_header(&self) -> Result<HeaderOf<C>>;
/// Get best header hash.
async fn best_header_hash(&self) -> Result<HashOf<C>> {
Ok(self.best_header().await?.hash())
}
/// Subscribe to new best headers.
async fn subscribe_best_headers(&self) -> Result<Subscription<HeaderOf<C>>>;
/// Subscribe to new finalized headers.
async fn subscribe_finalized_headers(&self) -> Result<Subscription<HeaderOf<C>>>;
/// Subscribe to GRANDPA finality justifications.
async fn subscribe_grandpa_finality_justifications(&self) -> Result<Subscription<Bytes>>
where
C: ChainWithGrandpa;
/// Generates a proof of key ownership for the given authority in the given set.
async fn generate_grandpa_key_ownership_proof(
&self,
at: HashOf<C>,
set_id: pezsp_consensus_grandpa::SetId,
authority_id: pezsp_consensus_grandpa::AuthorityId,
) -> Result<Option<pezsp_consensus_grandpa::OpaqueKeyOwnershipProof>>;
/// Subscribe to BEEFY finality justifications.
async fn subscribe_beefy_finality_justifications(&self) -> Result<Subscription<Bytes>>;
/// Return `tokenDecimals` property from the set of chain properties.
async fn token_decimals(&self) -> Result<Option<u64>>;
/// Get runtime version of the connected chain.
async fn runtime_version(&self) -> Result<RuntimeVersion>;
/// Get partial runtime version, to use when signing transactions.
async fn simple_runtime_version(&self) -> Result<SimpleRuntimeVersion>;
/// Returns `true` if version guard can be started.
///
/// There's no reason to run version guard when version mode is set to `Auto`. It can
/// lead to relay shutdown when chain is upgraded, even though we have explicitly
/// said that we don't want to shutdown.
fn can_start_version_guard(&self) -> bool;
/// Read raw value from runtime storage.
async fn raw_storage_value(
&self,
at: HashOf<C>,
storage_key: StorageKey,
) -> Result<Option<StorageData>>;
/// Read and decode value from runtime storage.
async fn storage_value<T: Decode + 'static>(
&self,
at: HashOf<C>,
storage_key: StorageKey,
) -> Result<Option<T>> {
self.raw_storage_value(at, storage_key.clone())
.await?
.map(|encoded_value| {
T::decode(&mut &encoded_value.0[..]).map_err(|e| {
Error::failed_to_read_storage_value::<C>(at, storage_key, e.into())
})
})
.transpose()
}
/// Read and decode value from runtime storage map.
///
/// `pezpallet_prefix` is the name of the pezpallet (used in `construct_runtime`), which
/// "contains" the storage map.
async fn storage_map_value<T: StorageMapKeyProvider>(
&self,
at: HashOf<C>,
pezpallet_prefix: &str,
storage_key: &T::Key,
) -> Result<Option<T::Value>> {
self.storage_value(at, T::final_key(pezpallet_prefix, storage_key)).await
}
/// Read and decode value from runtime storage double map.
///
/// `pezpallet_prefix` is the name of the pezpallet (used in `construct_runtime`), which
/// "contains" the storage double map.
async fn storage_double_map_value<T: StorageDoubleMapKeyProvider>(
&self,
at: HashOf<C>,
pezpallet_prefix: &str,
key1: &T::Key1,
key2: &T::Key2,
) -> Result<Option<T::Value>> {
self.storage_value(at, T::final_key(pezpallet_prefix, key1, key2)).await
}
/// Returns pending extrinsics from transaction pool.
async fn pending_extrinsics(&self) -> Result<Vec<Bytes>>;
/// Submit unsigned extrinsic for inclusion in a block.
///
/// Note: The given transaction needs to be SCALE encoded beforehand.
async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<HashOf<C>>;
/// Submit an extrinsic signed by given account.
///
/// All calls of this method are synchronized, so there can't be more than one active
/// `submit_signed_extrinsic()` call. This guarantees that no nonces collision may happen
/// if all client instances are clones of the same initial `Client`.
///
/// Note: The given transaction needs to be SCALE encoded beforehand.
async fn submit_signed_extrinsic(
&self,
signer: &AccountKeyPairOf<C>,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, NonceOf<C>) -> Result<UnsignedTransaction<C>>
+ Send
+ 'static,
) -> Result<HashOf<C>>
where
C: ChainWithTransactions,
AccountIdOf<C>: From<<AccountKeyPairOf<C> as Pair>::Public>;
/// Does exactly the same as `submit_signed_extrinsic`, but keeps watching for extrinsic status
/// after submission.
async fn submit_and_watch_signed_extrinsic(
&self,
signer: &AccountKeyPairOf<C>,
prepare_extrinsic: impl FnOnce(HeaderIdOf<C>, NonceOf<C>) -> Result<UnsignedTransaction<C>>
+ Send
+ 'static,
) -> Result<TransactionTracker<C, Self>>
where
C: ChainWithTransactions,
AccountIdOf<C>: From<<AccountKeyPairOf<C> as Pair>::Public>;
/// Validate transaction at given block.
async fn validate_transaction<SignedTransaction: Encode + Send + 'static>(
&self,
at: HashOf<C>,
transaction: SignedTransaction,
) -> Result<TransactionValidity>;
/// Returns weight of the given transaction.
async fn estimate_extrinsic_weight<SignedTransaction: Encode + Send + 'static>(
&self,
at: HashOf<C>,
transaction: SignedTransaction,
) -> Result<Weight>;
/// Execute runtime call at given block.
async fn raw_state_call<Args: Encode + Send>(
&self,
at: HashOf<C>,
method: String,
arguments: Args,
) -> Result<Bytes>;
/// Execute runtime call at given block, provided the input and output types.
/// It also performs the input encode and output decode.
async fn state_call<Args: Encode + Send, Ret: Decode>(
&self,
at: HashOf<C>,
method: String,
arguments: Args,
) -> Result<Ret> {
let encoded_arguments = arguments.encode();
let encoded_output = self.raw_state_call(at, method.clone(), arguments).await?;
Ret::decode(&mut &encoded_output.0[..]).map_err(|e| {
Error::failed_state_call::<C>(at, method, Bytes(encoded_arguments), e.into())
})
}
/// Returns storage proof of given storage keys and state root.
async fn prove_storage(
&self,
at: HashOf<C>,
keys: Vec<StorageKey>,
) -> Result<(StorageProof, HashOf<C>)>;
}
@@ -0,0 +1,431 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Bizinikiwi node RPC errors.
use crate::{BlockNumberOf, Chain, HashOf, SimpleRuntimeVersion};
use bp_header_pez_chain::SubmitFinalityProofCallExtras;
use bp_pezkuwi_core::teyrchains::ParaId;
use jsonrpsee::core::ClientError as RpcError;
use relay_utils::MaybeConnectionError;
use pezsc_rpc_api::system::Health;
use pezsp_core::{storage::StorageKey, Bytes};
use pezsp_runtime::transaction_validity::TransactionValidityError;
use thiserror::Error;
/// Result type used by Bizinikiwi client.
pub type Result<T> = std::result::Result<T, Error>;
/// Errors that can occur only when interacting with
/// a Bizinikiwi node through RPC.
#[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.
#[error("RPC error: {0}")]
RpcError(#[from] RpcError),
/// The response from the server could not be SCALE decoded.
#[error("Response parse failed: {0}")]
ResponseParseFailed(#[from] codec::Error),
/// Internal channel error - communication channel is either closed, or full.
/// It can be solved with reconnect.
#[error("Internal communication channel error: {0:?}.")]
ChannelError(String),
/// Required teyrchain head is not present at the relay chain.
#[error("Teyrchain {0:?} head {1} is missing from the relay chain storage.")]
MissingRequiredTeyrchainHead(ParaId, u64),
/// Failed to find finality proof for the given header.
#[error("Failed to find finality proof for header {0}.")]
FinalityProofNotFound(u64),
/// The client we're connected to is not synced, so we can't rely on its state.
#[error("Bizinikiwi client is not synced {0}.")]
ClientNotSynced(Health),
/// Failed to get system health.
#[error("Failed to get system health of {chain} node: {error:?}.")]
FailedToGetSystemHealth {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read best finalized header hash from given chain.
#[error("Failed to read best finalized header hash of {chain}: {error:?}.")]
FailedToReadBestFinalizedHeaderHash {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read best finalized header from given chain.
#[error("Failed to read best header of {chain}: {error:?}.")]
FailedToReadBestHeader {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read header hash by number from given chain.
#[error("Failed to read header hash by number {number} of {chain}: {error:?}.")]
FailedToReadHeaderHashByNumber {
/// Name of the chain where the error has happened.
chain: String,
/// Number of the header we've tried to read.
number: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read header by hash from given chain.
#[error("Failed to read header {hash} of {chain}: {error:?}.")]
FailedToReadHeaderByHash {
/// Name of the chain where the error has happened.
chain: String,
/// Hash of the header we've tried to read.
hash: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read block by hash from given chain.
#[error("Failed to read block {hash} of {chain}: {error:?}.")]
FailedToReadBlockByHash {
/// Name of the chain where the error has happened.
chain: String,
/// Hash of the header we've tried to read.
hash: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read sotrage value at given chain.
#[error("Failed to read storage value {key:?} at {chain}: {error:?}.")]
FailedToReadStorageValue {
/// Name of the chain where the error has happened.
chain: String,
/// Hash of the block we've tried to read value from.
hash: String,
/// Runtime storage key
key: StorageKey,
/// Underlying error.
error: Box<Error>,
},
/// Failed to read runtime version of given chain.
#[error("Failed to read runtime version of {chain}: {error:?}.")]
FailedToReadRuntimeVersion {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to get pending extrinsics.
#[error("Failed to get pending extrinsics of {chain}: {error:?}.")]
FailedToGetPendingExtrinsics {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to submit transaction.
#[error("Failed to submit {chain} transaction: {error:?}.")]
FailedToSubmitTransaction {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Runtime call has failed.
#[error("Runtime call {method} with arguments {arguments:?} of chain {chain} at {hash} has failed: {error:?}.")]
FailedStateCall {
/// Name of the chain where the error has happened.
chain: String,
/// Hash of the block we've tried to call at.
hash: String,
/// Runtime API method.
method: String,
/// Encoded method arguments.
arguments: Bytes,
/// Underlying error.
error: Box<Error>,
},
/// Failed to prove storage keys.
#[error("Failed to prove storage keys {storage_keys:?} of {chain} at {hash}: {error:?}.")]
FailedToProveStorage {
/// Name of the chain where the error has happened.
chain: String,
/// Hash of the block we've tried to prove keys at.
hash: String,
/// Storage keys we have tried to prove.
storage_keys: Vec<StorageKey>,
/// Underlying error.
error: Box<Error>,
},
/// Failed to subscribe to GRANDPA justifications stream.
#[error("Failed to subscribe to {chain} best headers: {error:?}.")]
FailedToSubscribeBestHeaders {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to subscribe to GRANDPA justifications stream.
#[error("Failed to subscribe to {chain} finalized headers: {error:?}.")]
FailedToSubscribeFinalizedHeaders {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Failed to subscribe to GRANDPA justifications stream.
#[error("Failed to subscribe to {chain} justifications: {error:?}.")]
FailedToSubscribeJustifications {
/// Name of the chain where the error has happened.
chain: String,
/// Underlying error.
error: Box<Error>,
},
/// Headers of the chain are finalized out of order. Maybe chain has been
/// restarted?
#[error("Finalized headers of {chain} are unordered: previously finalized {prev_number} vs new {next_number}")]
UnorderedFinalizedHeaders {
/// Name of the chain where the error has happened.
chain: String,
/// Previously finalized header number.
prev_number: String,
/// New finalized header number.
next_number: String,
},
/// The bridge pezpallet is halted and all transactions will be rejected.
#[error("Bridge pezpallet is halted.")]
BridgePalletIsHalted,
/// The bridge pezpallet is not yet initialized and all transactions will be rejected.
#[error("Bridge pezpallet is not initialized.")]
BridgePalletIsNotInitialized,
/// The Bizinikiwi transaction is invalid.
#[error("Bizinikiwi transaction is invalid: {0:?}")]
TransactionInvalid(#[from] TransactionValidityError),
/// The client is configured to use newer runtime version than the connected chain uses.
/// The client will keep waiting until chain is upgraded to given version.
#[error("Waiting for {chain} runtime upgrade: expected {expected:?} actual {actual:?}")]
WaitingForRuntimeUpgrade {
/// Name of the chain where the error has happened.
chain: String,
/// Expected runtime version.
expected: SimpleRuntimeVersion,
/// Actual runtime version.
actual: SimpleRuntimeVersion,
},
/// Finality proof submission exceeds size and/or weight limits.
#[error("Finality proof submission exceeds limits: {extras:?}")]
FinalityProofWeightLimitExceeded {
/// Finality proof submission extras.
extras: SubmitFinalityProofCallExtras,
},
/// Custom logic error.
#[error("{0}")]
Custom(String),
}
impl From<tokio::task::JoinError> for Error {
fn from(error: tokio::task::JoinError) -> Self {
Error::ChannelError(format!("failed to wait tokio task: {error}"))
}
}
impl<T> From<async_std::channel::TrySendError<T>> for Error {
fn from(error: async_std::channel::TrySendError<T>) -> Self {
Error::ChannelError(format!("`try_send` has failed: {error:?}"))
}
}
impl From<async_std::channel::RecvError> for Error {
fn from(error: async_std::channel::RecvError) -> Self {
Error::ChannelError(format!("`recv` has failed: {error:?}"))
}
}
impl Error {
/// Box the error.
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
/// Returns nested error reference.
pub fn nested(&self) -> Option<&Self> {
match *self {
Self::FailedToReadBestFinalizedHeaderHash { ref error, .. } => Some(&**error),
Self::FailedToReadBestHeader { ref error, .. } => Some(&**error),
Self::FailedToReadHeaderHashByNumber { ref error, .. } => Some(&**error),
Self::FailedToReadHeaderByHash { ref error, .. } => Some(&**error),
Self::FailedToReadBlockByHash { ref error, .. } => Some(&**error),
Self::FailedToReadStorageValue { ref error, .. } => Some(&**error),
Self::FailedToReadRuntimeVersion { ref error, .. } => Some(&**error),
Self::FailedToGetPendingExtrinsics { ref error, .. } => Some(&**error),
Self::FailedToSubmitTransaction { ref error, .. } => Some(&**error),
Self::FailedStateCall { ref error, .. } => Some(&**error),
Self::FailedToProveStorage { ref error, .. } => Some(&**error),
Self::FailedToGetSystemHealth { ref error, .. } => Some(&**error),
Self::FailedToSubscribeBestHeaders { ref error, .. } => Some(&**error),
Self::FailedToSubscribeFinalizedHeaders { ref error, .. } => Some(&**error),
Self::FailedToSubscribeJustifications { ref error, .. } => Some(&**error),
_ => None,
}
}
/// Constructs `FailedToReadHeaderHashByNumber` variant.
pub fn failed_to_read_header_hash_by_number<C: Chain>(
number: BlockNumberOf<C>,
e: Error,
) -> Self {
Error::FailedToReadHeaderHashByNumber {
chain: C::NAME.into(),
number: format!("{number}"),
error: e.boxed(),
}
}
/// Constructs `FailedToReadHeaderByHash` variant.
pub fn failed_to_read_header_by_hash<C: Chain>(hash: HashOf<C>, e: Error) -> Self {
Error::FailedToReadHeaderByHash {
chain: C::NAME.into(),
hash: format!("{hash}"),
error: e.boxed(),
}
}
/// Constructs `FailedToReadBlockByHash` variant.
pub fn failed_to_read_block_by_hash<C: Chain>(hash: HashOf<C>, e: Error) -> Self {
Error::FailedToReadHeaderByHash {
chain: C::NAME.into(),
hash: format!("{hash}"),
error: e.boxed(),
}
}
/// Constructs `FailedToReadBestFinalizedHeaderHash` variant.
pub fn failed_to_read_best_finalized_header_hash<C: Chain>(e: Error) -> Self {
Error::FailedToReadBestFinalizedHeaderHash { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToReadBestHeader` variant.
pub fn failed_to_read_best_header<C: Chain>(e: Error) -> Self {
Error::FailedToReadBestHeader { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToReadRuntimeVersion` variant.
pub fn failed_to_read_runtime_version<C: Chain>(e: Error) -> Self {
Error::FailedToReadRuntimeVersion { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToReadStorageValue` variant.
pub fn failed_to_read_storage_value<C: Chain>(
at: HashOf<C>,
key: StorageKey,
e: Error,
) -> Self {
Error::FailedToReadStorageValue {
chain: C::NAME.into(),
hash: format!("{at}"),
key,
error: e.boxed(),
}
}
/// Constructs `FailedToGetPendingExtrinsics` variant.
pub fn failed_to_get_pending_extrinsics<C: Chain>(e: Error) -> Self {
Error::FailedToGetPendingExtrinsics { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToSubmitTransaction` variant.
pub fn failed_to_submit_transaction<C: Chain>(e: Error) -> Self {
Error::FailedToSubmitTransaction { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedStateCall` variant.
pub fn failed_state_call<C: Chain>(
at: HashOf<C>,
method: String,
arguments: Bytes,
e: Error,
) -> Self {
Error::FailedStateCall {
chain: C::NAME.into(),
hash: format!("{at}"),
method,
arguments,
error: e.boxed(),
}
}
/// Constructs `FailedToProveStorage` variant.
pub fn failed_to_prove_storage<C: Chain>(
at: HashOf<C>,
storage_keys: Vec<StorageKey>,
e: Error,
) -> Self {
Error::FailedToProveStorage {
chain: C::NAME.into(),
hash: format!("{at}"),
storage_keys,
error: e.boxed(),
}
}
/// Constructs `FailedToGetSystemHealth` variant.
pub fn failed_to_get_system_health<C: Chain>(e: Error) -> Self {
Error::FailedToGetSystemHealth { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToSubscribeBestHeaders` variant.
pub fn failed_to_subscribe_best_headers<C: Chain>(e: Error) -> Self {
Error::FailedToSubscribeBestHeaders { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToSubscribeFinalizedHeaders` variant.
pub fn failed_to_subscribe_finalized_headers<C: Chain>(e: Error) -> Self {
Error::FailedToSubscribeFinalizedHeaders { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `FailedToSubscribeJustifications` variant.
pub fn failed_to_subscribe_justification<C: Chain>(e: Error) -> Self {
Error::FailedToSubscribeJustifications { chain: C::NAME.into(), error: e.boxed() }
}
/// Constructs `Un`
pub fn unordered_finalized_headers<C: Chain>(
prev_number: BlockNumberOf<C>,
next_number: BlockNumberOf<C>,
) -> Self {
Error::UnorderedFinalizedHeaders {
chain: C::NAME.into(),
prev_number: format!("{}", prev_number),
next_number: format!("{}", next_number),
}
}
}
impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool {
match *self {
Error::ChannelError(_) => true,
Error::RpcError(ref e) => {
matches!(*e, RpcError::Transport(_) | RpcError::RestartNeeded(_),)
},
Error::ClientNotSynced(_) => true,
Error::UnorderedFinalizedHeaders { .. } => true,
_ => self.nested().map(|e| e.is_connection_error()).unwrap_or(false),
}
}
}
@@ -0,0 +1,196 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Pezpallet provides a set of guard functions that are running in background threads
//! and are aborting process if some condition fails.
use crate::{error::Error, Chain, Client};
use async_trait::async_trait;
use pezsp_version::RuntimeVersion;
use std::{
fmt::Display,
time::{Duration, Instant},
};
/// Guards environment.
#[async_trait]
pub trait Environment<C>: Send + Sync + 'static {
/// Error type.
type Error: Display + Send + Sync + 'static;
/// Return current runtime version.
async fn runtime_version(&mut self) -> Result<RuntimeVersion, 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();
}
}
/// Abort when runtime spec version is different from specified.
pub fn abort_on_spec_version_change<C: Chain>(
mut env: impl Environment<C>,
expected_spec_version: u32,
) {
async_std::task::spawn(async move {
tracing::info!(
target: "bridge-guard",
node=%C::NAME,
%expected_spec_version,
"Starting spec_version guard."
);
loop {
let actual_spec_version = env.runtime_version().await;
match actual_spec_version {
Ok(version) if version.spec_version == expected_spec_version => (),
Ok(version) => {
tracing::error!(
target: "bridge-guard",
node=%C::NAME,
from=%expected_spec_version,
to=%version.spec_version,
"Runtime spec version has changed. Aborting relay"
);
env.abort().await;
},
Err(error) => tracing::warn!(
target: "bridge-guard",
%error,
node=%C::NAME,
"Failed to read runtime version. Relay may need to be stopped manually"
),
}
env.sleep(conditions_check_delay::<C>()).await;
}
});
}
/// Delay between conditions check.
fn conditions_check_delay<C: Chain>() -> Duration {
C::AVERAGE_BLOCK_INTERVAL * (10 + rand::random::<u32>() % 10)
}
#[async_trait]
impl<C: Chain, Clnt: Client<C>> Environment<C> for Clnt {
type Error = Error;
async fn runtime_version(&mut self) -> Result<RuntimeVersion, Self::Error> {
Client::<C>::runtime_version(self).await
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::test_chain::TestChain;
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future::FutureExt,
stream::StreamExt,
SinkExt,
};
pub struct TestEnvironment {
pub runtime_version_rx: UnboundedReceiver<RuntimeVersion>,
pub slept_tx: UnboundedSender<()>,
pub aborted_tx: UnboundedSender<()>,
}
#[async_trait]
impl Environment<TestChain> for TestEnvironment {
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 sleep(&mut self, _duration: Duration) {
let _ = self.slept_tx.send(()).await;
}
async fn abort(&mut self) {
let _ = self.aborted_tx.send(()).await;
// simulate process abort :)
async_std::task::sleep(Duration::from_secs(60)).await;
}
}
#[test]
fn aborts_when_spec_version_is_changed() {
async_std::task::block_on(async {
let (
(mut runtime_version_tx, runtime_version_rx),
(slept_tx, mut slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded());
abort_on_spec_version_change(
TestEnvironment { runtime_version_rx, slept_tx, aborted_tx },
0,
);
// client responds with wrong version
runtime_version_tx
.send(RuntimeVersion { spec_version: 42, ..Default::default() })
.await
.unwrap();
// then the `abort` function is called
aborted_rx.next().await;
// and we do not reach the `sleep` function call
assert!(slept_rx.next().now_or_never().is_none());
});
}
#[test]
fn does_not_aborts_when_spec_version_is_unchanged() {
async_std::task::block_on(async {
let (
(mut runtime_version_tx, runtime_version_rx),
(slept_tx, mut slept_rx),
(aborted_tx, mut aborted_rx),
) = (unbounded(), unbounded(), unbounded());
abort_on_spec_version_change(
TestEnvironment { runtime_version_rx, slept_tx, aborted_tx },
42,
);
// client responds with the same version
runtime_version_tx
.send(RuntimeVersion { spec_version: 42, ..Default::default() })
.await
.unwrap();
// then the `sleep` function is called
slept_rx.next().await;
// and the `abort` function is not called
assert!(aborted_rx.next().now_or_never().is_none());
});
}
}
@@ -0,0 +1,88 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Tools to interact with Bizinikiwi node using RPC methods.
#![warn(missing_docs)]
mod chain;
mod client;
mod error;
mod sync_header;
mod transaction_tracker;
pub mod calls;
pub mod guard;
pub mod metrics;
pub mod test_chain;
use std::time::Duration;
pub use crate::{
chain::{
AccountKeyPairOf, BlockWithJustification, CallOf, Chain, ChainWithBalances,
ChainWithGrandpa, ChainWithMessages, ChainWithRewards, ChainWithRuntimeVersion,
ChainWithTransactions, ChainWithUtilityPallet, FullRuntimeUtilityPallet,
MockedRuntimeUtilityPallet, RelayChain, SignParam, SignedBlockOf, Teyrchain,
TransactionStatusOf, UnsignedTransaction, UtilityPallet,
},
client::{
is_ancient_block, rpc_with_caching as new, ChainRuntimeVersion, Client,
OpaqueGrandpaAuthoritiesSet, RpcWithCachingClient, SimpleRuntimeVersion, StreamDescription,
Subscription, ANCIENT_BLOCK_THRESHOLD,
},
error::{Error, Result},
sync_header::SyncHeader,
transaction_tracker::TransactionTracker,
};
pub use pezbp_runtime::{
AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderIdOf,
HeaderOf, NonceOf, SignatureOf, Teyrchain as TeyrchainBase, TransactionEra, TransactionEraOf,
UnderlyingChainProvider,
};
/// Bizinikiwi-over-websocket connection params.
#[derive(Debug, Clone)]
pub struct ConnectionParams {
/// Websocket endpoint URL.
pub uri: String,
/// Defined chain runtime version
pub chain_runtime_version: ChainRuntimeVersion,
}
impl Default for ConnectionParams {
fn default() -> Self {
ConnectionParams {
uri: "ws://localhost:9944".into(),
chain_runtime_version: ChainRuntimeVersion::Auto,
}
}
}
/// 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)
}
@@ -0,0 +1,140 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{Chain, Client, Error as BizinikiwiError};
use async_std::sync::{Arc, RwLock};
use async_trait::async_trait;
use codec::Decode;
use num_traits::One;
use relay_utils::metrics::{
metric_name, register, F64SharedRef, Gauge, Metric, PrometheusError, Registry,
StandaloneMetric, F64,
};
use pezsp_core::storage::{StorageData, StorageKey};
use pezsp_runtime::{traits::UniqueSaturatedInto, FixedPointNumber, FixedU128};
use std::{marker::PhantomData, time::Duration};
/// Storage value update interval (in blocks).
const UPDATE_INTERVAL_IN_BLOCKS: u32 = 5;
/// Fied-point storage value and the way it is decoded from the raw storage value.
pub trait FloatStorageValue: 'static + Clone + Send + Sync {
/// Type of the value.
type Value: FixedPointNumber;
/// Try to decode value from the raw storage value.
fn decode(
&self,
maybe_raw_value: Option<StorageData>,
) -> Result<Option<Self::Value>, BizinikiwiError>;
}
/// Implementation of `FloatStorageValue` that expects encoded `FixedU128` value and returns `1` if
/// value is missing from the storage.
#[derive(Clone, Debug, Default)]
pub struct FixedU128OrOne;
impl FloatStorageValue for FixedU128OrOne {
type Value = FixedU128;
fn decode(
&self,
maybe_raw_value: Option<StorageData>,
) -> Result<Option<Self::Value>, BizinikiwiError> {
maybe_raw_value
.map(|raw_value| {
FixedU128::decode(&mut &raw_value.0[..])
.map_err(BizinikiwiError::ResponseParseFailed)
.map(Some)
})
.unwrap_or_else(|| Ok(Some(FixedU128::one())))
}
}
/// Metric that represents fixed-point runtime storage value as float gauge.
#[derive(Clone, Debug)]
pub struct FloatStorageValueMetric<C, Clnt, V> {
value_converter: V,
client: Clnt,
storage_key: StorageKey,
metric: Gauge<F64>,
shared_value_ref: F64SharedRef,
_phantom: PhantomData<(C, V)>,
}
impl<C, Clnt, V> FloatStorageValueMetric<C, Clnt, V> {
/// Create new metric.
pub fn new(
value_converter: V,
client: Clnt,
storage_key: StorageKey,
name: String,
help: String,
) -> Result<Self, PrometheusError> {
let shared_value_ref = Arc::new(RwLock::new(None));
Ok(FloatStorageValueMetric {
value_converter,
client,
storage_key,
metric: Gauge::new(metric_name(None, &name), help)?,
shared_value_ref,
_phantom: Default::default(),
})
}
/// Get shared reference to metric value.
pub fn shared_value_ref(&self) -> F64SharedRef {
self.shared_value_ref.clone()
}
}
impl<C: Chain, Clnt: Client<C>, V: FloatStorageValue> Metric
for FloatStorageValueMetric<C, Clnt, V>
{
fn register(&self, registry: &Registry) -> Result<(), PrometheusError> {
register(self.metric.clone(), registry).map(drop)
}
}
#[async_trait]
impl<C: Chain, Clnt: Client<C>, V: FloatStorageValue> StandaloneMetric
for FloatStorageValueMetric<C, Clnt, V>
{
fn update_interval(&self) -> Duration {
C::AVERAGE_BLOCK_INTERVAL * UPDATE_INTERVAL_IN_BLOCKS
}
async fn update(&self) {
let value = async move {
let best_header_hash = self.client.best_header_hash().await?;
let maybe_storage_value = self
.client
.raw_storage_value(best_header_hash, self.storage_key.clone())
.await?;
self.value_converter.decode(maybe_storage_value).map(|maybe_fixed_point_value| {
maybe_fixed_point_value.map(|fixed_point_value| {
fixed_point_value.into_inner().unique_saturated_into() as f64 /
V::Value::DIV.unique_saturated_into() as f64
})
})
}
.await
.map_err(|e| e.to_string());
relay_utils::metrics::set_gauge_value(&self.metric, value.clone());
*self.shared_value_ref.write().await = value.ok().and_then(|x| x);
}
}
@@ -0,0 +1,21 @@
// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Contains several Bizinikiwi-specific metrics that may be exposed by relay.
pub use float_storage_value::{FixedU128OrOne, FloatStorageValue, FloatStorageValueMetric};
mod float_storage_value;
@@ -0,0 +1,61 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use bp_header_pez_chain::ConsensusLogReader;
use pez_finality_relay::SourceHeader as FinalitySourceHeader;
use pezsp_runtime::traits::Header as HeaderT;
/// Generic wrapper for `pezsp_runtime::traits::Header` based headers, that
/// implements `pez_finality_relay::SourceHeader` and may be used in headers sync directly.
#[derive(Clone, Debug, PartialEq)]
pub struct SyncHeader<Header>(Header);
impl<Header> SyncHeader<Header> {
/// Extracts wrapped header from self.
pub fn into_inner(self) -> Header {
self.0
}
}
impl<Header> std::ops::Deref for SyncHeader<Header> {
type Target = Header;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<Header> From<Header> for SyncHeader<Header> {
fn from(header: Header) -> Self {
Self(header)
}
}
impl<Header: HeaderT, R: ConsensusLogReader> FinalitySourceHeader<Header::Hash, Header::Number, R>
for SyncHeader<Header>
{
fn hash(&self) -> Header::Hash {
self.0.hash()
}
fn number(&self) -> Header::Number {
*self.0.number()
}
fn is_mandatory(&self) -> bool {
R::schedules_authorities_change(self.digest())
}
}
@@ -0,0 +1,200 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Pezpallet provides a set of guard functions that are running in background threads
//! and are aborting process if some condition fails.
//! Test chain implementation to use in tests.
#![cfg(any(feature = "test-helpers", test))]
use crate::{
Chain, ChainWithBalances, ChainWithMessages, ChainWithRewards, ChainWithTransactions,
Error as BizinikiwiError, SignParam, UnsignedTransaction,
};
use bp_messages::{ChainWithMessages as ChainWithMessagesBase, MessageNonce};
use pezbp_runtime::ChainId;
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
use pezframe_support::{pezsp_runtime::StateVersion, weights::Weight};
use scale_info::TypeInfo;
use std::time::Duration;
/// Chain that may be used in tests.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TestChain;
impl pezbp_runtime::Chain for TestChain {
const ID: ChainId = *b"test";
type BlockNumber = u32;
type Hash = pezsp_core::H256;
type Hasher = pezsp_runtime::traits::BlakeTwo256;
type Header = pezsp_runtime::generic::Header<u32, pezsp_runtime::traits::BlakeTwo256>;
type AccountId = u32;
type Balance = u32;
type Nonce = u32;
type Signature = pezsp_runtime::testing::TestSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
100000
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
impl Chain for TestChain {
const NAME: &'static str = "Test";
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestMethod";
const FREE_HEADERS_INTERVAL_METHOD: &'static str = "TestMethod";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0);
type SignedBlock = pezsp_runtime::generic::SignedBlock<
pezsp_runtime::generic::Block<Self::Header, pezsp_runtime::OpaqueExtrinsic>,
>;
type Call = TestRuntimeCall;
}
impl ChainWithBalances for TestChain {
fn account_info_storage_key(_account_id: &u32) -> pezsp_core::storage::StorageKey {
unreachable!()
}
}
/// Reward type for the test chain.
#[derive(
Clone,
Copy,
Debug,
Decode,
DecodeWithMemTracking,
Encode,
Eq,
MaxEncodedLen,
PartialEq,
TypeInfo,
)]
pub enum ChainReward {
/// Reward 1 type.
Reward1,
}
impl ChainWithRewards for TestChain {
const WITH_CHAIN_RELAYERS_PALLET_NAME: Option<&'static str> = None;
type RewardBalance = u128;
type Reward = ChainReward;
fn account_reward_storage_key(
_account_id: &Self::AccountId,
_reward: impl Into<Self::Reward>,
) -> pezsp_core::storage::StorageKey {
unreachable!()
}
}
impl ChainWithMessagesBase for TestChain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "Test";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 0;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 0;
}
impl ChainWithMessages for TestChain {
const TO_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = "TestMessagesDetailsMethod";
const FROM_CHAIN_MESSAGE_DETAILS_METHOD: &'static str = "TestFromMessagesDetailsMethod";
}
impl ChainWithTransactions for TestChain {
type AccountKeyPair = pezsp_core::sr25519::Pair;
type SignedTransaction = bp_pezkuwi_core::UncheckedExtrinsic<
TestRuntimeCall,
bp_pezkuwi_core::SuffixedCommonTransactionExtension<(
pezbp_runtime::extensions::BridgeRejectObsoleteHeadersAndMessages,
pezbp_runtime::extensions::RefundBridgedTeyrchainMessagesSchema,
)>,
>;
fn sign_transaction(
_param: SignParam<Self>,
_unsigned: UnsignedTransaction<Self>,
) -> Result<Self::SignedTransaction, BizinikiwiError> {
unreachable!()
}
}
/// Dummy runtime call.
#[derive(Decode, Encode, Clone, Debug, PartialEq)]
pub enum TestRuntimeCall {
/// Dummy call.
#[codec(index = 0)]
Dummy,
}
/// Primitives-level teyrchain that may be used in tests.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TestTeyrchainBase;
impl pezbp_runtime::Chain for TestTeyrchainBase {
const ID: ChainId = *b"tstp";
type BlockNumber = u32;
type Hash = pezsp_core::H256;
type Hasher = pezsp_runtime::traits::BlakeTwo256;
type Header = pezsp_runtime::generic::Header<u32, pezsp_runtime::traits::BlakeTwo256>;
type AccountId = u32;
type Balance = u32;
type Nonce = u32;
type Signature = pezsp_runtime::testing::TestSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
unreachable!()
}
fn max_extrinsic_weight() -> Weight {
unreachable!()
}
}
impl pezbp_runtime::Teyrchain for TestTeyrchainBase {
const TEYRCHAIN_ID: u32 = 1000;
const MAX_HEADER_SIZE: u32 = 1_024;
}
/// Teyrchain that may be used in tests.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TestTeyrchain;
impl pezbp_runtime::UnderlyingChainProvider for TestTeyrchain {
type Chain = TestTeyrchainBase;
}
impl Chain for TestTeyrchain {
const NAME: &'static str = "TestTeyrchain";
const BEST_FINALIZED_HEADER_ID_METHOD: &'static str = "TestTeyrchainMethod";
const FREE_HEADERS_INTERVAL_METHOD: &'static str = "TestTeyrchainMethod";
const AVERAGE_BLOCK_INTERVAL: Duration = Duration::from_millis(0);
type SignedBlock = pezsp_runtime::generic::SignedBlock<
pezsp_runtime::generic::Block<Self::Header, pezsp_runtime::OpaqueExtrinsic>,
>;
type Call = TestRuntimeCall;
}
@@ -0,0 +1,473 @@
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Helper for tracking transaction invalidation events.
use crate::{Chain, Error, HashOf, HeaderIdOf, Subscription, TransactionStatusOf};
use async_trait::async_trait;
use futures::{future::Either, Future, FutureExt, Stream, StreamExt};
use relay_utils::{HeaderId, TrackedTransactionStatus};
use pezsp_runtime::traits::Header as _;
use std::time::Duration;
/// Transaction tracker environment.
#[async_trait]
pub trait Environment<C: Chain>: Send + Sync {
/// Returns header id by its hash.
async fn header_id_by_hash(&self, hash: HashOf<C>) -> Result<HeaderIdOf<C>, Error>;
}
// TODO (https://github.com/pezkuwichain/kurdistan-sdk/issues/84): remove `Environment` trait
// after test client is implemented
#[async_trait]
impl<C: Chain, T: crate::client::Client<C>> Environment<C> for T {
async fn header_id_by_hash(&self, hash: HashOf<C>) -> Result<HeaderIdOf<C>, Error> {
self.header_by_hash(hash).await.map(|h| HeaderId(*h.number(), hash))
}
}
/// Bizinikiwi transaction tracker implementation.
///
/// Bizinikiwi node provides RPC API to submit and watch for transaction events. This way
/// we may know when transaction is included into block, finalized or rejected. There are
/// some edge cases, when we can't fully trust this mechanism - e.g. transaction may broadcasted
/// and then dropped out of node transaction pool (some other cases are also possible - node
/// restarts, connection lost, ...). Then we can't know for sure - what is currently happening
/// with our transaction. Is the transaction really lost? Is it still alive on the chain network?
///
/// We have several options to handle such cases:
///
/// 1) hope that the transaction is still alive and wait for its mining until it is spoiled;
///
/// 2) assume that the transaction is lost and resubmit another transaction instantly;
///
/// 3) wait for some time (if transaction is mortal - then until block where it dies; if it is
/// immortal - then for some time that we assume is long enough to mine it) and assume that it is
/// lost.
///
/// This struct implements third option as it seems to be the most optimal.
pub struct TransactionTracker<C: Chain, E> {
environment: E,
transaction_hash: HashOf<C>,
stall_timeout: Duration,
subscription: Subscription<TransactionStatusOf<C>>,
}
impl<C: Chain, E: Environment<C>> TransactionTracker<C, E> {
/// Create transaction tracker.
pub fn new(
environment: E,
stall_timeout: Duration,
transaction_hash: HashOf<C>,
subscription: Subscription<TransactionStatusOf<C>>,
) -> Self {
Self { environment, stall_timeout, transaction_hash, subscription }
}
// TODO (https://github.com/pezkuwichain/kurdistan-sdk/issues/84): remove me after
// test client is implemented
/// Converts self into tracker with different environment.
pub fn switch_environment<NewE: Environment<C>>(
self,
environment: NewE,
) -> TransactionTracker<C, NewE> {
TransactionTracker {
environment,
stall_timeout: self.stall_timeout,
transaction_hash: self.transaction_hash,
subscription: self.subscription,
}
}
/// Wait for final transaction status and return it along with last known internal invalidation
/// status.
async fn do_wait(
self,
wait_for_stall_timeout: impl Future<Output = ()>,
wait_for_stall_timeout_rest: impl Future<Output = ()>,
) -> (TrackedTransactionStatus<HeaderIdOf<C>>, Option<InvalidationStatus<HeaderIdOf<C>>>) {
// sometimes we want to wait for the rest of the stall timeout even if
// `wait_for_invalidation` has been "select"ed first => it is shared
let wait_for_invalidation = watch_transaction_status::<_, C, _>(
self.environment,
self.transaction_hash,
self.subscription,
);
futures::pin_mut!(wait_for_stall_timeout, wait_for_invalidation);
match futures::future::select(wait_for_stall_timeout, wait_for_invalidation).await {
Either::Left((_, _)) => {
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?self.transaction_hash,
"Transaction is considered lost after timeout (no status response from the node)"
);
(TrackedTransactionStatus::Lost, None)
},
Either::Right((invalidation_status, _)) => match invalidation_status {
InvalidationStatus::Finalized(at_block) =>
(TrackedTransactionStatus::Finalized(at_block), Some(invalidation_status)),
InvalidationStatus::Invalid =>
(TrackedTransactionStatus::Lost, Some(invalidation_status)),
InvalidationStatus::Lost => {
// wait for the rest of stall timeout - this way we'll be sure that the
// transaction is actually dead if it has been crafted properly
wait_for_stall_timeout_rest.await;
// if someone is still watching for our transaction, then we're reporting
// an error here (which is treated as "transaction lost")
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?self.transaction_hash,
"Transaction is considered lost after timeout"
);
(TrackedTransactionStatus::Lost, Some(invalidation_status))
},
},
}
}
}
#[async_trait]
impl<C: Chain, E: Environment<C>> relay_utils::TransactionTracker for TransactionTracker<C, E> {
type HeaderId = HeaderIdOf<C>;
async fn wait(self) -> TrackedTransactionStatus<HeaderIdOf<C>> {
let wait_for_stall_timeout = async_std::task::sleep(self.stall_timeout).shared();
let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone();
self.do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest).await.0
}
}
/// Transaction invalidation status.
///
/// Note that in places where the `TransactionTracker` is used, the finalization event will be
/// ignored - relay loops are detecting the mining/finalization using their own
/// techniques. That's why we're using `InvalidationStatus` here.
#[derive(Debug, PartialEq)]
enum InvalidationStatus<BlockId> {
/// Transaction has been included into block and finalized at given block.
Finalized(BlockId),
/// Transaction has been invalidated.
Invalid,
/// We have lost track of transaction status.
Lost,
}
/// Watch for transaction status until transaction is finalized or we lose track of its status.
async fn watch_transaction_status<
E: Environment<C>,
C: Chain,
S: Stream<Item = TransactionStatusOf<C>>,
>(
environment: E,
transaction_hash: HashOf<C>,
subscription: S,
) -> InvalidationStatus<HeaderIdOf<C>> {
futures::pin_mut!(subscription);
loop {
match subscription.next().await {
Some(TransactionStatusOf::<C>::Finalized((block_hash, _))) => {
// the only "successful" outcome of this method is when the block with transaction
// has been finalized
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
block=?block_hash,
"Transaction has been finalized"
);
let header_id = match environment.header_id_by_hash(block_hash).await {
Ok(header_id) => header_id,
Err(e) => {
tracing::error!(
target: "bridge",
error=?e,
node=%C::NAME,
transaction=?transaction_hash,
block=?block_hash,
"Failed to read header when watching for transaction",
);
// that's the best option we have here
return InvalidationStatus::Lost;
},
};
return InvalidationStatus::Finalized(header_id);
},
Some(TransactionStatusOf::<C>::Invalid) => {
// if node says that the transaction is invalid, there are still chances that
// it is not actually invalid - e.g. if the block where transaction has been
// revalidated is retracted and transaction (at some other node pool) becomes
// valid again on other fork. But let's assume that the chances of this event
// are almost zero - there's a lot of things that must happen for this to be the
// case.
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
"Transaction has been invalidated"
);
return InvalidationStatus::Invalid;
},
Some(TransactionStatusOf::<C>::Future) |
Some(TransactionStatusOf::<C>::Ready) |
Some(TransactionStatusOf::<C>::Broadcast(_)) => {
// nothing important (for us) has happened
},
Some(TransactionStatusOf::<C>::InBlock(block_hash)) => {
// TODO: read matching system event (ExtrinsicSuccess or ExtrinsicFailed), log it
// here and use it later (on finality) for reporting invalid transaction
// https://github.com/pezkuwichain/kurdistan-sdk/issues/79
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
block=?block_hash,
"Transaction has been included"
);
},
Some(TransactionStatusOf::<C>::Retracted(block_hash)) => {
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
block=?block_hash,
"Transaction has been retracted"
);
},
Some(TransactionStatusOf::<C>::FinalityTimeout(block_hash)) => {
// finality is lagging? let's wait a bit more and report a stall
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
block=?block_hash,
"Transaction has not been finalized for too long"
);
return InvalidationStatus::Lost;
},
Some(TransactionStatusOf::<C>::Usurped(new_transaction_hash)) => {
// this may be result of our transaction resubmitter work or some manual
// intervention. In both cases - let's start stall timeout, because the meaning
// of transaction may have changed
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
new_transaction=?new_transaction_hash,
"Transaction has been usurped"
);
return InvalidationStatus::Lost;
},
Some(TransactionStatusOf::<C>::Dropped) => {
// the transaction has been removed from the pool because of its limits. Let's wait
// a bit and report a stall
tracing::trace!(
target: "bridge",
node=%C::NAME,
transaction=?transaction_hash,
"Transaction has been dropped from the pool"
);
return InvalidationStatus::Lost;
},
None => {
// the status of transaction is unknown to us (the subscription has been closed?).
// Let's wait a bit and report a stall
return InvalidationStatus::Lost;
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{test_chain::TestChain, StreamDescription};
use futures::{FutureExt, SinkExt};
use pezsc_transaction_pool_api::TransactionStatus;
struct TestEnvironment(Result<HeaderIdOf<TestChain>, Error>);
#[async_trait]
impl Environment<TestChain> for TestEnvironment {
async fn header_id_by_hash(
&self,
_hash: HashOf<TestChain>,
) -> Result<HeaderIdOf<TestChain>, Error> {
self.0.as_ref().map_err(|_| Error::BridgePalletIsNotInitialized).cloned()
}
}
async fn on_transaction_status(
status: TransactionStatus<HashOf<TestChain>, HashOf<TestChain>>,
) -> Option<(
TrackedTransactionStatus<HeaderIdOf<TestChain>>,
InvalidationStatus<HeaderIdOf<TestChain>>,
)> {
let (mut sender, receiver) = futures::channel::mpsc::channel(1);
let tx_tracker = TransactionTracker::<TestChain, TestEnvironment>::new(
TestEnvironment(Ok(HeaderId(0, Default::default()))),
Duration::from_secs(0),
Default::default(),
Subscription::new_forwarded(
StreamDescription::new("test".into(), "test".into()),
receiver,
),
);
// we can't do `.now_or_never()` on `do_wait()` call, because `Subscription` has its own
// background thread, which may cause additional async task switches => let's leave some
// relatively small timeout here
let wait_for_stall_timeout = async_std::task::sleep(std::time::Duration::from_millis(100));
let wait_for_stall_timeout_rest = futures::future::ready(());
sender.send(Ok(status)).await.unwrap();
let (ts, is) =
tx_tracker.do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest).await;
is.map(|is| (ts, is))
}
#[async_std::test]
async fn returns_finalized_on_finalized() {
assert_eq!(
on_transaction_status(TransactionStatus::Finalized(Default::default())).await,
Some((
TrackedTransactionStatus::Finalized(Default::default()),
InvalidationStatus::Finalized(Default::default())
)),
);
}
#[async_std::test]
async fn returns_lost_on_finalized_and_environment_error() {
assert_eq!(
watch_transaction_status::<_, TestChain, _>(
TestEnvironment(Err(Error::BridgePalletIsNotInitialized)),
Default::default(),
futures::stream::iter([TransactionStatus::Finalized(Default::default())])
)
.now_or_never(),
Some(InvalidationStatus::Lost),
);
}
#[async_std::test]
async fn returns_invalid_on_invalid() {
assert_eq!(
on_transaction_status(TransactionStatus::Invalid).await,
Some((TrackedTransactionStatus::Lost, InvalidationStatus::Invalid)),
);
}
#[async_std::test]
async fn waits_on_future() {
assert_eq!(on_transaction_status(TransactionStatus::Future).await, None,);
}
#[async_std::test]
async fn waits_on_ready() {
assert_eq!(on_transaction_status(TransactionStatus::Ready).await, None,);
}
#[async_std::test]
async fn waits_on_broadcast() {
assert_eq!(
on_transaction_status(TransactionStatus::Broadcast(Default::default())).await,
None,
);
}
#[async_std::test]
async fn waits_on_in_block() {
assert_eq!(
on_transaction_status(TransactionStatus::InBlock(Default::default())).await,
None,
);
}
#[async_std::test]
async fn waits_on_retracted() {
assert_eq!(
on_transaction_status(TransactionStatus::Retracted(Default::default())).await,
None,
);
}
#[async_std::test]
async fn lost_on_finality_timeout() {
assert_eq!(
on_transaction_status(TransactionStatus::FinalityTimeout(Default::default())).await,
Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)),
);
}
#[async_std::test]
async fn lost_on_usurped() {
assert_eq!(
on_transaction_status(TransactionStatus::Usurped(Default::default())).await,
Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)),
);
}
#[async_std::test]
async fn lost_on_dropped() {
assert_eq!(
on_transaction_status(TransactionStatus::Dropped).await,
Some((TrackedTransactionStatus::Lost, InvalidationStatus::Lost)),
);
}
#[async_std::test]
async fn lost_on_subscription_error() {
assert_eq!(
watch_transaction_status::<_, TestChain, _>(
TestEnvironment(Ok(HeaderId(0, Default::default()))),
Default::default(),
futures::stream::iter([])
)
.now_or_never(),
Some(InvalidationStatus::Lost),
);
}
#[async_std::test]
async fn lost_on_timeout_when_waiting_for_invalidation_status() {
let (_sender, receiver) = futures::channel::mpsc::channel(1);
let tx_tracker = TransactionTracker::<TestChain, TestEnvironment>::new(
TestEnvironment(Ok(HeaderId(0, Default::default()))),
Duration::from_secs(0),
Default::default(),
Subscription::new_forwarded(
StreamDescription::new("test".into(), "test".into()),
receiver,
),
);
let wait_for_stall_timeout = futures::future::ready(()).shared();
let wait_for_stall_timeout_rest = wait_for_stall_timeout.clone();
let wait_result = tx_tracker
.do_wait(wait_for_stall_timeout, wait_for_stall_timeout_rest)
.now_or_never();
assert_eq!(wait_result, Some((TrackedTransactionStatus::Lost, None)));
}
}