From 8a4f984a7a7c6f0dd98b06866ac3f1dbb95d3743 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Tue, 17 Jan 2023 14:58:30 +0200 Subject: [PATCH] Define separate signed extension for BHR/BHW (#1776) - Make signed extensions for indirect runtimes more extensible - Define separate signed extension for BHR/BHW --- .../chain-bridge-hub-cumulus/src/lib.rs | 6 +- bridges/primitives/polkadot-core/src/lib.rs | 248 ++++++++++-------- bridges/primitives/runtime/Cargo.toml | 1 + bridges/primitives/runtime/src/extensions.rs | 136 ++++++++++ bridges/primitives/runtime/src/lib.rs | 1 + .../client-bridge-hub-rococo/src/lib.rs | 3 +- .../src/runtime_wrapper.rs | 4 +- .../client-bridge-hub-wococo/src/lib.rs | 3 +- .../src/runtime_wrapper.rs | 4 +- .../relays/client-rialto-parachain/src/lib.rs | 6 +- 10 files changed, 293 insertions(+), 119 deletions(-) create mode 100644 bridges/primitives/runtime/src/extensions.rs diff --git a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs index 72faabe43c..e33131ff8a 100644 --- a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs @@ -18,9 +18,9 @@ use bp_messages::*; pub use bp_polkadot_core::{ - AccountId, AccountInfoStorageMapKeyProvider, AccountPublic, Balance, BlockNumber, Hash, Hasher, - Hashing, Header, Index, Nonce, Perbill, Signature, SignedBlock, SignedExtensions, - UncheckedExtrinsic, TX_EXTRA_BYTES, + AccountId, AccountInfoStorageMapKeyProvider, AccountPublic, Balance, BlockNumber, + BridgeSignedExtension, Hash, Hasher, Hashing, Header, Index, Nonce, Perbill, + PolkadotSignedExtension, Signature, SignedBlock, UncheckedExtrinsic, TX_EXTRA_BYTES, }; use frame_support::{ dispatch::DispatchClass, diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs index 7558ce762d..3d4b72fccb 100644 --- a/bridges/primitives/polkadot-core/src/lib.rs +++ b/bridges/primitives/polkadot-core/src/lib.rs @@ -18,7 +18,6 @@ use bp_messages::MessageNonce; use bp_runtime::{Chain, EncodedOrDecodedCall, StorageMapKeyProvider}; -use codec::Compact; use frame_support::{ dispatch::DispatchClass, parameter_types, @@ -29,17 +28,16 @@ use frame_support::{ Blake2_128Concat, RuntimeDebug, }; use frame_system::limits; -use scale_info::TypeInfo; use sp_core::{storage::StorageKey, Hasher as HasherT}; use sp_runtime::{ generic, - traits::{BlakeTwo256, DispatchInfoOf, IdentifyAccount, Verify}, - transaction_validity::TransactionValidityError, + traits::{BlakeTwo256, IdentifyAccount, Verify}, MultiAddress, MultiSignature, OpaqueExtrinsic, }; use sp_std::prelude::Vec; // Re-export's to avoid extra substrate dependencies in chain-specific crates. +use bp_runtime::extensions::*; pub use frame_support::{weights::constants::ExtrinsicBaseWeight, Parameter}; pub use sp_runtime::{traits::Convert, Perbill}; @@ -184,114 +182,12 @@ pub type SignedBlock = generic::SignedBlock; pub type Balance = u128; /// Unchecked Extrinsic type. -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic< - AccountAddress, - EncodedOrDecodedCall, - Signature, - SignedExtensions, ->; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic, Signature, SignedExt>; /// 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 = - ((), (), (), (), sp_runtime::generic::Era, Compact, (), Compact); - -/// Parameters which are part of the payload used to produce transaction signature, -/// but don't end up in the transaction itself (i.e. inherent part of the runtime). -pub type AdditionalSigned = ((), u32, u32, Hash, Hash, (), (), ()); - -/// A simplified version of signed extensions meant for producing signed transactions -/// and signed payload in the client code. -#[derive(codec::Encode, codec::Decode, PartialEq, Eq, Clone, RuntimeDebug, TypeInfo)] -pub struct SignedExtensions { - encode_payload: SignedExtra, - // It may be set to `None` if extensions are decoded. We are never reconstructing transactions - // (and it makes no sense to do that) => decoded version of `SignedExtensions` is only used to - // read fields of `encode_payload`. And when resigning transaction, we're reconstructing - // `SignedExtensions` from the scratch. - #[codec(skip)] - additional_signed: Option, -} - -impl SignedExtensions { - pub fn new( - spec_version: u32, - transaction_version: u32, - era: bp_runtime::TransactionEraOf, - genesis_hash: Hash, - nonce: Nonce, - tip: Balance, - ) -> Self { - Self { - encode_payload: ( - (), // non-zero sender - (), // spec version - (), // tx version - (), // genesis - era.frame_era(), // era - nonce.into(), // nonce (compact encoding) - (), // Check weight - tip.into(), // transaction payment / tip (compact encoding) - ), - additional_signed: Some(( - (), - spec_version, - transaction_version, - genesis_hash, - era.signed_payload(genesis_hash), - (), - (), - (), - )), - } - } -} - -impl SignedExtensions { - /// Return signer nonce, used to craft transaction. - pub fn nonce(&self) -> Nonce { - self.encode_payload.5.into() - } - - /// Return transaction tip. - pub fn tip(&self) -> Balance { - self.encode_payload.7.into() - } -} - -impl sp_runtime::traits::SignedExtension for SignedExtensions { - const IDENTIFIER: &'static str = "Not needed."; - - type AccountId = AccountId; - type Call = (); - type AdditionalSigned = AdditionalSigned; - type Pre = (); - - fn additional_signed( - &self, - ) -> Result { - // we shall not ever see this error in relay, because we are never signing decoded - // transactions. Instead we're constructing and signing new transactions. So the error code - // is kinda random here - self.additional_signed - .ok_or(frame_support::unsigned::TransactionValidityError::Unknown( - frame_support::unsigned::UnknownTransaction::Custom(0xFF), - )) - } - - fn pre_dispatch( - self, - _who: &Self::AccountId, - _call: &Self::Call, - _info: &DispatchInfoOf, - _len: usize, - ) -> Result { - Ok(()) - } -} - /// Polkadot-like chain. #[derive(RuntimeDebug)] pub struct PolkadotLike; @@ -319,6 +215,142 @@ impl Chain for PolkadotLike { } } +/// Some functionality associated with the default signed extension used by Polkadot and +/// Polkadot-like chains. +pub trait PolkadotSignedExtension { + fn from_params( + spec_version: u32, + transaction_version: u32, + era: bp_runtime::TransactionEraOf, + genesis_hash: Hash, + nonce: Nonce, + tip: Balance, + ) -> Self; + + fn nonce(&self) -> Nonce; + + fn tip(&self) -> Balance; +} + +type DefaultSignedExtra = ( + CheckNonZeroSender, + CheckSpecVersion, + CheckTxVersion, + CheckGenesis, + CheckEra, + CheckNonce, + CheckWeight, + ChargeTransactionPayment, +); + +/// The default signed extension used by Polkadot and Polkadot-like chains. +pub type DefaultSignedExtension = GenericSignedExtension; + +impl PolkadotSignedExtension for DefaultSignedExtension { + fn from_params( + spec_version: u32, + transaction_version: u32, + era: bp_runtime::TransactionEraOf, + genesis_hash: Hash, + nonce: Nonce, + tip: Balance, + ) -> Self { + Self::new( + ( + (), // non-zero sender + (), // spec version + (), // tx version + (), // genesis + era.frame_era(), // era + nonce.into(), // nonce (compact encoding) + (), // Check weight + tip.into(), // transaction payment / tip (compact encoding) + ), + ( + (), + spec_version, + transaction_version, + genesis_hash, + era.signed_payload(genesis_hash), + (), + (), + (), + ), + ) + } + + /// Return signer nonce, used to craft transaction. + fn nonce(&self) -> Nonce { + self.payload.5.into() + } + + /// Return transaction tip. + fn tip(&self) -> Balance { + self.payload.7.into() + } +} + +type BridgeSignedExtra = ( + CheckNonZeroSender, + CheckSpecVersion, + CheckTxVersion, + CheckGenesis, + CheckEra, + CheckNonce, + CheckWeight, + ChargeTransactionPayment, + BridgeRejectObsoleteHeadersAndMessages, +); + +/// The default signed extension used by Polkadot and Polkadot-like chains with bridging. +pub type BridgeSignedExtension = GenericSignedExtension; + +impl PolkadotSignedExtension for BridgeSignedExtension { + fn from_params( + spec_version: u32, + transaction_version: u32, + era: bp_runtime::TransactionEraOf, + genesis_hash: Hash, + nonce: Nonce, + tip: Balance, + ) -> Self { + Self::new( + ( + (), // non-zero sender + (), // spec version + (), // tx version + (), // genesis + era.frame_era(), // era + nonce.into(), // nonce (compact encoding) + (), // Check weight + tip.into(), // transaction payment / tip (compact encoding) + (), // bridge reject obsolete headers and msgs + ), + ( + (), + spec_version, + transaction_version, + genesis_hash, + era.signed_payload(genesis_hash), + (), + (), + (), + (), + ), + ) + } + + /// Return signer nonce, used to craft transaction. + fn nonce(&self) -> Nonce { + self.payload.5.into() + } + + /// Return transaction tip. + fn tip(&self) -> Balance { + self.payload.7.into() + } +} + /// Provides a storage key for account data. /// /// We need to use this approach when we don't have access to the runtime. diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index 79f2b9fe03..f8d698696d 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -9,6 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } hash-db = { version = "0.15.2", default-features = false } +impl-trait-for-tuples = "0.2.2" num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0", optional = true, features = ["derive"] } diff --git a/bridges/primitives/runtime/src/extensions.rs b/bridges/primitives/runtime/src/extensions.rs new file mode 100644 index 0000000000..287f484db4 --- /dev/null +++ b/bridges/primitives/runtime/src/extensions.rs @@ -0,0 +1,136 @@ +// 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 . + +//! Primitives that may be used for creating signed extensions for indirect runtimes. + +use crate::{BalanceOf, HashOf}; +use codec::{Compact, Decode, Encode}; +use impl_trait_for_tuples::impl_for_tuples; +use scale_info::{StaticTypeInfo, TypeInfo}; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::TransactionValidityError, +}; +use sp_std::{fmt::Debug, marker::PhantomData}; + +/// Trait that describes some properties of a `SignedExtension` that are needed in order to send a +/// transaction to the chain. +pub trait SignedExtensionSchema: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo { + /// A type of the data encoded as part of the transaction. + type Payload: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo; + /// Parameters which are part of the payload used to produce transaction signature, + /// but don't end up in the transaction itself (i.e. inherent part of the runtime). + type AdditionalSigned: Encode + Debug + Eq + Clone + StaticTypeInfo; +} + +// An implementation of `SignedExtensionSchema` using generic params. +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] +pub struct GenericSignedExtensionSchema(PhantomData<(P, S)>); + +impl SignedExtensionSchema for GenericSignedExtensionSchema +where + P: Encode + Decode + Debug + Eq + Clone + StaticTypeInfo, + S: Encode + Debug + Eq + Clone + StaticTypeInfo, +{ + type Payload = P; + type AdditionalSigned = S; +} + +/// The `SignedExtensionSchema` for `frame_system::CheckNonZeroSender`. +pub type CheckNonZeroSender = GenericSignedExtensionSchema<(), ()>; + +/// The `SignedExtensionSchema` for `frame_system::CheckSpecVersion`. +pub type CheckSpecVersion = GenericSignedExtensionSchema<(), u32>; + +/// The `SignedExtensionSchema` for `frame_system::CheckTxVersion`. +pub type CheckTxVersion = GenericSignedExtensionSchema<(), u32>; + +/// The `SignedExtensionSchema` for `frame_system::CheckGenesis`. +pub type CheckGenesis = GenericSignedExtensionSchema<(), HashOf>; + +/// The `SignedExtensionSchema` for `frame_system::CheckEra`. +pub type CheckEra = GenericSignedExtensionSchema>; + +/// The `SignedExtensionSchema` for `frame_system::CheckNonce`. +pub type CheckNonce = GenericSignedExtensionSchema, ()>; + +/// The `SignedExtensionSchema` for `frame_system::CheckWeight`. +pub type CheckWeight = GenericSignedExtensionSchema<(), ()>; + +/// The `SignedExtensionSchema` for `pallet_transaction_payment::ChargeTransactionPayment`. +pub type ChargeTransactionPayment = GenericSignedExtensionSchema>, ()>; + +/// The `SignedExtensionSchema` for `BridgeRejectObsoleteHeadersAndMessages`. +pub type BridgeRejectObsoleteHeadersAndMessages = GenericSignedExtensionSchema<(), ()>; + +#[impl_for_tuples(1, 12)] +impl SignedExtensionSchema for Tuple { + for_tuples!( type Payload = ( #( Tuple::Payload ),* ); ); + for_tuples!( type AdditionalSigned = ( #( Tuple::AdditionalSigned ),* ); ); +} + +/// A simplified version of signed extensions meant for producing signed transactions +/// and signed payloads in the client code. +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub struct GenericSignedExtension { + pub payload: S::Payload, + #[codec(skip)] + // It may be set to `None` if extensions are decoded. We are never reconstructing transactions + // (and it makes no sense to do that) => decoded version of `SignedExtensions` is only used to + // read fields of the `payload`. And when resigning transaction, we're reconstructing + // `SignedExtensions` from the scratch. + additional_signed: Option, +} + +impl GenericSignedExtension { + pub fn new(payload: S::Payload, additional_signed: S::AdditionalSigned) -> Self { + Self { payload, additional_signed: Some(additional_signed) } + } +} + +impl SignedExtension for GenericSignedExtension +where + S: SignedExtensionSchema, + S::Payload: Send + Sync, + S::AdditionalSigned: Send + Sync, +{ + const IDENTIFIER: &'static str = "Not needed."; + type AccountId = (); + type Call = (); + type AdditionalSigned = S::AdditionalSigned; + type Pre = (); + + fn additional_signed(&self) -> Result { + // we shall not ever see this error in relay, because we are never signing decoded + // transactions. Instead we're constructing and signing new transactions. So the error code + // is kinda random here + self.additional_signed.clone().ok_or( + frame_support::unsigned::TransactionValidityError::Unknown( + frame_support::unsigned::UnknownTransaction::Custom(0xFF), + ), + ) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(()) + } +} diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index 340088a85b..5eb1de2e3a 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -47,6 +47,7 @@ pub use storage_types::BoundedStorageValue; #[cfg(feature = "std")] pub use storage_proof::craft_valid_storage_proof; +pub mod extensions; pub mod messages; mod chain; diff --git a/bridges/relays/client-bridge-hub-rococo/src/lib.rs b/bridges/relays/client-bridge-hub-rococo/src/lib.rs index 653c5af2c5..8e6e971292 100644 --- a/bridges/relays/client-bridge-hub-rococo/src/lib.rs +++ b/bridges/relays/client-bridge-hub-rococo/src/lib.rs @@ -16,6 +16,7 @@ //! Types used to connect to the BridgeHub-Rococo-Substrate parachain. +use bp_bridge_hub_wococo::PolkadotSignedExtension; use bp_messages::MessageNonce; use codec::Encode; use relay_substrate_client::{ @@ -65,7 +66,7 @@ impl ChainWithTransactions for BridgeHubRococo { ) -> Result { let raw_payload = SignedPayload::new( unsigned.call, - bp_bridge_hub_rococo::SignedExtensions::new( + bp_bridge_hub_rococo::BridgeSignedExtension::from_params( param.spec_version, param.transaction_version, unsigned.era, diff --git a/bridges/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs b/bridges/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs index 2c50128201..1bb32a4089 100644 --- a/bridges/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs +++ b/bridges/relays/client-bridge-hub-rococo/src/runtime_wrapper.rs @@ -21,14 +21,14 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; +use bp_bridge_hub_rococo::BridgeSignedExtension; pub use bp_header_chain::BridgeGrandpaCallOf; pub use bp_parachains::BridgeParachainCall; pub use bridge_runtime_common::messages::BridgeMessagesCallOf; pub use relay_substrate_client::calls::SystemCall; -// TODO:check-parameter - check SignedExtension /// Unchecked BridgeHubRococo extrinsic. -pub type UncheckedExtrinsic = bp_bridge_hub_rococo::UncheckedExtrinsic; +pub type UncheckedExtrinsic = bp_bridge_hub_rococo::UncheckedExtrinsic; // The indirect pallet call used to sync `Wococo` GRANDPA finality to `BHRococo`. pub type BridgeWococoGrandpaCall = BridgeGrandpaCallOf; diff --git a/bridges/relays/client-bridge-hub-wococo/src/lib.rs b/bridges/relays/client-bridge-hub-wococo/src/lib.rs index 516f5d9fb8..3fd8187fa1 100644 --- a/bridges/relays/client-bridge-hub-wococo/src/lib.rs +++ b/bridges/relays/client-bridge-hub-wococo/src/lib.rs @@ -16,6 +16,7 @@ //! Types used to connect to the BridgeHub-Wococo-Substrate parachain. +use bp_bridge_hub_wococo::PolkadotSignedExtension; use bp_messages::MessageNonce; use codec::Encode; use relay_substrate_client::{ @@ -65,7 +66,7 @@ impl ChainWithTransactions for BridgeHubWococo { ) -> Result { let raw_payload = SignedPayload::new( unsigned.call, - bp_bridge_hub_wococo::SignedExtensions::new( + bp_bridge_hub_wococo::BridgeSignedExtension::from_params( param.spec_version, param.transaction_version, unsigned.era, diff --git a/bridges/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs b/bridges/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs index 0d73a91029..2158ad0e65 100644 --- a/bridges/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs +++ b/bridges/relays/client-bridge-hub-wococo/src/runtime_wrapper.rs @@ -19,14 +19,14 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; +use bp_bridge_hub_wococo::BridgeSignedExtension; pub use bp_header_chain::BridgeGrandpaCallOf; pub use bp_parachains::BridgeParachainCall; pub use bridge_runtime_common::messages::BridgeMessagesCallOf; pub use relay_substrate_client::calls::SystemCall; -// TODO:check-parameter - check SignedExtension /// Unchecked BridgeHubWococo extrinsic. -pub type UncheckedExtrinsic = bp_bridge_hub_wococo::UncheckedExtrinsic; +pub type UncheckedExtrinsic = bp_bridge_hub_wococo::UncheckedExtrinsic; // The indirect pallet call used to sync `Rococo` GRANDPA finality to `BHWococo`. pub type BridgeRococoGrandpaCall = BridgeGrandpaCallOf; diff --git a/bridges/relays/client-rialto-parachain/src/lib.rs b/bridges/relays/client-rialto-parachain/src/lib.rs index c3fb0da741..ebaac73e03 100644 --- a/bridges/relays/client-rialto-parachain/src/lib.rs +++ b/bridges/relays/client-rialto-parachain/src/lib.rs @@ -19,6 +19,7 @@ pub mod runtime_wrapper; use bp_messages::MessageNonce; +use bp_polkadot_core::{DefaultSignedExtension, PolkadotSignedExtension}; use codec::Encode; use relay_substrate_client::{ Chain, ChainWithBalances, ChainWithMessages, ChainWithTransactions, Error as SubstrateError, @@ -75,7 +76,8 @@ impl ChainWithMessages for RialtoParachain { impl ChainWithTransactions for RialtoParachain { type AccountKeyPair = sp_core::sr25519::Pair; - type SignedTransaction = bp_polkadot_core::UncheckedExtrinsic; + type SignedTransaction = + bp_polkadot_core::UncheckedExtrinsic; fn sign_transaction( param: SignParam, @@ -83,7 +85,7 @@ impl ChainWithTransactions for RialtoParachain { ) -> Result { let raw_payload = SignedPayload::new( unsigned.call, - bp_polkadot_core::SignedExtensions::new( + bp_polkadot_core::DefaultSignedExtension::from_params( param.spec_version, param.transaction_version, unsigned.era,