diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs index 5ad95ad477..3083112316 100644 --- a/bridges/primitives/polkadot-core/src/lib.rs +++ b/bridges/primitives/polkadot-core/src/lib.rs @@ -233,6 +233,9 @@ pub type Balance = u128; pub type UncheckedExtrinsic = generic::UncheckedExtrinsic, Call, Signature, SignedExtensions>; +/// Account address, used by the Polkadot-like chain. +pub type Address = MultiAddress; + /// A type of the data encoded as part of the transaction. pub type SignedExtra = ( (), @@ -301,6 +304,18 @@ impl SignedExtensions { } } +impl SignedExtensions { + /// Return signer nonce, used to craft transaction. + pub fn nonce(&self) -> Nonce { + self.encode_payload.4.into() + } + + /// Return transaction tip. + pub fn tip(&self) -> Balance { + self.encode_payload.6.into() + } +} + impl sp_runtime::traits::SignedExtension for SignedExtensions where Call: parity_scale_codec::Codec + sp_std::fmt::Debug + Sync + Send + Clone + Eq + PartialEq, diff --git a/bridges/relays/bin-ethereum/src/rialto_client.rs b/bridges/relays/bin-ethereum/src/rialto_client.rs index b340c4d82a..35518f5e19 100644 --- a/bridges/relays/bin-ethereum/src/rialto_client.rs +++ b/bridges/relays/bin-ethereum/src/rialto_client.rs @@ -24,7 +24,7 @@ use codec::{Decode, Encode}; use headers_relay::sync_types::SubmittedHeaders; use relay_ethereum_client::types::HeaderId as EthereumHeaderId; use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams}; -use relay_substrate_client::{Client as SubstrateClient, TransactionSignScheme}; +use relay_substrate_client::{Client as SubstrateClient, TransactionSignScheme, UnsignedTransaction}; use relay_utils::HeaderId; use sp_core::{crypto::Pair, Bytes}; use std::{collections::VecDeque, sync::Arc}; @@ -163,8 +163,7 @@ impl SubmitEthereumHeaders for SubstrateClient { genesis_hash, ¶ms, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - instance.build_signed_header_call(headers), + UnsignedTransaction::new(instance.build_signed_header_call(headers), transaction_nonce), ) .encode(), ) @@ -266,8 +265,7 @@ impl SubmitEthereumExchangeTransactionProof for SubstrateClient { genesis_hash, ¶ms, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - instance.build_currency_exchange_call(proof), + UnsignedTransaction::new(instance.build_currency_exchange_call(proof), transaction_nonce), ) .encode(), ) diff --git a/bridges/relays/bin-substrate/Cargo.toml b/bridges/relays/bin-substrate/Cargo.toml index bec9bbab68..8689821351 100644 --- a/bridges/relays/bin-substrate/Cargo.toml +++ b/bridges/relays/bin-substrate/Cargo.toml @@ -13,6 +13,7 @@ futures = "0.3.12" hex = "0.4" log = "0.4.14" num-format = "0.4" +num-traits = "0.2" paste = "1.0" structopt = "0.3" strum = { version = "0.21.0", features = ["derive"] } diff --git a/bridges/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs b/bridges/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs index 75d7b49c40..4fa9d83e58 100644 --- a/bridges/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs +++ b/bridges/relays/bin-substrate/src/chains/millau_headers_to_rialto.rs @@ -22,7 +22,7 @@ use sp_core::{Bytes, Pair}; use bp_header_chain::justification::GrandpaJustification; use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader}; use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate}; /// Millau-to-Rialto finality sync pipeline. @@ -66,8 +66,7 @@ impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto { genesis_hash, &self.finality_pipeline.target_sign, era, - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); Bytes(transaction.encode()) diff --git a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs index 51eb4e961b..a4dedaed1e 100644 --- a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs +++ b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs @@ -28,7 +28,7 @@ use frame_support::weights::Weight; use messages_relay::message_lane::MessageLane; use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams}; use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use substrate_relay_helper::messages_lane::{ select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics, SubstrateMessageLane, @@ -89,8 +89,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto { genesis_hash, &self.message_lane.source_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", @@ -134,8 +133,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto { genesis_hash, &self.message_lane.target_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", @@ -274,13 +272,15 @@ pub(crate) async fn update_rialto_to_millau_conversion_rate( genesis_hash, &signer, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - millau_runtime::MessagesCall::update_pallet_parameter( - millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate( - sp_runtime::FixedU128::from_float(updated_rate), - ), - ) - .into(), + UnsignedTransaction::new( + millau_runtime::MessagesCall::update_pallet_parameter( + millau_runtime::rialto_messages::MillauToRialtoMessagesParameter::RialtoToMillauConversionRate( + sp_runtime::FixedU128::from_float(updated_rate), + ), + ) + .into(), + transaction_nonce, + ), ) .encode(), ) diff --git a/bridges/relays/bin-substrate/src/chains/mod.rs b/bridges/relays/bin-substrate/src/chains/mod.rs index 0d655698c5..6f8273a7fc 100644 --- a/bridges/relays/bin-substrate/src/chains/mod.rs +++ b/bridges/relays/bin-substrate/src/chains/mod.rs @@ -67,7 +67,7 @@ mod tests { use frame_support::dispatch::GetDispatchInfo; use relay_millau_client::Millau; use relay_rialto_client::Rialto; - use relay_substrate_client::TransactionSignScheme; + use relay_substrate_client::{TransactionSignScheme, UnsignedTransaction}; use sp_core::Pair; use sp_runtime::traits::{IdentifyAccount, Verify}; @@ -215,8 +215,7 @@ mod tests { Default::default(), &sp_keyring::AccountKeyring::Alice.pair(), relay_substrate_client::TransactionEra::immortal(), - 0, - rialto_call.clone(), + UnsignedTransaction::new(rialto_call.clone(), 0), ); let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len(); assert!( @@ -234,8 +233,7 @@ mod tests { Default::default(), &sp_keyring::AccountKeyring::Alice.pair(), relay_substrate_client::TransactionEra::immortal(), - 0, - millau_call.clone(), + UnsignedTransaction::new(millau_call.clone(), 0), ); let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len(); assert!( diff --git a/bridges/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs b/bridges/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs index 69b750c542..d2e0f5e663 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto_headers_to_millau.rs @@ -22,7 +22,7 @@ use sp_core::{Bytes, Pair}; use bp_header_chain::justification::GrandpaJustification; use relay_millau_client::{Millau, SigningParams as MillauSigningParams}; use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate}; /// Rialto-to-Millau finality sync pipeline. @@ -71,8 +71,7 @@ impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau { genesis_hash, &self.finality_pipeline.target_sign, era, - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); Bytes(transaction.encode()) diff --git a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs index 0ced49a0a3..2e91c99488 100644 --- a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs +++ b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs @@ -28,7 +28,7 @@ use frame_support::weights::Weight; use messages_relay::message_lane::MessageLane; use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams}; use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use substrate_relay_helper::messages_lane::{ select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics, SubstrateMessageLane, @@ -89,8 +89,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau { genesis_hash, &self.message_lane.source_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", @@ -134,8 +133,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau { genesis_hash, &self.message_lane.target_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", @@ -273,13 +271,15 @@ pub(crate) async fn update_millau_to_rialto_conversion_rate( genesis_hash, &signer, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - rialto_runtime::MessagesCall::update_pallet_parameter( - rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate( - sp_runtime::FixedU128::from_float(updated_rate), - ), - ) - .into(), + UnsignedTransaction::new( + rialto_runtime::MessagesCall::update_pallet_parameter( + rialto_runtime::millau_messages::RialtoToMillauMessagesParameter::MillauToRialtoConversionRate( + sp_runtime::FixedU128::from_float(updated_rate), + ), + ) + .into(), + transaction_nonce, + ), ) .encode(), ) diff --git a/bridges/relays/bin-substrate/src/chains/rococo_headers_to_wococo.rs b/bridges/relays/bin-substrate/src/chains/rococo_headers_to_wococo.rs index 3cacce9a48..32eb988c89 100644 --- a/bridges/relays/bin-substrate/src/chains/rococo_headers_to_wococo.rs +++ b/bridges/relays/bin-substrate/src/chains/rococo_headers_to_wococo.rs @@ -21,7 +21,7 @@ use sp_core::{Bytes, Pair}; use bp_header_chain::justification::GrandpaJustification; use relay_rococo_client::{Rococo, SyncHeader as RococoSyncHeader}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo}; use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate}; @@ -90,8 +90,7 @@ impl SubstrateFinalitySyncPipeline for RococoFinalityToWococo { genesis_hash, &self.finality_pipeline.target_sign, era, - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); Bytes(transaction.encode()) diff --git a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs index 51f89c0dbe..78ed93372d 100644 --- a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs +++ b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs @@ -26,7 +26,7 @@ use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; use frame_support::weights::Weight; use messages_relay::message_lane::MessageLane; use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use relay_wococo_client::{HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo}; use substrate_relay_helper::messages_lane::{ @@ -91,8 +91,7 @@ impl SubstrateMessageLane for RococoMessagesToWococo { genesis_hash, &self.message_lane.source_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", @@ -136,8 +135,7 @@ impl SubstrateMessageLane for RococoMessagesToWococo { genesis_hash, &self.message_lane.target_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", diff --git a/bridges/relays/bin-substrate/src/chains/westend_headers_to_millau.rs b/bridges/relays/bin-substrate/src/chains/westend_headers_to_millau.rs index 20464b99bf..cb81e20699 100644 --- a/bridges/relays/bin-substrate/src/chains/westend_headers_to_millau.rs +++ b/bridges/relays/bin-substrate/src/chains/westend_headers_to_millau.rs @@ -21,7 +21,7 @@ use sp_core::{Bytes, Pair}; use bp_header_chain::justification::GrandpaJustification; use relay_millau_client::{Millau, SigningParams as MillauSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend}; use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate}; @@ -79,8 +79,7 @@ impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau { genesis_hash, &self.finality_pipeline.target_sign, era, - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); Bytes(transaction.encode()) diff --git a/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs b/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs index 4bb5b15fe0..883e54c983 100644 --- a/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs +++ b/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs @@ -21,7 +21,7 @@ use sp_core::{Bytes, Pair}; use bp_header_chain::justification::GrandpaJustification; use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use relay_wococo_client::{SyncHeader as WococoSyncHeader, Wococo}; use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate}; @@ -95,8 +95,7 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo { genesis_hash, &self.finality_pipeline.target_sign, era, - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); Bytes(transaction.encode()) diff --git a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs index 9bc13dec14..f02e2da1e6 100644 --- a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs +++ b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs @@ -26,7 +26,7 @@ use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof; use frame_support::weights::Weight; use messages_relay::message_lane::MessageLane; use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams}; -use relay_substrate_client::{Chain, Client, TransactionSignScheme}; +use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction}; use relay_utils::metrics::MetricsParams; use relay_wococo_client::{HeaderId as WococoHeaderId, SigningParams as WococoSigningParams, Wococo}; use substrate_relay_helper::messages_lane::{ @@ -90,8 +90,7 @@ impl SubstrateMessageLane for WococoMessagesToRococo { genesis_hash, &self.message_lane.source_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", @@ -135,8 +134,7 @@ impl SubstrateMessageLane for WococoMessagesToRococo { genesis_hash, &self.message_lane.target_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - call, + UnsignedTransaction::new(call, transaction_nonce), ); log::trace!( target: "bridge", diff --git a/bridges/relays/bin-substrate/src/cli/init_bridge.rs b/bridges/relays/bin-substrate/src/cli/init_bridge.rs index 76bc5aa273..81663ed429 100644 --- a/bridges/relays/bin-substrate/src/cli/init_bridge.rs +++ b/bridges/relays/bin-substrate/src/cli/init_bridge.rs @@ -18,7 +18,7 @@ use crate::cli::{SourceConnectionParams, TargetConnectionParams, TargetSigningPa use bp_header_chain::InitializationData; use bp_runtime::Chain as ChainBase; use codec::Encode; -use relay_substrate_client::{Chain, TransactionSignScheme}; +use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction}; use sp_core::{Bytes, Pair}; use structopt::StructOpt; use strum::{EnumString, EnumVariantNames, VariantNames}; @@ -151,8 +151,7 @@ impl InitBridge { *target_client.genesis_hash(), &target_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - encode_init_bridge(initialization_data), + UnsignedTransaction::new(encode_init_bridge(initialization_data), transaction_nonce), ) .encode(), ) diff --git a/bridges/relays/bin-substrate/src/cli/mod.rs b/bridges/relays/bin-substrate/src/cli/mod.rs index c02f6b5130..4831373eae 100644 --- a/bridges/relays/bin-substrate/src/cli/mod.rs +++ b/bridges/relays/bin-substrate/src/cli/mod.rs @@ -35,6 +35,7 @@ mod init_bridge; mod relay_headers; mod relay_headers_and_messages; mod relay_messages; +mod resubmit_transactions; /// Parse relay CLI args. pub fn parse_args() -> Command { @@ -86,6 +87,8 @@ pub enum Command { EstimateFee(estimate_fee::EstimateFee), /// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain. DeriveAccount(derive_account::DeriveAccount), + /// Resubmit transactions with increased tip if they are stalled. + ResubmitTransactions(resubmit_transactions::ResubmitTransactions), } impl Command { @@ -116,6 +119,7 @@ impl Command { Self::EncodeMessage(arg) => arg.run().await?, Self::EstimateFee(arg) => arg.run().await?, Self::DeriveAccount(arg) => arg.run().await?, + Self::ResubmitTransactions(arg) => arg.run().await?, } Ok(()) } @@ -360,7 +364,7 @@ macro_rules! declare_chain_options { ($chain:ident, $chain_prefix:ident) => { paste::item! { #[doc = $chain " connection params."] - #[derive(StructOpt, Debug, PartialEq, Eq)] + #[derive(StructOpt, Debug, PartialEq, Eq, Clone)] pub struct [<$chain ConnectionParams>] { #[doc = "Connect to " $chain " node at given host."] #[structopt(long, default_value = "127.0.0.1")] diff --git a/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs b/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs new file mode 100644 index 0000000000..ee9801b3fd --- /dev/null +++ b/bridges/relays/bin-substrate/src/cli/resubmit_transactions.rs @@ -0,0 +1,364 @@ +// 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 . + +use crate::cli::{TargetConnectionParams, TargetSigningParams}; + +use codec::{Decode, Encode}; +use num_traits::{One, Zero}; +use relay_substrate_client::{BlockWithJustification, Chain, Client, Error as SubstrateError, TransactionSignScheme}; +use relay_utils::FailedClient; +use sp_core::Bytes; +use sp_runtime::{ + traits::{Hash, Header as HeaderT}, + transaction_validity::TransactionPriority, +}; +use structopt::StructOpt; +use strum::{EnumString, EnumVariantNames, VariantNames}; + +/// Start resubmit transactions process. +#[derive(StructOpt)] +pub struct ResubmitTransactions { + /// A bridge instance to relay headers for. + #[structopt(possible_values = RelayChain::VARIANTS, case_insensitive = true)] + chain: RelayChain, + #[structopt(flatten)] + target: TargetConnectionParams, + #[structopt(flatten)] + target_sign: TargetSigningParams, +} + +/// Chain, which transactions we're going to track && resubmit. +#[derive(Debug, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab_case")] +pub enum RelayChain { + Millau, +} + +macro_rules! select_bridge { + ($bridge: expr, $generic: tt) => { + match $bridge { + RelayChain::Millau => { + type Target = relay_millau_client::Millau; + type TargetSign = relay_millau_client::Millau; + + const TIP_STEP: bp_millau::Balance = 1_000_000; + const TIP_LIMIT: bp_millau::Balance = 1_000_000_000; + + const STALLED_BLOCKS: bp_millau::BlockNumber = 5; + + $generic + } + } + }; +} + +impl ResubmitTransactions { + /// Run the command. + pub async fn run(self) -> anyhow::Result<()> { + select_bridge!(self.chain, { + let relay_loop_name = format!("ResubmitTransactions{}", Target::NAME); + let client = self.target.to_client::().await?; + let key_pair = self.target_sign.to_keypair::()?; + + relay_utils::relay_loop((), client) + .run(relay_loop_name, move |_, client, _| { + run_until_connection_lost::( + client, + key_pair.clone(), + Context { + transaction: None, + stalled_for: Zero::zero(), + stalled_for_limit: STALLED_BLOCKS, + tip_step: TIP_STEP, + tip_limit: TIP_LIMIT, + }, + ) + }) + .await + }) + } +} + +#[derive(Debug, Default)] +struct Context { + /// Hash of the (potentially) stalled transaction. + transaction: Option, + /// This transaction is in pool for `stalled_for` wakeup intervals. + stalled_for: C::BlockNumber, + /// When `stalled_for` reaching this limit, transaction is considered stalled. + stalled_for_limit: C::BlockNumber, + /// Tip step interval. + tip_step: C::Balance, + /// Maximal tip. + tip_limit: C::Balance, +} + +impl Context { + /// Return true if transaction has stalled. + fn is_stalled(&self) -> bool { + self.stalled_for >= self.stalled_for_limit + } + + /// Forget stalled transaction. + fn clear(mut self) -> Self { + self.transaction = None; + self.stalled_for = Zero::zero(); + self + } + + /// Notice transaction from the transaction pool. + fn notice_transaction(mut self, transaction: C::Hash) -> Self { + if self.transaction == Some(transaction) { + self.stalled_for += One::one(); + } else { + self.transaction = Some(transaction); + self.stalled_for = One::one(); + } + self + } +} + +/// Run resubmit transactions loop. +async fn run_until_connection_lost>( + client: Client, + key_pair: S::AccountKeyPair, + mut context: Context, +) -> Result<(), FailedClient> { + loop { + async_std::task::sleep(C::AVERAGE_BLOCK_INTERVAL).await; + + let result = run_loop_iteration::(client.clone(), key_pair.clone(), context).await; + context = match result { + Ok(context) => context, + Err(error) => { + log::error!( + target: "bridge", + "Resubmit {} transactions loop has failed with error: {:?}", + C::NAME, + error, + ); + return Err(FailedClient::Target); + } + }; + } +} + +/// Run single loop iteration. +async fn run_loop_iteration>( + client: Client, + key_pair: S::AccountKeyPair, + context: Context, +) -> Result, SubstrateError> { + let original_transaction = match lookup_signer_transaction::(&client, &key_pair).await? { + Some(original_transaction) => original_transaction, + None => { + log::trace!(target: "bridge", "No {} transactions from required signer in the txpool", C::NAME); + return Ok(context); + } + }; + let original_transaction_hash = C::Hasher::hash(&original_transaction.encode()); + let context = context.notice_transaction(original_transaction_hash); + + if !context.is_stalled() { + log::trace!( + target: "bridge", + "{} transaction {:?} is not yet stalled ({:?}/{:?})", + C::NAME, + context.transaction, + context.stalled_for, + context.stalled_for_limit, + ); + return Ok(context); + } + + let (best_block, target_priority) = match read_previous_best_priority::(&client).await? { + Some((best_block, target_priority)) => (best_block, target_priority), + None => { + log::trace!(target: "bridge", "Failed to read priority of best {} transaction in its best block", C::NAME); + return Ok(context); + } + }; + + let (is_updated, updated_transaction) = select_transaction_tip::( + &client, + &key_pair, + best_block, + original_transaction, + context.tip_step, + context.tip_limit, + target_priority, + ) + .await?; + + if !is_updated { + log::trace!(target: "bridge", "{} transaction tip can not be updated. Reached limit?", C::NAME); + return Ok(context); + } + + let updated_transaction = updated_transaction.encode(); + let updated_transaction_hash = C::Hasher::hash(&updated_transaction); + client.submit_unsigned_extrinsic(Bytes(updated_transaction)).await?; + + log::info!( + target: "bridge", + "Replaced {} transaction {} with {} in txpool", + C::NAME, + original_transaction_hash, + updated_transaction_hash, + ); + + Ok(context.clear()) +} + +/// Search transaction pool for transaction, signed by given key pair. +async fn lookup_signer_transaction>( + client: &Client, + key_pair: &S::AccountKeyPair, +) -> Result, SubstrateError> { + let pending_transactions = client.pending_extrinsics().await?; + for pending_transaction in pending_transactions { + let pending_transaction = S::SignedTransaction::decode(&mut &pending_transaction.0[..]) + .map_err(SubstrateError::ResponseParseFailed)?; + if !S::is_signed_by(key_pair, &pending_transaction) { + continue; + } + + return Ok(Some(pending_transaction)); + } + + Ok(None) +} + +/// Read priority of best signed transaction of previous block. +async fn read_previous_best_priority>( + client: &Client, +) -> Result, SubstrateError> { + let best_header = client.best_header().await?; + let best_header_hash = best_header.hash(); + let best_block = client.get_block(Some(best_header_hash)).await?; + let best_transaction = best_block + .extrinsics() + .iter() + .filter_map(|xt| S::SignedTransaction::decode(&mut &xt[..]).ok()) + .find(|xt| S::is_signed(xt)); + match best_transaction { + Some(best_transaction) => Ok(Some(( + best_header_hash, + client + .validate_transaction(*best_header.parent_hash(), best_transaction) + .await?? + .priority, + ))), + None => Ok(None), + } +} + +/// Try to find appropriate tip for transaction so that its priority is larger than given. +async fn select_transaction_tip>( + client: &Client, + key_pair: &S::AccountKeyPair, + at_block: C::Hash, + tx: S::SignedTransaction, + tip_step: C::Balance, + tip_limit: C::Balance, + target_priority: TransactionPriority, +) -> Result<(bool, S::SignedTransaction), SubstrateError> { + let stx = format!("{:?}", tx); + let mut current_priority = client.validate_transaction(at_block, tx.clone()).await??.priority; + let mut unsigned_tx = S::parse_transaction(tx) + .ok_or_else(|| SubstrateError::Custom(format!("Failed to parse {} transaction {}", C::NAME, stx,)))?; + let old_tip = unsigned_tx.tip; + + while current_priority < target_priority { + let next_tip = unsigned_tx.tip + tip_step; + if next_tip > tip_limit { + break; + } + + log::trace!( + target: "bridge", + "{} transaction priority with tip={:?}: {}. Target priority: {}", + C::NAME, + unsigned_tx.tip, + current_priority, + target_priority, + ); + + unsigned_tx.tip = next_tip; + current_priority = client + .validate_transaction( + at_block, + S::sign_transaction( + *client.genesis_hash(), + key_pair, + relay_substrate_client::TransactionEra::immortal(), + unsigned_tx.clone(), + ), + ) + .await?? + .priority; + } + + log::debug!( + target: "bridge", + "{} transaction tip has changed from {:?} to {:?}", + C::NAME, + old_tip, + unsigned_tx.tip, + ); + + Ok(( + old_tip != unsigned_tx.tip, + S::sign_transaction( + *client.genesis_hash(), + key_pair, + relay_substrate_client::TransactionEra::immortal(), + unsigned_tx, + ), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use relay_rialto_client::Rialto; + + #[test] + fn context_works() { + let mut context: Context = Context { + transaction: None, + stalled_for: Zero::zero(), + stalled_for_limit: 3, + tip_step: 100, + tip_limit: 1000, + }; + + // when transaction is noticed 2/3 times, it isn't stalled + context = context.notice_transaction(Default::default()); + assert!(!context.is_stalled()); + context = context.notice_transaction(Default::default()); + assert!(!context.is_stalled()); + + // when transaction is noticed for 3rd time in a row, it is considered stalled + context = context.notice_transaction(Default::default()); + assert!(context.is_stalled()); + + // and after we resubmit it, we forget previous transaction + context = context.clear(); + assert_eq!(context.transaction, None); + assert_eq!(context.stalled_for, 0); + } +} diff --git a/bridges/relays/bin-substrate/src/cli/send_message.rs b/bridges/relays/bin-substrate/src/cli/send_message.rs index aa9a597059..ad02e126b0 100644 --- a/bridges/relays/bin-substrate/src/cli/send_message.rs +++ b/bridges/relays/bin-substrate/src/cli/send_message.rs @@ -24,7 +24,7 @@ use crate::cli::{ use bp_message_dispatch::{CallOrigin, MessagePayload}; use codec::Encode; use frame_support::weights::Weight; -use relay_substrate_client::{Chain, TransactionSignScheme}; +use relay_substrate_client::{Chain, TransactionSignScheme, UnsignedTransaction}; use sp_core::{Bytes, Pair}; use sp_runtime::{traits::IdentifyAccount, AccountId32, MultiSignature, MultiSigner}; use std::fmt::Debug; @@ -183,8 +183,7 @@ impl SendMessage { source_genesis_hash, &source_sign, relay_substrate_client::TransactionEra::immortal(), - 0, - send_message_call.clone(), + UnsignedTransaction::new(send_message_call.clone(), 0), ) .encode(), )) @@ -195,8 +194,7 @@ impl SendMessage { source_genesis_hash, &source_sign, relay_substrate_client::TransactionEra::immortal(), - transaction_nonce, - send_message_call, + UnsignedTransaction::new(send_message_call, transaction_nonce), ) .encode(); diff --git a/bridges/relays/client-millau/src/lib.rs b/bridges/relays/client-millau/src/lib.rs index 36430dd83d..eaf677634e 100644 --- a/bridges/relays/client-millau/src/lib.rs +++ b/bridges/relays/client-millau/src/lib.rs @@ -16,8 +16,10 @@ //! Types used to connect to the Millau-Substrate chain. -use codec::Encode; -use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme}; +use codec::{Compact, Decode, Encode}; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme, UnsignedTransaction, +}; use sp_core::{storage::StorageKey, Pair}; use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; use std::time::Duration; @@ -67,20 +69,19 @@ impl TransactionSignScheme for Millau { fn sign_transaction( genesis_hash: ::Hash, signer: &Self::AccountKeyPair, - era: relay_substrate_client::TransactionEraOf, - signer_nonce: ::Index, - call: ::Call, + era: TransactionEraOf, + unsigned: UnsignedTransaction, ) -> Self::SignedTransaction { let raw_payload = SignedPayload::from_raw( - call, + unsigned.call, ( frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(era.frame_era()), - frame_system::CheckNonce::::from(signer_nonce), + frame_system::CheckNonce::::from(unsigned.nonce), frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0), + pallet_transaction_payment::ChargeTransactionPayment::::from(unsigned.tip), ), ( millau_runtime::VERSION.spec_version, @@ -98,6 +99,30 @@ impl TransactionSignScheme for Millau { millau_runtime::UncheckedExtrinsic::new_signed(call, signer.into_account(), signature.into(), extra) } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| *address == millau_runtime::Address::from(*signer.public().as_array_ref())) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some(UnsignedTransaction { + call: tx.function, + nonce: Compact::<::Index>::decode(&mut &extra.4.encode()[..]) + .ok()? + .into(), + tip: Compact::<::Balance>::decode(&mut &extra.6.encode()[..]) + .ok()? + .into(), + }) + } } /// Millau signing params. diff --git a/bridges/relays/client-rialto/src/lib.rs b/bridges/relays/client-rialto/src/lib.rs index 8024ab1fdc..459ea5bc4e 100644 --- a/bridges/relays/client-rialto/src/lib.rs +++ b/bridges/relays/client-rialto/src/lib.rs @@ -16,8 +16,10 @@ //! Types used to connect to the Rialto-Substrate chain. -use codec::Encode; -use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme}; +use codec::{Compact, Decode, Encode}; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme, UnsignedTransaction, +}; use sp_core::{storage::StorageKey, Pair}; use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; use std::time::Duration; @@ -67,20 +69,19 @@ impl TransactionSignScheme for Rialto { fn sign_transaction( genesis_hash: ::Hash, signer: &Self::AccountKeyPair, - era: relay_substrate_client::TransactionEraOf, - signer_nonce: ::Index, - call: ::Call, + era: TransactionEraOf, + unsigned: UnsignedTransaction, ) -> Self::SignedTransaction { let raw_payload = SignedPayload::from_raw( - call, + unsigned.call, ( frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(era.frame_era()), - frame_system::CheckNonce::::from(signer_nonce), + frame_system::CheckNonce::::from(unsigned.nonce), frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0), + pallet_transaction_payment::ChargeTransactionPayment::::from(unsigned.tip), ), ( rialto_runtime::VERSION.spec_version, @@ -98,6 +99,30 @@ impl TransactionSignScheme for Rialto { rialto_runtime::UncheckedExtrinsic::new_signed(call, signer.into_account(), signature.into(), extra) } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| *address == rialto_runtime::Address::from(*signer.public().as_array_ref())) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some(UnsignedTransaction { + call: tx.function, + nonce: Compact::<::Index>::decode(&mut &extra.4.encode()[..]) + .ok()? + .into(), + tip: Compact::<::Balance>::decode(&mut &extra.6.encode()[..]) + .ok()? + .into(), + }) + } } /// Rialto signing params. diff --git a/bridges/relays/client-rococo/src/lib.rs b/bridges/relays/client-rococo/src/lib.rs index c419610dad..5694d183d7 100644 --- a/bridges/relays/client-rococo/src/lib.rs +++ b/bridges/relays/client-rococo/src/lib.rs @@ -17,7 +17,9 @@ //! Types used to connect to the Rococo-Substrate chain. use codec::Encode; -use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme}; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme, UnsignedTransaction, +}; use sp_core::{storage::StorageKey, Pair}; use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; use std::time::Duration; @@ -69,13 +71,12 @@ impl TransactionSignScheme for Rococo { fn sign_transaction( genesis_hash: ::Hash, signer: &Self::AccountKeyPair, - era: relay_substrate_client::TransactionEraOf, - signer_nonce: ::Index, - call: ::Call, + era: TransactionEraOf, + unsigned: UnsignedTransaction, ) -> Self::SignedTransaction { let raw_payload = SignedPayload::new( - call, - bp_rococo::SignedExtensions::new(bp_rococo::VERSION, era, genesis_hash, signer_nonce, 0), + unsigned.call, + bp_rococo::SignedExtensions::new(bp_rococo::VERSION, era, genesis_hash, unsigned.nonce, unsigned.tip), ) .expect("SignedExtension never fails."); @@ -90,6 +91,26 @@ impl TransactionSignScheme for Rococo { extra, ) } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| *address == bp_rococo::AccountId::from(*signer.public().as_array_ref()).into()) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some(UnsignedTransaction { + call: tx.function, + nonce: extra.nonce(), + tip: extra.tip(), + }) + } } /// Rococo signing params. diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs index 81397e2c4a..627c01eb0e 100644 --- a/bridges/relays/client-substrate/src/chain.rs +++ b/bridges/relays/client-substrate/src/chain.rs @@ -14,7 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use bp_runtime::Chain as ChainBase; +use bp_runtime::{Chain as ChainBase, TransactionEraOf}; +use codec::{Codec, Encode}; use frame_support::{weights::WeightToFeePolynomial, Parameter}; use jsonrpsee_ws_client::{DeserializeOwned, Serialize}; use num_traits::{Bounded, CheckedSub, SaturatingAdd, Zero}; @@ -58,7 +59,7 @@ pub trait Chain: ChainBase + Clone { /// Block type. type SignedBlock: Member + Serialize + DeserializeOwned + BlockWithJustification; /// The aggregated `Call` type. - type Call: Dispatchable + Debug; + type Call: Clone + Dispatchable + Debug; /// Balance of an account in native tokens. /// /// The chain may support multiple tokens, but this particular type is for token that is used @@ -96,14 +97,47 @@ pub trait ChainWithBalances: Chain { fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey; } +/// SCALE-encoded extrinsic. +pub type EncodedExtrinsic = Vec; + /// Block with justification. pub trait BlockWithJustification
{ /// Return block header. fn header(&self) -> Header; + /// Return encoded block extrinsics. + fn extrinsics(&self) -> Vec; /// Return block justification, if known. fn justification(&self) -> Option<&EncodedJustification>; } +/// Transaction before it is signed. +#[derive(Clone, Debug)] +pub struct UnsignedTransaction { + /// Runtime call of this transaction. + pub call: C::Call, + /// Transaction nonce. + pub nonce: C::Index, + /// Tip included into transaction. + pub tip: C::Balance, +} + +impl UnsignedTransaction { + /// Create new unsigned transaction with given call, nonce and zero tip. + pub fn new(call: C::Call, nonce: C::Index) -> Self { + Self { + call, + nonce, + tip: Zero::zero(), + } + } + + /// Set transaction tip. + pub fn tip(mut self, tip: C::Balance) -> Self { + self.tip = tip; + self + } +} + /// Substrate-based chain transactions signing scheme. pub trait TransactionSignScheme { /// Chain that this scheme is to be used. @@ -111,16 +145,26 @@ pub trait TransactionSignScheme { /// Type of key pairs used to sign transactions. type AccountKeyPair: Pair; /// Signed transaction. - type SignedTransaction; + type SignedTransaction: Clone + Debug + Codec + Send + 'static; /// Create transaction for given runtime call, signed by given account. fn sign_transaction( genesis_hash: ::Hash, signer: &Self::AccountKeyPair, - era: bp_runtime::TransactionEraOf, - signer_nonce: ::Index, - call: ::Call, + era: TransactionEraOf, + unsigned: UnsignedTransaction, ) -> Self::SignedTransaction; + + /// Returns true if transaction is signed. + fn is_signed(tx: &Self::SignedTransaction) -> bool; + + /// Returns true if transaction is signed by given signer. + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool; + + /// Parse signed transaction into its unsigned part. + /// + /// Returns `None` if signed transaction has unsupported format. + fn parse_transaction(tx: Self::SignedTransaction) -> Option>; } impl BlockWithJustification for SignedBlock { @@ -128,6 +172,10 @@ impl BlockWithJustification for SignedBlock self.block.header().clone() } + fn extrinsics(&self) -> Vec { + self.block.extrinsics().iter().map(Encode::encode).collect() + } + fn justification(&self) -> Option<&EncodedJustification> { self.justifications .as_ref() diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs index 9fb778651b..bd239a5a5f 100644 --- a/bridges/relays/client-substrate/src/client.rs +++ b/bridges/relays/client-substrate/src/client.rs @@ -21,7 +21,8 @@ use crate::rpc::Substrate; use crate::{ConnectionParams, Error, HeaderIdOf, Result}; use async_std::sync::{Arc, Mutex}; -use codec::Decode; +use async_trait::async_trait; +use codec::{Decode, Encode}; use frame_system::AccountInfo; use futures::{SinkExt, StreamExt}; use jsonrpsee_ws_client::{traits::SubscriptionClient, v2::params::JsonRpcParams, DeserializeOwned}; @@ -31,12 +32,16 @@ use pallet_balances::AccountData; use pallet_transaction_payment::InclusionFee; use relay_utils::{relay_loop::RECONNECT_DELAY, HeaderId}; use sp_core::{storage::StorageKey, Bytes}; -use sp_runtime::traits::Header as HeaderT; +use sp_runtime::{ + traits::Header as HeaderT, + transaction_validity::{TransactionSource, TransactionValidity}, +}; use sp_trie::StorageProof; use sp_version::RuntimeVersion; use std::{convert::TryFrom, future::Future}; const SUB_API_GRANDPA_AUTHORITIES: &str = "GrandpaApi_grandpa_authorities"; +const SUB_API_TXPOOL_VALIDATE_TRANSACTION: &str = "TaggedTransactionQueue_validate_transaction"; const MAX_SUBSCRIPTION_CAPACITY: usize = 4096; /// Opaque justifications subscription type. @@ -63,6 +68,18 @@ pub struct Client { submit_signed_extrinsic_lock: Arc>, } +#[async_trait] +impl relay_utils::relay_loop::Client for Client { + type Error = Error; + + async fn reconnect(&mut self) -> Result<()> { + let (tokio, client) = Self::build_client(self.params.clone()).await?; + self.tokio = tokio; + self.client = client; + Ok(()) + } +} + impl Clone for Client { fn clone(&self) -> Self { Client { @@ -125,14 +142,6 @@ impl Client { }) } - /// Reopen client connection. - pub async fn reconnect(&mut self) -> Result<()> { - let (tokio, client) = Self::build_client(self.params.clone()).await?; - self.tokio = tokio; - self.client = client; - Ok(()) - } - /// Build client to use in connection. async fn build_client(params: ConnectionParams) -> Result<(Arc, Arc)> { let tokio = tokio::runtime::Runtime::new()?; @@ -309,6 +318,33 @@ impl Client { .await } + /// Returns pending extrinsics from transaction pool. + pub async fn pending_extrinsics(&self) -> Result> { + self.jsonrpsee_execute( + move |client| async move { Ok(Substrate::::author_pending_extrinsics(&*client).await?) }, + ) + .await + } + + /// Validate transaction at given block state. + pub async fn validate_transaction( + &self, + at_block: C::Hash, + transaction: SignedTransaction, + ) -> Result { + self.jsonrpsee_execute(move |client| async move { + let call = SUB_API_TXPOOL_VALIDATE_TRANSACTION.to_string(); + let data = Bytes((TransactionSource::External, transaction, at_block).encode()); + + let encoded_response = Substrate::::state_call(&*client, call, data, Some(at_block)).await?; + let validity = + TransactionValidity::decode(&mut &encoded_response.0[..]).map_err(Error::ResponseParseFailed)?; + + Ok(validity) + }) + .await + } + /// Estimate fee that will be spent on given extrinsic. pub async fn estimate_extrinsic_fee(&self, transaction: Bytes) -> Result> { self.jsonrpsee_execute(move |client| async move { diff --git a/bridges/relays/client-substrate/src/error.rs b/bridges/relays/client-substrate/src/error.rs index f06079ef5f..187b4a1e63 100644 --- a/bridges/relays/client-substrate/src/error.rs +++ b/bridges/relays/client-substrate/src/error.rs @@ -19,6 +19,7 @@ use jsonrpsee_ws_client::Error as RpcError; use relay_utils::MaybeConnectionError; use sc_rpc_api::system::Health; +use sp_runtime::transaction_validity::TransactionValidityError; /// Result type used by Substrate client. pub type Result = std::result::Result; @@ -44,6 +45,8 @@ pub enum Error { ClientNotSynced(Health), /// An error has happened when we have tried to parse storage proof. StorageProofError(bp_runtime::StorageProofError), + /// The Substrate transaction is invalid. + TransactionInvalid(TransactionValidityError), /// Custom logic error. Custom(String), } @@ -59,6 +62,7 @@ impl std::error::Error for Error { Self::MissingMandatoryCodeEntry => None, Self::ClientNotSynced(_) => None, Self::StorageProofError(_) => None, + Self::TransactionInvalid(_) => None, Self::Custom(_) => None, } } @@ -82,6 +86,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: TransactionValidityError) -> Self { + Error::TransactionInvalid(error) + } +} + impl MaybeConnectionError for Error { fn is_connection_error(&self) -> bool { matches!( @@ -105,6 +115,7 @@ impl std::fmt::Display for Error { Self::MissingMandatoryCodeEntry => "Mandatory :code: entry is missing from runtime storage".into(), Self::StorageProofError(e) => format!("Error when parsing storage proof: {:?}", e), Self::ClientNotSynced(health) => format!("Substrate client is not synced: {}", health), + Self::TransactionInvalid(e) => format!("Substrate transaction is invalid: {:?}", e), Self::Custom(e) => e.clone(), }; diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs index be1835df32..b607b68f8b 100644 --- a/bridges/relays/client-substrate/src/lib.rs +++ b/bridges/relays/client-substrate/src/lib.rs @@ -32,7 +32,8 @@ pub mod metrics; use std::time::Duration; pub use crate::chain::{ - BalanceOf, BlockWithJustification, Chain, ChainWithBalances, IndexOf, TransactionSignScheme, WeightToFeeOf, + BalanceOf, BlockWithJustification, Chain, ChainWithBalances, IndexOf, TransactionSignScheme, UnsignedTransaction, + WeightToFeeOf, }; pub use crate::client::{Client, JustificationsSubscription, OpaqueGrandpaAuthoritiesSet}; pub use crate::error::{Error, Result}; diff --git a/bridges/relays/client-substrate/src/rpc.rs b/bridges/relays/client-substrate/src/rpc.rs index ddc8c92f77..61d2562672 100644 --- a/bridges/relays/client-substrate/src/rpc.rs +++ b/bridges/relays/client-substrate/src/rpc.rs @@ -43,6 +43,8 @@ jsonrpsee_proc_macros::rpc_client_api! { fn system_account_next_index(account_id: C::AccountId) -> C::Index; #[rpc(method = "author_submitExtrinsic", positional_params)] fn author_submit_extrinsic(extrinsic: Bytes) -> C::Hash; + #[rpc(method = "author_pendingExtrinsics", positional_params)] + fn author_pending_extrinsics() -> Vec; #[rpc(method = "state_call", positional_params)] fn state_call(method: String, data: Bytes, at_block: Option) -> Bytes; #[rpc(method = "state_getStorage", positional_params)] diff --git a/bridges/relays/client-westend/src/lib.rs b/bridges/relays/client-westend/src/lib.rs index b33be7421c..726a699e3c 100644 --- a/bridges/relays/client-westend/src/lib.rs +++ b/bridges/relays/client-westend/src/lib.rs @@ -16,10 +16,8 @@ //! Types used to connect to the Westend chain. -use codec::Encode; -use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme}; -use sp_core::{storage::StorageKey, Pair}; -use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; +use relay_substrate_client::{Chain, ChainBase, ChainWithBalances}; +use sp_core::storage::StorageKey; use std::time::Duration; /// Westend header id. @@ -58,37 +56,3 @@ impl ChainWithBalances for Westend { StorageKey(bp_westend::account_info_storage_key(account_id)) } } - -impl TransactionSignScheme for Westend { - type Chain = Westend; - type AccountKeyPair = sp_core::sr25519::Pair; - type SignedTransaction = bp_westend::UncheckedExtrinsic; - - fn sign_transaction( - genesis_hash: ::Hash, - signer: &Self::AccountKeyPair, - era: relay_substrate_client::TransactionEraOf, - signer_nonce: ::Index, - call: ::Call, - ) -> Self::SignedTransaction { - let raw_payload = SignedPayload::new( - call, - bp_westend::SignedExtensions::new(bp_westend::VERSION, era, genesis_hash, signer_nonce, 0), - ) - .expect("SignedExtension never fails."); - - let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); - let signer: sp_runtime::MultiSigner = signer.public().into(); - let (call, extra, _) = raw_payload.deconstruct(); - - bp_westend::UncheckedExtrinsic::new_signed( - call, - sp_runtime::MultiAddress::Id(signer.into_account()), - signature.into(), - extra, - ) - } -} - -/// Westend signing params. -pub type SigningParams = sp_core::sr25519::Pair; diff --git a/bridges/relays/client-wococo/src/lib.rs b/bridges/relays/client-wococo/src/lib.rs index 03cb4d7156..eac2aa841b 100644 --- a/bridges/relays/client-wococo/src/lib.rs +++ b/bridges/relays/client-wococo/src/lib.rs @@ -17,7 +17,9 @@ //! Types used to connect to the Wococo-Substrate chain. use codec::Encode; -use relay_substrate_client::{Chain, ChainBase, ChainWithBalances, TransactionSignScheme}; +use relay_substrate_client::{ + Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme, UnsignedTransaction, +}; use sp_core::{storage::StorageKey, Pair}; use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; use std::time::Duration; @@ -69,13 +71,12 @@ impl TransactionSignScheme for Wococo { fn sign_transaction( genesis_hash: ::Hash, signer: &Self::AccountKeyPair, - era: bp_runtime::TransactionEraOf, - signer_nonce: ::Index, - call: ::Call, + era: TransactionEraOf, + unsigned: UnsignedTransaction, ) -> Self::SignedTransaction { let raw_payload = SignedPayload::new( - call, - bp_wococo::SignedExtensions::new(bp_wococo::VERSION, era, genesis_hash, signer_nonce, 0), + unsigned.call, + bp_wococo::SignedExtensions::new(bp_wococo::VERSION, era, genesis_hash, unsigned.nonce, unsigned.tip), ) .expect("SignedExtension never fails."); @@ -90,6 +91,26 @@ impl TransactionSignScheme for Wococo { extra, ) } + + fn is_signed(tx: &Self::SignedTransaction) -> bool { + tx.signature.is_some() + } + + fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool { + tx.signature + .as_ref() + .map(|(address, _, _)| *address == bp_wococo::AccountId::from(*signer.public().as_array_ref()).into()) + .unwrap_or(false) + } + + fn parse_transaction(tx: Self::SignedTransaction) -> Option> { + let extra = &tx.signature.as_ref()?.2; + Some(UnsignedTransaction { + call: tx.function, + nonce: extra.nonce(), + tip: extra.tip(), + }) + } } /// Wococo signing params. diff --git a/bridges/relays/utils/src/relay_loop.rs b/bridges/relays/utils/src/relay_loop.rs index 38b636a599..ef8ebf4e8a 100644 --- a/bridges/relays/utils/src/relay_loop.rs +++ b/bridges/relays/utils/src/relay_loop.rs @@ -34,6 +34,15 @@ pub trait Client: 'static + Clone + Send + Sync { async fn reconnect(&mut self) -> Result<(), Self::Error>; } +#[async_trait] +impl Client for () { + type Error = crate::StringifiedMaybeConnectionError; + + async fn reconnect(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + /// Returns generic loop that may be customized and started. pub fn relay_loop(source_client: SC, target_client: TC) -> Loop { Loop {