Skip to content
Commits on Source (43)
  • dependabot[bot]'s avatar
    Bump thiserror from 1.0.57 to 1.0.58 · 9be3e0b6
    dependabot[bot] authored
    
    
    Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.57 to 1.0.58.
    - [Release notes](https://github.com/dtolnay/thiserror/releases)
    - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.57...1.0.58)
    
    ---
    updated-dependencies:
    - dependency-name: thiserror
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    9be3e0b6
  • dependabot[bot]'s avatar
    Bump scale-info from 2.10.0 to 2.11.0 · d9c5e410
    dependabot[bot] authored
    
    
    Bumps [scale-info](https://github.com/paritytech/scale-info) from 2.10.0 to 2.11.0.
    - [Release notes](https://github.com/paritytech/scale-info/releases)
    - [Changelog](https://github.com/paritytech/scale-info/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/paritytech/scale-info/compare/v2.10.0...v2.11.0)
    
    ---
    updated-dependencies:
    - dependency-name: scale-info
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    d9c5e410
  • dependabot[bot]'s avatar
    Bump anyhow from 1.0.80 to 1.0.81 · 61e865bc
    dependabot[bot] authored
    
    
    Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.80 to 1.0.81.
    - [Release notes](https://github.com/dtolnay/anyhow/releases)
    - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.80...1.0.81)
    
    ---
    updated-dependencies:
    - dependency-name: anyhow
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    61e865bc
  • dependabot[bot]'s avatar
    Bump async-trait from 0.1.77 to 0.1.78 · 62372e74
    dependabot[bot] authored
    
    
    Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.77 to 0.1.78.
    - [Release notes](https://github.com/dtolnay/async-trait/releases)
    - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.77...0.1.78)
    
    ---
    updated-dependencies:
    - dependency-name: async-trait
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    62372e74
  • Svyatoslav Nikolsky's avatar
    9cb8a2ca
  • Serban Iorga's avatar
    Move generic CLI logic to different crate (#2885) · 2a76cbbb
    Serban Iorga authored
    * Move generic CLI logic to separate crate
    
    * Move and rename `CliChain` trait definition
    
    Move it to `relay-substrate-client`
    
    * Move generic cli logic to substrate-relay-helper
    
    * Fix docs warnings
    2a76cbbb
  • Serban Iorga's avatar
    Backport changes from `polkadot-sdk/master` (#2887) · 28c459be
    Serban Iorga authored
    
    
    * Add two new zombienet tests for bridges (manual run) (#3072)
    
    extracted useful code from #2982
    
    This PR:
    - adds test 2 for Rococo <> Westend bridge: checks that relayer doesn't
    submit any extra headers while there are no any messages;
    - adds test 3 for Rococo <> Westend bridge: checks that relayer doesn't
    submit any extra headers when there are messages;
    - fixes most of comments from #2439 (like: log names, ability to run
    specify test number when calling `run-tests.sh`).
    
    Right now of all our tests, only test 2 is working (until BHs will be
    upgraded to use async backing), so you can test it with
    `./bridges/zombienet/run-tests.sh --test 2` locally.
    
    (cherry picked from commit 2e6067d768a84e780258aa4580116f7180e24290)
    
    * [cumulus] Improved check for sane bridge fees calculations (#3175)
    
    - [x] change constants when CI fails (should fail :) )
    
    On the AssetHubRococo: 1701175800126 -> 1700929825257 = 0.15 %
    decreased.
    ```
    Feb 02 12:59:05.520 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1701175800126 for runtime: statemine-1006000 (statemine-0.tx14.au1)
    
    Feb 02 13:02:40.647 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1700929825257 for runtime: statemine-1006000 (statemine-0.tx14.au1)
    
    ```
    
    On the AssetHubWestend: 2116038876326 -> 1641718372993 = 22.4 %
    decreased.
    ```
    Feb 02 12:56:00.880 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 2116038876326 for runtime: westmint-1006000 (westmint-0.tx14.au1)
    
    Feb 02 13:04:42.515 ERROR bridges::estimate: `bridging::XcmBridgeHubRouterBaseFee` actual value: 1641718372993 for runtime: westmint-1006000 (westmint-0.tx14.au1)
    ```
    
    (cherry picked from commit 74b597fcaf143d8dd7f8d40e59f51065514f21d7)
    
    * Enable async backing on all testnet system chains (#2949)
    
    Built on top of https://github.com/paritytech/polkadot-sdk/pull/2826/
    which was a trial run.
    
    Guide:
    https://github.com/w3f/polkadot-wiki/blob/master/docs/maintain/maintain-guides-async-backing.md
    
    ---------
    
    Signed-off-by: default avatargeorgepisaltu <[email protected]>
    Co-authored-by: default avatarBranislav Kontur <[email protected]>
    Co-authored-by: default avatarDónal Murray <[email protected]>
    Co-authored-by: default avatarDmitry Sinyavin <[email protected]>
    Co-authored-by: default avatars0me0ne-unkn0wn <[email protected]>
    Co-authored-by: default avatarSvyatoslav Nikolsky <[email protected]>
    Co-authored-by: default avatarBastian Köcher <[email protected]>
    Co-authored-by: default avatargeorgepisaltu <[email protected]>
    (cherry picked from commit 700d5f85b768fe1867660938aa5edfcf4b26f632)
    
    * Introduce submit_finality_proof_ex call to bridges GRANDPA pallet (#3225)
    
    backport of
    https://github.com/paritytech/parity-bridges-common/pull/2821 (see
    detailed description there)
    
    (cherry picked from commit a462207158360b162228d9877fed7b9ca1f23fc2)
    
    * Bridge zombienet tests refactoring (#3260)
    
    Related to https://github.com/paritytech/polkadot-sdk/issues/3242
    
    Reorganizing the bridge zombienet tests in order to:
    - separate the environment spawning from the actual tests
    - offer better control over the tests and some possibility to
    orchestrate them as opposed to running everything from the zndsl file
    
    Only rewrote the asset transfer test using this new "framework". The old
    logic and old tests weren't functionally modified or deleted. The plan
    is to get feedback on this approach first and if this is agreed upon,
    migrate the other 2 tests later in separate PRs and also do other
    improvements later.
    
    (cherry picked from commit dfc8e4696c6edfb76ccb05f469a221ebb5b270ff)
    
    * Bridges: add test 0002 to CI (#3310)
    
    Bridges: add test 0002 to CI
    (cherry picked from commit 1b66bb51b52d3e6cacf155bd3e038b6ef44ac5da)
    
    * Bridge zombienet tests - move all test scripts to the same folder (#3333)
    
    Related to https://github.com/paritytech/polkadot-sdk/issues/3242
    
    (cherry picked from commit 5fc7622cb312f2d32ec8365012ee0a49622db8c8)
    
    * Lift dependencies to the workspace (Part 2/x) (#3366)
    
    Lifting some more dependencies to the workspace. Just using the
    most-often updated ones for now.
    It can be reproduced locally.
    
    ```sh
    $ zepter transpose dependency lift-to-workspace --ignore-errors syn quote thiserror "regex:^serde.*"
    
    $ zepter transpose dependency lift-to-workspace --version-resolver=highest syn quote thiserror "regex:^serde.*" --fix
    
    $ taplo format --config .config/taplo.toml
    ```
    
    ---------
    
    Signed-off-by: default avatarOliver Tale-Yazdi <[email protected]>
    (cherry picked from commit e89d0fca351de0712f104c55fe45ed124b5c6968)
    
    * Add support for BHP local and BHK local (#3443)
    
    Related to https://github.com/paritytech/polkadot-sdk/issues/3400
    
    Extracting small parts of
    https://github.com/paritytech/polkadot-sdk/pull/3429 into separate PR:
    
    - Add support for BHP local and BHK local
    - Increase the timeout for the bridge zomienet tests
    
    (cherry picked from commit e4b6b8cd7973633f86d1b92a56abf2a946b7be84)
    
    * Bridge zombienet tests: move all "framework" files under one folder (#3462)
    
    Related to https://github.com/paritytech/polkadot-sdk/issues/3400
    
    Moving all bridges testing "framework" files under one folder in order
    to be able to download the entire folder when we want to add tests in
    other repos
    
    No significant functional changes
    
    (cherry picked from commit 6fc1d41d4487b9164451cd8214674ce195ab06a0)
    
    * Bridge zombienet tests: Check amount received at destination (#3490)
    
    Related to https://github.com/paritytech/polkadot-sdk/issues/3475
    
    (cherry picked from commit 2cdda0e62dd3088d2fd09cea627059674070c277)
    
    * FRAME: Create `TransactionExtension` as a replacement for `SignedExtension` (#2280)
    
    Closes #2160
    
    First part of [Extrinsic
    Horizon](https://github.com/paritytech/polkadot-sdk/issues/2415)
    
    Introduces a new trait `TransactionExtension` to replace
    `SignedExtension`. Introduce the idea of transactions which obey the
    runtime's extensions and have according Extension data (né Extra data)
    yet do not have hard-coded signatures.
    
    Deprecate the terminology of "Unsigned" when used for
    transactions/extrinsics owing to there now being "proper" unsigned
    transactions which obey the extension framework and "old-style" unsigned
    which do not. Instead we have __*General*__ for the former and
    __*Bare*__ for the latter. (Ultimately, the latter will be phased out as
    a type of transaction, and Bare will only be used for Inherents.)
    
    Types of extrinsic are now therefore:
    - Bare (no hardcoded signature, no Extra data; used to be known as
    "Unsigned")
    - Bare transactions (deprecated): Gossiped, validated with
    `ValidateUnsigned` (deprecated) and the `_bare_compat` bits of
    `TransactionExtension` (deprecated).
      - Inherents: Not gossiped, validated with `ProvideInherent`.
    - Extended (Extra data): Gossiped, validated via `TransactionExtension`.
      - Signed transactions (with a hardcoded signature).
      - General transactions (without a hardcoded signature).
    
    `TransactionExtension` differs from `SignedExtension` because:
    - A signature on the underlying transaction may validly not be present.
    - It may alter the origin during validation.
    - `pre_dispatch` is renamed to `prepare` and need not contain the checks
    present in `validate`.
    - `validate` and `prepare` is passed an `Origin` rather than a
    `AccountId`.
    - `validate` may pass arbitrary information into `prepare` via a new
    user-specifiable type `Val`.
    - `AdditionalSigned`/`additional_signed` is renamed to
    `Implicit`/`implicit`. It is encoded *for the entire transaction* and
    passed in to each extension as a new argument to `validate`. This
    facilitates the ability of extensions to acts as underlying crypto.
    
    There is a new `DispatchTransaction` trait which contains only default
    function impls and is impl'ed for any `TransactionExtension` impler. It
    provides several utility functions which reduce some of the tedium from
    using `TransactionExtension` (indeed, none of its regular functions
    should now need to be called directly).
    
    Three transaction version discriminator ("versions") are now
    permissible:
    - 0b000000100: Bare (used to be called "Unsigned"): contains Signature
    or Extra (extension data). After bare transactions are no longer
    supported, this will strictly identify an Inherents only.
    - 0b100000100: Old-school "Signed" Transaction: contains Signature and
    Extra (extension data).
    - 0b010000100: New-school "General" Transaction: contains Extra
    (extension data), but no Signature.
    
    For the New-school General Transaction, it becomes trivial for authors
    to publish extensions to the mechanism for authorizing an Origin, e.g.
    through new kinds of key-signing schemes, ZK proofs, pallet state,
    mutations over pre-authenticated origins or any combination of the
    above.
    
    Wrap your `SignedExtension`s in `AsTransactionExtension`. This should be
    accompanied by renaming your aggregate type in line with the new
    terminology. E.g. Before:
    
    ```rust
    /// The SignedExtension to the basic transaction logic.
    pub type SignedExtra = (
    	/* snip */
    	MySpecialSignedExtension,
    );
    /// Unchecked extrinsic type as expected by this runtime.
    pub type UncheckedExtrinsic =
    	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>;
    ```
    
    After:
    
    ```rust
    /// The extension to the basic transaction logic.
    pub type TxExtension = (
    	/* snip */
    	AsTransactionExtension<MySpecialSignedExtension>,
    );
    /// Unchecked extrinsic type as expected by this runtime.
    pub type UncheckedExtrinsic =
    	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
    ```
    
    You'll also need to alter any transaction building logic to add a
    `.into()` to make the conversion happen. E.g. Before:
    
    ```rust
    fn construct_extrinsic(
    		/* snip */
    ) -> UncheckedExtrinsic {
    	let extra: SignedExtra = (
    		/* snip */
    		MySpecialSignedExtension::new(/* snip */),
    	);
    	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
    	let signature = payload.using_encoded(|e| sender.sign(e));
    	UncheckedExtrinsic::new_signed(
    		/* snip */
    		Signature::Sr25519(signature),
    		extra,
    	)
    }
    ```
    
    After:
    
    ```rust
    fn construct_extrinsic(
    		/* snip */
    ) -> UncheckedExtrinsic {
    	let tx_ext: TxExtension = (
    		/* snip */
    		MySpecialSignedExtension::new(/* snip */).into(),
    	);
    	let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap();
    	let signature = payload.using_encoded(|e| sender.sign(e));
    	UncheckedExtrinsic::new_signed(
    		/* snip */
    		Signature::Sr25519(signature),
    		tx_ext,
    	)
    }
    ```
    
    Most `SignedExtension`s can be trivially converted to become a
    `TransactionExtension`. There are a few things to know.
    
    - Instead of a single trait like `SignedExtension`, you should now
    implement two traits individually: `TransactionExtensionBase` and
    `TransactionExtension`.
    - Weights are now a thing and must be provided via the new function `fn
    weight`.
    
    This trait takes care of anything which is not dependent on types
    specific to your runtime, most notably `Call`.
    
    - `AdditionalSigned`/`additional_signed` is renamed to
    `Implicit`/`implicit`.
    - Weight must be returned by implementing the `weight` function. If your
    extension is associated with a pallet, you'll probably want to do this
    via the pallet's existing benchmarking infrastructure.
    
    Generally:
    - `pre_dispatch` is now `prepare` and you *should not reexecute the
    `validate` functionality in there*!
    - You don't get an account ID any more; you get an origin instead. If
    you need to presume an account ID, then you can use the trait function
    `AsSystemOriginSigner::as_system_origin_signer`.
    - You get an additional ticket, similar to `Pre`, called `Val`. This
    defines data which is passed from `validate` into `prepare`. This is
    important since you should not be duplicating logic from `validate` to
    `prepare`, you need a way of passing your working from the former into
    the latter. This is it.
    - This trait takes two type parameters: `Call` and `Context`. `Call` is
    the runtime call type which used to be an associated type; you can just
    move it to become a type parameter for your trait impl. `Context` is not
    currently used and you can safely implement over it as an unbounded
    type.
    - There's no `AccountId` associated type any more. Just remove it.
    
    Regarding `validate`:
    - You get three new parameters in `validate`; all can be ignored when
    migrating from `SignedExtension`.
    - `validate` returns a tuple on success; the second item in the tuple is
    the new ticket type `Self::Val` which gets passed in to `prepare`. If
    you use any information extracted during `validate` (off-chain and
    on-chain, non-mutating) in `prepare` (on-chain, mutating) then you can
    pass it through with this. For the tuple's last item, just return the
    `origin` argument.
    
    Regarding `prepare`:
    - This is renamed from `pre_dispatch`, but there is one change:
    - FUNCTIONALITY TO VALIDATE THE TRANSACTION NEED NOT BE DUPLICATED FROM
    `validate`!!
    - (This is different to `SignedExtension` which was required to run the
    same checks in `pre_dispatch` as in `validate`.)
    
    Regarding `post_dispatch`:
    - Since there are no unsigned transactions handled by
    `TransactionExtension`, `Pre` is always defined, so the first parameter
    is `Self::Pre` rather than `Option<Self::Pre>`.
    
    If you make use of `SignedExtension::validate_unsigned` or
    `SignedExtension::pre_dispatch_unsigned`, then:
    - Just use the regular versions of these functions instead.
    - Have your logic execute in the case that the `origin` is `None`.
    - Ensure your transaction creation logic creates a General Transaction
    rather than a Bare Transaction; this means having to include all
    `TransactionExtension`s' data.
    - `ValidateUnsigned` can still be used (for now) if you need to be able
    to construct transactions which contain none of the extension data,
    however these will be phased out in stage 2 of the Transactions Horizon,
    so you should consider moving to an extension-centric design.
    
    - [x] Introduce `CheckSignature` impl of `TransactionExtension` to
    ensure it's possible to have crypto be done wholly in a
    `TransactionExtension`.
    - [x] Deprecate `SignedExtension` and move all uses in codebase to
    `TransactionExtension`.
      - [x] `ChargeTransactionPayment`
      - [x] `DummyExtension`
      - [x] `ChargeAssetTxPayment` (asset-tx-payment)
      - [x] `ChargeAssetTxPayment` (asset-conversion-tx-payment)
      - [x] `CheckWeight`
      - [x] `CheckTxVersion`
      - [x] `CheckSpecVersion`
      - [x] `CheckNonce`
      - [x] `CheckNonZeroSender`
      - [x] `CheckMortality`
      - [x] `CheckGenesis`
      - [x] `CheckOnlySudoAccount`
      - [x] `WatchDummy`
      - [x] `PrevalidateAttests`
      - [x] `GenericSignedExtension`
      - [x] `SignedExtension` (chain-polkadot-bulletin)
      - [x] `RefundSignedExtensionAdapter`
    - [x] Implement `fn weight` across the board.
    - [ ] Go through all pre-existing extensions which assume an account
    signer and explicitly handle the possibility of another kind of origin.
    - [x] `CheckNonce` should probably succeed in the case of a non-account
    origin.
    - [x] `CheckNonZeroSender` should succeed in the case of a non-account
    origin.
    - [x] `ChargeTransactionPayment` and family should fail in the case of a
    non-account origin.
      - [ ]
    - [x] Fix any broken tests.
    
    ---------
    
    Signed-off-by: default avatargeorgepisaltu <[email protected]>
    Signed-off-by: default avatarAlexandru Vasile <[email protected]>
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    Signed-off-by: default avatarOliver Tale-Yazdi <[email protected]>
    Signed-off-by: default avatarAlexandru Gheorghe <[email protected]>
    Signed-off-by: default avatarAndrei Sandu <[email protected]>
    Co-authored-by: default avatarNikhil Gupta <[email protected]>
    Co-authored-by: default avatargeorgepisaltu <[email protected]>
    Co-authored-by: default avatarChevdor <[email protected]>
    Co-authored-by: default avatarBastian Köcher <[email protected]>
    Co-authored-by: default avatarMaciej <[email protected]>
    Co-authored-by: default avatarJavier Viola <[email protected]>
    Co-authored-by: default avatarMarcin S. <[email protected]>
    Co-authored-by: default avatarTsvetomir Dimitrov <[email protected]>
    Co-authored-by: default avatarJavier Bullrich <[email protected]>
    Co-authored-by: default avatarKoute <[email protected]>
    Co-authored-by: default avatarAdrian Catangiu <[email protected]>
    Co-authored-by: Vladimir Istyufeev's avatarVladimir Istyufeev <[email protected]>
    Co-authored-by: default avatarRoss Bulat <[email protected]>
    Co-authored-by: default avatarGonçalo Pestana <[email protected]>
    Co-authored-by: default avatarLiam Aharon <[email protected]>
    Co-authored-by: default avatarSvyatoslav Nikolsky <[email protected]>
    Co-authored-by: default avatarAndré Silva <[email protected]>
    Co-authored-by: default avatarOliver Tale-Yazdi <[email protected]>
    Co-authored-by: default avatars0me0ne-unkn0wn <[email protected]>
    Co-authored-by: default avatarordian <[email protected]>
    Co-authored-by: default avatarSebastian Kunert <[email protected]>
    Co-authored-by: default avatarAaro Altonen <[email protected]>
    Co-authored-by: default avatarDmitry Markin <[email protected]>
    Co-authored-by: default avatarAlexandru Vasile <[email protected]>
    Co-authored-by: default avatarAlexander Samusev <[email protected]>
    Co-authored-by: default avatarJulian Eager <[email protected]>
    Co-authored-by: default avatarMichal Kucharczyk <[email protected]>
    Co-authored-by: default avatarDavide Galassi <[email protected]>
    Co-authored-by: default avatarDónal Murray <[email protected]>
    Co-authored-by: default avataryjh <[email protected]>
    Co-authored-by: default avatarTom Mi <[email protected]>
    Co-authored-by: default avatardependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: default avatarWill | Paradox | ParaNodes.io <[email protected]>
    Co-authored-by: default avatarBastian Köcher <[email protected]>
    Co-authored-by: default avatarJoshy Orndorff <[email protected]>
    Co-authored-by: default avatarJoshy Orndorff <[email protected]>
    Co-authored-by: default avatarPG Herveou <[email protected]>
    Co-authored-by: default avatarAlexander Theißen <[email protected]>
    Co-authored-by: default avatarKian Paimani <[email protected]>
    Co-authored-by: default avatarJuan Girini <[email protected]>
    Co-authored-by: default avatarbader y <[email protected]>
    Co-authored-by: default avatarJames Wilson <[email protected]>
    Co-authored-by: default avatarjoe petrowski <[email protected]>
    Co-authored-by: default avatarasynchronous rob <[email protected]>
    Co-authored-by: default avatarParth <[email protected]>
    Co-authored-by: default avatarAndrew Jones <[email protected]>
    Co-authored-by: default avatarJonathan Udd <[email protected]>
    Co-authored-by: default avatarSerban Iorga <[email protected]>
    Co-authored-by: default avatarEgor_P <[email protected]>
    Co-authored-by: default avatarBranislav Kontur <[email protected]>
    Co-authored-by: default avatarEvgeny Snitko <[email protected]>
    Co-authored-by: default avatarJust van Stam <[email protected]>
    Co-authored-by: default avatarFrancisco Aguirre <[email protected]>
    Co-authored-by: default avatargupnik <[email protected]>
    Co-authored-by: default avatardzmitry-lahoda <[email protected]>
    Co-authored-by: default avatarzhiqiangxu <[email protected]>
    Co-authored-by: default avatarNazar Mokrynskyi <[email protected]>
    Co-authored-by: default avatarAnwesh <[email protected]>
    Co-authored-by: default avatarcheme <[email protected]>
    Co-authored-by: default avatarSam Johnson <[email protected]>
    Co-authored-by: default avatarkianenigma <[email protected]>
    Co-authored-by: default avatarJegor Sidorenko <[email protected]>
    Co-authored-by: default avatarMuharem <[email protected]>
    Co-authored-by: default avatarjoepetrowski <[email protected]>
    Co-authored-by: default avatarAlexandru Gheorghe <[email protected]>
    Co-authored-by: default avatarGabriel Facco de Arruda <[email protected]>
    Co-authored-by: default avatarSquirrel <[email protected]>
    Co-authored-by: default avatarAndrei Sandu <[email protected]>
    Co-authored-by: default avatargeorgepisaltu <[email protected]>
    Co-authored-by: command-bot <>
    (cherry picked from commit fd5f9292f500652e1d4792b09fb8ac60e1268ce4)
    
    * Revert "FRAME: Create `TransactionExtension` as a replacement for `SignedExtension` (#2280)" (#3665)
    
    This PR reverts #2280 which introduced `TransactionExtension` to replace
    `SignedExtension`.
    
    As a result of the discussion
    [here](https://github.com/paritytech/polkadot-sdk/pull/3623#issuecomment-1986789700),
    the changes will be reverted for now with plans to reintroduce the
    concept in the future.
    
    ---------
    
    Signed-off-by: default avatargeorgepisaltu <[email protected]>
    (cherry picked from commit bbd51ce867967f71657b901f1a956ad4f75d352e)
    
    * Increase timeout for assertions (#3680)
    
    Prevents timeouts in ci like
    https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/5516019
    
    (cherry picked from commit c4c9257386036a9e27e7ee001fe8eadb80958cc0)
    
    * Removes `as [disambiguation_path]` from `derive_impl` usage (#3652)
    
    Step in https://github.com/paritytech/polkadot-sdk/issues/171
    
    This PR removes `as [disambiguation_path]` syntax from `derive_impl`
    usage across the polkadot-sdk as introduced in
    https://github.com/paritytech/polkadot-sdk/pull/3505
    
    (cherry picked from commit 7099f6e1b1fa3c8cd894693902263d9ed0e38978)
    
    * Fix typo (#3691)
    
    (cherry picked from commit 6b1179f13b4815685769c9f523720ec9ed0e2ff4)
    
    * Bridge zombienet tests: remove unneeded accounts (#3700)
    
    Bridge zombienet tests: remove unneeded accounts
    
    (cherry picked from commit 0c6c837f689a287583508506e342ba07687e8d26)
    
    * Fix typos (#3753)
    
    (cherry picked from commit 7241a8db7b3496816503c6058dae67f66c666b00)
    
    * Update polkadot-sdk refs
    
    * Fix dependency conflicts
    
    * Fix build
    
    * cargo fmt
    
    * Fix spellcheck test
    
    ---------
    
    Co-authored-by: default avatarSvyatoslav Nikolsky <[email protected]>
    Co-authored-by: default avatarBranislav Kontur <[email protected]>
    Co-authored-by: default avatarMarcin S <[email protected]>
    Co-authored-by: default avatarOliver Tale-Yazdi <[email protected]>
    Co-authored-by: default avatarGavin Wood <[email protected]>
    Co-authored-by: default avatargeorgepisaltu <[email protected]>
    Co-authored-by: default avatarJavier Viola <[email protected]>
    Co-authored-by: default avatargupnik <[email protected]>
    Co-authored-by: default avatarjokess123 <[email protected]>
    Co-authored-by: default avatarslicejoke <[email protected]>
    28c459be
  • dependabot[bot]'s avatar
    Bump async-trait from 0.1.78 to 0.1.79 · 8e58eb92
    dependabot[bot] authored
    
    
    Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.78 to 0.1.79.
    - [Release notes](https://github.com/dtolnay/async-trait/releases)
    - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.78...0.1.79)
    
    ---
    updated-dependencies:
    - dependency-name: async-trait
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    8e58eb92
  • Serban Iorga's avatar
    [Backport from `polkadot-sdk`] Move chain definitions to separate folder (#2892) · 4bc73d80
    Serban Iorga authored
    * [Bridges] Move chain definitions to separate folder (#3822)
    
    Related to
    https://github.com/paritytech/parity-bridges-common/issues/2538
    
    This PR doesn't contain any functional changes.
    
    The PR moves specific bridged chain definitions from
    `bridges/primitives` to `bridges/chains` folder in order to facilitate
    the migration of the `parity-bridges-repo` into `polkadot-sdk` as
    discussed in https://hackmd.io/LprWjZ0bQXKpFeveYHIRXw?view
    
    Apart from this it also includes some cosmetic changes to some
    `Cargo.toml` files as a result of running `diener workspacify`.
    
    (cherry picked from commit 0711729d251efebf3486db602119ecfa67d98366)
    
    * diener workspacify
    4bc73d80
  • Serban Iorga's avatar
    a6bac6bc
  • Svyatoslav Nikolsky's avatar
    relayer waits until chain spec version matches the configured in Client... · 47b4c48c
    Svyatoslav Nikolsky authored
    relayer waits until chain spec version matches the configured in Client constructor/reconnect (#2894)
    
    47b4c48c
  • Svyatoslav Nikolsky's avatar
    Relayer v1.2.1 (#2895) · 1022b6d4
    Svyatoslav Nikolsky authored
    * bump relayer version
    
    * bump supported chain versions
    
    * updated lock file
    1022b6d4
  • Serban Iorga's avatar
    polkadot-sdk backport leftovers (#2896) · b9acdabb
    Serban Iorga authored
    b9acdabb
  • Serban Iorga's avatar
    e4e1ea60
  • Serban Iorga's avatar
    Backport changes from polkadot-sdk (#2899) · a7a47eae
    Serban Iorga authored
    
    
    * Fix spelling mistakes across the whole repository (#3808)
    
    **Update:** Pushed additional changes based on the review comments.
    
    **This pull request fixes various spelling mistakes in this
    repository.**
    
    Most of the changes are contained in the first **3** commits:
    
    - `Fix spelling mistakes in comments and docs`
    
    - `Fix spelling mistakes in test names`
    
    - `Fix spelling mistakes in error messages, panic messages, logs and
    tracing`
    
    Other source code spelling mistakes are separated into individual
    commits for easier reviewing:
    
    - `Fix the spelling of 'authority'`
    
    - `Fix the spelling of 'REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY'`
    
    - `Fix the spelling of 'prev_enqueud_messages'`
    
    - `Fix the spelling of 'endpoint'`
    
    - `Fix the spelling of 'children'`
    
    - `Fix the spelling of 'PenpalSiblingSovereignAccount'`
    
    - `Fix the spelling of 'PenpalSudoAccount'`
    
    - `Fix the spelling of 'insufficient'`
    
    - `Fix the spelling of 'PalletXcmExtrinsicsBenchmark'`
    
    - `Fix the spelling of 'subtracted'`
    
    - `Fix the spelling of 'CandidatePendingAvailability'`
    
    - `Fix the spelling of 'exclusive'`
    
    - `Fix the spelling of 'until'`
    
    - `Fix the spelling of 'discriminator'`
    
    - `Fix the spelling of 'nonexistent'`
    
    - `Fix the spelling of 'subsystem'`
    
    - `Fix the spelling of 'indices'`
    
    - `Fix the spelling of 'committed'`
    
    - `Fix the spelling of 'topology'`
    
    - `Fix the spelling of 'response'`
    
    - `Fix the spelling of 'beneficiary'`
    
    - `Fix the spelling of 'formatted'`
    
    - `Fix the spelling of 'UNKNOWN_PROOF_REQUEST'`
    
    - `Fix the spelling of 'succeeded'`
    
    - `Fix the spelling of 'reopened'`
    
    - `Fix the spelling of 'proposer'`
    
    - `Fix the spelling of 'InstantiationNonce'`
    
    - `Fix the spelling of 'depositor'`
    
    - `Fix the spelling of 'expiration'`
    
    - `Fix the spelling of 'phantom'`
    
    - `Fix the spelling of 'AggregatedKeyValue'`
    
    - `Fix the spelling of 'randomness'`
    
    - `Fix the spelling of 'defendant'`
    
    - `Fix the spelling of 'AquaticMammal'`
    
    - `Fix the spelling of 'transactions'`
    
    - `Fix the spelling of 'PassingTracingSubscriber'`
    
    - `Fix the spelling of 'TxSignaturePayload'`
    
    - `Fix the spelling of 'versioning'`
    
    - `Fix the spelling of 'descendant'`
    
    - `Fix the spelling of 'overridden'`
    
    - `Fix the spelling of 'network'`
    
    Let me know if this structure is adequate.
    
    **Note:** The usage of the words `Merkle`, `Merkelize`, `Merklization`,
    `Merkelization`, `Merkleization`, is somewhat inconsistent but I left it
    as it is.
    
    ~~**Note:** In some places the term `Receival` is used to refer to
    message reception, IMO `Reception` is the correct word here, but I left
    it as it is.~~
    
    ~~**Note:** In some places the term `Overlayed` is used instead of the
    more acceptable version `Overlaid` but I also left it as it is.~~
    
    ~~**Note:** In some places the term `Applyable` is used instead of the
    correct version `Applicable` but I also left it as it is.~~
    
    **Note:** Some usage of British vs American english e.g. `judgement` vs
    `judgment`, `initialise` vs `initialize`, `optimise` vs `optimize` etc.
    are both present in different places, but I suppose that's
    understandable given the number of contributors.
    
    ~~**Note:** There is a spelling mistake in `.github/CODEOWNERS` but it
    triggers errors in CI when I make changes to it, so I left it as it
    is.~~
    
    (cherry picked from commit 002d9260f9a0f844f87eefd0abce8bd95aae351b)
    
    * Fix
    
    ---------
    
    Co-authored-by: default avatarDcompoze <[email protected]>
    a7a47eae
  • Serban Iorga's avatar
    Leftover (#2900) · 1e4fd28e
    Serban Iorga authored
    1e4fd28e
  • Serban Iorga's avatar
    Fix polkadot-sdk CI failures (#2901) · 95660136
    Serban Iorga authored
    * taplo
    
    * markdown
    
    * publish = false
    
    * feature propagation
    95660136
  • dependabot[bot]'s avatar
    Bump serde_json from 1.0.114 to 1.0.115 · 35474455
    dependabot[bot] authored
    
    
    Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.114 to 1.0.115.
    - [Release notes](https://github.com/serde-rs/json/releases)
    - [Commits](https://github.com/serde-rs/json/compare/v1.0.114...v1.0.115)
    
    ---
    updated-dependencies:
    - dependency-name: serde_json
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    35474455
  • dependabot[bot]'s avatar
    Bump scale-info from 2.11.0 to 2.11.1 · 051d6ed6
    dependabot[bot] authored
    
    
    Bumps [scale-info](https://github.com/paritytech/scale-info) from 2.11.0 to 2.11.1.
    - [Release notes](https://github.com/paritytech/scale-info/releases)
    - [Changelog](https://github.com/paritytech/scale-info/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/paritytech/scale-info/compare/v2.11.0...v2.11.1)
    
    ---
    updated-dependencies:
    - dependency-name: scale-info
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    051d6ed6
  • dependabot[bot]'s avatar
    Bump tokio from 1.36.0 to 1.37.0 · f7f983c4
    dependabot[bot] authored
    
    
    Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.36.0 to 1.37.0.
    - [Release notes](https://github.com/tokio-rs/tokio/releases)
    - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.36.0...tokio-1.37.0)
    
    ---
    updated-dependencies:
    - dependency-name: tokio
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    f7f983c4
  • Svyatoslav Nikolsky's avatar
    Some relayer improvments (#2902) · 34817d81
    Svyatoslav Nikolsky authored
    * added CLI arguments: full WS URI + separate for WS path URI component + additional log
    
    * URI -> URL?
    
    * added TODO
    
    * fmt
    34817d81
  • Serban Iorga's avatar
    Address migration comments (#2910) · 8c4c99d1
    Serban Iorga authored
    * Use workspace.[authors|edition]
    
    * Add repository.workspace = true
    
    * Upgrade dependencies to the polkadot-sdk versions
    
    * Upgrade async-std version
    
    * Update jsonrpsee version
    
    * cargo update
    
    * use ci-unified image
    8c4c99d1
  • Serban Iorga's avatar
    ckb-merkle-mountain-range -> 0.5.2 (#2911) · bea13eab
    Serban Iorga authored
    bea13eab
  • Serban Iorga's avatar
    Backport changes from polakdot-sdk (#2920) · cfe1e7de
    Serban Iorga authored
    
    
    * Migrate fee payment from `Currency` to `fungible` (#2292)
    
    Part of https://github.com/paritytech/polkadot-sdk/issues/226
    Related https://github.com/paritytech/polkadot-sdk/issues/1833
    
    - Deprecate `CurrencyAdapter` and introduce `FungibleAdapter`
    - Deprecate `ToStakingPot` and replace usage with `ResolveTo`
    - Required creating a new `StakingPotAccountId` struct that implements
    `TypedGet` for the staking pot account ID
    - Update parachain common utils `DealWithFees`, `ToAuthor` and
    `AssetsToBlockAuthor` implementations to use `fungible`
    - Update runtime XCM Weight Traders to use `ResolveTo` instead of
    `ToStakingPot`
    - Update runtime Transaction Payment pallets to use `FungibleAdapter`
    instead of `CurrencyAdapter`
    - [x] Blocked by https://github.com/paritytech/polkadot-sdk/pull/1296,
    needs the `Unbalanced::decrease_balance` fix
    
    (cherry picked from commit bda4e75ac49786a7246531cf729b25c208cd38e6)
    
    * Upgrade `trie-db` from `0.28.0` to `0.29.0` (#3982)
    
    - What does this PR do?
    1. Upgrades `trie-db`'s version to the latest release. This release
    includes, among others, an implementation of `DoubleEndedIterator` for
    the `TrieDB` struct, allowing to iterate both backwards and forwards
    within the leaves of a trie.
    2. Upgrades `trie-bench` to `0.39.0` for compatibility.
    3. Upgrades `criterion` to `0.5.1` for compatibility.
    - Why are these changes needed?
    Besides keeping up with the upgrade of `trie-db`, this specifically adds
    the functionality of iterating back on the leafs of a trie, with
    `sp-trie`. In a project we're currently working on, this comes very
    handy to verify a Merkle proof that is the response to a challenge. The
    challenge is a random hash that (most likely) will not be an existing
    leaf in the trie. So the challenged user, has to provide a Merkle proof
    of the previous and next existing leafs in the trie, that surround the
    random challenged hash.
    
    Without having DoubleEnded iterators, we're forced to iterate until we
    find the first existing leaf, like so:
    ```rust
            // ************* VERIFIER (RUNTIME) *************
            // Verify proof. This generates a partial trie based on the proof and
            // checks that the root hash matches the `expected_root`.
            let (memdb, root) = proof.to_memory_db(Some(&root)).unwrap();
            let trie = TrieDBBuilder::<LayoutV1<RefHasher>>::new(&memdb, &root).build();
    
            // Print all leaf node keys and values.
            println!("\nPrinting leaf nodes of partial tree...");
            for key in trie.key_iter().unwrap() {
                if key.is_ok() {
                    println!("Leaf node key: {:?}", key.clone().unwrap());
    
                    let val = trie.get(&key.unwrap());
    
                    if val.is_ok() {
                        println!("Leaf node value: {:?}", val.unwrap());
                    } else {
                        println!("Leaf node value: None");
                    }
                }
            }
    
            println!("RECONSTRUCTED TRIE {:#?}", trie);
    
            // Create an iterator over the leaf nodes.
            let mut iter = trie.iter().unwrap();
    
            // First element with a value should be the previous existing leaf to the challenged hash.
            let mut prev_key = None;
            for element in &mut iter {
                if element.is_ok() {
                    let (key, _) = element.unwrap();
                    prev_key = Some(key);
                    break;
                }
            }
            assert!(prev_key.is_some());
    
            // Since hashes are `Vec<u8>` ordered in big-endian, we can compare them directly.
            assert!(prev_key.unwrap() <= challenge_hash.to_vec());
    
            // The next element should exist (meaning there is no other existing leaf between the
            // previous and next leaf) and it should be greater than the challenged hash.
            let next_key = iter.next().unwrap().unwrap().0;
            assert!(next_key >= challenge_hash.to_vec());
    ```
    
    With DoubleEnded iterators, we can avoid that, like this:
    ```rust
            // ************* VERIFIER (RUNTIME) *************
            // Verify proof. This generates a partial trie based on the proof and
            // checks that the root hash matches the `expected_root`.
            let (memdb, root) = proof.to_memory_db(Some(&root)).unwrap();
            let trie = TrieDBBuilder::<LayoutV1<RefHasher>>::new(&memdb, &root).build();
    
            // Print all leaf node keys and values.
            println!("\nPrinting leaf nodes of partial tree...");
            for key in trie.key_iter().unwrap() {
                if key.is_ok() {
                    println!("Leaf node key: {:?}", key.clone().unwrap());
    
                    let val = trie.get(&key.unwrap());
    
                    if val.is_ok() {
                        println!("Leaf node value: {:?}", val.unwrap());
                    } else {
                        println!("Leaf node value: None");
                    }
                }
            }
    
            // println!("RECONSTRUCTED TRIE {:#?}", trie);
            println!("\nChallenged key: {:?}", challenge_hash);
    
            // Create an iterator over the leaf nodes.
            let mut double_ended_iter = trie.into_double_ended_iter().unwrap();
    
            // First element with a value should be the previous existing leaf to the challenged hash.
            double_ended_iter.seek(&challenge_hash.to_vec()).unwrap();
            let next_key = double_ended_iter.next_back().unwrap().unwrap().0;
            let prev_key = double_ended_iter.next_back().unwrap().unwrap().0;
    
            // Since hashes are `Vec<u8>` ordered in big-endian, we can compare them directly.
            println!("Prev key: {:?}", prev_key);
            assert!(prev_key <= challenge_hash.to_vec());
    
            println!("Next key: {:?}", next_key);
            assert!(next_key >= challenge_hash.to_vec());
    ```
    - How were these changes implemented and what do they affect?
    All that is needed for this functionality to be exposed is changing the
    version number of `trie-db` in all the `Cargo.toml`s applicable, and
    re-exporting some additional structs from `trie-db` in `sp-trie`.
    
    ---------
    
    Co-authored-by: default avatarBastian Köcher <[email protected]>
    (cherry picked from commit 4e73c0fcd37e4e8c14aeb83b5c9e680981e16079)
    
    * Update polkadot-sdk refs
    
    * Fix Cargo.lock
    
    ---------
    
    Co-authored-by: default avatarLiam Aharon <[email protected]>
    Co-authored-by: default avatarFacundo Farall <[email protected]>
    cfe1e7de
  • Serban Iorga's avatar
    Consume migrated crates from `polkadot-sdk` (#2921) · a174cfa9
    Serban Iorga authored
    * Remove migrated crates
    
    * Reference polkadot-sdk for the migrated crates
    
    * Leftovers
    
    * Fixes
    a174cfa9
  • Serban Iorga's avatar
    Delete the testing folder (#2922) · 11b56b74
    Serban Iorga authored
    The testing folder has also been moved to polkadot-sdk
    11b56b74
  • dependabot[bot]'s avatar
    Bump quote from 1.0.35 to 1.0.36 · 92a722ab
    dependabot[bot] authored
    
    
    Bumps [quote](https://github.com/dtolnay/quote) from 1.0.35 to 1.0.36.
    - [Release notes](https://github.com/dtolnay/quote/releases)
    - [Commits](https://github.com/dtolnay/quote/compare/1.0.35...1.0.36)
    
    ---
    updated-dependencies:
    - dependency-name: quote
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    92a722ab
  • Serban Iorga's avatar
    [dependabot] ignore migrated crates (#2943) · 581b81dc
    Serban Iorga authored
    * [dependabot] ignore migrated crates
    
    * ignore more migrated crates
    581b81dc
  • dependabot[bot]'s avatar
    Bump anyhow from 1.0.81 to 1.0.82 · df0d367e
    dependabot[bot] authored
    
    
    Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.81 to 1.0.82.
    - [Release notes](https://github.com/dtolnay/anyhow/releases)
    - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.81...1.0.82)
    
    ---
    updated-dependencies:
    - dependency-name: anyhow
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    df0d367e
  • dependabot[bot]'s avatar
    Bump subxt from 0.32.1 to 0.35.2 · 0364a0aa
    dependabot[bot] authored
    
    
    Bumps [subxt](https://github.com/paritytech/subxt) from 0.32.1 to 0.35.2.
    - [Release notes](https://github.com/paritytech/subxt/releases)
    - [Changelog](https://github.com/paritytech/subxt/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/paritytech/subxt/compare/v0.32.1...v0.35.2)
    
    ---
    updated-dependencies:
    - dependency-name: subxt
      dependency-type: direct:production
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    0364a0aa
  • Serban Iorga's avatar
    Update CI image (#2951) · c752f16c
    Serban Iorga authored
    * Update CI image
    
    * cargo update -p [email protected]
    c752f16c
  • dependabot[bot]'s avatar
    Bump async-trait from 0.1.79 to 0.1.80 · 4dd72be5
    dependabot[bot] authored
    
    
    Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.79 to 0.1.80.
    - [Release notes](https://github.com/dtolnay/async-trait/releases)
    - [Commits](https://github.com/dtolnay/async-trait/compare/0.1.79...0.1.80)
    
    ---
    updated-dependencies:
    - dependency-name: async-trait
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    4dd72be5
  • dependabot[bot]'s avatar
    Bump subxt from 0.35.2 to 0.35.3 · f80f01d4
    dependabot[bot] authored
    
    
    Bumps [subxt](https://github.com/paritytech/subxt) from 0.35.2 to 0.35.3.
    - [Release notes](https://github.com/paritytech/subxt/releases)
    - [Changelog](https://github.com/paritytech/subxt/blob/v0.35.3/CHANGELOG.md)
    - [Commits](https://github.com/paritytech/subxt/compare/v0.35.2...v0.35.3)
    
    ---
    updated-dependencies:
    - dependency-name: subxt
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    f80f01d4
  • Svyatoslav Nikolsky's avatar
    Relayer 1.3.0 (#2959) · 83193de0
    Svyatoslav Nikolsky authored
    * updated RELEASE.md
    
    * bump relay version
    
    * bump BHK version to 1_002_000
    83193de0
  • Svyatoslav Nikolsky's avatar
    exported P<>K dashboards (#2960) · bacc6bfd
    Svyatoslav Nikolsky authored
    bacc6bfd
  • Svyatoslav Nikolsky's avatar
    7f41e098
  • dependabot[bot]'s avatar
    Bump rustls from 0.21.8 to 0.21.11 in /tools/runtime-codegen (#2965) · 5c8d4df5
    dependabot[bot] authored
    
    
    Bumps [rustls](https://github.com/rustls/rustls) from 0.21.8 to 0.21.11.
    - [Release notes](https://github.com/rustls/rustls/releases)
    - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/rustls/rustls/compare/v/0.21.8...v/0.21.11)
    
    ---
    updated-dependencies:
    - dependency-name: rustls
      dependency-type: indirect
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    Co-authored-by: default avatardependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    5c8d4df5
  • Svyatoslav Nikolsky's avatar
  • dependabot[bot]'s avatar
    Bump thiserror from 1.0.58 to 1.0.59 · f14e95c4
    dependabot[bot] authored
    
    
    Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.58 to 1.0.59.
    - [Release notes](https://github.com/dtolnay/thiserror/releases)
    - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.59)
    
    ---
    updated-dependencies:
    - dependency-name: thiserror
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: default avatardependabot[bot] <[email protected]>
    f14e95c4
  • Svyatoslav Nikolsky's avatar
    69616d69
  • Svyatoslav Nikolsky's avatar
    deleted moved files (merge conflict) · ee09b007
    Svyatoslav Nikolsky authored
    ee09b007
  • Svyatoslav Nikolsky's avatar
    a1cbcc75
  • Svyatoslav Nikolsky's avatar
    removed another moved file · d9105160
    Svyatoslav Nikolsky authored
    d9105160
...@@ -84,7 +84,7 @@ SS58Prefix ...@@ -84,7 +84,7 @@ SS58Prefix
STALL_SYNC_TIMEOUT STALL_SYNC_TIMEOUT
SURI SURI
ServiceFactory/MS ServiceFactory/MS
TransactionExtension SignedExtension
Stringified Stringified
Submitter1 Submitter1
S|N S|N
......
...@@ -8,7 +8,35 @@ updates: ...@@ -8,7 +8,35 @@ updates:
timezone: Europe/Berlin timezone: Europe/Berlin
open-pull-requests-limit: 20 open-pull-requests-limit: 20
ignore: ignore:
# Substrate (+ Polkadot/Cumulus pallets) dependencies # Bridges polkadot-sdk dependencies
- dependency-name: bp-*
versions:
- ">= 0"
- dependency-name: bridge-runtime-common
versions:
- ">= 0"
- dependency-name: equivocation-detector
versions:
- ">= 0"
- dependency-name: finality-relay
versions:
- ">= 0"
- dependency-name: messages-relay
versions:
- ">= 0"
- dependency-name: parachains-relay
versions:
- ">= 0"
- dependency-name: relay-substrate-client
versions:
- ">= 0"
- dependency-name: relay-utils
versions:
- ">= 0"
- dependency-name: substrate-relay-helper
versions:
- ">= 0"
# Substrate polkadot-sdk (+ Polkadot/Cumulus pallets) dependencies
- dependency-name: beefy-* - dependency-name: beefy-*
versions: versions:
- ">= 0" - ">= 0"
...@@ -42,7 +70,7 @@ updates: ...@@ -42,7 +70,7 @@ updates:
- dependency-name: binary-merkle-tree - dependency-name: binary-merkle-tree
versions: versions:
- ">= 0" - ">= 0"
# Polkadot dependencies # Polkadot polkadot-sdk dependencies
- dependency-name: kusama-* - dependency-name: kusama-*
versions: versions:
- ">= 0" - ">= 0"
...@@ -52,7 +80,7 @@ updates: ...@@ -52,7 +80,7 @@ updates:
- dependency-name: xcm* - dependency-name: xcm*
versions: versions:
- ">= 0" - ">= 0"
# Cumulus dependencies # Cumulus polkadot-sdk dependencies
- dependency-name: cumulus-* - dependency-name: cumulus-*
versions: versions:
- ">= 0" - ">= 0"
......
...@@ -10,7 +10,7 @@ variables: ...@@ -10,7 +10,7 @@ variables:
GIT_DEPTH: 100 GIT_DEPTH: 100
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
ARCH: "x86_64" ARCH: "x86_64"
CI_IMAGE: "paritytech/bridges-ci:production" CI_IMAGE: "paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408"
RUST_BACKTRACE: full RUST_BACKTRACE: full
BUILDAH_IMAGE: "quay.io/buildah/stable:v1.29" BUILDAH_IMAGE: "quay.io/buildah/stable:v1.29"
BUILDAH_COMMAND: "buildah --storage-driver overlay2" BUILDAH_COMMAND: "buildah --storage-driver overlay2"
...@@ -121,7 +121,7 @@ check: ...@@ -121,7 +121,7 @@ check:
<<: *docker-env <<: *docker-env
<<: *test-refs <<: *test-refs
script: &check-script script: &check-script
- SKIP_WASM_BUILD=1 time cargo check --locked --verbose --workspace --features runtime-benchmarks - SKIP_WASM_BUILD=1 time cargo check --locked --verbose --workspace
check-nightly: check-nightly:
stage: test stage: test
...@@ -142,7 +142,7 @@ test: ...@@ -142,7 +142,7 @@ test:
# Enable this, when you see: "`cargo metadata` can not fail on project `Cargo.toml`" # Enable this, when you see: "`cargo metadata` can not fail on project `Cargo.toml`"
#- time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"polkadot-runtime\").manifest_path"` #- time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"polkadot-runtime\").manifest_path"`
#- time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"kusama-runtime\").manifest_path"` #- time cargo fetch --manifest-path=`cargo metadata --format-version=1 | jq --compact-output --raw-output ".packages[] | select(.name == \"kusama-runtime\").manifest_path"`
- CARGO_NET_OFFLINE=true SKIP_WASM_BUILD=1 time cargo test --verbose --workspace --features runtime-benchmarks - CARGO_NET_OFFLINE=true SKIP_WASM_BUILD=1 time cargo test --verbose --workspace
test-nightly: test-nightly:
stage: test stage: test
......
source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -6,54 +6,17 @@ license = "GPL-3.0-only" ...@@ -6,54 +6,17 @@ license = "GPL-3.0-only"
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"bin/runtime-common", "relay-clients/client-bridge-hub-kusama",
"modules/beefy", "relay-clients/client-bridge-hub-polkadot",
"modules/grandpa", "relay-clients/client-bridge-hub-rococo",
"modules/messages", "relay-clients/client-bridge-hub-westend",
"modules/parachains", "relay-clients/client-kusama",
"modules/relayers", "relay-clients/client-polkadot",
"modules/xcm-bridge-hub", "relay-clients/client-polkadot-bulletin",
"modules/xcm-bridge-hub-router", "relay-clients/client-rococo",
"primitives/beefy", "relay-clients/client-westend",
"primitives/chain-asset-hub-rococo", "substrate-relay",
"primitives/chain-asset-hub-westend",
"primitives/chain-bridge-hub-cumulus",
"primitives/chain-bridge-hub-kusama",
"primitives/chain-bridge-hub-polkadot",
"primitives/chain-bridge-hub-rococo",
"primitives/chain-bridge-hub-westend",
"primitives/chain-kusama",
"primitives/chain-polkadot",
"primitives/chain-polkadot-bulletin",
"primitives/chain-rococo",
"primitives/chain-westend",
"primitives/header-chain",
"primitives/messages",
"primitives/parachains",
"primitives/polkadot-core",
"primitives/relayers",
"primitives/runtime",
"primitives/test-utils",
"primitives/xcm-bridge-hub-router",
"relays/bin-substrate",
"relays/client-bridge-hub-kusama",
"relays/client-bridge-hub-polkadot",
"relays/client-bridge-hub-rococo",
"relays/client-bridge-hub-westend",
"relays/client-kusama",
"relays/client-polkadot",
"relays/client-polkadot-bulletin",
"relays/client-rococo",
"relays/client-substrate",
"relays/client-westend",
"relays/equivocation",
"relays/finality",
"relays/lib-substrate-relay",
"relays/messages",
"relays/parachains",
"relays/utils",
] ]
# Setup clippy lints as `polkadot-sdk`, # Setup clippy lints as `polkadot-sdk`,
...@@ -89,7 +52,7 @@ complexity = { level = "deny", priority = 1 } ...@@ -89,7 +52,7 @@ complexity = { level = "deny", priority = 1 }
[workspace.dependencies] [workspace.dependencies]
log = { version = "0.4.20", default-features = false } log = { version = "0.4.20", default-features = false }
quote = { version = "1.0.33" } quote = { version = "1.0.36" }
serde = { version = "1.0.197", default-features = false } serde = { version = "1.0.197", default-features = false }
serde_json = { version = "1.0.114", default-features = false } serde_json = { version = "1.0.115", default-features = false }
thiserror = { version = "1.0.48" } thiserror = { version = "1.0.59" }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# #
# See the `deployments/README.md` for all the available `PROJECT` values. # See the `deployments/README.md` for all the available `PROJECT` values.
FROM docker.io/paritytech/bridges-ci:production as builder FROM docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 as builder
USER root USER root
WORKDIR /parity-bridges-common WORKDIR /parity-bridges-common
......
...@@ -38,10 +38,10 @@ cargo test --all ...@@ -38,10 +38,10 @@ cargo test --all
``` ```
Also you can build the repo with [Parity CI Docker Also you can build the repo with [Parity CI Docker
image](https://github.com/paritytech/scripts/tree/master/dockerfiles/bridges-ci): image](https://github.com/paritytech/scripts/tree/master/dockerfiles/ci-unified):
```bash ```bash
docker pull paritytech/bridges-ci:production docker pull paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408
mkdir ~/cache mkdir ~/cache
chown 1000:1000 ~/cache #processes in the container runs as "nonroot" user with UID 1000 chown 1000:1000 ~/cache #processes in the container runs as "nonroot" user with UID 1000
docker run --rm -it -w /shellhere/parity-bridges-common \ docker run --rm -it -w /shellhere/parity-bridges-common \
...@@ -49,7 +49,7 @@ docker run --rm -it -w /shellhere/parity-bridges-common \ ...@@ -49,7 +49,7 @@ docker run --rm -it -w /shellhere/parity-bridges-common \
-v "$(pwd)":/shellhere/parity-bridges-common \ -v "$(pwd)":/shellhere/parity-bridges-common \
-e CARGO_HOME=/cache/cargo/ \ -e CARGO_HOME=/cache/cargo/ \
-e SCCACHE_DIR=/cache/sccache/ \ -e SCCACHE_DIR=/cache/sccache/ \
-e CARGO_TARGET_DIR=/cache/target/ paritytech/bridges-ci:production cargo build --all -e CARGO_TARGET_DIR=/cache/target/ paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 cargo build --all
#artifacts can be found in ~/cache/target #artifacts can be found in ~/cache/target
``` ```
......
...@@ -6,16 +6,16 @@ come first and details come in the last sections. ...@@ -6,16 +6,16 @@ come first and details come in the last sections.
### Making a Release ### Making a Release
All releases are supposed to be done from the All releases are supposed to be done from the
[`polkadot-staging` branch](https://github.com/paritytech/parity-bridges-common/tree/polkadot-staging). [`master` branch](https://github.com/paritytech/parity-bridges-common/tree/master).
This branch is assumed to contain changes, that are reviewed and audited. This branch is assumed to contain changes, that are reviewed and audited.
To prepare a release: To prepare a release:
1. Make sure all required changes are merged to the 1. Make sure all required changes are merged to the
[`polkadot-staging` branch](https://github.com/paritytech/parity-bridges-common/tree/polkadot-staging); [`master` branch](https://github.com/paritytech/parity-bridges-common/tree/master);
2. Select release version: go to the `Cargo.toml` of `substrate-relay` crate 2. Select release version: go to the `Cargo.toml` of `substrate-relay` crate
([here](https://github.com/paritytech/parity-bridges-common/blob/polkadot-staging/relays/bin-substrate/Cargo.toml#L3)) ([here](https://github.com/paritytech/parity-bridges-common/blob/master/relays/bin-substrate/Cargo.toml#L3))
to look for the latest version. Then increment the minor or major version. to look for the latest version. Then increment the minor or major version.
**NOTE**: we are not going to properly support [semver](https://semver.org) **NOTE**: we are not going to properly support [semver](https://semver.org)
...@@ -28,11 +28,11 @@ To prepare a release: ...@@ -28,11 +28,11 @@ To prepare a release:
It could be combined with the (1) if changes are not large. Make sure to It could be combined with the (1) if changes are not large. Make sure to
add the [`A-release`](https://github.com/paritytech/parity-bridges-common/labels/A-release) add the [`A-release`](https://github.com/paritytech/parity-bridges-common/labels/A-release)
label to your PR - in the future we'll add workflow to make pre-releases label to your PR - in the future we'll add workflow to make pre-releases
when such PR is merged to the `polkadot-staging` branch; when such PR is merged to the `master` branch;
4. Wait for approvals and merge PR, mentioned in (3); 4. Wait for approvals and merge PR, mentioned in (3);
5. Checkout updated `polkadot-staging` branch and do `git pull`; 5. Checkout updated `master` branch and do `git pull`;
6. Make a new git tag with the `substrate-relay` version: 6. Make a new git tag with the `substrate-relay` version:
```sh ```sh
...@@ -123,15 +123,15 @@ support it. Normally it means: ...@@ -123,15 +123,15 @@ support it. Normally it means:
1. Bumping bundled chain versions in following places: 1. Bumping bundled chain versions in following places:
- for `Rococo` and `RBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/polkadot-staging/relays/bin-substrate/src/chains/rococo.rs); - for `Rococo` and `RBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/master/relays/bin-substrate/src/chains/rococo.rs);
- for `Westend` and `WBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/polkadot-staging/relays/bin-substrate/src/chains/westend.rs); - for `Westend` and `WBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/master/relays/bin-substrate/src/chains/westend.rs);
- for `Kusama` and `KBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/polkadot-staging/relays/bin-substrate/src/chains/polkadot.rs) - for `Kusama` and `KBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/master/relays/bin-substrate/src/chains/polkadot.rs)
- for `Polkadot` and `PBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/polkadot-staging/relays/bin-substrate/src/chains/polkadot.rs); - for `Polkadot` and `PBH`: [here](https://github.com/paritytech/parity-bridges-common/blob/master/relays/bin-substrate/src/chains/polkadot.rs);
- for `PBC`: [here](https://github.com/paritytech/parity-bridges-common/blob/polkadot-staging/relays/bin-substrate/src/chains/polkadot_bulletin.rs). - for `PBC`: [here](https://github.com/paritytech/parity-bridges-common/blob/master/relays/bin-substrate/src/chains/polkadot_bulletin.rs).
2. Regenerating bundled runtime wrapper code using `runtime-codegen` binary: 2. Regenerating bundled runtime wrapper code using `runtime-codegen` binary:
......
[package]
name = "bridge-runtime-common"
version = "0.7.0"
description = "Common types and functions that may be used by substrate-based runtimes of all bridged chains"
authors.workspace = true
edition.workspace = true
repository.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[lints]
workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] }
hash-db = { version = "0.16.0", default-features = false }
log = { workspace = true }
scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
static_assertions = { version = "1.1", optional = true }
tuplex = { version = "0.1", default-features = false }
# Bridge dependencies
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
bp-messages = { path = "../../primitives/messages", default-features = false }
bp-parachains = { path = "../../primitives/parachains", default-features = false }
bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false }
bp-relayers = { path = "../../primitives/relayers", default-features = false }
bp-runtime = { path = "../../primitives/runtime", default-features = false }
bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false }
bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false }
pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false }
pallet-bridge-messages = { path = "../../modules/messages", default-features = false }
pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false }
pallet-bridge-relayers = { path = "../../modules/relayers", default-features = false }
# Substrate dependencies
frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
sp-trie = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master", default-features = false }
# Polkadot dependencies
xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", default-features = false , branch = "master" }
xcm-builder = { package = "staging-xcm-builder", git = "https://github.com/paritytech/polkadot-sdk", default-features = false , branch = "master" }
[dev-dependencies]
bp-test-utils = { path = "../../primitives/test-utils" }
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "master" }
[features]
default = ["std"]
std = [
"bp-header-chain/std",
"bp-messages/std",
"bp-parachains/std",
"bp-polkadot-core/std",
"bp-relayers/std",
"bp-runtime/std",
"bp-xcm-bridge-hub-router/std",
"bp-xcm-bridge-hub/std",
"codec/std",
"frame-support/std",
"frame-system/std",
"hash-db/std",
"log/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-bridge-parachains/std",
"pallet-bridge-relayers/std",
"pallet-transaction-payment/std",
"pallet-utility/std",
"scale-info/std",
"sp-api/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
"sp-trie/std",
"tuplex/std",
"xcm-builder/std",
"xcm/std",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-bridge-grandpa/runtime-benchmarks",
"pallet-bridge-messages/runtime-benchmarks",
"pallet-bridge-parachains/runtime-benchmarks",
"pallet-bridge-relayers/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
]
integrity-test = ["static_assertions"]
// Copyright (C) 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/>.
//! Transaction extension that rejects bridge-related transactions, that include
//! obsolete (duplicated) data or do not pass some additional pallet-specific
//! checks.
use crate::{messages_call_ext::MessagesCallSubType, RefundableParachainId};
use bp_relayers::ExplicitOrAccountParams;
use pallet_bridge_grandpa::{
BridgedBlockNumber, CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper,
};
use pallet_bridge_parachains::{
CallSubType as ParachainsCallSubtype, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo,
};
use pallet_bridge_relayers::Pallet as RelayersPallet;
use sp_runtime::{
traits::{Get, PhantomData, UniqueSaturatedInto},
transaction_validity::{TransactionPriority, TransactionValidity, ValidTransactionBuilder},
};
/// A duplication of the `FilterCall` trait.
///
/// We need this trait in order to be able to implement it for the messages pallet,
/// since the implementation is done outside of the pallet crate.
pub trait BridgeRuntimeFilterCall<AccountId, Call> {
/// Data that may be passed from the validate to `on_failure`.
type ToPostDispatch;
/// Called during validation. Needs to checks whether a runtime call, submitted
/// by the `who` is valid. `who` may be `None` if transaction is not signed
/// by a regular account.
fn validate(
who: &Option<AccountId>,
call: &Call,
) -> (Self::ToPostDispatch, TransactionValidity);
/// Called after transaction is dispatched.
fn post_dispatch(
_who: &Option<AccountId>,
_has_failed: bool,
_to_on_failure: Self::ToPostDispatch,
) {
}
}
/// Wrapper for the bridge GRANDPA pallet that checks calls for obsolete submissions
/// and also boosts transaction priority if it has submitted by registered relayer.
/// The boost is computed as
/// `(BundledHeaderNumber - 1 - BestFinalizedHeaderNumber) * Priority::get()`.
/// The boost is only applied if submitter has active registration in the relayers
/// pallet.
pub struct CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>(
PhantomData<(T, I, Priority, SlashAccount)>,
);
impl<T, I: 'static, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for CheckAndBoostBridgeGrandpaTransactions<T, I, Priority, SlashAccount>
where
T: pallet_bridge_relayers::Config + pallet_bridge_grandpa::Config<I>,
T::RuntimeCall: GrandpaCallSubType<T, I>,
{
// bridged header number, bundled in transaction
type ToPostDispatch = Option<BridgedBlockNumber<T, I>>;
fn validate(
who: &Option<T::AccountId>,
call: &T::RuntimeCall,
) -> (Self::ToPostDispatch, TransactionValidity) {
match GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call) {
Ok(Some(our_tx)) => {
let to_post_dispatch = Some(our_tx.base.block_number);
let total_priority_boost =
compute_priority_boost::<T, _, Priority>(&who, our_tx.improved_by);
(
to_post_dispatch,
ValidTransactionBuilder::default().priority(total_priority_boost).build(),
)
},
Ok(None) => (None, ValidTransactionBuilder::default().build()),
Err(e) => (None, Err(e)),
}
}
fn post_dispatch(
who: &Option<T::AccountId>,
has_failed: bool,
bundled_block_number: Self::ToPostDispatch,
) {
// we are only interested in associated pallet submissions
let Some(bundled_block_number) = bundled_block_number else { return };
// we are only interested in failed or unneeded transactions
let has_failed =
has_failed || !SubmitFinalityProofHelper::<T, I>::was_successful(bundled_block_number);
if !has_failed {
return
}
// let's slash registered relayer
if let Some(ref relayer) = *who {
RelayersPallet::<T>::slash_and_deregister(
relayer,
ExplicitOrAccountParams::Explicit(SlashAccount::get()),
);
}
}
}
/// Wrapper for the bridge parachains pallet that checks calls for obsolete submissions
/// and also boosts transaction priority if it has submitted by registered relayer.
/// The boost is computed as
/// `(BundledHeaderNumber - 1 - BestKnownHeaderNumber) * Priority::get()`.
/// The boost is only applied if submitter has active registration in the relayers
/// pallet.
pub struct CheckAndBoostBridgeParachainsTransactions<T, RefPara, Priority, SlashAccount>(
PhantomData<(T, RefPara, Priority, SlashAccount)>,
);
impl<T, RefPara, Priority: Get<TransactionPriority>, SlashAccount: Get<T::AccountId>>
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for CheckAndBoostBridgeParachainsTransactions<T, RefPara, Priority, SlashAccount>
where
T: pallet_bridge_relayers::Config + pallet_bridge_parachains::Config<RefPara::Instance>,
RefPara: RefundableParachainId,
T::RuntimeCall: ParachainsCallSubtype<T, RefPara::Instance>,
{
// bridged header number, bundled in transaction
type ToPostDispatch = Option<SubmitParachainHeadsInfo>;
fn validate(
who: &Option<T::AccountId>,
call: &T::RuntimeCall,
) -> (Self::ToPostDispatch, TransactionValidity) {
match ParachainsCallSubtype::<T, RefPara::Instance>::check_obsolete_submit_parachain_heads(
call,
) {
Ok(Some(our_tx)) if our_tx.base.para_id.0 == RefPara::Id::get() => {
let to_post_dispatch = Some(our_tx.base);
let total_priority_boost =
compute_priority_boost::<T, _, Priority>(&who, our_tx.improved_by);
(
to_post_dispatch,
ValidTransactionBuilder::default().priority(total_priority_boost).build(),
)
},
Ok(_) => (None, ValidTransactionBuilder::default().build()),
Err(e) => (None, Err(e)),
}
}
fn post_dispatch(
who: &Option<T::AccountId>,
has_failed: bool,
maybe_update: Self::ToPostDispatch,
) {
// we are only interested in associated pallet submissions
let Some(update) = maybe_update else { return };
// we are only interested in failed or unneeded transactions
let has_failed = has_failed ||
!SubmitParachainHeadsHelper::<T, RefPara::Instance>::was_successful(&update);
if !has_failed {
return
}
// let's slash registered relayer
if let Some(ref relayer) = *who {
RelayersPallet::<T>::slash_and_deregister(
relayer,
ExplicitOrAccountParams::Explicit(SlashAccount::get()),
);
}
}
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for pallet_bridge_grandpa::Pallet<T, I>
where
T: pallet_bridge_grandpa::Config<I>,
T::RuntimeCall: GrandpaCallSubType<T, I>,
{
type ToPostDispatch = ();
fn validate(_who: &Option<T::AccountId>, call: &T::RuntimeCall) -> ((), TransactionValidity) {
(
(),
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
.and_then(|_| ValidTransactionBuilder::default().build()),
)
}
}
impl<T, I: 'static> BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall>
for pallet_bridge_parachains::Pallet<T, I>
where
T: pallet_bridge_parachains::Config<I>,
T::RuntimeCall: ParachainsCallSubtype<T, I>,
{
type ToPostDispatch = ();
fn validate(_who: &Option<T::AccountId>, call: &T::RuntimeCall) -> ((), TransactionValidity) {
(
(),
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
.and_then(|_| ValidTransactionBuilder::default().build()),
)
}
}
impl<T: pallet_bridge_messages::Config<I>, I: 'static>
BridgeRuntimeFilterCall<T::AccountId, T::RuntimeCall> for pallet_bridge_messages::Pallet<T, I>
where
T::RuntimeCall: MessagesCallSubType<T, I>,
{
type ToPostDispatch = ();
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
/// transactions, that are delivering outdated messages/confirmations. Without this validation,
/// even honest relayers may lose their funds if there are multiple relays running and
/// submitting the same messages/confirmations.
fn validate(_who: &Option<T::AccountId>, call: &T::RuntimeCall) -> ((), TransactionValidity) {
((), call.check_obsolete_call())
}
}
/// Computes priority boost that improved known header by `improved_by`
fn compute_priority_boost<T, N, Priority>(
who: &Option<T::AccountId>,
improved_by: N,
) -> TransactionPriority
where
T: pallet_bridge_relayers::Config,
N: UniqueSaturatedInto<TransactionPriority>,
Priority: Get<TransactionPriority>,
{
// we only boost priority if relayer has staked required balance
let is_relayer_registration_active = who
.as_ref()
.map(|relayer| RelayersPallet::<T>::is_registration_active(relayer))
.unwrap_or(false);
// if tx improves by just one, there's no need to bump its priority
let improved_by: TransactionPriority = improved_by.unique_saturated_into().saturating_sub(1);
// if relayer is registered, for every skipped header we improve by `Priority`
let boost_per_header = if is_relayer_registration_active { Priority::get() } else { 0 };
improved_by.saturating_mul(boost_per_header)
}
/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
///
/// ## Example
///
/// ```nocompile
/// generate_bridge_reject_obsolete_headers_and_messages!{
/// Call, AccountId
/// BridgeRococoGrandpa, BridgeRococoMessages,
/// BridgeRococoParachains
/// }
/// ```
///
/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
/// headers and messages. Without that extension, even honest relayers may lose their funds if
/// there are multiple relays running and submitting the same information.
#[macro_export]
macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
($call:ty, $account_id:ty, $($filter_call:ty),*) => {
#[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
pub struct BridgeRejectObsoleteHeadersAndMessages;
impl sp_runtime::traits::TransactionExtensionBase for BridgeRejectObsoleteHeadersAndMessages {
const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
type Implicit = ();
}
impl<Context> sp_runtime::traits::TransactionExtension<$call, Context> for BridgeRejectObsoleteHeadersAndMessages
where
$account_id: Clone,
<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin: sp_runtime::traits::AsSystemOriginSigner<$account_id>,
{
type Val = (
Option<$account_id>,
( $(
<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
$account_id,
$call,
>>::ToPostDispatch,
)* ),
);
type Pre = Self::Val;
fn validate(
&self,
origin: <$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
call: &$call,
_info: &sp_runtime::traits::DispatchInfoOf<$call>,
_len: usize,
_context: &mut Context,
_self_implicit: Self::Implicit,
_inherited_implication: &impl codec::Encode,
) -> Result<
(
sp_runtime::transaction_validity::ValidTransaction,
Self::Val,
<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
), sp_runtime::transaction_validity::TransactionValidityError
> {
use tuplex::PushBack;
use sp_runtime::traits::AsSystemOriginSigner;
let maybe_relayer = origin.as_system_origin_signer().cloned();
let tx_validity = sp_runtime::transaction_validity::ValidTransaction::default();
let to_prepare = ();
$(
let (from_validate, call_filter_validity) = <
$filter_call as
$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
$account_id,
$call,
>>::validate(&maybe_relayer, call);
let tx_validity = tx_validity.combine_with(call_filter_validity?);
let to_prepare = to_prepare.push_back(from_validate);
)*
Ok((tx_validity, (maybe_relayer, to_prepare), origin))
}
fn prepare(
self,
to_post_dispatch: Self::Val,
_origin: &<$call as sp_runtime::traits::Dispatchable>::RuntimeOrigin,
_call: &$call,
_info: &sp_runtime::traits::DispatchInfoOf<$call>,
_len: usize,
_context: &Context,
) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
Ok(to_post_dispatch)
}
#[allow(unused_variables)]
fn post_dispatch(
to_post_dispatch: Self::Pre,
_info: &sp_runtime::traits::DispatchInfoOf<$call>,
_post_info: &sp_runtime::traits::PostDispatchInfoOf<$call>,
_len: usize,
result: &sp_runtime::DispatchResult,
_context: &Context,
) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> {
if result.is_ok() {
return Ok(());
}
use tuplex::PopFront;
let has_failed = result.is_err();
let (maybe_relayer, to_post_dispatch) = to_post_dispatch;
$(
let (item, to_post_dispatch) = to_post_dispatch.pop_front();
<
$filter_call as
$crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<
$account_id,
$call,
>>::post_dispatch(&maybe_relayer, has_failed, item);
)*
Ok(())
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
extensions::refund_relayer_extension::tests::{
initialize_environment, relayer_account_at_this_chain, submit_parachain_head_call_ex,
submit_relay_header_call_ex, TestParachain,
},
mock::*,
DefaultRefundableParachainId,
};
use bp_polkadot_core::parachains::ParaId;
use codec::Encode;
use frame_support::assert_err;
use sp_runtime::{
traits::{ConstU64, DispatchTransaction},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
};
#[derive(Clone, Debug, PartialEq)]
pub struct AccountId;
impl sp_runtime::traits::AsSystemOriginSigner<AccountId> for AccountId {
fn as_system_origin_signer(&self) -> Option<&AccountId> {
None
}
}
#[derive(Encode)]
pub struct MockCall {
data: u32,
}
impl sp_runtime::traits::Dispatchable for MockCall {
type RuntimeOrigin = AccountId;
type Config = ();
type Info = ();
type PostInfo = ();
fn dispatch(
self,
_origin: Self::RuntimeOrigin,
) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
unimplemented!()
}
}
pub struct FirstFilterCall;
impl BridgeRuntimeFilterCall<AccountId, MockCall> for FirstFilterCall {
type ToPostDispatch = u64;
fn validate(_who: &Option<AccountId>, call: &MockCall) -> (u64, TransactionValidity) {
if call.data <= 1 {
return (1, InvalidTransaction::Custom(1).into())
}
(1, Ok(ValidTransaction { priority: 1, ..Default::default() }))
}
}
pub struct SecondFilterCall;
impl BridgeRuntimeFilterCall<AccountId, MockCall> for SecondFilterCall {
type ToPostDispatch = u64;
fn validate(_who: &Option<AccountId>, call: &MockCall) -> (u64, TransactionValidity) {
if call.data <= 2 {
return (2, InvalidTransaction::Custom(2).into())
}
(2, Ok(ValidTransaction { priority: 2, ..Default::default() }))
}
}
#[test]
fn test() {
generate_bridge_reject_obsolete_headers_and_messages!(
MockCall,
AccountId,
FirstFilterCall,
SecondFilterCall
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate_only(
AccountId,
&MockCall { data: 1 },
&(),
0
),
InvalidTransaction::Custom(1)
);
assert_err!(
BridgeRejectObsoleteHeadersAndMessages.validate_only(
AccountId,
&MockCall { data: 2 },
&(),
0
),
InvalidTransaction::Custom(2)
);
let result = BridgeRejectObsoleteHeadersAndMessages
.validate_only(AccountId, &MockCall { data: 3 }, &(), 0)
.unwrap();
assert_eq!(result.0, ValidTransaction { priority: 3, ..Default::default() });
assert_eq!(result.1, (None, (1, 2)));
}
frame_support::parameter_types! {
pub SlashDestination: ThisChainAccountId = 42;
}
type BridgeGrandpaWrapper =
CheckAndBoostBridgeGrandpaTransactions<TestRuntime, (), ConstU64<1_000>, SlashDestination>;
#[test]
fn grandpa_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
let priority_boost = BridgeGrandpaWrapper::validate(
&Some(relayer_account_at_this_chain()),
&submit_relay_header_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 0);
})
}
#[test]
fn grandpa_wrapper_boosts_extensions_for_registered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let priority_boost = BridgeGrandpaWrapper::validate(
&Some(relayer_account_at_this_chain()),
&submit_relay_header_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 99_000);
})
}
#[test]
fn grandpa_wrapper_slashes_registered_relayer_if_transaction_fails() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeGrandpaWrapper::post_dispatch(
&Some(relayer_account_at_this_chain()),
true,
Some(150),
);
assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
#[test]
fn grandpa_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeGrandpaWrapper::post_dispatch(
&Some(relayer_account_at_this_chain()),
false,
Some(100),
);
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
type BridgeParachainsWrapper = CheckAndBoostBridgeParachainsTransactions<
TestRuntime,
DefaultRefundableParachainId<(), TestParachain>,
ConstU64<1_000>,
SlashDestination,
>;
#[test]
fn parachains_wrapper_does_not_boost_extensions_for_unregistered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
let priority_boost = BridgeParachainsWrapper::validate(
&Some(relayer_account_at_this_chain()),
&submit_parachain_head_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 0);
})
}
#[test]
fn parachains_wrapper_boosts_extensions_for_registered_relayer() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let priority_boost = BridgeParachainsWrapper::validate(
&Some(relayer_account_at_this_chain()),
&submit_parachain_head_call_ex(200),
)
.1
.unwrap()
.priority;
assert_eq!(priority_boost, 99_000);
})
}
#[test]
fn parachains_wrapper_slashes_registered_relayer_if_transaction_fails() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeParachainsWrapper::post_dispatch(
&Some(relayer_account_at_this_chain()),
true,
Some(SubmitParachainHeadsInfo {
at_relay_block: (150, Default::default()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [150u8; 32].into(),
is_free_execution_expected: false,
}),
);
assert!(!BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
#[test]
fn parachains_wrapper_does_not_slash_registered_relayer_if_transaction_succeeds() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
BridgeParachainsWrapper::post_dispatch(
&Some(relayer_account_at_this_chain()),
false,
Some(SubmitParachainHeadsInfo {
at_relay_block: (100, Default::default()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [100u8; 32].into(),
is_free_execution_expected: false,
}),
);
assert!(BridgeRelayers::is_registration_active(&relayer_account_at_this_chain()));
})
}
}
// Copyright (C) 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/>.
//! Bridge-specific transaction extensions.
pub mod check_obsolete_extension;
pub mod priority_calculator;
pub mod refund_relayer_extension;
// Copyright (C) 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/>.
//! Bridge transaction priority calculator.
//!
//! We want to prioritize message delivery transactions with more messages over
//! transactions with less messages. That's because we reject delivery transactions
//! if it contains already delivered message. And if some transaction delivers
//! single message with nonce `N`, then the transaction with nonces `N..=N+100` will
//! be rejected. This can lower bridge throughput down to one message per block.
use bp_messages::MessageNonce;
use frame_support::traits::Get;
use sp_runtime::transaction_validity::TransactionPriority;
// reexport everything from `integrity_tests` module
#[allow(unused_imports)]
pub use integrity_tests::*;
/// Compute priority boost for message delivery transaction that delivers
/// given number of messages.
pub fn compute_priority_boost<PriorityBoostPerMessage>(
messages: MessageNonce,
) -> TransactionPriority
where
PriorityBoostPerMessage: Get<TransactionPriority>,
{
// we don't want any boost for transaction with single message => minus one
PriorityBoostPerMessage::get().saturating_mul(messages.saturating_sub(1))
}
#[cfg(not(feature = "integrity-test"))]
mod integrity_tests {}
#[cfg(feature = "integrity-test")]
mod integrity_tests {
use super::compute_priority_boost;
use bp_messages::MessageNonce;
use bp_runtime::PreComputedSize;
use frame_support::{
dispatch::{DispatchClass, DispatchInfo, Pays, PostDispatchInfo},
traits::Get,
};
use pallet_bridge_messages::WeightInfoExt;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{
traits::{Dispatchable, UniqueSaturatedInto, Zero},
transaction_validity::TransactionPriority,
FixedPointOperand, SaturatedConversion, Saturating,
};
type BalanceOf<T> =
<<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
T,
>>::Balance;
/// Ensures that the value of `PriorityBoostPerMessage` matches the value of
/// `tip_boost_per_message`.
///
/// We want two transactions, `TX1` with `N` messages and `TX2` with `N+1` messages, have almost
/// the same priority if we'll add `tip_boost_per_message` tip to the `TX1`. We want to be sure
/// that if we add plain `PriorityBoostPerMessage` priority to `TX1`, the priority will be close
/// to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, MessagesInstance, PriorityBoostPerMessage>(
tip_boost_per_message: BalanceOf<Runtime>,
) where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
PriorityBoostPerMessage: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
let priority_boost_per_message = PriorityBoostPerMessage::get();
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
for messages in 1..=maximal_messages_in_delivery_transaction {
let base_priority = estimate_message_delivery_transaction_priority::<
Runtime,
MessagesInstance,
>(messages, Zero::zero());
let priority_boost = compute_priority_boost::<PriorityBoostPerMessage>(messages);
let priority_with_boost = base_priority + priority_boost;
let tip = tip_boost_per_message.saturating_mul((messages - 1).unique_saturated_into());
let priority_with_tip =
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(1, tip);
const ERROR_MARGIN: TransactionPriority = 5; // 5%
if priority_with_boost.abs_diff(priority_with_tip).saturating_mul(100) /
priority_with_tip >
ERROR_MARGIN
{
panic!(
"The PriorityBoostPerMessage value ({}) must be fixed to: {}",
priority_boost_per_message,
compute_priority_boost_per_message::<Runtime, MessagesInstance>(
tip_boost_per_message
),
);
}
}
}
/// Compute priority boost that we give to message delivery transaction for additional message.
#[cfg(feature = "integrity-test")]
fn compute_priority_boost_per_message<Runtime, MessagesInstance>(
tip_boost_per_message: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// esimate priority of transaction that delivers one message and has large tip
let maximal_messages_in_delivery_transaction =
Runtime::MaxUnconfirmedMessagesAtInboundLane::get();
let small_with_tip_priority =
estimate_message_delivery_transaction_priority::<Runtime, MessagesInstance>(
1,
tip_boost_per_message
.saturating_mul(maximal_messages_in_delivery_transaction.saturated_into()),
);
// estimate priority of transaction that delivers maximal number of messages, but has no tip
let large_without_tip_priority = estimate_message_delivery_transaction_priority::<
Runtime,
MessagesInstance,
>(maximal_messages_in_delivery_transaction, Zero::zero());
small_with_tip_priority
.saturating_sub(large_without_tip_priority)
.saturating_div(maximal_messages_in_delivery_transaction - 1)
}
/// Estimate message delivery transaction priority.
#[cfg(feature = "integrity-test")]
fn estimate_message_delivery_transaction_priority<Runtime, MessagesInstance>(
messages: MessageNonce,
tip: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime:
pallet_transaction_payment::Config + pallet_bridge_messages::Config<MessagesInstance>,
MessagesInstance: 'static,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
// just an estimation of extra transaction bytes that are added to every transaction
// (including signature, signed extensions extra and etc + in our case it includes
// all call arguments extept the proof itself)
let base_tx_size = 512;
// let's say we are relaying similar small messages and for every message we add more trie
// nodes to the proof (x0.5 because we expect some nodes to be reused)
let estimated_message_size = 512;
// let's say all our messages have the same dispatch weight
let estimated_message_dispatch_weight = <Runtime as pallet_bridge_messages::Config<
MessagesInstance,
>>::WeightInfo::message_dispatch_weight(
estimated_message_size
);
// messages proof argument size is (for every message) messages size + some additional
// trie nodes. Some of them are reused by different messages, so let's take 2/3 of default
// "overhead" constant
let messages_proof_size = <Runtime as pallet_bridge_messages::Config<MessagesInstance>>::WeightInfo::expected_extra_storage_proof_size()
.saturating_mul(2)
.saturating_div(3)
.saturating_add(estimated_message_size)
.saturating_mul(messages as _);
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(messages_proof_size);
let transaction_weight = <Runtime as pallet_bridge_messages::Config<MessagesInstance>>::WeightInfo::receive_messages_proof_weight(
&PreComputedSize(transaction_size as _),
messages as _,
estimated_message_dispatch_weight.saturating_mul(messages),
);
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
transaction_size as _,
tip,
Zero::zero(),
)
}
}
// Copyright (C) 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/>.
//! Transaction extension that refunds relayer if he has delivered some new messages.
//! It also refunds transaction cost if the transaction is an `utility.batchAll()`
//! with calls that are: delivering new messsage and all necessary underlying headers
//! (parachain or relay chain).
use crate::{
messages_call_ext::{
CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType,
},
RefundableParachainId,
};
use bp_messages::{LaneId, MessageNonce};
use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams};
use bp_runtime::{RangeInclusiveExt, StaticStrProvider};
use codec::{Codec, Decode, Encode};
use frame_support::{
dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
traits::IsSubType,
weights::Weight,
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use pallet_bridge_grandpa::{
CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
};
use pallet_bridge_messages::Config as MessagesConfig;
use pallet_bridge_parachains::{
BoundedBridgeGrandpaConfig, CallSubType as ParachainsCallSubType, Config as ParachainsConfig,
RelayBlockNumber, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo,
};
use pallet_bridge_relayers::{
Config as RelayersConfig, Pallet as RelayersPallet, WeightInfoExt as _,
};
use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTransaction};
use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf,
TransactionExtension, TransactionExtensionBase, ValidateResult, Zero,
},
transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionValidityError, ValidTransactionBuilder,
},
DispatchResult, FixedPointOperand, RuntimeDebug,
};
use sp_std::{marker::PhantomData, vec, vec::Vec};
type AccountIdOf<R> = <R as frame_system::Config>::AccountId;
// without this typedef rustfmt fails with internal err
type BalanceOf<R> =
<<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance;
type CallOf<R> = <R as frame_system::Config>::RuntimeCall;
/// Trait identifying a bridged messages lane. A relayer might be refunded for delivering messages
/// coming from this lane.
pub trait RefundableMessagesLaneId {
/// The instance of the bridge messages pallet.
type Instance: 'static;
/// The messages lane id.
type Id: Get<LaneId>;
}
/// Default implementation of `RefundableMessagesLaneId`.
pub struct RefundableMessagesLane<Instance, Id>(PhantomData<(Instance, Id)>);
impl<Instance, Id> RefundableMessagesLaneId for RefundableMessagesLane<Instance, Id>
where
Instance: 'static,
Id: Get<LaneId>,
{
type Instance = Instance;
type Id = Id;
}
/// Refund calculator.
pub trait RefundCalculator {
/// The underlying integer type in which the refund is calculated.
type Balance;
/// Compute refund for given transaction.
fn compute_refund(
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
tip: Self::Balance,
) -> Self::Balance;
}
/// `RefundCalculator` implementation which refunds the actual transaction fee.
pub struct ActualFeeRefund<R>(PhantomData<R>);
impl<R> RefundCalculator for ActualFeeRefund<R>
where
R: TransactionPaymentConfig,
CallOf<R>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<R>: FixedPointOperand,
{
type Balance = BalanceOf<R>;
fn compute_refund(
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
tip: BalanceOf<R>,
) -> BalanceOf<R> {
pallet_transaction_payment::Pallet::<R>::compute_actual_fee(len as _, info, post_info, tip)
}
}
/// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct PreDispatchData<AccountId> {
/// Transaction submitter (relayer) account.
relayer: AccountId,
/// Type of the call.
call_info: CallInfo,
}
/// Type of the call that the extension recognizes.
#[derive(RuntimeDebugNoBound, PartialEq)]
pub enum CallInfo {
/// Relay chain finality + parachain finality + message delivery/confirmation calls.
AllFinalityAndMsgs(
SubmitFinalityProofInfo<RelayBlockNumber>,
SubmitParachainHeadsInfo,
MessagesCallInfo,
),
/// Relay chain finality + message delivery/confirmation calls.
RelayFinalityAndMsgs(SubmitFinalityProofInfo<RelayBlockNumber>, MessagesCallInfo),
/// Parachain finality + message delivery/confirmation calls.
///
/// This variant is used only when bridging with parachain.
ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo),
/// Standalone message delivery/confirmation call.
Msgs(MessagesCallInfo),
}
impl CallInfo {
/// Returns true if call is a message delivery call (with optional finality calls).
fn is_receive_messages_proof_call(&self) -> bool {
match self.messages_call_info() {
MessagesCallInfo::ReceiveMessagesProof(_) => true,
MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => false,
}
}
/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
fn submit_finality_proof_info(&self) -> Option<SubmitFinalityProofInfo<RelayBlockNumber>> {
match *self {
Self::AllFinalityAndMsgs(info, _, _) => Some(info),
Self::RelayFinalityAndMsgs(info, _) => Some(info),
_ => None,
}
}
/// Returns mutable reference to pre-dispatch `finality_target` sent to the
/// `SubmitFinalityProof` call.
#[cfg(test)]
fn submit_finality_proof_info_mut(
&mut self,
) -> Option<&mut SubmitFinalityProofInfo<RelayBlockNumber>> {
match *self {
Self::AllFinalityAndMsgs(ref mut info, _, _) => Some(info),
Self::RelayFinalityAndMsgs(ref mut info, _) => Some(info),
_ => None,
}
}
/// Returns the pre-dispatch `SubmitParachainHeadsInfo`.
fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> {
match self {
Self::AllFinalityAndMsgs(_, info, _) => Some(info),
Self::ParachainFinalityAndMsgs(info, _) => Some(info),
_ => None,
}
}
/// Returns the pre-dispatch `ReceiveMessagesProofInfo`.
fn messages_call_info(&self) -> &MessagesCallInfo {
match self {
Self::AllFinalityAndMsgs(_, _, info) => info,
Self::RelayFinalityAndMsgs(_, info) => info,
Self::ParachainFinalityAndMsgs(_, info) => info,
Self::Msgs(info) => info,
}
}
}
/// The actions on relayer account that need to be performed because of his actions.
#[derive(RuntimeDebug, PartialEq)]
pub enum RelayerAccountAction<AccountId, Reward> {
/// Do nothing with relayer account.
None,
/// Reward the relayer.
Reward(AccountId, RewardsAccountParams, Reward),
/// Slash the relayer.
Slash(AccountId, RewardsAccountParams),
}
/// Everything common among our refund transaction extensions.
pub trait RefundTransactionExtension:
'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
{
/// This chain runtime.
type Runtime: MessagesConfig<<Self::Msgs as RefundableMessagesLaneId>::Instance>
+ RelayersConfig;
/// Messages pallet and lane reference.
type Msgs: RefundableMessagesLaneId;
/// Refund amount calculator.
type Refund: RefundCalculator<Balance = <Self::Runtime as RelayersConfig>::Reward>;
/// Priority boost calculator.
type Priority: Get<TransactionPriority>;
/// Signed extension unique identifier.
type Id: StaticStrProvider;
/// Unpack batch runtime call.
fn expand_call(call: &CallOf<Self::Runtime>) -> Vec<&CallOf<Self::Runtime>>;
/// Given runtime call, check if it has supported format. Additionally, check if any of
/// (optionally batched) calls are obsolete and we shall reject the transaction.
fn parse_and_check_for_obsolete_call(
call: &CallOf<Self::Runtime>,
) -> Result<Option<CallInfo>, TransactionValidityError>;
/// Check if parsed call is already obsolete.
fn check_obsolete_parsed_call(
call: &CallOf<Self::Runtime>,
) -> Result<&CallOf<Self::Runtime>, TransactionValidityError>;
/// Called from post-dispatch and shall perform additional checks (apart from messages
/// transaction success) of given call result.
fn additional_call_result_check(
relayer: &AccountIdOf<Self::Runtime>,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool;
/// Given post-dispatch information, analyze the outcome of relayer call and return
/// actions that need to be performed on relayer account.
fn analyze_call_result(
pre: Option<Option<PreDispatchData<AccountIdOf<Self::Runtime>>>>,
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
result: &DispatchResult,
) -> RelayerAccountAction<AccountIdOf<Self::Runtime>, <Self::Runtime as RelayersConfig>::Reward>
{
let mut extra_weight = Weight::zero();
let mut extra_size = 0;
// We don't refund anything for transactions that we don't support.
let (relayer, call_info) = match pre {
Some(Some(pre)) => (pre.relayer, pre.call_info),
_ => return RelayerAccountAction::None,
};
// now we know that the relayer either needs to be rewarded, or slashed
// => let's prepare the correspondent account that pays reward/receives slashed amount
let reward_account_params =
RewardsAccountParams::new(
<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
<Self::Runtime as MessagesConfig<
<Self::Msgs as RefundableMessagesLaneId>::Instance,
>>::BridgedChainId::get(),
if call_info.is_receive_messages_proof_call() {
RewardsAccountOwner::ThisChain
} else {
RewardsAccountOwner::BridgedChain
},
);
// prepare return value for the case if the call has failed or it has not caused
// expected side effects (e.g. not all messages have been accepted)
//
// we are not checking if relayer is registered here - it happens during the slash attempt
//
// there are couple of edge cases here:
//
// - when the relayer becomes registered during message dispatch: this is unlikely + relayer
// should be ready for slashing after registration;
//
// - when relayer is registered after `validate` is called and priority is not boosted:
// relayer should be ready for slashing after registration.
let may_slash_relayer =
Self::bundled_messages_for_priority_boost(Some(&call_info)).is_some();
let slash_relayer_if_delivery_result = may_slash_relayer
.then(|| RelayerAccountAction::Slash(relayer.clone(), reward_account_params))
.unwrap_or(RelayerAccountAction::None);
// We don't refund anything if the transaction has failed.
if let Err(e) = result {
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
Self::Id::STR,
<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
relayer,
e,
);
return slash_relayer_if_delivery_result
}
// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
// it contained. If this happens, we consider the transaction "helpful" and refund it.
let msgs_call_info = call_info.messages_call_info();
if !MessagesCallHelper::<Self::Runtime, <Self::Msgs as RefundableMessagesLaneId>::Instance>::was_successful(msgs_call_info) {
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid messages call",
Self::Id::STR,
<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
relayer,
);
return slash_relayer_if_delivery_result
}
// do additional checks
if !Self::additional_call_result_check(
&relayer,
&call_info,
&mut extra_weight,
&mut extra_size,
) {
return slash_relayer_if_delivery_result
}
// regarding the tip - refund that happens here (at this side of the bridge) isn't the whole
// relayer compensation. He'll receive some amount at the other side of the bridge. It shall
// (in theory) cover the tip there. Otherwise, if we'll be compensating tip here, some
// malicious relayer may use huge tips, effectively depleting account that pay rewards. The
// cost of this attack is nothing. Hence we use zero as tip here.
let tip = Zero::zero();
// decrease post-dispatch weight/size using extra weight/size that we know now
let post_info_len = len.saturating_sub(extra_size as usize);
let mut post_info_weight =
post_info.actual_weight.unwrap_or(info.weight).saturating_sub(extra_weight);
// let's also replace the weight of slashing relayer with the weight of rewarding relayer
if call_info.is_receive_messages_proof_call() {
post_info_weight = post_info_weight.saturating_sub(
<Self::Runtime as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(),
);
}
// compute the relayer refund
let mut post_info = *post_info;
post_info.actual_weight = Some(post_info_weight);
let refund = Self::Refund::compute_refund(info, &post_info, post_info_len, tip);
// we can finally reward relayer
RelayerAccountAction::Reward(relayer, reward_account_params, refund)
}
/// Returns number of bundled messages `Some(_)`, if the given call info is a:
///
/// - message delivery transaction;
///
/// - with reasonable bundled messages that may be accepted by the messages pallet.
///
/// This function is used to check whether the transaction priority should be
/// virtually boosted. The relayer registration (we only boost priority for registered
/// relayer transactions) must be checked outside.
fn bundled_messages_for_priority_boost(call_info: Option<&CallInfo>) -> Option<MessageNonce> {
// we only boost priority of message delivery transactions
let parsed_call = match call_info {
Some(parsed_call) if parsed_call.is_receive_messages_proof_call() => parsed_call,
_ => return None,
};
// compute total number of messages in transaction
let bundled_messages = parsed_call.messages_call_info().bundled_messages().saturating_len();
// a quick check to avoid invalid high-priority transactions
let max_unconfirmed_messages_in_confirmation_tx = <Self::Runtime as MessagesConfig<
<Self::Msgs as RefundableMessagesLaneId>::Instance,
>>::MaxUnconfirmedMessagesAtInboundLane::get(
);
if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
return None
}
Some(bundled_messages)
}
}
/// Adapter that allow implementing `sp_runtime::traits::TransactionExtension` for any
/// `RefundTransactionExtension`.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
pub struct RefundTransactionExtensionAdapter<T: RefundTransactionExtension>(T);
impl<T: RefundTransactionExtension> TransactionExtensionBase
for RefundTransactionExtensionAdapter<T>
where
CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
{
const IDENTIFIER: &'static str = T::Id::STR;
type Implicit = ();
}
impl<T: RefundTransactionExtension, Context> TransactionExtension<CallOf<T::Runtime>, Context>
for RefundTransactionExtensionAdapter<T>
where
CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
<CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin:
AsSystemOriginSigner<AccountIdOf<T::Runtime>> + Clone,
{
type Pre = Option<PreDispatchData<AccountIdOf<T::Runtime>>>;
type Val = Option<CallInfo>;
fn validate(
&self,
origin: <CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin,
call: &CallOf<T::Runtime>,
_info: &DispatchInfoOf<CallOf<T::Runtime>>,
_len: usize,
_context: &mut Context,
_self_implicit: Self::Implicit,
_inherited_implication: &impl Encode,
) -> ValidateResult<Self::Val, CallOf<T::Runtime>> {
let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
// this is the only relevant line of code for the `pre_dispatch`
//
// we're not calling `validate` from `pre_dispatch` directly because of performance
// reasons, so if you're adding some code that may fail here, please check if it needs
// to be added to the `pre_dispatch` as well
let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
// the following code just plays with transaction priority and never returns an error
// we only boost priority of presumably correct message delivery transactions
let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
Some(bundled_messages) => bundled_messages,
None => return Ok((Default::default(), parsed_call, origin)),
};
// we only boost priority if relayer has staked required balance
if !RelayersPallet::<T::Runtime>::is_registration_active(who) {
return Ok((Default::default(), parsed_call, origin))
}
// compute priority boost
let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::<
T::Priority,
>(bundled_messages);
let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
log::trace!(
target: "runtime::bridge",
"{} via {:?} has boosted priority of message delivery transaction \
of relayer {:?}: {} messages -> {} priority",
Self::IDENTIFIER,
<T::Msgs as RefundableMessagesLaneId>::Id::get(),
who,
bundled_messages,
priority_boost,
);
let validity = valid_transaction.build()?;
Ok((validity, parsed_call, origin))
}
fn prepare(
self,
val: Self::Val,
origin: &<CallOf<T::Runtime> as Dispatchable>::RuntimeOrigin,
_call: &CallOf<T::Runtime>,
_info: &DispatchInfoOf<CallOf<T::Runtime>>,
_len: usize,
_context: &Context,
) -> Result<Self::Pre, TransactionValidityError> {
let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
Ok(val.map(|call_info| {
log::trace!(
target: "runtime::bridge",
"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
Self::IDENTIFIER,
<T::Msgs as RefundableMessagesLaneId>::Id::get(),
call_info,
);
PreDispatchData { relayer: who.clone(), call_info }
}))
}
fn post_dispatch(
pre: Self::Pre,
info: &DispatchInfoOf<CallOf<T::Runtime>>,
post_info: &PostDispatchInfoOf<CallOf<T::Runtime>>,
len: usize,
result: &DispatchResult,
_context: &Context,
) -> Result<(), TransactionValidityError> {
let call_result = T::analyze_call_result(Some(pre), info, post_info, len, result);
match call_result {
RelayerAccountAction::None => (),
RelayerAccountAction::Reward(relayer, reward_account, reward) => {
RelayersPallet::<T::Runtime>::register_relayer_reward(
reward_account,
&relayer,
reward,
);
log::trace!(
target: "runtime::bridge",
"{} via {:?} has registered reward: {:?} for {:?}",
Self::IDENTIFIER,
<T::Msgs as RefundableMessagesLaneId>::Id::get(),
reward,
relayer,
);
},
RelayerAccountAction::Slash(relayer, slash_account) =>
RelayersPallet::<T::Runtime>::slash_and_deregister(
&relayer,
ExplicitOrAccountParams::Params(slash_account),
),
}
Ok(())
}
}
/// Transaction extension that refunds a relayer for new messages coming from a parachain.
///
/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
/// with message delivery transaction. Batch may deliver either both relay chain header and
/// parachain head, or just parachain head. Corresponding headers must be used in messages
/// proof verification.
///
/// Extension does not refund transaction tip due to security reasons.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>(
PhantomData<(
// runtime with `frame-utility`, `pallet-bridge-grandpa`, `pallet-bridge-parachains`,
// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// implementation of `RefundableParachainId` trait, which specifies the instance of
// the used `pallet-bridge-parachains` pallet and the bridged parachain id
Para,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl<Runtime, Para, Msgs, Refund, Priority, Id> RefundTransactionExtension
for RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>
where
Self: 'static + Send + Sync,
RefundBridgedGrandpaMessages<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>: 'static + Send + Sync,
Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
+ ParachainsConfig<Para::Instance>
+ MessagesConfig<Msgs::Instance>
+ RelayersConfig,
Para: RefundableParachainId,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator<Balance = Runtime::Reward>,
Priority: Get<TransactionPriority>,
Id: StaticStrProvider,
CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
+ ParachainsCallSubType<Runtime, Para::Instance>
+ MessagesCallSubType<Runtime, Msgs::Instance>,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
match call.is_sub_type() {
Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 3 =>
calls.iter().collect(),
Some(_) => vec![],
None => vec![call],
}
}
fn parse_and_check_for_obsolete_call(
call: &CallOf<Runtime>,
) -> Result<Option<CallInfo>, TransactionValidityError> {
let calls = Self::expand_call(call);
let total_calls = calls.len();
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
let para_finality_call = calls
.next()
.transpose()?
.and_then(|c| c.submit_parachain_heads_info_for(Para::Id::get()));
let relay_finality_call =
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
Ok(match (total_calls, relay_finality_call, para_finality_call, msgs_call) {
(3, Some(relay_finality_call), Some(para_finality_call), Some(msgs_call)) => Some(
CallInfo::AllFinalityAndMsgs(relay_finality_call, para_finality_call, msgs_call),
),
(2, None, Some(para_finality_call), Some(msgs_call)) =>
Some(CallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
(1, None, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
_ => None,
})
}
fn check_obsolete_parsed_call(
call: &CallOf<Runtime>,
) -> Result<&CallOf<Runtime>, TransactionValidityError> {
call.check_obsolete_submit_finality_proof()?;
call.check_obsolete_submit_parachain_heads()?;
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
relayer: &Runtime::AccountId,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool {
// check if relay chain state has been updated
let is_granda_call_succeeded =
RefundBridgedGrandpaMessages::<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>::additional_call_result_check(relayer, call_info, extra_weight, extra_size);
if !is_granda_call_succeeded {
return false
}
// check if parachain state has been updated
if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
para_proof_info,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: "runtime::bridge",
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
Id::STR,
Para::Id::get(),
Msgs::Id::get(),
relayer,
);
return false
}
}
true
}
}
/// Transaction extension that refunds a relayer for new messages coming from a standalone (GRANDPA)
/// chain.
///
/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
/// with message delivery transaction. Batch may deliver either both relay chain header and
/// parachain head, or just parachain head. Corresponding headers must be used in messages proof
/// verification.
///
/// Extension does not refund transaction tip due to security reasons.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>(
PhantomData<(
// runtime with `frame-utility`, `pallet-bridge-grandpa`,
// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// bridge GRANDPA pallet instance, used to track bridged chain state
GrandpaInstance,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id> RefundTransactionExtension
for RefundBridgedGrandpaMessages<Runtime, GrandpaInstance, Msgs, Refund, Priority, Id>
where
Self: 'static + Send + Sync,
Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
+ BoundedBridgeGrandpaConfig<GrandpaInstance>
+ MessagesConfig<Msgs::Instance>
+ RelayersConfig,
GrandpaInstance: 'static,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator<Balance = Runtime::Reward>,
Priority: Get<TransactionPriority>,
Id: StaticStrProvider,
CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
+ GrandpaCallSubType<Runtime, GrandpaInstance>
+ MessagesCallSubType<Runtime, Msgs::Instance>,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
match call.is_sub_type() {
Some(UtilityCall::<Runtime>::batch_all { ref calls }) if calls.len() <= 2 =>
calls.iter().collect(),
Some(_) => vec![],
None => vec![call],
}
}
fn parse_and_check_for_obsolete_call(
call: &CallOf<Runtime>,
) -> Result<Option<CallInfo>, TransactionValidityError> {
let calls = Self::expand_call(call);
let total_calls = calls.len();
let mut calls = calls.into_iter().map(Self::check_obsolete_parsed_call).rev();
let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info_for(Msgs::Id::get()));
let relay_finality_call =
calls.next().transpose()?.and_then(|c| c.submit_finality_proof_info());
Ok(match (total_calls, relay_finality_call, msgs_call) {
(2, Some(relay_finality_call), Some(msgs_call)) =>
Some(CallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
(1, None, Some(msgs_call)) => Some(CallInfo::Msgs(msgs_call)),
_ => None,
})
}
fn check_obsolete_parsed_call(
call: &CallOf<Runtime>,
) -> Result<&CallOf<Runtime>, TransactionValidityError> {
call.check_obsolete_submit_finality_proof()?;
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
relayer: &Runtime::AccountId,
call_info: &CallInfo,
extra_weight: &mut Weight,
extra_size: &mut u32,
) -> bool {
// check if relay chain state has been updated
if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
if !SubmitFinalityProofHelper::<Self::Runtime, GrandpaInstance>::was_successful(
finality_proof_info.block_number,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: "runtime::bridge",
"{} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
Self::Id::STR,
<Self::Msgs as RefundableMessagesLaneId>::Id::get(),
relayer,
);
return false
}
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
// `utility.batchAll` transaction always requires payment. But in both cases we'll
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
// to submit dedicated transaction.
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
*extra_weight = (*extra_weight).saturating_add(finality_proof_info.extra_weight);
*extra_size = (*extra_size).saturating_add(finality_proof_info.extra_size);
}
true
}
}
/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
/// transactions. Finality transactions are not refunded.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, GrandpaInstance, Msgs, Refund, Priority, Id))]
pub struct RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>(
PhantomData<(
// runtime with `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
Runtime,
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
// the used `pallet-bridge-messages` pallet and the lane within this pallet
Msgs,
// implementation of the `RefundCalculator` trait, that is used to compute refund that
// we give to relayer for his transaction
Refund,
// getter for per-message `TransactionPriority` boost that we give to message
// delivery transactions
Priority,
// the runtime-unique identifier of this signed extension
Id,
)>,
);
impl<Runtime, Msgs, Refund, Priority, Id> RefundTransactionExtension
for RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>
where
Self: 'static + Send + Sync,
Runtime: MessagesConfig<Msgs::Instance> + RelayersConfig,
Msgs: RefundableMessagesLaneId,
Refund: RefundCalculator<Balance = Runtime::Reward>,
Priority: Get<TransactionPriority>,
Id: StaticStrProvider,
CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ MessagesCallSubType<Runtime, Msgs::Instance>,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
fn expand_call(call: &CallOf<Runtime>) -> Vec<&CallOf<Runtime>> {
vec![call]
}
fn parse_and_check_for_obsolete_call(
call: &CallOf<Runtime>,
) -> Result<Option<CallInfo>, TransactionValidityError> {
let call = Self::check_obsolete_parsed_call(call)?;
Ok(call.call_info_for(Msgs::Id::get()).map(CallInfo::Msgs))
}
fn check_obsolete_parsed_call(
call: &CallOf<Runtime>,
) -> Result<&CallOf<Runtime>, TransactionValidityError> {
call.check_obsolete_call()?;
Ok(call)
}
fn additional_call_result_check(
_relayer: &Runtime::AccountId,
_call_info: &CallInfo,
_extra_weight: &mut Weight,
_extra_size: &mut u32,
) -> bool {
// everything is checked by the `RefundTransactionExtension`
true
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
},
messages_call_ext::{
BaseMessagesProofInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
UnrewardedRelayerOccupation,
},
mock::*,
DefaultRefundableParachainId,
};
use bp_header_chain::StoredHeaderDataBuilder;
use bp_messages::{
DeliveredMessages, InboundLaneData, MessageNonce, MessagesOperatingMode, OutboundLaneData,
UnrewardedRelayer, UnrewardedRelayersState,
};
use bp_parachains::{BestParaHeadHash, ParaInfo};
use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
use bp_runtime::{BasicOperatingMode, HeaderId};
use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
use frame_support::{
assert_storage_noop, parameter_types,
traits::{fungible::Mutate, ReservableCurrency},
weights::Weight,
};
use pallet_bridge_grandpa::{Call as GrandpaCall, Pallet as GrandpaPallet, StoredAuthoritySet};
use pallet_bridge_messages::{Call as MessagesCall, Pallet as MessagesPallet};
use pallet_bridge_parachains::{
Call as ParachainsCall, Pallet as ParachainsPallet, RelayBlockHash,
};
use sp_runtime::{
traits::{ConstU64, DispatchTransaction, Header as HeaderT},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
DispatchError,
};
parameter_types! {
pub TestParachain: u32 = 1000;
pub TestLaneId: LaneId = TEST_LANE_ID;
pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
TEST_BRIDGED_CHAIN_ID,
RewardsAccountOwner::ThisChain,
);
pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
TEST_BRIDGED_CHAIN_ID,
RewardsAccountOwner::BridgedChain,
);
}
bp_runtime::generate_static_str_provider!(TestExtension);
type TestMessagesExtensionProvider = RefundBridgedMessages<
TestRuntime,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
ConstU64<1>,
StrTestExtension,
>;
type TestMessagesExtension = RefundTransactionExtensionAdapter<TestMessagesExtensionProvider>;
type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
TestRuntime,
(),
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
ConstU64<1>,
StrTestExtension,
>;
type TestGrandpaExtension = RefundTransactionExtensionAdapter<TestGrandpaExtensionProvider>;
type TestExtensionProvider = RefundBridgedParachainMessages<
TestRuntime,
DefaultRefundableParachainId<(), TestParachain>,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
ConstU64<1>,
StrTestExtension,
>;
type TestExtension = RefundTransactionExtensionAdapter<TestExtensionProvider>;
fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
let test_stake: ThisChainBalance = TestStake::get();
ExistentialDeposit::get().saturating_add(test_stake * 100)
}
// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
// works)
fn delivery_rewards_account() -> ThisChainAccountId {
TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
}
fn confirmation_rewards_account() -> ThisChainAccountId {
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
}
pub fn relayer_account_at_this_chain() -> ThisChainAccountId {
0
}
fn relayer_account_at_bridged_chain() -> BridgedChainAccountId {
0
}
pub fn initialize_environment(
best_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) {
let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default());
pallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
);
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(best_relay_header);
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
best_relay_header.hash(),
bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
);
let para_id = ParaId(TestParachain::get());
let para_info = ParaInfo {
best_head_hash: BestParaHeadHash {
at_relay_block_number: parachain_head_at_relay_header_number,
head_hash: [parachain_head_at_relay_header_number as u8; 32].into(),
},
next_imported_hash_position: 0,
};
pallet_bridge_parachains::ParasInfo::<TestRuntime>::insert(para_id, para_info);
let lane_id = TestLaneId::get();
let in_lane_data =
InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
let out_lane_data =
OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
Balances::mint_into(
&relayer_account_at_this_chain(),
initial_balance_of_relayer_account_at_this_chain(),
)
.unwrap();
}
fn submit_relay_header_call(relay_header_number: RelayBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
let relay_justification = make_default_justification(&relay_header);
RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof {
finality_target: Box::new(relay_header),
justification: relay_justification,
})
}
pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
let relay_justification = make_default_justification(&relay_header);
RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof_ex {
finality_target: Box::new(relay_header),
justification: relay_justification,
current_set_id: TEST_GRANDPA_SET_ID,
is_free_execution_expected: false,
})
}
fn submit_parachain_head_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![(
ParaId(TestParachain::get()),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
})
}
pub fn submit_parachain_head_call_ex(
parachain_head_at_relay_header_number: RelayBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
parachains: vec![(
ParaId(TestParachain::get()),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
is_free_execution_expected: false,
})
}
fn message_delivery_call(best_message: MessageNonce) -> RuntimeCall {
RuntimeCall::BridgeMessages(MessagesCall::receive_messages_proof {
relayer_id_at_bridged_chain: relayer_account_at_bridged_chain(),
proof: FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: vec![],
lane: TestLaneId::get(),
nonces_start: pallet_bridge_messages::InboundLanes::<TestRuntime>::get(
TEST_LANE_ID,
)
.last_delivered_nonce() +
1,
nonces_end: best_message,
},
messages_count: 1,
dispatch_weight: Weight::zero(),
})
}
fn message_confirmation_call(best_message: MessageNonce) -> RuntimeCall {
RuntimeCall::BridgeMessages(MessagesCall::receive_messages_delivery_proof {
proof: FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: vec![],
lane: TestLaneId::get(),
},
relayers_state: UnrewardedRelayersState {
last_delivered_nonce: best_message,
..Default::default()
},
})
}
fn parachain_finality_and_delivery_batch_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
}
fn parachain_finality_and_confirmation_batch_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn relay_finality_and_delivery_batch_call(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
message_delivery_call(best_message),
],
})
}
fn relay_finality_and_delivery_batch_call_ex(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
message_delivery_call(best_message),
],
})
}
fn relay_finality_and_confirmation_batch_call(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn relay_finality_and_confirmation_batch_call_ex(
relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn all_finality_and_delivery_batch_call(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
}
fn all_finality_and_delivery_batch_call_ex(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
submit_parachain_head_call_ex(parachain_head_at_relay_header_number),
message_delivery_call(best_message),
],
})
}
fn all_finality_and_confirmation_batch_call(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call(relay_header_number),
submit_parachain_head_call(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn all_finality_and_confirmation_batch_call_ex(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
submit_relay_header_call_ex(relay_header_number),
submit_parachain_head_call_ex(parachain_head_at_relay_header_number),
message_confirmation_call(best_message),
],
})
}
fn all_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::AllFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
}),
),
}
}
fn all_finality_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
let mut data = all_finality_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::AllFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
)),
),
}
}
fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
let mut data = all_finality_confirmation_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn relay_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::RelayFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
}),
),
}
}
fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
let mut data = relay_finality_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::RelayFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
extra_weight: Weight::zero(),
extra_size: 0,
is_mandatory: false,
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
)),
),
}
}
fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
let mut data = relay_finality_confirmation_pre_dispatch_data();
data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id =
Some(TEST_GRANDPA_SET_ID);
data
}
fn parachain_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
}),
),
}
}
fn parachain_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block: (200, [0u8; 32].into()),
para_id: ParaId(TestParachain::get()),
para_head_hash: [200u8; 32].into(),
is_free_execution_expected: false,
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
)),
),
}
}
fn delivery_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof(
ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: MaxUnrewardedRelayerEntriesAtInboundLane::get(),
free_message_slots: MaxUnconfirmedMessagesAtInboundLane::get(),
},
},
)),
}
}
fn confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof(
ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
bundled_range: 101..=200,
best_stored_nonce: 100,
}),
)),
}
}
fn set_bundled_range_end(
mut pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
end: MessageNonce,
) -> PreDispatchData<ThisChainAccountId> {
let msg_info = match pre_dispatch_data.call_info {
CallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
CallInfo::RelayFinalityAndMsgs(_, ref mut info) => info,
CallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
CallInfo::Msgs(ref mut info) => info,
};
if let MessagesCallInfo::ReceiveMessagesProof(ref mut msg_info) = msg_info {
msg_info.base.bundled_range = *msg_info.base.bundled_range.start()..=end
}
pre_dispatch_data
}
fn run_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestExtension =
RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|res| res.0)
}
fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestGrandpaExtension =
RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|res| res.0)
}
fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestMessagesExtension =
RefundTransactionExtensionAdapter(RefundBridgedMessages(PhantomData));
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|res| res.0)
}
fn ignore_priority(tx: TransactionValidity) -> TransactionValidity {
tx.map(|mut tx| {
tx.priority = 0;
tx
})
}
fn run_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestExtension =
RefundTransactionExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|(pre, _)| pre)
}
fn run_grandpa_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestGrandpaExtension =
RefundTransactionExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|(pre, _)| pre)
}
fn run_messages_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestMessagesExtension =
RefundTransactionExtensionAdapter(RefundBridgedMessages(PhantomData));
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
)
.map(|(pre, _)| pre)
}
fn dispatch_info() -> DispatchInfo {
DispatchInfo {
weight: Weight::from_parts(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
0,
),
class: frame_support::dispatch::DispatchClass::Normal,
pays_fee: frame_support::dispatch::Pays::Yes,
}
}
fn post_dispatch_info() -> PostDispatchInfo {
PostDispatchInfo { actual_weight: None, pays_fee: frame_support::dispatch::Pays::Yes }
}
fn run_post_dispatch(
pre_dispatch_data: Option<PreDispatchData<ThisChainAccountId>>,
dispatch_result: DispatchResult,
) {
let post_dispatch_result = TestExtension::post_dispatch(
pre_dispatch_data,
&dispatch_info(),
&post_dispatch_info(),
1024,
&dispatch_result,
&(),
);
assert_eq!(post_dispatch_result, Ok(()));
}
fn expected_delivery_reward() -> ThisChainBalance {
let mut post_dispatch_info = post_dispatch_info();
let extra_weight = <TestRuntime as RelayersConfig>::WeightInfo::extra_weight_of_successful_receive_messages_proof_call();
post_dispatch_info.actual_weight =
Some(dispatch_info().weight.saturating_sub(extra_weight));
pallet_transaction_payment::Pallet::<TestRuntime>::compute_actual_fee(
1024,
&dispatch_info(),
&post_dispatch_info,
Zero::zero(),
)
}
fn expected_confirmation_reward() -> ThisChainBalance {
pallet_transaction_payment::Pallet::<TestRuntime>::compute_actual_fee(
1024,
&dispatch_info(),
&post_dispatch_info(),
Zero::zero(),
)
}
#[test]
fn validate_doesnt_boost_transaction_priority_if_relayer_is_not_registered() {
run_test(|| {
initialize_environment(100, 100, 100);
Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get());
// message delivery is failing
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
assert_eq!(f(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
f(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
f(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Default::default()),
);
assert_eq!(
f(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Default::default()),
);
}
// message confirmation validation is passing
assert_eq!(
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
200, 200, 200
))),
Ok(Default::default()),
);
});
}
#[test]
fn validate_boosts_priority_of_message_delivery_transactons() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_100_messages_delivery =
f(message_delivery_call(200)).unwrap().priority;
let priority_of_200_messages_delivery =
f(message_delivery_call(300)).unwrap().priority;
assert!(
priority_of_200_messages_delivery > priority_of_100_messages_delivery,
"Invalid priorities: {} for 200 messages vs {} for 100 messages",
priority_of_200_messages_delivery,
priority_of_100_messages_delivery,
);
let priority_of_100_messages_confirmation =
f(message_confirmation_call(200)).unwrap().priority;
let priority_of_200_messages_confirmation =
f(message_confirmation_call(300)).unwrap().priority;
assert_eq!(
priority_of_100_messages_confirmation,
priority_of_200_messages_confirmation
);
}
});
}
#[test]
fn validate_does_not_boost_priority_of_message_delivery_transactons_with_too_many_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
let fns = [run_validate, run_grandpa_validate, run_messages_validate];
for f in fns {
let priority_of_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get()))
.unwrap()
.priority;
let priority_of_more_than_max_messages_delivery =
f(message_delivery_call(100 + MaxUnconfirmedMessagesAtInboundLane::get() + 1))
.unwrap()
.priority;
assert!(
priority_of_max_messages_delivery > priority_of_more_than_max_messages_delivery,
"Invalid priorities: {} for MAX messages vs {} for MAX+1 messages",
priority_of_max_messages_delivery,
priority_of_more_than_max_messages_delivery,
);
}
});
}
#[test]
fn validate_allows_non_obsolete_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
ignore_priority(run_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_delivery_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_delivery_batch_call(200, 200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_delivery_batch_call(200, 200, 200))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_delivery_batch_call_ex(
200, 200, 200
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
))),
Ok(ValidTransaction::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call_ex(
200, 200, 200
))),
Ok(ValidTransaction::default()),
);
});
}
#[test]
fn ext_rejects_batch_with_obsolete_relay_chain_header() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(100, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn ext_rejects_batch_with_obsolete_parachain_head() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(101, 100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(parachain_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn ext_rejects_batch_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_confirmation_batch_call(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(all_finality_and_confirmation_batch_call_ex(200, 200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(parachain_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_validate(parachain_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn ext_rejects_batch_with_grandpa_finality_proof_when_grandpa_pallet_is_halted() {
run_test(|| {
initialize_environment(100, 100, 100);
GrandpaPallet::<TestRuntime, ()>::set_operating_mode(
RuntimeOrigin::root(),
BasicOperatingMode::Halted,
)
.unwrap();
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}
#[test]
fn ext_rejects_batch_with_parachain_finality_proof_when_parachains_pallet_is_halted() {
run_test(|| {
initialize_environment(100, 100, 100);
ParachainsPallet::<TestRuntime, ()>::set_operating_mode(
RuntimeOrigin::root(),
BasicOperatingMode::Halted,
)
.unwrap();
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}
#[test]
fn ext_rejects_transaction_when_messages_pallet_is_halted() {
run_test(|| {
initialize_environment(100, 100, 100);
MessagesPallet::<TestRuntime, ()>::set_operating_mode(
RuntimeOrigin::root(),
MessagesOperatingMode::Basic(BasicOperatingMode::Halted),
)
.unwrap();
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(message_delivery_call(200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
assert_eq!(
run_pre_dispatch(message_confirmation_call(200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}
#[test]
fn pre_dispatch_parses_batch_with_relay_chain_and_parachain_headers() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Some(all_finality_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)),
Ok(Some(all_finality_pre_dispatch_data_ex())),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)),
Ok(Some(all_finality_confirmation_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)),
Ok(Some(all_finality_confirmation_pre_dispatch_data_ex())),
);
});
}
#[test]
fn pre_dispatch_parses_batch_with_parachain_header() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Some(parachain_finality_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(parachain_finality_and_confirmation_batch_call(200, 200)),
Ok(Some(parachain_finality_confirmation_pre_dispatch_data())),
);
});
}
#[test]
fn pre_dispatch_fails_to_parse_batch_with_multiple_parachain_headers() {
run_test(|| {
initialize_environment(100, 100, 100);
let call = RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (100, RelayBlockHash::default()),
parachains: vec![
(ParaId(TestParachain::get()), [1u8; 32].into()),
(ParaId(TestParachain::get() + 1), [1u8; 32].into()),
],
parachain_heads_proof: ParaHeadsProof { storage_proof: vec![] },
}),
message_delivery_call(200),
],
});
assert_eq!(run_pre_dispatch(call), Ok(None),);
});
}
#[test]
fn pre_dispatch_parses_message_transaction() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
});
}
#[test]
fn post_dispatch_ignores_unknown_transaction() {
run_test(|| {
assert_storage_noop!(run_post_dispatch(None, Ok(())));
});
}
#[test]
fn post_dispatch_ignores_failed_transaction() {
run_test(|| {
assert_storage_noop!(run_post_dispatch(
Some(all_finality_pre_dispatch_data()),
Err(DispatchError::BadOrigin)
));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_updated_relay_chain_state() {
run_test(|| {
initialize_environment(100, 200, 200);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_updated_parachain_state() {
run_test(|| {
initialize_environment(200, 100, 200);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_pre_dispatch_data()),
Ok(())
));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_delivered_any_messages() {
run_test(|| {
initialize_environment(200, 200, 100);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(all_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(())));
});
}
#[test]
fn post_dispatch_ignores_transaction_that_has_not_delivered_all_messages() {
run_test(|| {
initialize_environment(200, 200, 150);
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(())));
assert_storage_noop!(run_post_dispatch(
Some(all_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(
Some(parachain_finality_confirmation_pre_dispatch_data()),
Ok(())
));
assert_storage_noop!(run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(())));
});
}
#[test]
fn post_dispatch_refunds_relayer_in_all_finality_batch_with_extra_weight() {
run_test(|| {
initialize_environment(200, 200, 200);
let mut dispatch_info = dispatch_info();
dispatch_info.weight = Weight::from_parts(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2,
0,
);
// without any size/weight refund: we expect regular reward
let pre_dispatch_data = all_finality_pre_dispatch_data();
let regular_reward = expected_delivery_reward();
run_post_dispatch(Some(pre_dispatch_data), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(regular_reward),
);
// now repeat the same with size+weight refund: we expect smaller reward
let mut pre_dispatch_data = all_finality_pre_dispatch_data();
match pre_dispatch_data.call_info {
CallInfo::AllFinalityAndMsgs(ref mut info, ..) => {
info.extra_weight.set_ref_time(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
);
info.extra_size = 32;
},
_ => unreachable!(),
}
run_post_dispatch(Some(pre_dispatch_data), Ok(()));
let reward_after_two_calls = RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get(),
)
.unwrap();
assert!(
reward_after_two_calls < 2 * regular_reward,
"{} must be < 2 * {}",
reward_after_two_calls,
2 * regular_reward,
);
});
}
#[test]
fn post_dispatch_refunds_relayer_in_all_finality_batch() {
run_test(|| {
initialize_environment(200, 200, 200);
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(expected_delivery_reward()),
);
run_post_dispatch(Some(all_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgDeliveryProofsRewardsAccount::get()
),
Some(expected_confirmation_reward()),
);
});
}
#[test]
fn post_dispatch_refunds_relayer_in_parachain_finality_batch() {
run_test(|| {
initialize_environment(200, 200, 200);
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(expected_delivery_reward()),
);
run_post_dispatch(Some(parachain_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgDeliveryProofsRewardsAccount::get()
),
Some(expected_confirmation_reward()),
);
});
}
#[test]
fn post_dispatch_refunds_relayer_in_message_transaction() {
run_test(|| {
initialize_environment(200, 200, 200);
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
Some(expected_delivery_reward()),
);
run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(
RelayersPallet::<TestRuntime>::relayer_reward(
relayer_account_at_this_chain(),
MsgDeliveryProofsRewardsAccount::get()
),
Some(expected_confirmation_reward()),
);
});
}
#[test]
fn post_dispatch_slashing_relayer_stake() {
run_test(|| {
initialize_environment(200, 200, 100);
let delivery_rewards_account_balance =
Balances::free_balance(delivery_rewards_account());
let test_stake: ThisChainBalance = TestStake::get();
Balances::set_balance(
&relayer_account_at_this_chain(),
ExistentialDeposit::get() + test_stake * 10,
);
// slashing works for message delivery calls
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
assert_eq!(
delivery_rewards_account_balance + test_stake,
Balances::free_balance(delivery_rewards_account())
);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
assert_eq!(
delivery_rewards_account_balance + test_stake * 2,
Balances::free_balance(delivery_rewards_account())
);
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
assert_eq!(
delivery_rewards_account_balance + test_stake * 3,
Balances::free_balance(delivery_rewards_account())
);
// reserve doesn't work for message confirmation calls
let confirmation_rewards_account_balance =
Balances::free_balance(confirmation_rewards_account());
Balances::reserve(&relayer_account_at_this_chain(), test_stake).unwrap();
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
assert_eq!(
confirmation_rewards_account_balance,
Balances::free_balance(confirmation_rewards_account())
);
run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(parachain_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
run_post_dispatch(Some(all_finality_confirmation_pre_dispatch_data()), Ok(()));
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
// check that unreserve has happened, not slashing
assert_eq!(
delivery_rewards_account_balance + test_stake * 3,
Balances::free_balance(delivery_rewards_account())
);
assert_eq!(
confirmation_rewards_account_balance,
Balances::free_balance(confirmation_rewards_account())
);
});
}
fn run_analyze_call_result(
pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
dispatch_result: DispatchResult,
) -> RelayerAccountAction<ThisChainAccountId, ThisChainBalance> {
TestExtensionProvider::analyze_call_result(
Some(Some(pre_dispatch_data)),
&dispatch_info(),
&post_dispatch_info(),
1024,
&dispatch_result,
)
}
#[test]
fn analyze_call_result_shall_not_slash_for_transactions_with_too_many_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
// the `analyze_call_result` should return slash if number of bundled messages is
// within reasonable limits
assert_eq!(
run_analyze_call_result(all_finality_pre_dispatch_data(), Ok(())),
RelayerAccountAction::Slash(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
);
assert_eq!(
run_analyze_call_result(parachain_finality_pre_dispatch_data(), Ok(())),
RelayerAccountAction::Slash(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
);
assert_eq!(
run_analyze_call_result(delivery_pre_dispatch_data(), Ok(())),
RelayerAccountAction::Slash(
relayer_account_at_this_chain(),
MsgProofsRewardsAccount::get()
),
);
// the `analyze_call_result` should not return slash if number of bundled messages is
// larger than the
assert_eq!(
run_analyze_call_result(
set_bundled_range_end(all_finality_pre_dispatch_data(), 1_000_000),
Ok(())
),
RelayerAccountAction::None,
);
assert_eq!(
run_analyze_call_result(
set_bundled_range_end(parachain_finality_pre_dispatch_data(), 1_000_000),
Ok(())
),
RelayerAccountAction::None,
);
assert_eq!(
run_analyze_call_result(
set_bundled_range_end(delivery_pre_dispatch_data(), 1_000_000),
Ok(())
),
RelayerAccountAction::None,
);
});
}
#[test]
fn messages_ext_only_parses_standalone_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// parachain + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
// parachain + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call_ex(200, 200)
),
Ok(None),
);
// relay + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call_ex(200, 200)
),
Ok(None),
);
// message delivery call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
);
// message confirmation call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
);
});
}
#[test]
fn messages_ext_rejects_calls_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_messages_validate(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn messages_ext_accepts_calls_with_new_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_messages_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_messages_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
assert_eq!(run_messages_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_messages_validate(message_confirmation_call(200)),
Ok(Default::default()),
);
});
}
#[test]
fn grandpa_ext_only_parses_valid_batches() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call(200, 200, 200)
),
Ok(None),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
);
// parachain + message delivery call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
);
// parachain + message confirmation call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&parachain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(Some(relay_finality_pre_dispatch_data().call_info)),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call_ex(200, 200)
),
Ok(Some(relay_finality_pre_dispatch_data_ex().call_info)),
);
// relay + message confirmation call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call_ex(200, 200)
),
Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex().call_info)),
);
// message delivery call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
);
// message confirmation call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
);
});
}
#[test]
fn grandpa_ext_rejects_batch_with_obsolete_relay_chain_header() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(100, 200)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn grandpa_ext_rejects_calls_with_obsolete_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_pre_dispatch(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(message_delivery_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
assert_eq!(
run_grandpa_validate(message_confirmation_call(100)),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
});
}
#[test]
fn grandpa_ext_accepts_calls_with_new_messages() {
run_test(|| {
initialize_environment(100, 100, 100);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 200)),
Ok(Some(relay_finality_pre_dispatch_data()),)
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 200)),
Ok(Some(relay_finality_pre_dispatch_data_ex()),)
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 200)),
Ok(Some(relay_finality_confirmation_pre_dispatch_data())),
);
assert_eq!(
run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 200)),
Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex())),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 200)),
Ok(Default::default()),
);
assert_eq!(
run_grandpa_pre_dispatch(message_delivery_call(200)),
Ok(Some(delivery_pre_dispatch_data())),
);
assert_eq!(
run_grandpa_pre_dispatch(message_confirmation_call(200)),
Ok(Some(confirmation_pre_dispatch_data())),
);
assert_eq!(run_grandpa_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
run_grandpa_validate(message_confirmation_call(200)),
Ok(Default::default()),
);
});
}
#[test]
fn does_not_panic_on_boosting_priority_of_empty_message_delivery_transaction() {
run_test(|| {
let best_delivered_message = MaxUnconfirmedMessagesAtInboundLane::get();
initialize_environment(100, 100, best_delivered_message);
// register relayer so it gets priority boost
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
.unwrap();
// allow empty message delivery transactions
let lane_id = TestLaneId::get();
let in_lane_data = InboundLaneData {
last_confirmed_nonce: 0,
relayers: vec![UnrewardedRelayer {
relayer: relayer_account_at_bridged_chain(),
messages: DeliveredMessages { begin: 1, end: best_delivered_message },
}]
.into(),
};
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
// now check that the priority of empty tx is the same as priority of 1-message tx
let priority_of_zero_messages_delivery =
run_validate(message_delivery_call(best_delivered_message)).unwrap().priority;
let priority_of_one_messages_delivery =
run_validate(message_delivery_call(best_delivered_message + 1))
.unwrap()
.priority;
assert_eq!(priority_of_zero_messages_delivery, priority_of_one_messages_delivery);
});
}
}
// Copyright (C) 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/>.
//! Integrity tests for chain constants and pallets configuration.
//!
//! Most of the tests in this module assume that the bridge is using standard (see `crate::messages`
//! module for details) configuration.
use crate::{messages, messages::MessageBridge};
use bp_messages::{InboundLaneData, MessageNonce};
use bp_runtime::{Chain, ChainId};
use codec::Encode;
use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight};
use frame_system::limits;
use pallet_bridge_messages::WeightInfoExt as _;
/// Macro that ensures that the runtime configuration and chain primitives crate are sharing
/// the same types (nonce, block number, hash, hasher, account id and header).
#[macro_export]
macro_rules! assert_chain_types(
( runtime: $r:path, this_chain: $this:path ) => {
{
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
// and relays will stop functioning)
use frame_system::{Config as SystemConfig, pallet_prelude::{BlockNumberFor, HeaderFor}};
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as SystemConfig>::Nonce, bp_runtime::NonceOf<$this>);
assert_type_eq_all!(BlockNumberFor<$r>, bp_runtime::BlockNumberOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::Hash, bp_runtime::HashOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::Hashing, bp_runtime::HasherOf<$this>);
assert_type_eq_all!(<$r as SystemConfig>::AccountId, bp_runtime::AccountIdOf<$this>);
assert_type_eq_all!(HeaderFor<$r>, bp_runtime::HeaderOf<$this>);
}
}
);
/// Macro that ensures that the bridge GRANDPA pallet is configured properly to bridge with given
/// chain.
#[macro_export]
macro_rules! assert_bridge_grandpa_pallet_types(
( runtime: $r:path, with_bridged_chain_grandpa_instance: $i:path, bridged_chain: $bridged:path ) => {
{
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
// and relays will stop functioning)
use pallet_bridge_grandpa::Config as GrandpaConfig;
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as GrandpaConfig<$i>>::BridgedChain, $bridged);
}
}
);
/// Macro that ensures that the bridge messages pallet is configured properly to bridge using given
/// configuration.
#[macro_export]
macro_rules! assert_bridge_messages_pallet_types(
(
runtime: $r:path,
with_bridged_chain_messages_instance: $i:path,
bridge: $bridge:path
) => {
{
// if one of asserts fail, then either bridge isn't configured properly (or alternatively - non-standard
// configuration is used), or something has broke existing configuration (meaning that all bridged chains
// and relays will stop functioning)
use $crate::messages::{
source::{FromThisChainMessagePayload, TargetHeaderChainAdapter},
target::{FromBridgedChainMessagePayload, SourceHeaderChainAdapter},
AccountIdOf, BalanceOf, BridgedChain, ThisChain,
};
use pallet_bridge_messages::Config as MessagesConfig;
use static_assertions::assert_type_eq_all;
assert_type_eq_all!(<$r as MessagesConfig<$i>>::OutboundPayload, FromThisChainMessagePayload);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::InboundRelayer, AccountIdOf<BridgedChain<$bridge>>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::TargetHeaderChain, TargetHeaderChainAdapter<$bridge>);
assert_type_eq_all!(<$r as MessagesConfig<$i>>::SourceHeaderChain, SourceHeaderChainAdapter<$bridge>);
}
}
);
/// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`,
/// `assert_bridge_grandpa_pallet_types` and `assert_bridge_messages_pallet_types`. It may be used
/// at the chain that is implementing complete standard messages bridge (i.e. with bridge GRANDPA
/// and messages pallets deployed).
#[macro_export]
macro_rules! assert_complete_bridge_types(
(
runtime: $r:path,
with_bridged_chain_grandpa_instance: $gi:path,
with_bridged_chain_messages_instance: $mi:path,
bridge: $bridge:path,
this_chain: $this:path,
bridged_chain: $bridged:path,
) => {
$crate::assert_chain_types!(runtime: $r, this_chain: $this);
$crate::assert_bridge_grandpa_pallet_types!(
runtime: $r,
with_bridged_chain_grandpa_instance: $gi,
bridged_chain: $bridged
);
$crate::assert_bridge_messages_pallet_types!(
runtime: $r,
with_bridged_chain_messages_instance: $mi,
bridge: $bridge
);
}
);
/// Parameters for asserting chain-related constants.
#[derive(Debug)]
pub struct AssertChainConstants {
/// Block length limits of the chain.
pub block_length: limits::BlockLength,
/// Block weight limits of the chain.
pub block_weights: limits::BlockWeights,
}
/// Test that our hardcoded, chain-related constants, are matching chain runtime configuration.
///
/// In particular, this test ensures that:
///
/// 1) block weight limits are matching;
/// 2) block size limits are matching.
pub fn assert_chain_constants<R>(params: AssertChainConstants)
where
R: frame_system::Config,
{
// we don't check runtime version here, because in our case we'll be building relay from one
// repo and runtime will live in another repo, along with outdated relay version. To avoid
// unneeded commits, let's not raise an error in case of version mismatch.
// if one of following assert fails, it means that we may need to upgrade bridged chain and
// relay to use updated constants. If constants are now smaller than before, it may lead to
// undeliverable messages.
// `BlockLength` struct is not implementing `PartialEq`, so we compare encoded values here.
assert_eq!(
R::BlockLength::get().encode(),
params.block_length.encode(),
"BlockLength from runtime ({:?}) differ from hardcoded: {:?}",
R::BlockLength::get(),
params.block_length,
);
// `BlockWeights` struct is not implementing `PartialEq`, so we compare encoded values here
assert_eq!(
R::BlockWeights::get().encode(),
params.block_weights.encode(),
"BlockWeights from runtime ({:?}) differ from hardcoded: {:?}",
R::BlockWeights::get(),
params.block_weights,
);
}
/// Test that the constants, used in GRANDPA pallet configuration are valid.
pub fn assert_bridge_grandpa_pallet_constants<R, GI>()
where
R: pallet_bridge_grandpa::Config<GI>,
GI: 'static,
{
assert!(
R::HeadersToKeep::get() > 0,
"HeadersToKeep ({}) must be larger than zero",
R::HeadersToKeep::get(),
);
}
/// Parameters for asserting messages pallet constants.
#[derive(Debug)]
pub struct AssertBridgeMessagesPalletConstants {
/// Maximal number of unrewarded relayer entries in a confirmation transaction at the bridged
/// chain.
pub max_unrewarded_relayers_in_bridged_confirmation_tx: MessageNonce,
/// Maximal number of unconfirmed messages in a confirmation transaction at the bridged chain.
pub max_unconfirmed_messages_in_bridged_confirmation_tx: MessageNonce,
/// Identifier of the bridged chain.
pub bridged_chain_id: ChainId,
}
/// Test that the constants, used in messages pallet configuration are valid.
pub fn assert_bridge_messages_pallet_constants<R, MI>(params: AssertBridgeMessagesPalletConstants)
where
R: pallet_bridge_messages::Config<MI>,
MI: 'static,
{
assert!(
!R::ActiveOutboundLanes::get().is_empty(),
"ActiveOutboundLanes ({:?}) must not be empty",
R::ActiveOutboundLanes::get(),
);
assert!(
R::MaxUnrewardedRelayerEntriesAtInboundLane::get() <= params.max_unrewarded_relayers_in_bridged_confirmation_tx,
"MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}",
R::MaxUnrewardedRelayerEntriesAtInboundLane::get(),
params.max_unrewarded_relayers_in_bridged_confirmation_tx,
);
assert!(
R::MaxUnconfirmedMessagesAtInboundLane::get() <= params.max_unconfirmed_messages_in_bridged_confirmation_tx,
"MaxUnrewardedRelayerEntriesAtInboundLane ({}) must be <= than the hardcoded value for bridged chain: {}",
R::MaxUnconfirmedMessagesAtInboundLane::get(),
params.max_unconfirmed_messages_in_bridged_confirmation_tx,
);
assert_eq!(R::BridgedChainId::get(), params.bridged_chain_id);
}
/// Parameters for asserting bridge pallet names.
#[derive(Debug)]
pub struct AssertBridgePalletNames<'a> {
/// Name of the messages pallet, deployed at the bridged chain and used to bridge with this
/// chain.
pub with_this_chain_messages_pallet_name: &'a str,
/// Name of the GRANDPA pallet, deployed at this chain and used to bridge with the bridged
/// chain.
pub with_bridged_chain_grandpa_pallet_name: &'a str,
/// Name of the messages pallet, deployed at this chain and used to bridge with the bridged
/// chain.
pub with_bridged_chain_messages_pallet_name: &'a str,
}
/// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants
/// from chain primitives crates.
pub fn assert_bridge_pallet_names<B, R, GI, MI>(params: AssertBridgePalletNames)
where
B: MessageBridge,
R: pallet_bridge_grandpa::Config<GI> + pallet_bridge_messages::Config<MI>,
GI: 'static,
MI: 'static,
{
assert_eq!(B::BRIDGED_MESSAGES_PALLET_NAME, params.with_this_chain_messages_pallet_name);
assert_eq!(
pallet_bridge_grandpa::PalletOwner::<R, GI>::storage_value_final_key().to_vec(),
bp_runtime::storage_value_key(params.with_bridged_chain_grandpa_pallet_name, "PalletOwner",).0,
);
assert_eq!(
pallet_bridge_messages::PalletOwner::<R, MI>::storage_value_final_key().to_vec(),
bp_runtime::storage_value_key(
params.with_bridged_chain_messages_pallet_name,
"PalletOwner",
)
.0,
);
}
/// Parameters for asserting complete standard messages bridge.
#[derive(Debug)]
pub struct AssertCompleteBridgeConstants<'a> {
/// Parameters to assert this chain constants.
pub this_chain_constants: AssertChainConstants,
/// Parameters to assert messages pallet constants.
pub messages_pallet_constants: AssertBridgeMessagesPalletConstants,
/// Parameters to assert pallet names constants.
pub pallet_names: AssertBridgePalletNames<'a>,
}
/// All bridge-related constants tests for the complete standard messages bridge (i.e. with bridge
/// GRANDPA and messages pallets deployed).
pub fn assert_complete_bridge_constants<R, GI, MI, B>(params: AssertCompleteBridgeConstants)
where
R: frame_system::Config
+ pallet_bridge_grandpa::Config<GI>
+ pallet_bridge_messages::Config<MI>,
GI: 'static,
MI: 'static,
B: MessageBridge,
{
assert_chain_constants::<R>(params.this_chain_constants);
assert_bridge_grandpa_pallet_constants::<R, GI>();
assert_bridge_messages_pallet_constants::<R, MI>(params.messages_pallet_constants);
assert_bridge_pallet_names::<B, R, GI, MI>(params.pallet_names);
}
/// Check that the message lane weights are correct.
pub fn check_message_lane_weights<
C: Chain,
T: frame_system::Config + pallet_bridge_messages::Config<MessagesPalletInstance>,
MessagesPalletInstance: 'static,
>(
bridged_chain_extra_storage_proof_size: u32,
this_chain_max_unrewarded_relayers: MessageNonce,
this_chain_max_unconfirmed_messages: MessageNonce,
// whether `RefundBridgedParachainMessages` extension is deployed at runtime and is used for
// refunding this bridge transactions?
//
// in other words: pass true for all known production chains
runtime_includes_refund_extension: bool,
) {
type Weights<T, MI> = <T as pallet_bridge_messages::Config<MI>>::WeightInfo;
// check basic weight assumptions
pallet_bridge_messages::ensure_weights_are_correct::<Weights<T, MessagesPalletInstance>>();
// check that weights allow us to receive messages
let max_incoming_message_proof_size = bridged_chain_extra_storage_proof_size
.saturating_add(messages::target::maximal_incoming_message_size(C::max_extrinsic_size()));
pallet_bridge_messages::ensure_able_to_receive_message::<Weights<T, MessagesPalletInstance>>(
C::max_extrinsic_size(),
C::max_extrinsic_weight(),
max_incoming_message_proof_size,
messages::target::maximal_incoming_message_dispatch_weight(C::max_extrinsic_weight()),
);
// check that weights allow us to receive delivery confirmations
let max_incoming_inbound_lane_data_proof_size =
InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _);
pallet_bridge_messages::ensure_able_to_receive_confirmation::<Weights<T, MessagesPalletInstance>>(
C::max_extrinsic_size(),
C::max_extrinsic_weight(),
max_incoming_inbound_lane_data_proof_size,
this_chain_max_unrewarded_relayers,
this_chain_max_unconfirmed_messages,
);
// check that extra weights of delivery/confirmation transactions include the weight
// of `RefundBridgedParachainMessages` operations. This signed extension assumes the worst case
// (i.e. slashing if delivery transaction was invalid) and refunds some weight if
// assumption was wrong (i.e. if we did refund instead of slashing). This check
// ensures the extension will not refund weight when it doesn't need to (i.e. if pallet
// weights do not account weights of refund extension).
if runtime_includes_refund_extension {
assert_ne!(
Weights::<T, MessagesPalletInstance>::receive_messages_proof_overhead_from_runtime(),
Weight::zero()
);
assert_ne!(
Weights::<T, MessagesPalletInstance>::receive_messages_delivery_proof_overhead_from_runtime(),
Weight::zero()
);
}
}
// Copyright (C) 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/>.
//! Common types/functions that may be used by runtimes of all bridged chains.
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use bp_runtime::{Parachain, ParachainIdOf};
use sp_runtime::traits::{Get, PhantomData};
pub mod extensions;
pub mod messages;
pub mod messages_api;
pub mod messages_benchmarking;
pub mod messages_call_ext;
pub mod messages_generation;
pub mod messages_xcm_extension;
pub mod parachains_benchmarking;
mod mock;
#[cfg(feature = "integrity-test")]
pub mod integrity;
const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch";
/// Trait identifying a bridged parachain. A relayer might be refunded for delivering messages
/// coming from this parachain.
pub trait RefundableParachainId {
/// The instance of the bridge parachains pallet.
type Instance: 'static;
/// The parachain Id.
type Id: Get<u32>;
}
/// Default implementation of `RefundableParachainId`.
pub struct DefaultRefundableParachainId<Instance, Id>(PhantomData<(Instance, Id)>);
impl<Instance, Id> RefundableParachainId for DefaultRefundableParachainId<Instance, Id>
where
Instance: 'static,
Id: Get<u32>,
{
type Instance = Instance;
type Id = Id;
}
/// Implementation of `RefundableParachainId` for `trait Parachain`.
pub struct RefundableParachain<Instance, Para>(PhantomData<(Instance, Para)>);
impl<Instance, Para> RefundableParachainId for RefundableParachain<Instance, Para>
where
Instance: 'static,
Para: Parachain,
{
type Instance = Instance;
type Id = ParachainIdOf<Para>;
}
// Copyright (C) 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/>.
//! Types that allow runtime to act as a source/target endpoint of message lanes.
//!
//! Messages are assumed to be encoded `Call`s of the target chain. Call-dispatch
//! pallet is used to dispatch incoming messages. Message identified by a tuple
//! of to elements - message lane id and message nonce.
pub use bp_runtime::{RangeInclusiveExt, UnderlyingChainOf, UnderlyingChainProvider};
use bp_header_chain::HeaderChain;
use bp_messages::{
source_chain::TargetHeaderChain,
target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
VerificationError,
};
use bp_runtime::{Chain, RawStorageProof, Size, StorageProofChecker};
use codec::{Decode, Encode};
use frame_support::{traits::Get, weights::Weight};
use hash_db::Hasher;
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
use sp_std::{convert::TryFrom, marker::PhantomData, vec::Vec};
/// Bidirectional message bridge.
pub trait MessageBridge {
/// Name of the paired messages pallet instance at the Bridged chain.
///
/// Should be the name that is used in the `construct_runtime!()` macro.
const BRIDGED_MESSAGES_PALLET_NAME: &'static str;
/// This chain in context of message bridge.
type ThisChain: ThisChainWithMessages;
/// Bridged chain in context of message bridge.
type BridgedChain: BridgedChainWithMessages;
/// Bridged header chain.
type BridgedHeaderChain: HeaderChain<UnderlyingChainOf<Self::BridgedChain>>;
}
/// This chain that has `pallet-bridge-messages` module.
pub trait ThisChainWithMessages: UnderlyingChainProvider {
/// Call origin on the chain.
type RuntimeOrigin;
}
/// Bridged chain that has `pallet-bridge-messages` module.
pub trait BridgedChainWithMessages: UnderlyingChainProvider {}
/// This chain in context of message bridge.
pub type ThisChain<B> = <B as MessageBridge>::ThisChain;
/// Bridged chain in context of message bridge.
pub type BridgedChain<B> = <B as MessageBridge>::BridgedChain;
/// Hash used on the chain.
pub type HashOf<C> = bp_runtime::HashOf<<C as UnderlyingChainProvider>::Chain>;
/// Hasher used on the chain.
pub type HasherOf<C> = bp_runtime::HasherOf<UnderlyingChainOf<C>>;
/// Account id used on the chain.
pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>;
/// Type of balances that is used on the chain.
pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>;
/// Sub-module that is declaring types required for processing This -> Bridged chain messages.
pub mod source {
use super::*;
/// Message payload for This -> Bridged chain messages.
pub type FromThisChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
/// Maximal size of outbound message payload.
pub struct FromThisChainMaximalOutboundPayloadSize<B>(PhantomData<B>);
impl<B: MessageBridge> Get<u32> for FromThisChainMaximalOutboundPayloadSize<B> {
fn get() -> u32 {
maximal_message_size::<B>()
}
}
/// Messages delivery proof from bridged chain:
///
/// - hash of finalized header;
/// - storage proof of inbound lane state;
/// - lane id.
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
/// Hash of the bridge header the proof is for.
pub bridged_header_hash: BridgedHeaderHash,
/// Storage trie proof generated for [`Self::bridged_header_hash`].
pub storage_proof: RawStorageProof,
/// Lane id of which messages were delivered and the proof is for.
pub lane: LaneId,
}
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
fn size(&self) -> u32 {
u32::try_from(
self.storage_proof
.iter()
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
)
.unwrap_or(u32::MAX)
}
}
/// 'Parsed' message delivery proof - inbound lane id and its state.
pub type ParsedMessagesDeliveryProofFromBridgedChain<B> =
(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>);
/// Return maximal message size of This -> Bridged chain message.
pub fn maximal_message_size<B: MessageBridge>() -> u32 {
super::target::maximal_incoming_message_size(
UnderlyingChainOf::<BridgedChain<B>>::max_extrinsic_size(),
)
}
/// `TargetHeaderChain` implementation that is using default types and perform default checks.
pub struct TargetHeaderChainAdapter<B>(PhantomData<B>);
impl<B: MessageBridge> TargetHeaderChain<FromThisChainMessagePayload, AccountIdOf<ThisChain<B>>>
for TargetHeaderChainAdapter<B>
{
type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>;
fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), VerificationError> {
verify_chain_message::<B>(payload)
}
fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>), VerificationError> {
verify_messages_delivery_proof::<B>(proof)
}
}
/// Do basic Bridged-chain specific verification of This -> Bridged chain message.
///
/// Ok result from this function means that the delivery transaction with this message
/// may be 'mined' by the target chain.
pub fn verify_chain_message<B: MessageBridge>(
payload: &FromThisChainMessagePayload,
) -> Result<(), VerificationError> {
// IMPORTANT: any error that is returned here is fatal for the bridge, because
// this code is executed at the bridge hub and message sender actually lives
// at some sibling parachain. So we are failing **after** the message has been
// sent and we can't report it back to sender (unless error report mechanism is
// embedded into message and its dispatcher).
// apart from maximal message size check (see below), we should also check the message
// dispatch weight here. But we assume that the bridged chain will just push the message
// to some queue (XCMP, UMP, DMP), so the weight is constant and fits the block.
// The maximal size of extrinsic at Substrate-based chain depends on the
// `frame_system::Config::MaximumBlockLength` and
// `frame_system::Config::AvailableBlockRatio` constants. This check is here to be sure that
// the lane won't stuck because message is too large to fit into delivery transaction.
//
// **IMPORTANT NOTE**: the delivery transaction contains storage proof of the message, not
// the message itself. The proof is always larger than the message. But unless chain state
// is enormously large, it should be several dozens/hundreds of bytes. The delivery
// transaction also contains signatures and signed extensions. Because of this, we reserve
// 1/3 of the the maximal extrinsic size for this data.
if payload.len() > maximal_message_size::<B>() as usize {
return Err(VerificationError::MessageTooLarge)
}
Ok(())
}
/// Verify proof of This -> Bridged chain messages delivery.
///
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
/// parachains, please use the `verify_messages_delivery_proof_from_parachain`.
pub fn verify_messages_delivery_proof<B: MessageBridge>(
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>,
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, VerificationError> {
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
proof;
let mut storage =
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
.map_err(VerificationError::HeaderChain)?;
// Messages delivery proof is just proof of single storage key read => any error
// is fatal.
let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
&lane,
);
let inbound_lane_data = storage
.read_and_decode_mandatory_value(storage_inbound_lane_data_key.0.as_ref())
.map_err(VerificationError::InboundLaneStorage)?;
// check that the storage proof doesn't have any untouched trie nodes
storage.ensure_no_unused_nodes().map_err(VerificationError::StorageProof)?;
Ok((lane, inbound_lane_data))
}
}
/// Sub-module that is declaring types required for processing Bridged -> This chain messages.
pub mod target {
use super::*;
/// Decoded Bridged -> This message payload.
pub type FromBridgedChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
/// Messages proof from bridged chain:
///
/// - hash of finalized header;
/// - storage proof of messages and (optionally) outbound lane state;
/// - lane id;
/// - nonces (inclusive range) of messages which are included in this proof.
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct FromBridgedChainMessagesProof<BridgedHeaderHash> {
/// Hash of the finalized bridged header the proof is for.
pub bridged_header_hash: BridgedHeaderHash,
/// A storage trie proof of messages being delivered.
pub storage_proof: RawStorageProof,
/// Messages in this proof are sent over this lane.
pub lane: LaneId,
/// Nonce of the first message being delivered.
pub nonces_start: MessageNonce,
/// Nonce of the last message being delivered.
pub nonces_end: MessageNonce,
}
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesProof<BridgedHeaderHash> {
fn size(&self) -> u32 {
u32::try_from(
self.storage_proof
.iter()
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
)
.unwrap_or(u32::MAX)
}
}
/// Return maximal dispatch weight of the message we're able to receive.
pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
maximal_extrinsic_weight / 2
}
/// Return maximal message size given maximal extrinsic size.
pub fn maximal_incoming_message_size(maximal_extrinsic_size: u32) -> u32 {
maximal_extrinsic_size / 3 * 2
}
/// `SourceHeaderChain` implementation that is using default types and perform default checks.
pub struct SourceHeaderChainAdapter<B>(PhantomData<B>);
impl<B: MessageBridge> SourceHeaderChain for SourceHeaderChainAdapter<B> {
type MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>;
fn verify_messages_proof(
proof: Self::MessagesProof,
messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
verify_messages_proof::<B>(proof, messages_count)
}
}
/// Verify proof of Bridged -> This chain messages.
///
/// This function is used when Bridged chain is directly using GRANDPA finality. For Bridged
/// parachains, please use the `verify_messages_proof_from_parachain`.
///
/// The `messages_count` argument verification (sane limits) is supposed to be made
/// outside of this function. This function only verifies that the proof declares exactly
/// `messages_count` messages.
pub fn verify_messages_proof<B: MessageBridge>(
proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
let FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
} = proof;
let storage =
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
.map_err(VerificationError::HeaderChain)?;
let mut parser = StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() };
let nonces_range = nonces_start..=nonces_end;
// receiving proofs where end < begin is ok (if proof includes outbound lane state)
let messages_in_the_proof = nonces_range.checked_len().unwrap_or(0);
if messages_in_the_proof != MessageNonce::from(messages_count) {
return Err(VerificationError::MessagesCountMismatch)
}
// Read messages first. All messages that are claimed to be in the proof must
// be in the proof. So any error in `read_value`, or even missing value is fatal.
//
// Mind that we allow proofs with no messages if outbound lane state is proved.
let mut messages = Vec::with_capacity(messages_in_the_proof as _);
for nonce in nonces_range {
let message_key = MessageKey { lane_id: lane, nonce };
let message_payload = parser.read_and_decode_message_payload(&message_key)?;
messages.push(Message { key: message_key, payload: message_payload });
}
// Now let's check if proof contains outbound lane state proof. It is optional, so
// we simply ignore `read_value` errors and missing value.
let proved_lane_messages = ProvedLaneMessages {
lane_state: parser.read_and_decode_outbound_lane_data(&lane)?,
messages,
};
// Now we may actually check if the proof is empty or not.
if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
return Err(VerificationError::EmptyMessageProof)
}
// check that the storage proof doesn't have any untouched trie nodes
parser
.storage
.ensure_no_unused_nodes()
.map_err(VerificationError::StorageProof)?;
// We only support single lane messages in this generated_schema
let mut proved_messages = ProvedMessages::new();
proved_messages.insert(lane, proved_lane_messages);
Ok(proved_messages)
}
struct StorageProofCheckerAdapter<H: Hasher, B> {
storage: StorageProofChecker<H>,
_dummy: sp_std::marker::PhantomData<B>,
}
impl<H: Hasher, B: MessageBridge> StorageProofCheckerAdapter<H, B> {
fn read_and_decode_outbound_lane_data(
&mut self,
lane_id: &LaneId,
) -> Result<Option<OutboundLaneData>, VerificationError> {
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
lane_id,
);
self.storage
.read_and_decode_opt_value(storage_outbound_lane_data_key.0.as_ref())
.map_err(VerificationError::OutboundLaneStorage)
}
fn read_and_decode_message_payload(
&mut self,
message_key: &MessageKey,
) -> Result<MessagePayload, VerificationError> {
let storage_message_key = bp_messages::storage_keys::message_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
&message_key.lane_id,
message_key.nonce,
);
self.storage
.read_and_decode_mandatory_value(storage_message_key.0.as_ref())
.map_err(VerificationError::MessageStorage)
}
}
}
/// The `BridgeMessagesCall` used by a chain.
pub type BridgeMessagesCallOf<C> = bp_messages::BridgeMessagesCall<
bp_runtime::AccountIdOf<C>,
target::FromBridgedChainMessagesProof<bp_runtime::HashOf<C>>,
source::FromBridgedChainMessagesDeliveryProof<bp_runtime::HashOf<C>>,
>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{
messages_generation::{
encode_all_messages, encode_lane_data, prepare_messages_storage_proof,
},
mock::*,
};
use bp_header_chain::{HeaderChainError, StoredHeaderDataBuilder};
use bp_runtime::{HeaderId, StorageProofError};
use codec::Encode;
use sp_core::H256;
use sp_runtime::traits::Header as _;
#[test]
fn verify_chain_message_rejects_message_with_too_large_declared_weight() {
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
42;
BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT -
1
])
.is_err());
}
#[test]
fn verify_chain_message_rejects_message_too_large_message() {
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as usize + 1
],)
.is_err());
}
#[test]
fn verify_chain_message_accepts_maximal_message() {
assert_eq!(
source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as _
],),
Ok(()),
);
}
fn using_messages_proof<R>(
nonces_end: MessageNonce,
outbound_lane_data: Option<OutboundLaneData>,
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
test: impl Fn(target::FromBridgedChainMessagesProof<H256>) -> R,
) -> R {
let (state_root, storage_proof) = prepare_messages_storage_proof::<OnThisChainBridge>(
TEST_LANE_ID,
1..=nonces_end,
outbound_lane_data,
bp_runtime::StorageProofSize::Minimal(0),
vec![42],
encode_message,
encode_outbound_lane_data,
);
sp_io::TestExternalities::new(Default::default()).execute_with(move || {
let bridged_header = BridgedChainHeader::new(
0,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
0,
bridged_header_hash,
));
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
bridged_header.build(),
);
test(target::FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane: TEST_LANE_ID,
nonces_start: 1,
nonces_end,
})
})
}
#[test]
fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 5)
}),
Err(VerificationError::MessagesCountMismatch),
);
}
#[test]
fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 15)
}),
Err(VerificationError::MessagesCountMismatch),
);
}
#[test]
fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
let bridged_header_hash =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(bridged_header_hash);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
}),
Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)),
);
}
#[test]
fn message_proof_is_rejected_if_header_state_root_mismatches() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
let bridged_header_hash =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
BridgedChainHeader::new(
0,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.build(),
);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
}),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::StorageRootMismatch
))),
);
}
#[test]
fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |mut proof| {
let node = proof.storage_proof.pop().unwrap();
proof.storage_proof.push(node.clone());
proof.storage_proof.push(node);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
},),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::DuplicateNodesInProof
))),
);
}
#[test]
fn message_proof_is_rejected_if_it_has_unused_trie_nodes() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |mut proof| {
proof.storage_proof.push(vec![42]);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
},),
Err(VerificationError::StorageProof(StorageProofError::UnusedNodesInTheProof)),
);
}
#[test]
fn message_proof_is_rejected_if_required_message_is_missing() {
matches!(
using_messages_proof(
10,
None,
|n, m| if n != 5 { Some(m.encode()) } else { None },
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
),
Err(VerificationError::MessageStorage(StorageProofError::StorageValueEmpty)),
);
}
#[test]
fn message_proof_is_rejected_if_message_decode_fails() {
matches!(
using_messages_proof(
10,
None,
|n, m| {
let mut m = m.encode();
if n == 5 {
m = vec![42]
}
Some(m)
},
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
),
Err(VerificationError::MessageStorage(StorageProofError::StorageValueDecodeFailed(_))),
);
}
#[test]
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
matches!(
using_messages_proof(
10,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
|d| {
let mut d = d.encode();
d.truncate(1);
d
},
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
),
Err(VerificationError::OutboundLaneStorage(
StorageProofError::StorageValueDecodeFailed(_)
)),
);
}
#[test]
fn message_proof_is_rejected_if_it_is_empty() {
assert_eq!(
using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 0)
},),
Err(VerificationError::EmptyMessageProof),
);
}
#[test]
fn non_empty_message_proof_without_messages_is_accepted() {
assert_eq!(
using_messages_proof(
0,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 0),
),
Ok(vec![(
TEST_LANE_ID,
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: Vec::new(),
},
)]
.into_iter()
.collect()),
);
}
#[test]
fn non_empty_message_proof_is_accepted() {
assert_eq!(
using_messages_proof(
1,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 1),
),
Ok(vec![(
TEST_LANE_ID,
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: vec![Message {
key: MessageKey { lane_id: TEST_LANE_ID, nonce: 1 },
payload: vec![42],
}],
},
)]
.into_iter()
.collect()),
);
}
#[test]
fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() {
assert_eq!(
using_messages_proof(1, None, encode_all_messages, encode_lane_data, |mut proof| {
proof.nonces_end = u64::MAX;
target::verify_messages_proof::<OnThisChainBridge>(proof, u32::MAX)
},),
Err(VerificationError::MessagesCountMismatch),
);
}
}
// Copyright (C) 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/>.
//! Helpers for implementing various message-related runtime API mthods.
use bp_messages::{
InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails,
};
use sp_std::vec::Vec;
/// Implementation of the `To*OutboundLaneApi::message_details`.
pub fn outbound_message_details<Runtime, MessagesPalletInstance>(
lane: LaneId,
begin: MessageNonce,
end: MessageNonce,
) -> Vec<OutboundMessageDetails>
where
Runtime: pallet_bridge_messages::Config<MessagesPalletInstance>,
MessagesPalletInstance: 'static,
{
(begin..=end)
.filter_map(|nonce| {
let message_data =
pallet_bridge_messages::Pallet::<Runtime, MessagesPalletInstance>::outbound_message_data(lane, nonce)?;
Some(OutboundMessageDetails {
nonce,
// dispatch message weight is always zero at the source chain, since we're paying for
// dispatch at the target chain
dispatch_weight: frame_support::weights::Weight::zero(),
size: message_data.len() as _,
})
})
.collect()
}
/// Implementation of the `To*InboundLaneApi::message_details`.
pub fn inbound_message_details<Runtime, MessagesPalletInstance>(
lane: LaneId,
messages: Vec<(MessagePayload, OutboundMessageDetails)>,
) -> Vec<InboundMessageDetails>
where
Runtime: pallet_bridge_messages::Config<MessagesPalletInstance>,
MessagesPalletInstance: 'static,
{
messages
.into_iter()
.map(|(payload, details)| {
pallet_bridge_messages::Pallet::<Runtime, MessagesPalletInstance>::inbound_message_data(
lane, payload, details,
)
})
.collect()
}
// Copyright (C) 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/>.
//! Everything required to run benchmarks of messages module, based on
//! `bridge_runtime_common::messages` implementation.
#![cfg(feature = "runtime-benchmarks")]
use crate::{
messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
AccountIdOf, BridgedChain, HashOf, MessageBridge, ThisChain,
},
messages_generation::{
encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof,
prepare_messages_storage_proof,
},
};
use bp_messages::MessagePayload;
use bp_polkadot_core::parachains::ParaHash;
use bp_runtime::{Chain, Parachain, StorageProofSize, UnderlyingChainOf};
use codec::Encode;
use frame_support::weights::Weight;
use pallet_bridge_messages::benchmarking::{MessageDeliveryProofParams, MessageProofParams};
use sp_runtime::traits::{Header, Zero};
use sp_std::prelude::*;
use xcm::latest::prelude::*;
/// Prepare inbound bridge message according to given message proof parameters.
fn prepare_inbound_message(
params: &MessageProofParams,
successful_dispatch_message_generator: impl Fn(usize) -> MessagePayload,
) -> MessagePayload {
// we only care about **this** message size when message proof needs to be `Minimal`
let expected_size = match params.size {
StorageProofSize::Minimal(size) => size as usize,
_ => 0,
};
// if we don't need a correct message, then we may just return some random blob
if !params.is_successful_dispatch_expected {
return vec![0u8; expected_size]
}
// else let's prepare successful message.
let msg = successful_dispatch_message_generator(expected_size);
assert!(
msg.len() >= expected_size,
"msg.len(): {} does not match expected_size: {}",
expected_size,
msg.len()
);
msg
}
/// Prepare proof of messages for the `receive_messages_proof` call.
///
/// In addition to returning valid messages proof, environment is prepared to verify this message
/// proof.
///
/// This method is intended to be used when benchmarking pallet, linked to the chain that
/// uses GRANDPA finality. For parachains, please use the `prepare_message_proof_from_parachain`
/// function.
pub fn prepare_message_proof_from_grandpa_chain<R, FI, B>(
params: MessageProofParams,
message_generator: impl Fn(usize) -> MessagePayload,
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
where
R: pallet_bridge_grandpa::Config<FI, BridgedChain = UnderlyingChainOf<BridgedChain<B>>>,
FI: 'static,
B: MessageBridge,
{
// prepare storage proof
let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
params.lane,
params.message_nonces.clone(),
params.outbound_lane_data.clone(),
params.size,
prepare_inbound_message(&params, message_generator),
encode_all_messages,
encode_lane_data,
);
// update runtime storage
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
(
FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane: params.lane,
nonces_start: *params.message_nonces.start(),
nonces_end: *params.message_nonces.end(),
},
Weight::MAX / 1000,
)
}
/// Prepare proof of messages for the `receive_messages_proof` call.
///
/// In addition to returning valid messages proof, environment is prepared to verify this message
/// proof.
///
/// This method is intended to be used when benchmarking pallet, linked to the chain that
/// uses parachain finality. For GRANDPA chains, please use the
/// `prepare_message_proof_from_grandpa_chain` function.
pub fn prepare_message_proof_from_parachain<R, PI, B>(
params: MessageProofParams,
message_generator: impl Fn(usize) -> MessagePayload,
) -> (FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>, Weight)
where
R: pallet_bridge_parachains::Config<PI>,
PI: 'static,
B: MessageBridge,
UnderlyingChainOf<BridgedChain<B>>: Chain<Hash = ParaHash> + Parachain,
{
// prepare storage proof
let (state_root, storage_proof) = prepare_messages_storage_proof::<B>(
params.lane,
params.message_nonces.clone(),
params.outbound_lane_data.clone(),
params.size,
prepare_inbound_message(&params, message_generator),
encode_all_messages,
encode_lane_data,
);
// update runtime storage
let (_, bridged_header_hash) =
insert_header_to_parachains_pallet::<R, PI, UnderlyingChainOf<BridgedChain<B>>>(state_root);
(
FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane: params.lane,
nonces_start: *params.message_nonces.start(),
nonces_end: *params.message_nonces.end(),
},
Weight::MAX / 1000,
)
}
/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call.
///
/// This method is intended to be used when benchmarking pallet, linked to the chain that
/// uses GRANDPA finality. For parachains, please use the
/// `prepare_message_delivery_proof_from_parachain` function.
pub fn prepare_message_delivery_proof_from_grandpa_chain<R, FI, B>(
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>
where
R: pallet_bridge_grandpa::Config<FI, BridgedChain = UnderlyingChainOf<BridgedChain<B>>>,
FI: 'static,
B: MessageBridge,
{
// prepare storage proof
let lane = params.lane;
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<B>(
params.lane,
params.inbound_lane_data,
params.size,
);
// update runtime storage
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);
FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: bridged_header_hash.into(),
storage_proof,
lane,
}
}
/// Prepare proof of messages delivery for the `receive_messages_delivery_proof` call.
///
/// This method is intended to be used when benchmarking pallet, linked to the chain that
/// uses parachain finality. For GRANDPA chains, please use the
/// `prepare_message_delivery_proof_from_grandpa_chain` function.
pub fn prepare_message_delivery_proof_from_parachain<R, PI, B>(
params: MessageDeliveryProofParams<AccountIdOf<ThisChain<B>>>,
) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>
where
R: pallet_bridge_parachains::Config<PI>,
PI: 'static,
B: MessageBridge,
UnderlyingChainOf<BridgedChain<B>>: Chain<Hash = ParaHash> + Parachain,
{
// prepare storage proof
let lane = params.lane;
let (state_root, storage_proof) = prepare_message_delivery_storage_proof::<B>(
params.lane,
params.inbound_lane_data,
params.size,
);
// update runtime storage
let (_, bridged_header_hash) =
insert_header_to_parachains_pallet::<R, PI, UnderlyingChainOf<BridgedChain<B>>>(state_root);
FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: bridged_header_hash.into(),
storage_proof,
lane,
}
}
/// Insert header to the bridge GRANDPA pallet.
pub(crate) fn insert_header_to_grandpa_pallet<R, GI>(
state_root: bp_runtime::HashOf<R::BridgedChain>,
) -> (bp_runtime::BlockNumberOf<R::BridgedChain>, bp_runtime::HashOf<R::BridgedChain>)
where
R: pallet_bridge_grandpa::Config<GI>,
GI: 'static,
R::BridgedChain: bp_runtime::Chain,
{
let bridged_block_number = Zero::zero();
let bridged_header = bp_runtime::HeaderOf::<R::BridgedChain>::new(
bridged_block_number,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_grandpa::initialize_for_benchmarks::<R, GI>(bridged_header);
(bridged_block_number, bridged_header_hash)
}
/// Insert header to the bridge parachains pallet.
pub(crate) fn insert_header_to_parachains_pallet<R, PI, PC>(
state_root: bp_runtime::HashOf<PC>,
) -> (bp_runtime::BlockNumberOf<PC>, bp_runtime::HashOf<PC>)
where
R: pallet_bridge_parachains::Config<PI>,
PI: 'static,
PC: Chain<Hash = ParaHash> + Parachain,
{
let bridged_block_number = Zero::zero();
let bridged_header = bp_runtime::HeaderOf::<PC>::new(
bridged_block_number,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_parachains::initialize_for_benchmarks::<R, PI, PC>(bridged_header);
(bridged_block_number, bridged_header_hash)
}
/// Returns callback which generates `BridgeMessage` from Polkadot XCM builder based on
/// `expected_message_size` for benchmark.
pub fn generate_xcm_builder_bridge_message_sample(
destination: InteriorLocation,
) -> impl Fn(usize) -> MessagePayload {
move |expected_message_size| -> MessagePayload {
// For XCM bridge hubs, it is the message that
// will be pushed further to some XCM queue (XCMP/UMP)
let location = xcm::VersionedInteriorLocation::V4(destination.clone());
let location_encoded_size = location.encoded_size();
// we don't need to be super-precise with `expected_size` here
let xcm_size = expected_message_size.saturating_sub(location_encoded_size);
let xcm_data_size = xcm_size.saturating_sub(
// minus empty instruction size
Instruction::<()>::ExpectPallet {
index: 0,
name: vec![],
module_name: vec![],
crate_major: 0,
min_crate_minor: 0,
}
.encoded_size(),
);
log::trace!(
target: "runtime::bridge-benchmarks",
"generate_xcm_builder_bridge_message_sample with expected_message_size: {}, location_encoded_size: {}, xcm_size: {}, xcm_data_size: {}",
expected_message_size, location_encoded_size, xcm_size, xcm_data_size,
);
let xcm = xcm::VersionedXcm::<()>::V4(
vec![Instruction::<()>::ExpectPallet {
index: 0,
name: vec![42; xcm_data_size],
module_name: vec![],
crate_major: 0,
min_crate_minor: 0,
}]
.into(),
);
// this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor
// or public fields, so just tuple
// (double encoding, because `.encode()` is called on original Xcm BLOB when it is pushed
// to the storage)
(location, xcm).encode().encode()
}
}
// Copyright (C) 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/>.
//! Signed extension for the `pallet-bridge-messages` that is able to reject obsolete
//! (and some other invalid) transactions.
use crate::messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
};
use bp_messages::{target_chain::MessageDispatch, InboundLaneData, LaneId, MessageNonce};
use bp_runtime::OwnedBridgeModule;
use frame_support::{
dispatch::CallableCallFor,
traits::{Get, IsSubType},
};
use pallet_bridge_messages::{Config, Pallet};
use sp_runtime::{transaction_validity::TransactionValidity, RuntimeDebug};
use sp_std::ops::RangeInclusive;
/// Generic info about a messages delivery/confirmation proof.
#[derive(PartialEq, RuntimeDebug)]
pub struct BaseMessagesProofInfo {
/// Message lane, used by the call.
pub lane_id: LaneId,
/// Nonces of messages, included in the call.
///
/// For delivery transaction, it is nonces of bundled messages. For confirmation
/// transaction, it is nonces that are to be confirmed during the call.
pub bundled_range: RangeInclusive<MessageNonce>,
/// Nonce of the best message, stored by this chain before the call is dispatched.
///
/// For delivery transaction, it is the nonce of best delivered message before the call.
/// For confirmation transaction, it is the nonce of best confirmed message before the call.
pub best_stored_nonce: MessageNonce,
}
impl BaseMessagesProofInfo {
/// Returns true if `bundled_range` continues the `0..=best_stored_nonce` range.
fn appends_to_stored_nonce(&self) -> bool {
Some(*self.bundled_range.start()) == self.best_stored_nonce.checked_add(1)
}
}
/// Occupation state of the unrewarded relayers vector.
#[derive(PartialEq, RuntimeDebug)]
#[cfg_attr(test, derive(Default))]
pub struct UnrewardedRelayerOccupation {
/// The number of remaining unoccupied entries for new relayers.
pub free_relayer_slots: MessageNonce,
/// The number of messages that we are ready to accept.
pub free_message_slots: MessageNonce,
}
/// Info about a `ReceiveMessagesProof` call which tries to update a single lane.
#[derive(PartialEq, RuntimeDebug)]
pub struct ReceiveMessagesProofInfo {
/// Base messages proof info
pub base: BaseMessagesProofInfo,
/// State of unrewarded relayers vector.
pub unrewarded_relayers: UnrewardedRelayerOccupation,
}
impl ReceiveMessagesProofInfo {
/// Returns true if:
///
/// - either inbound lane is ready to accept bundled messages;
///
/// - or there are no bundled messages, but the inbound lane is blocked by too many unconfirmed
/// messages and/or unrewarded relayers.
fn is_obsolete(&self, is_dispatcher_active: bool) -> bool {
// if dispatcher is inactive, we don't accept any delivery transactions
if !is_dispatcher_active {
return true
}
// transactions with zero bundled nonces are not allowed, unless they're message
// delivery transactions, which brings reward confirmations required to unblock
// the lane
if self.base.bundled_range.is_empty() {
let empty_transactions_allowed =
// we allow empty transactions when we can't accept delivery from new relayers
self.unrewarded_relayers.free_relayer_slots == 0 ||
// or if we can't accept new messages at all
self.unrewarded_relayers.free_message_slots == 0;
return !empty_transactions_allowed
}
// otherwise we require bundled messages to continue stored range
!self.base.appends_to_stored_nonce()
}
}
/// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane.
#[derive(PartialEq, RuntimeDebug)]
pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo);
impl ReceiveMessagesDeliveryProofInfo {
/// Returns true if outbound lane is ready to accept confirmations of bundled messages.
fn is_obsolete(&self) -> bool {
self.0.bundled_range.is_empty() || !self.0.appends_to_stored_nonce()
}
}
/// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call
/// which tries to update a single lane.
#[derive(PartialEq, RuntimeDebug)]
pub enum CallInfo {
/// Messages delivery call info.
ReceiveMessagesProof(ReceiveMessagesProofInfo),
/// Messages delivery confirmation call info.
ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo),
}
impl CallInfo {
/// Returns range of messages, bundled with the call.
pub fn bundled_messages(&self) -> RangeInclusive<MessageNonce> {
match *self {
Self::ReceiveMessagesProof(ref info) => info.base.bundled_range.clone(),
Self::ReceiveMessagesDeliveryProof(ref info) => info.0.bundled_range.clone(),
}
}
}
/// Helper struct that provides methods for working with a call supported by `CallInfo`.
pub struct CallHelper<T: Config<I>, I: 'static> {
_phantom_data: sp_std::marker::PhantomData<(T, I)>,
}
impl<T: Config<I>, I: 'static> CallHelper<T, I> {
/// Returns true if:
///
/// - call is `receive_messages_proof` and all messages have been delivered;
///
/// - call is `receive_messages_delivery_proof` and all messages confirmations have been
/// received.
pub fn was_successful(info: &CallInfo) -> bool {
match info {
CallInfo::ReceiveMessagesProof(info) => {
let inbound_lane_data =
pallet_bridge_messages::InboundLanes::<T, I>::get(info.base.lane_id);
if info.base.bundled_range.is_empty() {
let post_occupation =
unrewarded_relayers_occupation::<T, I>(&inbound_lane_data);
// we don't care about `free_relayer_slots` here - it is checked in
// `is_obsolete` and every relayer has delivered at least one message,
// so if relayer slots are released, then message slots are also
// released
return post_occupation.free_message_slots >
info.unrewarded_relayers.free_message_slots
}
inbound_lane_data.last_delivered_nonce() == *info.base.bundled_range.end()
},
CallInfo::ReceiveMessagesDeliveryProof(info) => {
let outbound_lane_data =
pallet_bridge_messages::OutboundLanes::<T, I>::get(info.0.lane_id);
outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
},
}
}
}
/// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`.
pub trait MessagesCallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
IsSubType<CallableCallFor<Pallet<T, I>, T>>
{
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo>;
/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
/// a `ReceiveMessagesDeliveryProof` call.
fn receive_messages_delivery_proof_info(&self) -> Option<ReceiveMessagesDeliveryProofInfo>;
/// Create a new instance of `CallInfo` from a `ReceiveMessagesProof`
/// or a `ReceiveMessagesDeliveryProof` call.
fn call_info(&self) -> Option<CallInfo>;
/// Create a new instance of `CallInfo` from a `ReceiveMessagesProof`
/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
fn call_info_for(&self, lane_id: LaneId) -> Option<CallInfo>;
/// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call:
///
/// - does not deliver already delivered messages. We require all messages in the
/// `ReceiveMessagesProof` call to be undelivered;
///
/// - does not submit empty `ReceiveMessagesProof` call with zero messages, unless the lane
/// needs to be unblocked by providing relayer rewards proof;
///
/// - brings no new delivery confirmations in a `ReceiveMessagesDeliveryProof` call. We require
/// at least one new delivery confirmation in the unrewarded relayers set;
///
/// - does not violate some basic (easy verifiable) messages pallet rules obsolete (like
/// submitting a call when a pallet is halted or delivering messages when a dispatcher is
/// inactive).
///
/// If one of above rules is violated, the transaction is treated as invalid.
fn check_obsolete_call(&self) -> TransactionValidity;
}
impl<
BridgedHeaderHash,
SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
MessagesProof = FromBridgedChainMessagesProof<BridgedHeaderHash>,
>,
TargetHeaderChain: bp_messages::source_chain::TargetHeaderChain<
<T as Config<I>>::OutboundPayload,
<T as frame_system::Config>::AccountId,
MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash>,
>,
Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
T: frame_system::Config<RuntimeCall = Call>
+ Config<I, SourceHeaderChain = SourceHeaderChain, TargetHeaderChain = TargetHeaderChain>,
I: 'static,
> MessagesCallSubType<T, I> for T::RuntimeCall
{
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo> {
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_proof {
ref proof,
..
}) = self.is_sub_type()
{
let inbound_lane_data = pallet_bridge_messages::InboundLanes::<T, I>::get(proof.lane);
return Some(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: proof.lane,
// we want all messages in this range to be new for us. Otherwise transaction
// will be considered obsolete.
bundled_range: proof.nonces_start..=proof.nonces_end,
best_stored_nonce: inbound_lane_data.last_delivered_nonce(),
},
unrewarded_relayers: unrewarded_relayers_occupation::<T, I>(&inbound_lane_data),
})
}
None
}
fn receive_messages_delivery_proof_info(&self) -> Option<ReceiveMessagesDeliveryProofInfo> {
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_delivery_proof {
ref proof,
ref relayers_state,
..
}) = self.is_sub_type()
{
let outbound_lane_data = pallet_bridge_messages::OutboundLanes::<T, I>::get(proof.lane);
return Some(ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: proof.lane,
// there's a time frame between message delivery, message confirmation and reward
// confirmation. Because of that, we can't assume that our state has been confirmed
// to the bridged chain. So we are accepting any proof that brings new
// confirmations.
bundled_range: outbound_lane_data.latest_received_nonce + 1..=
relayers_state.last_delivered_nonce,
best_stored_nonce: outbound_lane_data.latest_received_nonce,
}))
}
None
}
fn call_info(&self) -> Option<CallInfo> {
if let Some(info) = self.receive_messages_proof_info() {
return Some(CallInfo::ReceiveMessagesProof(info))
}
if let Some(info) = self.receive_messages_delivery_proof_info() {
return Some(CallInfo::ReceiveMessagesDeliveryProof(info))
}
None
}
fn call_info_for(&self, lane_id: LaneId) -> Option<CallInfo> {
self.call_info().filter(|info| {
let actual_lane_id = match info {
CallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
CallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
};
actual_lane_id == lane_id
})
}
fn check_obsolete_call(&self) -> TransactionValidity {
let is_pallet_halted = Pallet::<T, I>::ensure_not_halted().is_err();
match self.call_info() {
Some(proof_info) if is_pallet_halted => {
log::trace!(
target: pallet_bridge_messages::LOG_TARGET,
"Rejecting messages transaction on halted pallet: {:?}",
proof_info
);
return sp_runtime::transaction_validity::InvalidTransaction::Call.into()
},
Some(CallInfo::ReceiveMessagesProof(proof_info))
if proof_info.is_obsolete(T::MessageDispatch::is_active()) =>
{
log::trace!(
target: pallet_bridge_messages::LOG_TARGET,
"Rejecting obsolete messages delivery transaction: {:?}",
proof_info
);
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
},
Some(CallInfo::ReceiveMessagesDeliveryProof(proof_info))
if proof_info.is_obsolete() =>
{
log::trace!(
target: pallet_bridge_messages::LOG_TARGET,
"Rejecting obsolete messages confirmation transaction: {:?}",
proof_info,
);
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
},
_ => {},
}
Ok(sp_runtime::transaction_validity::ValidTransaction::default())
}
}
/// Returns occupation state of unrewarded relayers vector.
fn unrewarded_relayers_occupation<T: Config<I>, I: 'static>(
inbound_lane_data: &InboundLaneData<T::InboundRelayer>,
) -> UnrewardedRelayerOccupation {
UnrewardedRelayerOccupation {
free_relayer_slots: T::MaxUnrewardedRelayerEntriesAtInboundLane::get()
.saturating_sub(inbound_lane_data.relayers.len() as MessageNonce),
free_message_slots: {
let unconfirmed_messages = inbound_lane_data
.last_delivered_nonce()
.saturating_sub(inbound_lane_data.last_confirmed_nonce);
T::MaxUnconfirmedMessagesAtInboundLane::get().saturating_sub(unconfirmed_messages)
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
messages::{
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
},
messages_call_ext::MessagesCallSubType,
mock::{
DummyMessageDispatch, MaxUnconfirmedMessagesAtInboundLane,
MaxUnrewardedRelayerEntriesAtInboundLane, TestRuntime, ThisChainRuntimeCall,
},
};
use bp_messages::{DeliveredMessages, UnrewardedRelayer, UnrewardedRelayersState};
use sp_std::ops::RangeInclusive;
fn fill_unrewarded_relayers() {
let mut inbound_lane_state =
pallet_bridge_messages::InboundLanes::<TestRuntime>::get(LaneId([0, 0, 0, 0]));
for n in 0..MaxUnrewardedRelayerEntriesAtInboundLane::get() {
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
relayer: Default::default(),
messages: DeliveredMessages { begin: n + 1, end: n + 1 },
});
}
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
inbound_lane_state,
);
}
fn fill_unrewarded_messages() {
let mut inbound_lane_state =
pallet_bridge_messages::InboundLanes::<TestRuntime>::get(LaneId([0, 0, 0, 0]));
inbound_lane_state.relayers.push_back(UnrewardedRelayer {
relayer: Default::default(),
messages: DeliveredMessages {
begin: 1,
end: MaxUnconfirmedMessagesAtInboundLane::get(),
},
});
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
inbound_lane_state,
);
}
fn deliver_message_10() {
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
bp_messages::InboundLaneData { relayers: Default::default(), last_confirmed_nonce: 10 },
);
}
fn validate_message_delivery(
nonces_start: bp_messages::MessageNonce,
nonces_end: bp_messages::MessageNonce,
) -> bool {
ThisChainRuntimeCall::BridgeMessages(
pallet_bridge_messages::Call::<TestRuntime, ()>::receive_messages_proof {
relayer_id_at_bridged_chain: 42,
messages_count: nonces_end.checked_sub(nonces_start).map(|x| x + 1).unwrap_or(0)
as u32,
dispatch_weight: frame_support::weights::Weight::zero(),
proof: FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: vec![],
lane: LaneId([0, 0, 0, 0]),
nonces_start,
nonces_end,
},
},
)
.check_obsolete_call()
.is_ok()
}
#[test]
fn extension_rejects_obsolete_messages() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best delivered is message#10 and we're trying to deliver messages 8..=9
// => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(8, 9));
});
}
#[test]
fn extension_rejects_same_message() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best delivered is message#10 and we're trying to import messages 10..=10
// => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(8, 10));
});
}
#[test]
fn extension_rejects_call_with_some_obsolete_messages() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best delivered is message#10 and we're trying to deliver messages
// 10..=15 => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(10, 15));
});
}
#[test]
fn extension_rejects_call_with_future_messages() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best delivered is message#10 and we're trying to deliver messages
// 13..=15 => tx is rejected
deliver_message_10();
assert!(!validate_message_delivery(13, 15));
});
}
#[test]
fn extension_reject_call_when_dispatcher_is_inactive() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best delivered is message#10 and we're trying to deliver message 11..=15
// => tx is accepted, but we have inactive dispatcher, so...
deliver_message_10();
DummyMessageDispatch::deactivate();
assert!(!validate_message_delivery(11, 15));
});
}
#[test]
fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
) {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
assert!(!validate_message_delivery(10, 9));
});
}
#[test]
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_relayer_slots(
) {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
fill_unrewarded_relayers();
assert!(validate_message_delivery(10, 9));
});
}
#[test]
fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_message_slots(
) {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
fill_unrewarded_messages();
assert!(validate_message_delivery(
MaxUnconfirmedMessagesAtInboundLane::get(),
MaxUnconfirmedMessagesAtInboundLane::get() - 1
));
});
}
#[test]
fn extension_accepts_new_messages() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best delivered is message#10 and we're trying to deliver message 11..=15
// => tx is accepted
deliver_message_10();
assert!(validate_message_delivery(11, 15));
});
}
fn confirm_message_10() {
pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(
LaneId([0, 0, 0, 0]),
bp_messages::OutboundLaneData {
oldest_unpruned_nonce: 0,
latest_received_nonce: 10,
latest_generated_nonce: 10,
},
);
}
fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
ThisChainRuntimeCall::BridgeMessages(
pallet_bridge_messages::Call::<TestRuntime>::receive_messages_delivery_proof {
proof: FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: Vec::new(),
lane: LaneId([0, 0, 0, 0]),
},
relayers_state: UnrewardedRelayersState {
last_delivered_nonce,
..Default::default()
},
},
)
.check_obsolete_call()
.is_ok()
}
#[test]
fn extension_rejects_obsolete_confirmations() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best confirmed is message#10 and we're trying to confirm message#5 => tx
// is rejected
confirm_message_10();
assert!(!validate_message_confirmation(5));
});
}
#[test]
fn extension_rejects_same_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best confirmed is message#10 and we're trying to confirm message#10 =>
// tx is rejected
confirm_message_10();
assert!(!validate_message_confirmation(10));
});
}
#[test]
fn extension_rejects_empty_confirmation_even_if_there_are_no_free_unrewarded_entries() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
fill_unrewarded_relayers();
assert!(!validate_message_confirmation(10));
});
}
#[test]
fn extension_accepts_new_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
// when current best confirmed is message#10 and we're trying to confirm message#15 =>
// tx is accepted
confirm_message_10();
assert!(validate_message_confirmation(15));
});
}
fn was_message_delivery_successful(
bundled_range: RangeInclusive<MessageNonce>,
is_empty: bool,
) -> bool {
CallHelper::<TestRuntime, ()>::was_successful(&CallInfo::ReceiveMessagesProof(
ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: LaneId([0, 0, 0, 0]),
bundled_range,
best_stored_nonce: 0, // doesn't matter for `was_successful`
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots: 0, // doesn't matter for `was_successful`
free_message_slots: if is_empty {
0
} else {
MaxUnconfirmedMessagesAtInboundLane::get()
},
},
},
))
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
fill_unrewarded_messages();
assert!(!was_message_delivery_successful(10..=9, true));
});
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
fn was_successful_returns_true_for_successful_reward_confirmation_transaction() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
assert!(was_message_delivery_successful(10..=9, true));
});
}
#[test]
fn was_successful_returns_false_for_failed_delivery() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
assert!(!was_message_delivery_successful(10..=12, false));
});
}
#[test]
fn was_successful_returns_false_for_partially_successful_delivery() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
assert!(!was_message_delivery_successful(9..=12, false));
});
}
#[test]
fn was_successful_returns_true_for_successful_delivery() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
deliver_message_10();
assert!(was_message_delivery_successful(9..=10, false));
});
}
fn was_message_confirmation_successful(bundled_range: RangeInclusive<MessageNonce>) -> bool {
CallHelper::<TestRuntime, ()>::was_successful(&CallInfo::ReceiveMessagesDeliveryProof(
ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: LaneId([0, 0, 0, 0]),
bundled_range,
best_stored_nonce: 0, // doesn't matter for `was_successful`
}),
))
}
#[test]
fn was_successful_returns_false_for_failed_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
assert!(!was_message_confirmation_successful(10..=12));
});
}
#[test]
fn was_successful_returns_false_for_partially_successful_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
assert!(!was_message_confirmation_successful(9..=12));
});
}
#[test]
fn was_successful_returns_true_for_successful_confirmation() {
sp_io::TestExternalities::new(Default::default()).execute_with(|| {
confirm_message_10();
assert!(was_message_confirmation_successful(9..=10));
});
}
}