mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 14:01:02 +00:00
Squashed 'bridges/' changes from b2099c5..23dda62 (#3369)
23dda62 Rococo <> Wococo messages relay (#1030) bcde21d Update the wasm builder to substrate master (#1029) a8318ce Make target signer optional when sending message. (#1018) f8602e1 Fix insufficient balance when send message. (#1020) d95c0a7 greedy relayer don't need message dispatch to be prepaid if dispatch is supposed to be paid at the target chain (#1016) ad5876f Update types. (#1027) 116cbbc CI: fix starting the pipeline (#1022) 7e0fadd Add temporary `canary` job (#1019) 6787091 Update types to contain dispatch_fee_payment (#1017) 03f79ad Allow Root to assume SourceAccount. (#1011) 372d019 Return dispatch_fee_payment from message details RPC (#1014) 604eb1c Relay basic single-bit message dispatch results back to the source chain (#935) bf52fff Use plain source_queue view when selecting nonces for delivery (#1010) fc5cf7d pay dispatch fee at target chain (#911) 1e35477 Bump Substrate to `286d7ce` (#1006) 7ad07b3 Add --only-mandatory-headers mode (#1004) 5351dc9 Messages relayer operating mode (#995) 9bc29a7 Rococo <> Wococo relayer balance guard (#998) bc17341 rename messages_dispatch_weight -> message_details (#996) 95be244 Bump Rococo and Wococo spec versions (#999) c35567b Move ChainWithBalances::NativeBalance -> Chain::Balance (#990) 1bfece1 Fix some nits (#988) 334ea0f Increase pause before starting relays again (#989) 7fb8248 Fix clippy in test code (#993) d60ae50 fix clippy issues (#991) 75ca813 Make sure GRANDPA shares state with RPC. (#987) da2a38a Bump Substrate (#986) 5a9862f Update submit finality proof weight formula (#981) 69df513 Flag for rejecting all outbound messages (#982) 14d0506 Add script to setup bench machine. (#984) e74e8ab Move CI from GitHub Actions to GitLab (#814) c5ca5dd Custom justification verification (#979) 643f10d Always run on-demand headers relay in complex relay (#975) a35b0ef Add JSON type definitions for Rococo<>Wococo bridge (#977) 0eb83f2 Update cargo.deny (#980) e1d1f4c Bump Rococo/Wococo spec_version (#976) deac90d increase pause before starting relays (#974) 68d6d79 Revert to use InspectCmd, bump substrate `6bef4f4` (#966) 66e1508 Avoid hashing headers twice in verify_justification (#973) a31844f Bump `environmental` dependency (#972) 2a4c29a in auto-relays keep trying to connect to nodes until connection is established (#971) 0e767b3 removed stray file (#969) b9545dc Serve multiple lanes with single complex relay instance (#964) 73419f4 Correct type error (#968) bac256f Start finality relay spec-version guards for Rococo <> Wococo finality relays (#965) bfd7037 pass source and target chain ids to account_ownership_proof (#963) 8436073 Upstream changes from Polkadot repo (#961) e58d851 Increase account endowment amount (#960) git-subtree-dir: bridges git-subtree-split: 23dda6248236b27f20d76cbedc30e189cc6f736c
This commit is contained in:
committed by
GitHub
parent
022e8bc11c
commit
feefc34567
@@ -20,7 +20,7 @@
|
||||
// Runtime-generated DecodeLimit::decode_all_with_depth_limit
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub use bp_polkadot_core::*;
|
||||
@@ -31,7 +31,7 @@ pub type Kusama = PolkadotLike;
|
||||
// We use this to get the account on Kusama (target) which is derived from Polkadot's (source)
|
||||
// account.
|
||||
pub fn derive_account_from_polkadot_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::POLKADOT_BRIDGE_INSTANCE, id);
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::POLKADOT_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ pub const IS_KNOWN_KUSAMA_HEADER_METHOD: &str = "KusamaFinalityApi_is_known_head
|
||||
/// Name of the `ToKusamaOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_KUSAMA_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToKusamaOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToKusamaOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_KUSAMA_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToKusamaOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToKusamaOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_KUSAMA_MESSAGE_DETAILS_METHOD: &str = "ToKusamaOutboundLaneApi_message_details";
|
||||
/// Name of the `ToKusamaOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
pub const TO_KUSAMA_LATEST_GENERATED_NONCE_METHOD: &str = "ToKusamaOutboundLaneApi_latest_generated_nonce";
|
||||
/// Name of the `ToKusamaOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
@@ -87,15 +87,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -23,6 +23,7 @@ serde = { version = "1.0.101", optional = true, features = ["derive"] }
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
max-encoded-len = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false, features = ["derive"] }
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
@@ -41,6 +42,7 @@ std = [
|
||||
"hash256-std-hasher/std",
|
||||
"impl-codec/std",
|
||||
"impl-serde",
|
||||
"max-encoded-len/std",
|
||||
"parity-util-mem/std",
|
||||
"serde",
|
||||
"sp-api/std",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
mod millau_hash;
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_runtime::Chain;
|
||||
use frame_support::{
|
||||
weights::{constants::WEIGHT_PER_SECOND, DispatchClass, Weight},
|
||||
@@ -80,7 +80,7 @@ pub const MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE: MessageNonce = 1024;
|
||||
/// for the case when single message of `pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH` bytes is delivered.
|
||||
/// The message must have dispatch weight set to zero. The result then must be rounded up to account
|
||||
/// possible future runtime upgrades.
|
||||
pub const DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT: Weight = 1_000_000_000;
|
||||
pub const DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT: Weight = 1_500_000_000;
|
||||
|
||||
/// Increase of delivery transaction weight on Millau chain with every additional message byte.
|
||||
///
|
||||
@@ -95,6 +95,13 @@ pub const ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT: Weight = 25_000;
|
||||
/// runtime upgrades.
|
||||
pub const MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT: Weight = 2_000_000_000;
|
||||
|
||||
/// Weight of pay-dispatch-fee operation for inbound messages at Millau chain.
|
||||
///
|
||||
/// This value corresponds to the result of `pallet_bridge_messages::WeightInfoExt::pay_inbound_dispatch_fee_overhead()`
|
||||
/// call for your chain. Don't put too much reserve there, because it is used to **decrease**
|
||||
/// `DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT` cost. So putting large reserve would make delivery transactions cheaper.
|
||||
pub const PAY_INBOUND_DISPATCH_FEE_WEIGHT: Weight = 600_000_000;
|
||||
|
||||
/// The target length of a session (how often authorities change) on Millau measured in of number of
|
||||
/// blocks.
|
||||
///
|
||||
@@ -201,7 +208,7 @@ impl sp_runtime::traits::Convert<sp_core::H256, AccountId> for AccountIdConverte
|
||||
///
|
||||
/// Note that this should only be used for testing.
|
||||
pub fn derive_account_from_rialto_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::RIALTO_BRIDGE_INSTANCE, id);
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::RIALTO_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
@@ -244,8 +251,8 @@ pub const BEST_FINALIZED_MILLAU_HEADER_METHOD: &str = "MillauFinalityApi_best_fi
|
||||
/// Name of the `ToMillauOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToMillauOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToMillauOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_MILLAU_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToMillauOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToMillauOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_MILLAU_MESSAGE_DETAILS_METHOD: &str = "ToMillauOutboundLaneApi_message_details";
|
||||
/// Name of the `ToMillauOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
pub const TO_MILLAU_LATEST_RECEIVED_NONCE_METHOD: &str = "ToMillauOutboundLaneApi_latest_received_nonce";
|
||||
/// Name of the `ToMillauOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
@@ -288,15 +295,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// 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 frame_support::traits::MaxEncodedLen;
|
||||
use parity_util_mem::MallocSizeOf;
|
||||
use sp_runtime::traits::CheckEqual;
|
||||
|
||||
@@ -22,7 +23,7 @@ use sp_runtime::traits::CheckEqual;
|
||||
|
||||
fixed_hash::construct_fixed_hash! {
|
||||
/// Hash type used in Millau chain.
|
||||
#[derive(MallocSizeOf)]
|
||||
#[derive(MallocSizeOf, MaxEncodedLen)]
|
||||
pub struct MillauHash(64);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// Runtime-generated DecodeLimit::decode_all_with_depth_limit
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub use bp_polkadot_core::*;
|
||||
@@ -31,7 +31,7 @@ pub type Polkadot = PolkadotLike;
|
||||
// We use this to get the account on Polkadot (target) which is derived from Kusama's (source)
|
||||
// account.
|
||||
pub fn derive_account_from_kusama_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::KUSAMA_BRIDGE_INSTANCE, id);
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::KUSAMA_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ pub const IS_KNOWN_POLKADOT_HEADER_METHOD: &str = "PolkadotFinalityApi_is_known_
|
||||
/// Name of the `ToPolkadotOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_POLKADOT_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToPolkadotOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToPolkadotOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_POLKADOT_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToPolkadotOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToPolkadotOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_POLKADOT_MESSAGE_DETAILS_METHOD: &str = "ToPolkadotOutboundLaneApi_message_details";
|
||||
/// Name of the `ToPolkadotOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
pub const TO_POLKADOT_LATEST_GENERATED_NONCE_METHOD: &str = "ToPolkadotOutboundLaneApi_latest_generated_nonce";
|
||||
/// Name of the `ToPolkadotOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
@@ -87,15 +87,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// Runtime-generated DecodeLimit::decode_all_With_depth_limit
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_runtime::Chain;
|
||||
use frame_support::{
|
||||
weights::{constants::WEIGHT_PER_SECOND, DispatchClass, Weight},
|
||||
@@ -71,7 +71,7 @@ pub const MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE: MessageNonce = 128;
|
||||
/// for the case when single message of `pallet_bridge_messages::EXPECTED_DEFAULT_MESSAGE_LENGTH` bytes is delivered.
|
||||
/// The message must have dispatch weight set to zero. The result then must be rounded up to account
|
||||
/// possible future runtime upgrades.
|
||||
pub const DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT: Weight = 1_000_000_000;
|
||||
pub const DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT: Weight = 1_500_000_000;
|
||||
|
||||
/// Increase of delivery transaction weight on Rialto chain with every additional message byte.
|
||||
///
|
||||
@@ -86,6 +86,13 @@ pub const ADDITIONAL_MESSAGE_BYTE_DELIVERY_WEIGHT: Weight = 25_000;
|
||||
/// runtime upgrades.
|
||||
pub const MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT: Weight = 2_000_000_000;
|
||||
|
||||
/// Weight of pay-dispatch-fee operation for inbound messages at Rialto chain.
|
||||
///
|
||||
/// This value corresponds to the result of `pallet_bridge_messages::WeightInfoExt::pay_inbound_dispatch_fee_overhead()`
|
||||
/// call for your chain. Don't put too much reserve there, because it is used to **decrease**
|
||||
/// `DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT` cost. So putting large reserve would make delivery transactions cheaper.
|
||||
pub const PAY_INBOUND_DISPATCH_FEE_WEIGHT: Weight = 600_000_000;
|
||||
|
||||
/// The target length of a session (how often authorities change) on Rialto measured in of number of
|
||||
/// blocks.
|
||||
///
|
||||
@@ -162,7 +169,7 @@ impl Convert<sp_core::H256, AccountId> for AccountIdConverter {
|
||||
//
|
||||
// Note that this should only be used for testing.
|
||||
pub fn derive_account_from_millau_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::MILLAU_BRIDGE_INSTANCE, id);
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::MILLAU_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
@@ -205,8 +212,8 @@ pub const BEST_FINALIZED_RIALTO_HEADER_METHOD: &str = "RialtoFinalityApi_best_fi
|
||||
/// Name of the `ToRialtoOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToRialtoOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToRialtoOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_RIALTO_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToRialtoOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToRialtoOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_RIALTO_MESSAGE_DETAILS_METHOD: &str = "ToRialtoOutboundLaneApi_message_details";
|
||||
/// Name of the `ToRialtoOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
pub const TO_RIALTO_LATEST_GENERATED_NONCE_METHOD: &str = "ToRialtoOutboundLaneApi_latest_generated_nonce";
|
||||
/// Name of the `ToRialtoOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
@@ -249,15 +256,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -8,14 +8,15 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
|
||||
smallvec = "1.6"
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-chain = { path = "../header-chain", default-features = false }
|
||||
bp-messages = { path = "../messages", default-features = false }
|
||||
bp-polkadot-core = { path = "../polkadot-core", default-features = false }
|
||||
bp-runtime = { path = "../runtime", default-features = false }
|
||||
|
||||
# Substrate Based Dependencies
|
||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
@@ -24,10 +25,10 @@ sp-version = { git = "https://github.com/paritytech/substrate", branch = "master
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-chain/std",
|
||||
"bp-messages/std",
|
||||
"bp-polkadot-core/std",
|
||||
"bp-runtime/std",
|
||||
"frame-support/std",
|
||||
"parity-scale-codec/std",
|
||||
"sp-api/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
// Runtime-generated DecodeLimit::decode_all_with_depth_limit
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_runtime::Chain;
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
|
||||
use sp_std::prelude::*;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
@@ -30,59 +30,48 @@ pub use bp_polkadot_core::*;
|
||||
/// Rococo Chain
|
||||
pub type Rococo = PolkadotLike;
|
||||
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
/// The target length of a session (how often authorities change) on Westend measured in of number of
|
||||
/// blocks.
|
||||
///
|
||||
/// Note that since this is a target sessions may change before/after this time depending on network
|
||||
/// conditions.
|
||||
pub const SESSION_LENGTH: BlockNumber = 10 * time_units::MINUTES;
|
||||
|
||||
// NOTE: This needs to be kept up to date with the Rococo runtime found in the Polkadot repo.
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: sp_version::create_runtime_str!("rococo"),
|
||||
impl_name: sp_version::create_runtime_str!("parity-rococo-v1.5"),
|
||||
impl_name: sp_version::create_runtime_str!("parity-rococo-v1.6"),
|
||||
authoring_version: 0,
|
||||
spec_version: 232,
|
||||
spec_version: 9004,
|
||||
impl_version: 0,
|
||||
apis: sp_version::create_apis_vec![[]],
|
||||
transaction_version: 0,
|
||||
};
|
||||
|
||||
/// Rococo Runtime `Call` enum.
|
||||
///
|
||||
/// The enum represents a subset of possible `Call`s we can send to Rococo chain.
|
||||
/// Ideally this code would be auto-generated from Metadata, because we want to
|
||||
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
|
||||
///
|
||||
/// All entries here (like pretty much in the entire file) must be kept in sync with Rococo
|
||||
/// `construct_runtime`, so that we maintain SCALE-compatibility.
|
||||
///
|
||||
/// See: https://github.com/paritytech/polkadot/blob/master/runtime/rococo/src/lib.rs
|
||||
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Call {
|
||||
/// Wococo bridge pallet.
|
||||
#[codec(index = 41)]
|
||||
BridgeGrandpaWococo(BridgeGrandpaWococoCall),
|
||||
}
|
||||
|
||||
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaWococoCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
<PolkadotLike as Chain>::Header,
|
||||
bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
|
||||
),
|
||||
#[codec(index = 1)]
|
||||
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
|
||||
}
|
||||
|
||||
impl sp_runtime::traits::Dispatchable for Call {
|
||||
type Origin = ();
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = ();
|
||||
|
||||
fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
unimplemented!("The Call is not expected to be dispatched.")
|
||||
// NOTE: This needs to be kept up to date with the Rococo runtime found in the Polkadot repo.
|
||||
pub struct WeightToFee;
|
||||
impl WeightToFeePolynomial for WeightToFee {
|
||||
type Balance = Balance;
|
||||
fn polynomial() -> WeightToFeeCoefficients<Balance> {
|
||||
const CENTS: Balance = 1_000_000_000_000 / 100;
|
||||
let p = CENTS;
|
||||
let q = 10 * Balance::from(ExtrinsicBaseWeight::get());
|
||||
smallvec::smallvec![WeightToFeeCoefficient {
|
||||
degree: 1,
|
||||
negative: false,
|
||||
coeff_frac: Perbill::from_rational(p % q, q),
|
||||
coeff_integer: p / q,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// We use this to get the account on Rococo (target) which is derived from Wococo's (source)
|
||||
// account.
|
||||
pub fn derive_account_from_wococo_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::WOCOCO_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
/// Name of the `RococoFinalityApi::best_finalized` runtime method.
|
||||
pub const BEST_FINALIZED_ROCOCO_HEADER_METHOD: &str = "RococoFinalityApi_best_finalized";
|
||||
/// Name of the `RococoFinalityApi::is_known_header` runtime method.
|
||||
@@ -91,8 +80,8 @@ pub const IS_KNOWN_ROCOCO_HEADER_METHOD: &str = "RococoFinalityApi_is_known_head
|
||||
/// Name of the `ToRococoOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_ROCOCO_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToRococoOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToRococoOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_ROCOCO_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToRococoOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToRococoOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_ROCOCO_MESSAGE_DETAILS_METHOD: &str = "ToRococoOutboundLaneApi_message_details";
|
||||
/// Name of the `ToRococoOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
pub const TO_ROCOCO_LATEST_GENERATED_NONCE_METHOD: &str = "ToRococoOutboundLaneApi_latest_generated_nonce";
|
||||
/// Name of the `ToRococoOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
@@ -135,15 +124,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// Runtime-generated DecodeLimit::decode_all_with_depth_limit
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use bp_runtime::Chain;
|
||||
use sp_std::prelude::*;
|
||||
use sp_version::RuntimeVersion;
|
||||
@@ -86,7 +86,7 @@ impl sp_runtime::traits::Dispatchable for Call {
|
||||
// We use this to get the account on Westend (target) which is derived from Rococo's (source)
|
||||
// account.
|
||||
pub fn derive_account_from_rococo_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::ROCOCO_BRIDGE_INSTANCE, id);
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::ROCOCO_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ pub const IS_KNOWN_WESTEND_HEADER_METHOD: &str = "WestendFinalityApi_is_known_he
|
||||
/// Name of the `ToWestendOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_WESTEND_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToWestendOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToWestendOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_WESTEND_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToWestendOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToWestendOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_WESTEND_MESSAGE_DETAILS_METHOD: &str = "ToWestendOutboundLaneApi_message_details";
|
||||
/// Name of the `ToWestendOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
pub const TO_WESTEND_LATEST_GENERATED_NONCE_METHOD: &str = "ToWestendOutboundLaneApi_latest_generated_nonce";
|
||||
/// Name of the `ToWestendOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
@@ -149,15 +149,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -10,27 +10,25 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
|
||||
|
||||
# Bridge Dependencies
|
||||
bp-header-chain = { path = "../header-chain", default-features = false }
|
||||
bp-messages = { path = "../messages", default-features = false }
|
||||
bp-polkadot-core = { path = "../polkadot-core", default-features = false }
|
||||
bp-rococo = { path = "../chain-rococo", default-features = false }
|
||||
bp-runtime = { path = "../runtime", default-features = false }
|
||||
|
||||
# Substrate Based Dependencies
|
||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-chain/std",
|
||||
"bp-messages/std",
|
||||
"bp-polkadot-core/std",
|
||||
"bp-runtime/std",
|
||||
"bp-rococo/std",
|
||||
"parity-scale-codec/std",
|
||||
"sp-api/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"sp-version/std",
|
||||
]
|
||||
|
||||
@@ -20,73 +20,20 @@
|
||||
// Runtime-generated DecodeLimit::decode_all_with_depth_limit
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight};
|
||||
use bp_runtime::Chain;
|
||||
use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
|
||||
use sp_std::prelude::*;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
pub use bp_polkadot_core::*;
|
||||
// Rococo runtime = Wococo runtime
|
||||
pub use bp_rococo::{WeightToFee, SESSION_LENGTH, VERSION};
|
||||
|
||||
/// Wococo Chain
|
||||
pub type Wococo = PolkadotLike;
|
||||
|
||||
pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
|
||||
|
||||
// NOTE: This needs to be kept up to date with the Rococo runtime found in the Polkadot repo.
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: sp_version::create_runtime_str!("rococo"),
|
||||
impl_name: sp_version::create_runtime_str!("parity-rococo-v1.5"),
|
||||
authoring_version: 0,
|
||||
spec_version: 232,
|
||||
impl_version: 0,
|
||||
apis: sp_version::create_apis_vec![[]],
|
||||
transaction_version: 0,
|
||||
};
|
||||
|
||||
/// Wococo Runtime `Call` enum.
|
||||
///
|
||||
/// The enum represents a subset of possible `Call`s we can send to Rococo chain.
|
||||
/// Ideally this code would be auto-generated from Metadata, because we want to
|
||||
/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
|
||||
///
|
||||
/// All entries here (like pretty much in the entire file) must be kept in sync with Rococo
|
||||
/// `construct_runtime`, so that we maintain SCALE-compatibility.
|
||||
///
|
||||
/// See: https://github.com/paritytech/polkadot/blob/master/runtime/rococo/src/lib.rs
|
||||
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Call {
|
||||
/// Rococo bridge pallet.
|
||||
#[codec(index = 40)]
|
||||
BridgeGrandpaRococo(BridgeGrandpaRococoCall),
|
||||
}
|
||||
|
||||
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, Debug, PartialEq, Eq, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum BridgeGrandpaRococoCall {
|
||||
#[codec(index = 0)]
|
||||
submit_finality_proof(
|
||||
<PolkadotLike as Chain>::Header,
|
||||
bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
|
||||
),
|
||||
#[codec(index = 1)]
|
||||
initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
|
||||
}
|
||||
|
||||
impl sp_runtime::traits::Dispatchable for Call {
|
||||
type Origin = ();
|
||||
type Config = ();
|
||||
type Info = ();
|
||||
type PostInfo = ();
|
||||
|
||||
fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
unimplemented!("The Call is not expected to be dispatched.")
|
||||
}
|
||||
}
|
||||
|
||||
// We use this to get the account on Wococo (target) which is derived from Rococo's (source)
|
||||
// account.
|
||||
pub fn derive_account_from_rococo_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::ROCOCO_BRIDGE_INSTANCE, id);
|
||||
let encoded_id = bp_runtime::derive_account_id(bp_runtime::ROCOCO_CHAIN_ID, id);
|
||||
AccountIdConverter::convert(encoded_id)
|
||||
}
|
||||
|
||||
@@ -98,8 +45,8 @@ pub const IS_KNOWN_WOCOCO_HEADER_METHOD: &str = "WococoFinalityApi_is_known_head
|
||||
/// Name of the `ToWococoOutboundLaneApi::estimate_message_delivery_and_dispatch_fee` runtime method.
|
||||
pub const TO_WOCOCO_ESTIMATE_MESSAGE_FEE_METHOD: &str =
|
||||
"ToWococoOutboundLaneApi_estimate_message_delivery_and_dispatch_fee";
|
||||
/// Name of the `ToWococoOutboundLaneApi::messages_dispatch_weight` runtime method.
|
||||
pub const TO_WOCOCO_MESSAGES_DISPATCH_WEIGHT_METHOD: &str = "ToWococoOutboundLaneApi_messages_dispatch_weight";
|
||||
/// Name of the `ToWococoOutboundLaneApi::message_details` runtime method.
|
||||
pub const TO_WOCOCO_MESSAGE_DETAILS_METHOD: &str = "ToWococoOutboundLaneApi_message_details";
|
||||
/// Name of the `ToWococoOutboundLaneApi::latest_generated_nonce` runtime method.
|
||||
pub const TO_WOCOCO_LATEST_GENERATED_NONCE_METHOD: &str = "ToWococoOutboundLaneApi_latest_generated_nonce";
|
||||
/// Name of the `ToWococoOutboundLaneApi::latest_received_nonce` runtime method.
|
||||
@@ -142,15 +89,16 @@ sp_api::decl_runtime_apis! {
|
||||
lane_id: LaneId,
|
||||
payload: OutboundPayload,
|
||||
) -> Option<OutboundMessageFee>;
|
||||
/// Returns total dispatch weight and encoded payload size of all messages in given inclusive range.
|
||||
/// Returns dispatch weight, encoded payload size and delivery+dispatch fee of all
|
||||
/// messages in given inclusive range.
|
||||
///
|
||||
/// If some (or all) messages are missing from the storage, they'll also will
|
||||
/// be missing from the resulting vector. The vector is ordered by the nonce.
|
||||
fn messages_dispatch_weight(
|
||||
fn message_details(
|
||||
lane: LaneId,
|
||||
begin: MessageNonce,
|
||||
end: MessageNonce,
|
||||
) -> Vec<(MessageNonce, Weight, u32)>;
|
||||
) -> Vec<MessageDetails<OutboundMessageFee>>;
|
||||
/// Returns nonce of the latest message, received by bridged chain.
|
||||
fn latest_received_nonce(lane: LaneId) -> MessageNonce;
|
||||
/// Returns nonce of the latest message, generated by given lane.
|
||||
|
||||
@@ -245,7 +245,7 @@ impl AuraHeader {
|
||||
|
||||
/// Get step this header is generated for.
|
||||
pub fn step(&self) -> Option<u64> {
|
||||
self.seal.get(0).map(|x| Rlp::new(&x)).and_then(|x| x.as_val().ok())
|
||||
self.seal.get(0).map(|x| Rlp::new(x)).and_then(|x| x.as_val().ok())
|
||||
}
|
||||
|
||||
/// Get header author' signature.
|
||||
@@ -496,7 +496,7 @@ pub fn transaction_decode_rlp(raw_tx: &[u8]) -> Result<Transaction, DecoderError
|
||||
let message = unsigned.message(chain_id);
|
||||
|
||||
// recover tx sender
|
||||
let sender_public = sp_io::crypto::secp256k1_ecdsa_recover(&signature, &message.as_fixed_bytes())
|
||||
let sender_public = sp_io::crypto::secp256k1_ecdsa_recover(&signature, message.as_fixed_bytes())
|
||||
.map_err(|_| rlp::DecoderError::Custom("Failed to recover transaction sender"))?;
|
||||
let sender_address = public_to_address(&sender_public);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master
|
||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5"
|
||||
bp-test-utils = { path = "../test-utils" }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -20,107 +20,13 @@
|
||||
//! will ever be moved to the sp_finality_grandpa, we should reuse that implementation.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use finality_grandpa::{voter_set::VoterSet, Chain, Error as GrandpaError};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use frame_support::RuntimeDebug;
|
||||
use sp_finality_grandpa::{AuthorityId, AuthoritySignature, SetId};
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
/// Justification verification error.
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Failed to decode justification.
|
||||
JustificationDecode,
|
||||
/// Justification is finalizing unexpected header.
|
||||
InvalidJustificationTarget,
|
||||
/// Invalid commit in justification.
|
||||
InvalidJustificationCommit,
|
||||
/// Justification has invalid authority singature.
|
||||
InvalidAuthoritySignature,
|
||||
/// The justification has precommit for the header that has no route from the target header.
|
||||
InvalidPrecommitAncestryProof,
|
||||
/// The justification has 'unused' headers in its precommit ancestries.
|
||||
InvalidPrecommitAncestries,
|
||||
}
|
||||
|
||||
/// Decode justification target.
|
||||
pub fn decode_justification_target<Header: HeaderT>(
|
||||
raw_justification: &[u8],
|
||||
) -> Result<(Header::Hash, Header::Number), Error> {
|
||||
GrandpaJustification::<Header>::decode(&mut &*raw_justification)
|
||||
.map(|justification| (justification.commit.target_hash, justification.commit.target_number))
|
||||
.map_err(|_| Error::JustificationDecode)
|
||||
}
|
||||
|
||||
/// Verify that justification, that is generated by given authority set, finalizes given header.
|
||||
pub fn verify_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &VoterSet<AuthorityId>,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
Header::Number: finality_grandpa::BlockNumberOps,
|
||||
{
|
||||
// Ensure that it is justification for the expected header
|
||||
if (justification.commit.target_hash, justification.commit.target_number) != finalized_target {
|
||||
return Err(Error::InvalidJustificationTarget);
|
||||
}
|
||||
|
||||
// Validate commit of the justification. Note that `validate_commit()` assumes that all
|
||||
// signatures are valid. We'll check the validity of the signatures later since they're more
|
||||
// resource intensive to verify.
|
||||
let ancestry_chain = AncestryChain::new(&justification.votes_ancestries);
|
||||
match finality_grandpa::validate_commit(&justification.commit, authorities_set, &ancestry_chain) {
|
||||
Ok(ref result) if result.ghost().is_some() => {}
|
||||
_ => return Err(Error::InvalidJustificationCommit),
|
||||
}
|
||||
|
||||
// Now that we know that the commit is correct, check authorities signatures
|
||||
let mut buf = Vec::new();
|
||||
let mut visited_hashes = BTreeSet::new();
|
||||
for signed in &justification.commit.precommits {
|
||||
if !sp_finality_grandpa::check_message_signature_with_buffer(
|
||||
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
|
||||
&signed.id,
|
||||
&signed.signature,
|
||||
justification.round,
|
||||
authorities_set_id,
|
||||
&mut buf,
|
||||
) {
|
||||
return Err(Error::InvalidAuthoritySignature);
|
||||
}
|
||||
|
||||
if justification.commit.target_hash == signed.precommit.target_hash {
|
||||
continue;
|
||||
}
|
||||
|
||||
match ancestry_chain.ancestry(justification.commit.target_hash, signed.precommit.target_hash) {
|
||||
Ok(route) => {
|
||||
// ancestry starts from parent hash but the precommit target hash has been visited
|
||||
visited_hashes.insert(signed.precommit.target_hash);
|
||||
visited_hashes.extend(route);
|
||||
}
|
||||
_ => {
|
||||
// could this happen in practice? I don't think so, but original code has this check
|
||||
return Err(Error::InvalidPrecommitAncestryProof);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ancestry_hashes = justification
|
||||
.votes_ancestries
|
||||
.iter()
|
||||
.map(|h: &Header| h.hash())
|
||||
.collect();
|
||||
if visited_hashes != ancestry_hashes {
|
||||
return Err(Error::InvalidPrecommitAncestries);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A GRANDPA Justification is a proof that a given header was finalized
|
||||
/// at a certain height and with a certain set of authorities.
|
||||
///
|
||||
@@ -142,44 +48,172 @@ impl<H: HeaderT> crate::FinalityProof<H::Number> for GrandpaJustification<H> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers.
|
||||
#[derive(RuntimeDebug)]
|
||||
struct AncestryChain<Header: HeaderT> {
|
||||
ancestry: BTreeMap<Header::Hash, Header::Hash>,
|
||||
/// Justification verification error.
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// Failed to decode justification.
|
||||
JustificationDecode,
|
||||
/// Justification is finalizing unexpected header.
|
||||
InvalidJustificationTarget,
|
||||
/// The authority has provided an invalid signature.
|
||||
InvalidAuthoritySignature,
|
||||
/// The justification contains precommit for header that is not a descendant of the commit header.
|
||||
PrecommitIsNotCommitDescendant,
|
||||
/// The cumulative weight of all votes in the justification is not enough to justify commit
|
||||
/// header finalization.
|
||||
TooLowCumulativeWeight,
|
||||
/// The justification contains extra (unused) headers in its `votes_ancestries` field.
|
||||
ExtraHeadersInVotesAncestries,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> AncestryChain<Header> {
|
||||
fn new(ancestry: &[Header]) -> AncestryChain<Header> {
|
||||
AncestryChain {
|
||||
ancestry: ancestry
|
||||
.iter()
|
||||
.map(|header| (header.hash(), *header.parent_hash()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
/// Decode justification target.
|
||||
pub fn decode_justification_target<Header: HeaderT>(
|
||||
raw_justification: &[u8],
|
||||
) -> Result<(Header::Hash, Header::Number), Error> {
|
||||
GrandpaJustification::<Header>::decode(&mut &*raw_justification)
|
||||
.map(|justification| (justification.commit.target_hash, justification.commit.target_number))
|
||||
.map_err(|_| Error::JustificationDecode)
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> finality_grandpa::Chain<Header::Hash, Header::Number> for AncestryChain<Header>
|
||||
/// Verify that justification, that is generated by given authority set, finalizes given header.
|
||||
pub fn verify_justification<Header: HeaderT>(
|
||||
finalized_target: (Header::Hash, Header::Number),
|
||||
authorities_set_id: SetId,
|
||||
authorities_set: &VoterSet<AuthorityId>,
|
||||
justification: &GrandpaJustification<Header>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
Header::Number: finality_grandpa::BlockNumberOps,
|
||||
{
|
||||
fn ancestry(&self, base: Header::Hash, block: Header::Hash) -> Result<Vec<Header::Hash>, GrandpaError> {
|
||||
let mut route = Vec::new();
|
||||
let mut current_hash = block;
|
||||
loop {
|
||||
if current_hash == base {
|
||||
break;
|
||||
}
|
||||
match self.ancestry.get(¤t_hash).cloned() {
|
||||
Some(parent_hash) => {
|
||||
current_hash = parent_hash;
|
||||
route.push(current_hash);
|
||||
}
|
||||
_ => return Err(GrandpaError::NotDescendent),
|
||||
}
|
||||
}
|
||||
route.pop(); // remove the base
|
||||
// ensure that it is justification for the expected header
|
||||
if (justification.commit.target_hash, justification.commit.target_number) != finalized_target {
|
||||
return Err(Error::InvalidJustificationTarget);
|
||||
}
|
||||
|
||||
Ok(route)
|
||||
let mut chain = AncestryChain::new(&justification.votes_ancestries);
|
||||
let mut signature_buffer = Vec::new();
|
||||
let mut votes = BTreeSet::new();
|
||||
let mut cumulative_weight = 0u64;
|
||||
for signed in &justification.commit.precommits {
|
||||
// authority must be in the set
|
||||
let authority_info = match authorities_set.get(&signed.id) {
|
||||
Some(authority_info) => authority_info,
|
||||
None => {
|
||||
// just ignore precommit from unknown authority as `finality_grandpa::import_precommit` does
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// check if authority has already voted in the same round.
|
||||
//
|
||||
// there's a lot of code in `validate_commit` and `import_precommit` functions inside
|
||||
// `finality-grandpa` crate (mostly related to reporing equivocations). But the only thing that we
|
||||
// care about is that only first vote from the authority is accepted
|
||||
if !votes.insert(signed.id.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// everything below this line can't just `continue`, because state is already altered
|
||||
|
||||
// all precommits must be for block higher than the target
|
||||
if signed.precommit.target_number < justification.commit.target_number {
|
||||
return Err(Error::PrecommitIsNotCommitDescendant);
|
||||
}
|
||||
// all precommits must be for target block descendents
|
||||
chain = chain.ensure_descendant(&justification.commit.target_hash, &signed.precommit.target_hash)?;
|
||||
// since we know now that the precommit target is the descendant of the justification target,
|
||||
// we may increase 'weight' of the justification target
|
||||
//
|
||||
// there's a lot of code in the `VoteGraph::insert` method inside `finality-grandpa` crate,
|
||||
// but in the end it is only used to find GHOST, which we don't care about. The only thing
|
||||
// that we care about is that the justification target has enough weight
|
||||
cumulative_weight = cumulative_weight.checked_add(authority_info.weight().0.into()).expect(
|
||||
"sum of weights of ALL authorities is expected not to overflow - this is guaranteed by\
|
||||
existence of VoterSet;\
|
||||
the order of loop conditions guarantees that we can account vote from same authority\
|
||||
multiple times;\
|
||||
thus we'll never overflow the u64::MAX;\
|
||||
qed",
|
||||
);
|
||||
// verify authority signature
|
||||
if !sp_finality_grandpa::check_message_signature_with_buffer(
|
||||
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
|
||||
&signed.id,
|
||||
&signed.signature,
|
||||
justification.round,
|
||||
authorities_set_id,
|
||||
&mut signature_buffer,
|
||||
) {
|
||||
return Err(Error::InvalidAuthoritySignature);
|
||||
}
|
||||
}
|
||||
|
||||
// check that there are no extra headers in the justification
|
||||
if !chain.unvisited.is_empty() {
|
||||
return Err(Error::ExtraHeadersInVotesAncestries);
|
||||
}
|
||||
|
||||
// check that the cumulative weight of validators voted for the justification target (or one
|
||||
// of its descendents) is larger than required threshold.
|
||||
let threshold = authorities_set.threshold().0.into();
|
||||
if cumulative_weight >= threshold {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::TooLowCumulativeWeight)
|
||||
}
|
||||
}
|
||||
|
||||
/// Votes ancestries with useful methods.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct AncestryChain<Header: HeaderT> {
|
||||
/// Header hash => parent header hash mapping.
|
||||
pub parents: BTreeMap<Header::Hash, Header::Hash>,
|
||||
/// Hashes of headers that weren't visited by `is_ancestor` method.
|
||||
pub unvisited: BTreeSet<Header::Hash>,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> AncestryChain<Header> {
|
||||
/// Create new ancestry chain.
|
||||
pub fn new(ancestry: &[Header]) -> AncestryChain<Header> {
|
||||
let mut parents = BTreeMap::new();
|
||||
let mut unvisited = BTreeSet::new();
|
||||
for ancestor in ancestry {
|
||||
let hash = ancestor.hash();
|
||||
let parent_hash = *ancestor.parent_hash();
|
||||
parents.insert(hash, parent_hash);
|
||||
unvisited.insert(hash);
|
||||
}
|
||||
AncestryChain { parents, unvisited }
|
||||
}
|
||||
|
||||
/// Returns `Err(_)` if `precommit_target` is a descendant of the `commit_target` block and `Ok(_)` otherwise.
|
||||
pub fn ensure_descendant(
|
||||
mut self,
|
||||
commit_target: &Header::Hash,
|
||||
precommit_target: &Header::Hash,
|
||||
) -> Result<Self, Error> {
|
||||
let mut current_hash = *precommit_target;
|
||||
loop {
|
||||
if current_hash == *commit_target {
|
||||
break;
|
||||
}
|
||||
|
||||
let is_visited_before = !self.unvisited.remove(¤t_hash);
|
||||
current_hash = match self.parents.get(¤t_hash) {
|
||||
Some(parent_hash) => {
|
||||
if is_visited_before {
|
||||
// `Some(parent_hash)` means that the `current_hash` is in the `parents` container
|
||||
// `is_visited_before` means that it has been visited before in some of previous calls
|
||||
// => since we assume that previous call has finished with `true`, this also will
|
||||
// be finished with `true`
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
*parent_hash
|
||||
}
|
||||
None => return Err(Error::PrecommitIsNotCommitDescendant),
|
||||
};
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
// Copyright 2020-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/>.
|
||||
|
||||
//! Tests inside this module are made to ensure that our custom justification verification
|
||||
//! implementation works exactly as `fn finality_grandpa::validate_commit`.
|
||||
//!
|
||||
//! Some of tests in this module may partially duplicate tests from `justification.rs`,
|
||||
//! but their purpose is different.
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use bp_header_chain::justification::{verify_justification, Error, GrandpaJustification};
|
||||
use bp_test_utils::{
|
||||
header_id, make_justification_for_header, signed_precommit, test_header, Account, JustificationGeneratorParams,
|
||||
ALICE, BOB, CHARLIE, DAVE, EVE, TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_finality_grandpa::{AuthorityId, AuthorityWeight};
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
|
||||
type TestHeader = sp_runtime::testing::Header;
|
||||
type TestHash = <TestHeader as HeaderT>::Hash;
|
||||
type TestNumber = <TestHeader as HeaderT>::Number;
|
||||
|
||||
/// Implementation of `finality_grandpa::Chain` that is used in tests.
|
||||
struct AncestryChain(bp_header_chain::justification::AncestryChain<TestHeader>);
|
||||
|
||||
impl AncestryChain {
|
||||
fn new(ancestry: &[TestHeader]) -> Self {
|
||||
Self(bp_header_chain::justification::AncestryChain::new(ancestry))
|
||||
}
|
||||
}
|
||||
|
||||
impl finality_grandpa::Chain<TestHash, TestNumber> for AncestryChain {
|
||||
fn ancestry(&self, base: TestHash, block: TestHash) -> Result<Vec<TestHash>, finality_grandpa::Error> {
|
||||
let mut route = Vec::new();
|
||||
let mut current_hash = block;
|
||||
loop {
|
||||
if current_hash == base {
|
||||
break;
|
||||
}
|
||||
match self.0.parents.get(¤t_hash).cloned() {
|
||||
Some(parent_hash) => {
|
||||
current_hash = parent_hash;
|
||||
route.push(current_hash);
|
||||
}
|
||||
_ => return Err(finality_grandpa::Error::NotDescendent),
|
||||
}
|
||||
}
|
||||
route.pop(); // remove the base
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a full set of accounts.
|
||||
fn full_accounts_set() -> Vec<(Account, AuthorityWeight)> {
|
||||
vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1), (EVE, 1)]
|
||||
}
|
||||
|
||||
/// Get a full set of GRANDPA authorities.
|
||||
fn full_voter_set() -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(full_accounts_set().iter().map(|(id, w)| (AuthorityId::from(*id), *w))).unwrap()
|
||||
}
|
||||
|
||||
/// Get a minimal set of accounts.
|
||||
fn minimal_accounts_set() -> Vec<(Account, AuthorityWeight)> {
|
||||
// there are 5 accounts in the full set => we need 2/3 + 1 accounts, which results in 4 accounts
|
||||
vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)]
|
||||
}
|
||||
|
||||
/// Get a minimal subset of GRANDPA authorities that have enough cumulative vote weight to justify a header finality.
|
||||
pub fn minimal_voter_set() -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(
|
||||
minimal_accounts_set()
|
||||
.iter()
|
||||
.map(|(id, w)| (AuthorityId::from(*id), *w)),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Make a valid GRANDPA justification with sensible defaults.
|
||||
pub fn make_default_justification(header: &TestHeader) -> GrandpaJustification<TestHeader> {
|
||||
make_justification_for_header(JustificationGeneratorParams {
|
||||
header: header.clone(),
|
||||
authorities: minimal_accounts_set(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
// the `finality_grandpa::validate_commit` function has two ways to report an unsuccessful
|
||||
// commit validation:
|
||||
//
|
||||
// 1) to return `Err()` (which only may happen if `finality_grandpa::Chain` implementation
|
||||
// returns an error);
|
||||
// 2) to return `Ok(validation_result) if validation_result.ghost().is_none()`.
|
||||
//
|
||||
// Our implementation would just return error in both cases.
|
||||
|
||||
#[test]
|
||||
fn same_result_when_precommit_target_has_lower_number_than_commit_target() {
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// the number of header in precommit (0) is lower than number of header in commit (1)
|
||||
justification.commit.precommits[0].precommit.target_number = 0;
|
||||
|
||||
// our implementation returns an error
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Err(Error::PrecommitIsNotCommitDescendant),
|
||||
);
|
||||
// original implementation returns empty GHOST
|
||||
assert_matches!(
|
||||
finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification.votes_ancestries),
|
||||
)
|
||||
.map(|result| result.ghost().cloned()),
|
||||
Ok(None)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_precommit_target_is_not_descendant_of_commit_target() {
|
||||
let not_descendant = test_header::<TestHeader>(10);
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// the route from header of commit (1) to header of precommit (10) is missing from
|
||||
// the votes ancestries
|
||||
justification.commit.precommits[0].precommit.target_number = *not_descendant.number();
|
||||
justification.commit.precommits[0].precommit.target_hash = not_descendant.hash();
|
||||
justification.votes_ancestries.push(not_descendant);
|
||||
|
||||
// our implementation returns an error
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Err(Error::PrecommitIsNotCommitDescendant),
|
||||
);
|
||||
// original implementation returns empty GHOST
|
||||
assert_matches!(
|
||||
finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification.votes_ancestries),
|
||||
)
|
||||
.map(|result| result.ghost().cloned()),
|
||||
Ok(None)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_justification_contains_duplicate_vote() {
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// the justification may contain exactly the same vote (i.e. same precommit and same signature)
|
||||
// multiple times && it isn't treated as an error by original implementation
|
||||
justification
|
||||
.commit
|
||||
.precommits
|
||||
.push(justification.commit.precommits[0].clone());
|
||||
justification
|
||||
.commit
|
||||
.precommits
|
||||
.push(justification.commit.precommits[0].clone());
|
||||
|
||||
// our implementation succeeds
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
// original implementation returns non-empty GHOST
|
||||
assert_matches!(
|
||||
finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification.votes_ancestries),
|
||||
)
|
||||
.map(|result| result.ghost().cloned()),
|
||||
Ok(Some(_))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_authority_equivocates_once_in_a_round() {
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// the justification original implementation allows authority to submit two different
|
||||
// votes in a single round, of which only first is 'accepted'
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
|
||||
// our implementation succeeds
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
// original implementation returns non-empty GHOST
|
||||
assert_matches!(
|
||||
finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification.votes_ancestries),
|
||||
)
|
||||
.map(|result| result.ghost().cloned()),
|
||||
Ok(Some(_))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_authority_equivocates_twice_in_a_round() {
|
||||
let mut justification = make_default_justification(&test_header(1));
|
||||
// there's some code in the original implementation that should return an error when
|
||||
// same authority submits more than two different votes in a single round:
|
||||
// https://github.com/paritytech/finality-grandpa/blob/6aeea2d1159d0f418f0b86e70739f2130629ca09/src/lib.rs#L473
|
||||
// but there's also a code that prevents this from happening:
|
||||
// https://github.com/paritytech/finality-grandpa/blob/6aeea2d1159d0f418f0b86e70739f2130629ca09/src/round.rs#L287
|
||||
// => so now we are also just ignoring all votes from the same authority, except the first one
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
justification.commit.precommits.push(signed_precommit::<TestHeader>(
|
||||
&ALICE,
|
||||
header_id::<TestHeader>(1),
|
||||
justification.round,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
));
|
||||
|
||||
// our implementation succeeds
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Ok(()),
|
||||
);
|
||||
// original implementation returns non-empty GHOST
|
||||
assert_matches!(
|
||||
finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification.votes_ancestries),
|
||||
)
|
||||
.map(|result| result.ghost().cloned()),
|
||||
Ok(Some(_))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_result_when_there_are_not_enough_cumulative_weight_to_finalize_commit_target() {
|
||||
// just remove one authority from the minimal set and we shall not reach the threshold
|
||||
let mut authorities_set = minimal_accounts_set();
|
||||
authorities_set.pop();
|
||||
let justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
authorities: authorities_set,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// our implementation returns an error
|
||||
assert_eq!(
|
||||
verify_justification::<TestHeader>(
|
||||
header_id::<TestHeader>(1),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
&full_voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Err(Error::TooLowCumulativeWeight),
|
||||
);
|
||||
// original implementation returns empty GHOST
|
||||
assert_matches!(
|
||||
finality_grandpa::validate_commit(
|
||||
&justification.commit,
|
||||
&full_voter_set(),
|
||||
&AncestryChain::new(&justification.votes_ancestries),
|
||||
)
|
||||
.map(|result| result.ghost().cloned()),
|
||||
Ok(None)
|
||||
);
|
||||
}
|
||||
@@ -23,13 +23,13 @@ type TestHeader = sp_runtime::testing::Header;
|
||||
|
||||
#[test]
|
||||
fn valid_justification_accepted() {
|
||||
let authorities = vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1), (EVE, 1)];
|
||||
let authorities = vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1)];
|
||||
let params = JustificationGeneratorParams {
|
||||
header: test_header(1),
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: authorities.clone(),
|
||||
votes: 7,
|
||||
ancestors: 7,
|
||||
forks: 3,
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ fn valid_justification_accepted() {
|
||||
);
|
||||
|
||||
assert_eq!(justification.commit.precommits.len(), authorities.len());
|
||||
assert_eq!(justification.votes_ancestries.len(), params.votes as usize);
|
||||
assert_eq!(justification.votes_ancestries.len(), params.ancestors as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -55,7 +55,7 @@ fn valid_justification_accepted_with_single_fork() {
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: vec![(ALICE, 1), (BOB, 1), (CHARLIE, 1), (DAVE, 1), (EVE, 1)],
|
||||
votes: 5,
|
||||
ancestors: 5,
|
||||
forks: 1,
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ fn valid_justification_accepted_with_arbitrary_number_of_authorities() {
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: authorities.clone(),
|
||||
votes: n.into(),
|
||||
ancestors: n.into(),
|
||||
forks: n.into(),
|
||||
};
|
||||
|
||||
@@ -129,7 +129,7 @@ fn justification_with_invalid_commit_rejected() {
|
||||
&voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Err(Error::InvalidJustificationCommit),
|
||||
Err(Error::ExtraHeadersInVotesAncestries),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ fn justification_with_invalid_precommit_ancestry() {
|
||||
&voter_set(),
|
||||
&justification,
|
||||
),
|
||||
Err(Error::InvalidPrecommitAncestries),
|
||||
Err(Error::ExtraHeadersInVotesAncestries),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ fn justification_is_invalid_if_we_dont_meet_threshold() {
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: authorities.clone(),
|
||||
votes: 2 * authorities.len() as u32,
|
||||
ancestors: 2 * authorities.len() as u32,
|
||||
forks: 2,
|
||||
};
|
||||
|
||||
@@ -186,6 +186,6 @@ fn justification_is_invalid_if_we_dont_meet_threshold() {
|
||||
&voter_set(),
|
||||
&make_justification_for_header::<TestHeader>(params)
|
||||
),
|
||||
Err(Error::InvalidJustificationCommit),
|
||||
Err(Error::TooLowCumulativeWeight),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use bp_runtime::{InstanceId, Size};
|
||||
use bp_runtime::{
|
||||
messages::{DispatchFeePayment, MessageDispatchResult},
|
||||
ChainId, Size,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::RuntimeDebug;
|
||||
use sp_std::prelude::*;
|
||||
@@ -31,7 +34,7 @@ pub type Weight = u64;
|
||||
pub type SpecVersion = u32;
|
||||
|
||||
/// A generic trait to dispatch arbitrary messages delivered over the bridge.
|
||||
pub trait MessageDispatch<MessageId> {
|
||||
pub trait MessageDispatch<AccountId, MessageId> {
|
||||
/// A type of the message to be dispatched.
|
||||
type Message: codec::Decode;
|
||||
|
||||
@@ -43,7 +46,8 @@ pub trait MessageDispatch<MessageId> {
|
||||
|
||||
/// Dispatches the message internally.
|
||||
///
|
||||
/// `bridge` indicates instance of deployed bridge where the message came from.
|
||||
/// `source_chain` indicates the chain where the message came from.
|
||||
/// `target_chain` indicates the chain where message dispatch happens.
|
||||
///
|
||||
/// `id` is a short unique identifier of the message.
|
||||
///
|
||||
@@ -51,7 +55,15 @@ pub trait MessageDispatch<MessageId> {
|
||||
/// a sign that some other component has rejected the message even before it has
|
||||
/// reached `dispatch` method (right now this may only be caused if we fail to decode
|
||||
/// the whole message).
|
||||
fn dispatch(bridge: InstanceId, id: MessageId, message: Result<Self::Message, ()>);
|
||||
///
|
||||
/// Returns unspent dispatch weight.
|
||||
fn dispatch<P: FnOnce(&AccountId, Weight) -> Result<(), ()>>(
|
||||
source_chain: ChainId,
|
||||
target_chain: ChainId,
|
||||
id: MessageId,
|
||||
message: Result<Self::Message, ()>,
|
||||
pay_dispatch_fee: P,
|
||||
) -> MessageDispatchResult;
|
||||
}
|
||||
|
||||
/// Origin of a Call when it is dispatched on the target chain.
|
||||
@@ -90,7 +102,7 @@ pub enum CallOrigin<SourceChainAccountId, TargetChainAccountPublic, TargetChainS
|
||||
/// Call is sent by the `SourceChainAccountId` on the source chain. On the target chain it is
|
||||
/// dispatched from a derived account ID.
|
||||
///
|
||||
/// The account ID on the target chain is derived from the source account ID This is useful if
|
||||
/// The account ID on the target chain is derived from the source account ID. This is useful if
|
||||
/// you need a way to represent foreign accounts on this chain for call dispatch purposes.
|
||||
///
|
||||
/// Note that the derived account does not need to have a private key on the target chain. This
|
||||
@@ -109,6 +121,8 @@ pub struct MessagePayload<SourceChainAccountId, TargetChainAccountPublic, Target
|
||||
pub weight: Weight,
|
||||
/// Call origin to be used during dispatch.
|
||||
pub origin: CallOrigin<SourceChainAccountId, TargetChainAccountPublic, TargetChainSignature>,
|
||||
/// Where the fee for dispatching message is paid?
|
||||
pub dispatch_fee_payment: DispatchFeePayment,
|
||||
/// The call itself.
|
||||
pub call: Call,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
|
||||
bitvec = { version = "0.20", default-features = false, features = ["alloc"] }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive", "bit-vec"] }
|
||||
impl-trait-for-tuples = "0.2"
|
||||
serde = { version = "1.0.101", optional = true, features = ["derive"] }
|
||||
|
||||
# Bridge dependencies
|
||||
|
||||
@@ -26,5 +29,6 @@ std = [
|
||||
"codec/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"serde",
|
||||
"sp-std/std"
|
||||
]
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
// Generated by `DecodeLimit::decode_with_depth_limit`
|
||||
#![allow(clippy::unnecessary_mut_passed)]
|
||||
|
||||
use bitvec::prelude::*;
|
||||
use bp_runtime::messages::DispatchFeePayment;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::RuntimeDebug;
|
||||
use sp_std::{collections::vec_deque::VecDeque, prelude::*};
|
||||
@@ -32,12 +34,40 @@ pub mod target_chain;
|
||||
// Weight is reexported to avoid additional frame-support dependencies in related crates.
|
||||
pub use frame_support::weights::Weight;
|
||||
|
||||
/// Messages pallet operating mode.
|
||||
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum OperatingMode {
|
||||
/// Normal mode, when all operations are allowed.
|
||||
Normal,
|
||||
/// The pallet is not accepting outbound messages. Inbound messages and receival proofs
|
||||
/// are still accepted.
|
||||
///
|
||||
/// This mode may be used e.g. when bridged chain expects upgrade. Then to avoid dispatch
|
||||
/// failures, the pallet owner may stop accepting new messages, while continuing to deliver
|
||||
/// queued messages to the bridged chain. Once upgrade is completed, the mode may be switched
|
||||
/// back to `Normal`.
|
||||
RejectingOutboundMessages,
|
||||
/// The pallet is halted. All operations (except operating mode change) are prohibited.
|
||||
Halted,
|
||||
}
|
||||
|
||||
impl Default for OperatingMode {
|
||||
fn default() -> Self {
|
||||
OperatingMode::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// Messages pallet parameter.
|
||||
pub trait Parameter: frame_support::Parameter {
|
||||
/// Save parameter value in the runtime storage.
|
||||
fn save(&self);
|
||||
}
|
||||
|
||||
impl Parameter for () {
|
||||
fn save(&self) {}
|
||||
}
|
||||
|
||||
/// Lane identifier.
|
||||
pub type LaneId = [u8; 4];
|
||||
|
||||
@@ -96,7 +126,7 @@ pub struct InboundLaneData<RelayerId> {
|
||||
/// When a relayer sends a single message, both of MessageNonces are the same.
|
||||
/// When relayer sends messages in a batch, the first arg is the lowest nonce, second arg the highest nonce.
|
||||
/// Multiple dispatches from the same relayer are allowed.
|
||||
pub relayers: VecDeque<(MessageNonce, MessageNonce, RelayerId)>,
|
||||
pub relayers: VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||
|
||||
/// Nonce of the last message that
|
||||
/// a) has been delivered to the target (this) chain and
|
||||
@@ -123,22 +153,106 @@ impl<RelayerId> InboundLaneData<RelayerId> {
|
||||
/// size of each entry.
|
||||
///
|
||||
/// Returns `None` if size overflows `u32` limits.
|
||||
pub fn encoded_size_hint(relayer_id_encoded_size: u32, relayers_entries: u32) -> Option<u32> {
|
||||
pub fn encoded_size_hint(relayer_id_encoded_size: u32, relayers_entries: u32, messages_count: u32) -> Option<u32> {
|
||||
let message_nonce_size = 8;
|
||||
let relayers_entry_size = relayer_id_encoded_size.checked_add(2 * message_nonce_size)?;
|
||||
let relayers_size = relayers_entries.checked_mul(relayers_entry_size)?;
|
||||
relayers_size.checked_add(message_nonce_size)
|
||||
let dispatch_results_per_byte = 8;
|
||||
let dispatch_result_size = sp_std::cmp::max(relayers_entries, messages_count / dispatch_results_per_byte);
|
||||
relayers_size
|
||||
.checked_add(message_nonce_size)
|
||||
.and_then(|result| result.checked_add(dispatch_result_size))
|
||||
}
|
||||
|
||||
/// Nonce of the last message that has been delivered to this (target) chain.
|
||||
pub fn last_delivered_nonce(&self) -> MessageNonce {
|
||||
self.relayers
|
||||
.back()
|
||||
.map(|(_, last_nonce, _)| *last_nonce)
|
||||
.map(|entry| entry.messages.end)
|
||||
.unwrap_or(self.last_confirmed_nonce)
|
||||
}
|
||||
}
|
||||
|
||||
/// Message details, returned by runtime APIs.
|
||||
#[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct MessageDetails<OutboundMessageFee> {
|
||||
/// Nonce assigned to the message.
|
||||
pub nonce: MessageNonce,
|
||||
/// Message dispatch weight, declared by the submitter.
|
||||
pub dispatch_weight: Weight,
|
||||
/// Size of the encoded message.
|
||||
pub size: u32,
|
||||
/// Delivery+dispatch fee paid by the message submitter at the source chain.
|
||||
pub delivery_and_dispatch_fee: OutboundMessageFee,
|
||||
/// Where the fee for dispatching message is paid?
|
||||
pub dispatch_fee_payment: DispatchFeePayment,
|
||||
}
|
||||
|
||||
/// Bit vector of message dispatch results.
|
||||
pub type DispatchResultsBitVec = BitVec<Msb0, u8>;
|
||||
|
||||
/// Unrewarded relayer entry stored in the inbound lane data.
|
||||
///
|
||||
/// This struct represents a continuous range of messages that have been delivered by the same relayer
|
||||
/// and whose confirmations are still pending.
|
||||
#[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct UnrewardedRelayer<RelayerId> {
|
||||
/// Identifier of the relayer.
|
||||
pub relayer: RelayerId,
|
||||
/// Messages range, delivered by this relayer.
|
||||
pub messages: DeliveredMessages,
|
||||
}
|
||||
|
||||
/// Delivered messages with their dispatch result.
|
||||
#[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct DeliveredMessages {
|
||||
/// Nonce of the first message that has been delivered (inclusive).
|
||||
pub begin: MessageNonce,
|
||||
/// Nonce of the last message that has been delivered (inclusive).
|
||||
pub end: MessageNonce,
|
||||
/// Dispatch result (`false`/`true`), returned by the message dispatcher for every
|
||||
/// message in the `[begin; end]` range. See `dispatch_result` field of the
|
||||
/// `bp_runtime::messages::MessageDispatchResult` structure for more information.
|
||||
pub dispatch_results: DispatchResultsBitVec,
|
||||
}
|
||||
|
||||
impl DeliveredMessages {
|
||||
/// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given dispatch result.
|
||||
pub fn new(nonce: MessageNonce, dispatch_result: bool) -> Self {
|
||||
DeliveredMessages {
|
||||
begin: nonce,
|
||||
end: nonce,
|
||||
dispatch_results: bitvec![Msb0, u8; if dispatch_result { 1 } else { 0 }],
|
||||
}
|
||||
}
|
||||
|
||||
/// Note new dispatched message.
|
||||
pub fn note_dispatched_message(&mut self, dispatch_result: bool) {
|
||||
self.end += 1;
|
||||
self.dispatch_results.push(dispatch_result);
|
||||
}
|
||||
|
||||
/// Returns true if delivered messages contain message with given nonce.
|
||||
pub fn contains_message(&self, nonce: MessageNonce) -> bool {
|
||||
(self.begin..=self.end).contains(&nonce)
|
||||
}
|
||||
|
||||
/// Get dispatch result flag by message nonce.
|
||||
///
|
||||
/// Dispatch result flag must be interpreted using the knowledge of dispatch mechanism
|
||||
/// at the target chain. See `dispatch_result` field of the
|
||||
/// `bp_runtime::messages::MessageDispatchResult` structure for more information.
|
||||
///
|
||||
/// Panics if message nonce is not in the `begin..=end` range. Typically you'll first
|
||||
/// check if message is within the range by calling `contains_message`.
|
||||
pub fn message_dispatch_result(&self, nonce: MessageNonce) -> bool {
|
||||
const INVALID_NONCE: &str = "Invalid nonce used to index dispatch_results";
|
||||
|
||||
let index = nonce.checked_sub(self.begin).expect(INVALID_NONCE) as usize;
|
||||
*self.dispatch_results.get(index).expect(INVALID_NONCE)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gist of `InboundLaneData::relayers` field used by runtime APIs.
|
||||
#[derive(Clone, Default, Encode, Decode, RuntimeDebug, PartialEq, Eq)]
|
||||
pub struct UnrewardedRelayersState {
|
||||
@@ -177,12 +291,10 @@ impl Default for OutboundLaneData {
|
||||
/// Returns total number of messages in the `InboundLaneData::relayers` vector.
|
||||
///
|
||||
/// Returns `None` if there are more messages that `MessageNonce` may fit (i.e. `MessageNonce + 1`).
|
||||
pub fn total_unrewarded_messages<RelayerId>(
|
||||
relayers: &VecDeque<(MessageNonce, MessageNonce, RelayerId)>,
|
||||
) -> Option<MessageNonce> {
|
||||
pub fn total_unrewarded_messages<RelayerId>(relayers: &VecDeque<UnrewardedRelayer<RelayerId>>) -> Option<MessageNonce> {
|
||||
match (relayers.front(), relayers.back()) {
|
||||
(Some((begin, _, _)), Some((_, end, _))) => {
|
||||
if let Some(difference) = end.checked_sub(*begin) {
|
||||
(Some(front), Some(back)) => {
|
||||
if let Some(difference) = back.messages.end.checked_sub(front.messages.begin) {
|
||||
difference.checked_add(1)
|
||||
} else {
|
||||
Some(0)
|
||||
@@ -200,9 +312,18 @@ mod tests {
|
||||
fn total_unrewarded_messages_does_not_overflow() {
|
||||
assert_eq!(
|
||||
total_unrewarded_messages(
|
||||
&vec![(0, 0, 1), (MessageNonce::MAX, MessageNonce::MAX, 2)]
|
||||
.into_iter()
|
||||
.collect()
|
||||
&vec![
|
||||
UnrewardedRelayer {
|
||||
relayer: 1,
|
||||
messages: DeliveredMessages::new(0, true)
|
||||
},
|
||||
UnrewardedRelayer {
|
||||
relayer: 2,
|
||||
messages: DeliveredMessages::new(MessageNonce::MAX, true)
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
),
|
||||
None,
|
||||
);
|
||||
@@ -210,19 +331,60 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn inbound_lane_data_returns_correct_hint() {
|
||||
let expected_size = InboundLaneData::<u8>::encoded_size_hint(1, 13);
|
||||
let actual_size = InboundLaneData {
|
||||
relayers: (1u8..=13u8).map(|i| (i as _, i as _, i)).collect(),
|
||||
last_confirmed_nonce: 13,
|
||||
let test_cases = vec![
|
||||
// single relayer, multiple messages
|
||||
(1, 128u8),
|
||||
// multiple relayers, single message per relayer
|
||||
(128u8, 128u8),
|
||||
// several messages per relayer
|
||||
(13u8, 128u8),
|
||||
];
|
||||
for (relayer_entries, messages_count) in test_cases {
|
||||
let expected_size = InboundLaneData::<u8>::encoded_size_hint(1, relayer_entries as _, messages_count as _);
|
||||
let actual_size = InboundLaneData {
|
||||
relayers: (1u8..=relayer_entries)
|
||||
.map(|i| {
|
||||
let mut entry = UnrewardedRelayer {
|
||||
relayer: i,
|
||||
messages: DeliveredMessages::new(i as _, true),
|
||||
};
|
||||
entry.messages.dispatch_results = bitvec![
|
||||
Msb0, u8;
|
||||
1;
|
||||
(messages_count / relayer_entries) as _
|
||||
];
|
||||
entry
|
||||
})
|
||||
.collect(),
|
||||
last_confirmed_nonce: messages_count as _,
|
||||
}
|
||||
.encode()
|
||||
.len();
|
||||
let difference = (expected_size.unwrap() as f64 - actual_size as f64).abs();
|
||||
assert!(
|
||||
difference / (std::cmp::min(actual_size, expected_size.unwrap() as usize) as f64) < 0.1,
|
||||
"Too large difference between actual ({}) and expected ({:?}) inbound lane data size. Test case: {}+{}",
|
||||
actual_size,
|
||||
expected_size,
|
||||
relayer_entries,
|
||||
messages_count,
|
||||
);
|
||||
}
|
||||
.encode()
|
||||
.len();
|
||||
let difference = (expected_size.unwrap() as f64 - actual_size as f64).abs();
|
||||
assert!(
|
||||
difference / (std::cmp::min(actual_size, expected_size.unwrap() as usize) as f64) < 0.1,
|
||||
"Too large difference between actual ({}) and expected ({:?}) inbound lane data size",
|
||||
actual_size,
|
||||
expected_size,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_dispatch_result_works() {
|
||||
let delivered_messages = DeliveredMessages {
|
||||
begin: 100,
|
||||
end: 150,
|
||||
dispatch_results: bitvec![Msb0, u8; 1; 151],
|
||||
};
|
||||
|
||||
assert!(!delivered_messages.contains_message(99));
|
||||
assert!(delivered_messages.contains_message(100));
|
||||
assert!(delivered_messages.contains_message(150));
|
||||
assert!(!delivered_messages.contains_message(151));
|
||||
|
||||
assert!(delivered_messages.message_dispatch_result(125));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
//! Primitives of messages module, that are used on the source chain.
|
||||
|
||||
use crate::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData};
|
||||
use crate::{DeliveredMessages, InboundLaneData, LaneId, MessageNonce, OutboundLaneData};
|
||||
|
||||
use bp_runtime::Size;
|
||||
use frame_support::{Parameter, RuntimeDebug};
|
||||
@@ -135,6 +135,15 @@ pub trait MessageDeliveryAndDispatchPayment<AccountId, Balance> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for messages delivery confirmation.
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
pub trait OnDeliveryConfirmed {
|
||||
/// Called when we receive confirmation that our messages have been delivered to the
|
||||
/// target chain. The confirmation also has single bit dispatch result for every
|
||||
/// confirmed message (see `DeliveredMessages` for details).
|
||||
fn on_messages_delivered(_lane: &LaneId, _messages: &DeliveredMessages) {}
|
||||
}
|
||||
|
||||
/// Structure that may be used in place of `TargetHeaderChain`, `LaneMessageVerifier` and
|
||||
/// `MessageDeliveryAndDispatchPayment` on chains, where outbound messages are forbidden.
|
||||
pub struct ForbidOutboundMessages;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
use crate::{LaneId, Message, MessageData, MessageKey, OutboundLaneData};
|
||||
|
||||
use bp_runtime::Size;
|
||||
use bp_runtime::{messages::MessageDispatchResult, Size};
|
||||
use codec::{Decode, Encode, Error as CodecError};
|
||||
use frame_support::{weights::Weight, Parameter, RuntimeDebug};
|
||||
use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, prelude::*};
|
||||
@@ -84,7 +84,7 @@ pub trait SourceHeaderChain<Fee> {
|
||||
}
|
||||
|
||||
/// Called when inbound message is received.
|
||||
pub trait MessageDispatch<Fee> {
|
||||
pub trait MessageDispatch<AccountId, Fee> {
|
||||
/// Decoded message payload type. Valid message may contain invalid payload. In this case
|
||||
/// message is delivered, but dispatch fails. Therefore, two separate types of payload
|
||||
/// (opaque `MessagePayload` used in delivery and this `DispatchPayload` used in dispatch).
|
||||
@@ -100,7 +100,13 @@ pub trait MessageDispatch<Fee> {
|
||||
///
|
||||
/// It is up to the implementers of this trait to determine whether the message
|
||||
/// is invalid (i.e. improperly encoded, has too large weight, ...) or not.
|
||||
fn dispatch(message: DispatchMessage<Self::DispatchPayload, Fee>);
|
||||
///
|
||||
/// If your configuration allows paying dispatch fee at the target chain, then
|
||||
/// it must be paid inside this method to the `relayer_account`.
|
||||
fn dispatch(
|
||||
relayer_account: &AccountId,
|
||||
message: DispatchMessage<Self::DispatchPayload, Fee>,
|
||||
) -> MessageDispatchResult;
|
||||
}
|
||||
|
||||
impl<Message> Default for ProvedLaneMessages<Message> {
|
||||
@@ -149,12 +155,18 @@ impl<Fee> SourceHeaderChain<Fee> for ForbidInboundMessages {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fee> MessageDispatch<Fee> for ForbidInboundMessages {
|
||||
impl<AccountId, Fee> MessageDispatch<AccountId, Fee> for ForbidInboundMessages {
|
||||
type DispatchPayload = ();
|
||||
|
||||
fn dispatch_weight(_message: &DispatchMessage<Self::DispatchPayload, Fee>) -> Weight {
|
||||
Weight::MAX
|
||||
}
|
||||
|
||||
fn dispatch(_message: DispatchMessage<Self::DispatchPayload, Fee>) {}
|
||||
fn dispatch(_: &AccountId, _: DispatchMessage<Self::DispatchPayload, Fee>) -> MessageDispatchResult {
|
||||
MessageDispatchResult {
|
||||
dispatch_result: false,
|
||||
unspent_weight: 0,
|
||||
dispatch_fee_paid_during_dispatch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use frame_support::{
|
||||
dispatch::Dispatchable,
|
||||
parameter_types,
|
||||
weights::{
|
||||
constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_PER_SECOND},
|
||||
constants::{BlockExecutionWeight, WEIGHT_PER_SECOND},
|
||||
DispatchClass, Weight,
|
||||
},
|
||||
Blake2_128Concat, RuntimeDebug, StorageHasher, Twox128,
|
||||
@@ -33,13 +33,13 @@ use sp_core::Hasher as HasherT;
|
||||
use sp_runtime::{
|
||||
generic,
|
||||
traits::{BlakeTwo256, IdentifyAccount, Verify},
|
||||
MultiAddress, MultiSignature, OpaqueExtrinsic, Perbill,
|
||||
MultiAddress, MultiSignature, OpaqueExtrinsic,
|
||||
};
|
||||
use sp_std::prelude::Vec;
|
||||
|
||||
// Re-export's to avoid extra substrate dependencies in chain-specific crates.
|
||||
pub use frame_support::Parameter;
|
||||
pub use sp_runtime::traits::Convert;
|
||||
pub use frame_support::{weights::constants::ExtrinsicBaseWeight, Parameter};
|
||||
pub use sp_runtime::{traits::Convert, Perbill};
|
||||
|
||||
/// Number of extra bytes (excluding size of storage value itself) of storage proof, built at
|
||||
/// Polkadot-like chain. This mostly depends on number of entries in the storage trie.
|
||||
|
||||
@@ -29,29 +29,31 @@ pub use storage_proof::{Error as StorageProofError, StorageProofChecker};
|
||||
#[cfg(feature = "std")]
|
||||
pub use storage_proof::craft_valid_storage_proof;
|
||||
|
||||
pub mod messages;
|
||||
|
||||
mod chain;
|
||||
mod storage_proof;
|
||||
|
||||
/// Use this when something must be shared among all instances.
|
||||
pub const NO_INSTANCE_ID: InstanceId = [0, 0, 0, 0];
|
||||
pub const NO_INSTANCE_ID: ChainId = [0, 0, 0, 0];
|
||||
|
||||
/// Bridge-with-Rialto instance id.
|
||||
pub const RIALTO_BRIDGE_INSTANCE: InstanceId = *b"rlto";
|
||||
pub const RIALTO_CHAIN_ID: ChainId = *b"rlto";
|
||||
|
||||
/// Bridge-with-Millau instance id.
|
||||
pub const MILLAU_BRIDGE_INSTANCE: InstanceId = *b"mlau";
|
||||
pub const MILLAU_CHAIN_ID: ChainId = *b"mlau";
|
||||
|
||||
/// Bridge-with-Polkadot instance id.
|
||||
pub const POLKADOT_BRIDGE_INSTANCE: InstanceId = *b"pdot";
|
||||
pub const POLKADOT_CHAIN_ID: ChainId = *b"pdot";
|
||||
|
||||
/// Bridge-with-Kusama instance id.
|
||||
pub const KUSAMA_BRIDGE_INSTANCE: InstanceId = *b"ksma";
|
||||
pub const KUSAMA_CHAIN_ID: ChainId = *b"ksma";
|
||||
|
||||
/// Bridge-with-Rococo instance id.
|
||||
pub const ROCOCO_BRIDGE_INSTANCE: InstanceId = *b"roco";
|
||||
pub const ROCOCO_CHAIN_ID: ChainId = *b"roco";
|
||||
|
||||
/// Bridge-with-Wococo instance id.
|
||||
pub const WOCOCO_BRIDGE_INSTANCE: InstanceId = *b"woco";
|
||||
pub const WOCOCO_CHAIN_ID: ChainId = *b"woco";
|
||||
|
||||
/// Call-dispatch module prefix.
|
||||
pub const CALL_DISPATCH_MODULE_PREFIX: &[u8] = b"pallet-bridge/dispatch";
|
||||
@@ -62,11 +64,13 @@ pub const ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/
|
||||
/// A unique prefix for entropy when generating a cross-chain account ID for the Root account.
|
||||
pub const ROOT_ACCOUNT_DERIVATION_PREFIX: &[u8] = b"pallet-bridge/account-derivation/root";
|
||||
|
||||
/// Id of deployed module instance. We have a bunch of pallets that may be used in
|
||||
/// different bridges. E.g. messages pallet may be deployed twice in the same
|
||||
/// runtime to bridge ThisChain with Chain1 and Chain2. Sometimes we need to be able
|
||||
/// to identify deployed instance dynamically. This type is used for that.
|
||||
pub type InstanceId = [u8; 4];
|
||||
/// Unique identifier of the chain.
|
||||
///
|
||||
/// In addition to its main function (identifying the chain), this type may also be used to
|
||||
/// identify module instance. We have a bunch of pallets that may be used in different bridges. E.g.
|
||||
/// messages pallet may be deployed twice in the same runtime to bridge ThisChain with Chain1 and Chain2.
|
||||
/// Sometimes we need to be able to identify deployed instance dynamically. This type may be used for that.
|
||||
pub type ChainId = [u8; 4];
|
||||
|
||||
/// Type of accounts on the source chain.
|
||||
pub enum SourceAccount<T> {
|
||||
@@ -90,7 +94,7 @@ pub enum SourceAccount<T> {
|
||||
/// Note: If the same `bridge_id` is used across different chains (for example, if one source chain
|
||||
/// is bridged to multiple target chains), then all the derived accounts would be the same across
|
||||
/// the different chains. This could negatively impact users' privacy across chains.
|
||||
pub fn derive_account_id<AccountId>(bridge_id: InstanceId, id: SourceAccount<AccountId>) -> H256
|
||||
pub fn derive_account_id<AccountId>(bridge_id: ChainId, id: SourceAccount<AccountId>) -> H256
|
||||
where
|
||||
AccountId: Encode,
|
||||
{
|
||||
@@ -107,7 +111,7 @@ where
|
||||
///
|
||||
/// The account ID can be the same across different instances of `pallet-bridge-messages` if the same
|
||||
/// `bridge_id` is used.
|
||||
pub fn derive_relayer_fund_account_id(bridge_id: InstanceId) -> H256 {
|
||||
pub fn derive_relayer_fund_account_id(bridge_id: ChainId) -> H256 {
|
||||
("relayer-fund-account", bridge_id).using_encoded(blake2_256).into()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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/>.
|
||||
|
||||
//! Primitives that may be used by different message delivery and dispatch mechanisms.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{weights::Weight, RuntimeDebug};
|
||||
|
||||
/// Where message dispatch fee is paid?
|
||||
#[derive(Encode, Decode, RuntimeDebug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DispatchFeePayment {
|
||||
/// The dispacth fee is paid at the source chain.
|
||||
AtSourceChain,
|
||||
/// The dispatch fee is paid at the target chain.
|
||||
///
|
||||
/// The fee will be paid right before the message is dispatched. So in case of any other
|
||||
/// issues (like invalid call encoding, invalid signature, ...) the dispatch module won't
|
||||
/// do any direct transfers. Instead, it'll return fee related to this message dispatch to the
|
||||
/// relayer.
|
||||
AtTargetChain,
|
||||
}
|
||||
|
||||
/// Message dispatch result.
|
||||
#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq)]
|
||||
pub struct MessageDispatchResult {
|
||||
/// Dispatch result flag. This flag is relayed back to the source chain and, generally
|
||||
/// speaking, may bring any (that fits in single bit) information from the dispatcher at
|
||||
/// the target chain to the message submitter at the source chain. If you're using immediate
|
||||
/// call dispatcher, then it'll be result of the dispatch - `true` if dispatch has succeeded
|
||||
/// and `false` otherwise.
|
||||
pub dispatch_result: bool,
|
||||
/// Unspent dispatch weight. This weight that will be deducted from total delivery transaction
|
||||
/// weight, thus reducing the transaction cost. This shall not be zero in (at least) two cases:
|
||||
///
|
||||
/// 1) if message has been dispatched successfully, but post-dispatch weight is less than
|
||||
/// the weight, declared by the message sender;
|
||||
/// 2) if message has not been dispatched at all.
|
||||
pub unspent_weight: Weight,
|
||||
/// Whether the message dispatch fee has been paid during dispatch. This will be true if your
|
||||
/// configuration supports pay-dispatch-fee-at-target-chain option and message sender has enabled
|
||||
/// this option.
|
||||
pub dispatch_fee_paid_during_dispatch: bool,
|
||||
}
|
||||
@@ -7,9 +7,9 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
[dependencies]
|
||||
bp-header-chain = { path = "../header-chain", default-features = false }
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] }
|
||||
finality-grandpa = { version = "0.14.1", default-features = false }
|
||||
parity-scale-codec = { version = "2.0.0", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||
@@ -19,9 +19,9 @@ sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", d
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-chain/std",
|
||||
"codec/std",
|
||||
"ed25519-dalek/std",
|
||||
"finality-grandpa/std",
|
||||
"parity-scale-codec/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-finality-grandpa/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
//! Utilities for working with test accounts.
|
||||
|
||||
use codec::Encode;
|
||||
use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use parity_scale_codec::Encode;
|
||||
use sp_application_crypto::Public;
|
||||
use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthorityWeight};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use bp_header_chain::justification::GrandpaJustification;
|
||||
use codec::Encode;
|
||||
use sp_application_crypto::TryFrom;
|
||||
use sp_finality_grandpa::{AuthorityId, AuthorityWeight};
|
||||
use sp_finality_grandpa::{AuthoritySignature, SetId};
|
||||
@@ -46,10 +47,10 @@ pub struct JustificationGeneratorParams<H> {
|
||||
///
|
||||
/// The size of the set will determine the number of pre-commits in our justification.
|
||||
pub authorities: Vec<(Account, AuthorityWeight)>,
|
||||
/// The total number of vote ancestries in our justification.
|
||||
/// The total number of precommit ancestors in the `votes_ancestries` field our justification.
|
||||
///
|
||||
/// These may be distributed among many different forks.
|
||||
pub votes: u32,
|
||||
pub ancestors: u32,
|
||||
/// The number of forks.
|
||||
///
|
||||
/// Useful for creating a "worst-case" scenario in which each authority is on its own fork.
|
||||
@@ -63,7 +64,7 @@ impl<H: HeaderT> Default for JustificationGeneratorParams<H> {
|
||||
round: TEST_GRANDPA_ROUND,
|
||||
set_id: TEST_GRANDPA_SET_ID,
|
||||
authorities: test_keyring(),
|
||||
votes: 2,
|
||||
ancestors: 2,
|
||||
forks: 1,
|
||||
}
|
||||
}
|
||||
@@ -94,35 +95,33 @@ pub fn make_justification_for_header<H: HeaderT>(params: JustificationGeneratorP
|
||||
round,
|
||||
set_id,
|
||||
authorities,
|
||||
mut votes,
|
||||
mut ancestors,
|
||||
forks,
|
||||
} = params;
|
||||
|
||||
let (target_hash, target_number) = (header.hash(), *header.number());
|
||||
let mut precommits = vec![];
|
||||
let mut votes_ancestries = vec![];
|
||||
let mut precommits = vec![];
|
||||
|
||||
assert!(forks != 0, "Need at least one fork to have a chain..");
|
||||
assert!(votes >= forks, "Need at least one header per fork.");
|
||||
assert!(
|
||||
forks as usize <= authorities.len(),
|
||||
"If we have more forks than authorities we can't create valid pre-commits for all the forks."
|
||||
);
|
||||
|
||||
// Roughly, how many vote ancestries do we want per fork
|
||||
let target_depth = (votes + forks - 1) / forks;
|
||||
let target_depth = (ancestors + forks - 1) / forks;
|
||||
|
||||
let mut unsigned_precommits = vec![];
|
||||
for i in 0..forks {
|
||||
let depth = if votes >= target_depth {
|
||||
votes -= target_depth;
|
||||
let depth = if ancestors >= target_depth {
|
||||
ancestors -= target_depth;
|
||||
target_depth
|
||||
} else {
|
||||
votes
|
||||
ancestors
|
||||
};
|
||||
|
||||
// Note: Adding 1 to account for the target header
|
||||
let chain = generate_chain(i as u8, depth + 1, &header);
|
||||
let chain = generate_chain(i as u32, depth + 1, &header);
|
||||
|
||||
// We don't include our finality target header in the vote ancestries
|
||||
for child in &chain[1..] {
|
||||
@@ -138,7 +137,7 @@ pub fn make_justification_for_header<H: HeaderT>(params: JustificationGeneratorP
|
||||
for (i, (id, _weight)) in authorities.iter().enumerate() {
|
||||
// Assign authorities to sign pre-commits in a round-robin fashion
|
||||
let target = unsigned_precommits[i % forks as usize];
|
||||
let precommit = signed_precommit::<H>(&id, target, round, set_id);
|
||||
let precommit = signed_precommit::<H>(id, target, round, set_id);
|
||||
|
||||
precommits.push(precommit);
|
||||
}
|
||||
@@ -154,7 +153,7 @@ pub fn make_justification_for_header<H: HeaderT>(params: JustificationGeneratorP
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_chain<H: HeaderT>(fork_id: u8, depth: u32, ancestor: &H) -> Vec<H> {
|
||||
fn generate_chain<H: HeaderT>(fork_id: u32, depth: u32, ancestor: &H) -> Vec<H> {
|
||||
let mut headers = vec![ancestor.clone()];
|
||||
|
||||
for i in 1..depth {
|
||||
@@ -169,7 +168,7 @@ fn generate_chain<H: HeaderT>(fork_id: u8, depth: u32, ancestor: &H) -> Vec<H> {
|
||||
header
|
||||
.digest_mut()
|
||||
.logs
|
||||
.push(sp_runtime::DigestItem::Other(vec![fork_id]));
|
||||
.push(sp_runtime::DigestItem::Other(fork_id.encode()));
|
||||
|
||||
headers.push(header);
|
||||
}
|
||||
@@ -177,7 +176,8 @@ fn generate_chain<H: HeaderT>(fork_id: u8, depth: u32, ancestor: &H) -> Vec<H> {
|
||||
headers
|
||||
}
|
||||
|
||||
fn signed_precommit<H: HeaderT>(
|
||||
/// Create signed precommit with given target.
|
||||
pub fn signed_precommit<H: HeaderT>(
|
||||
signer: &Account,
|
||||
target: (H::Hash, H::Number),
|
||||
round: u64,
|
||||
|
||||
Reference in New Issue
Block a user