Skip to content

Signed extension to refund relayer at the target chain #1657

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8999d63
add utlity pallet to the Millau runtime
svyatonik Nov 22, 2022
89a9268
RefundRelayerForMessagesDeliveryFromParachain prototype
svyatonik Nov 22, 2022
20f90aa
Merge branch 'master' into signed-extension-to-refund-relayer-at-targ…
svyatonik Nov 25, 2022
dc35853
done with RefundRelayerForMessagesDeliveryFromParachain::post_dispatch
svyatonik Nov 25, 2022
25e4c09
parse calls
svyatonik Nov 25, 2022
e72c723
check batch for obsolete headers/messages
svyatonik Nov 28, 2022
4bb3766
fmt
svyatonik Nov 28, 2022
54e350b
shorten generic arg names + add parachain id generic arg
svyatonik Nov 28, 2022
92cc156
check lane_id
svyatonik Nov 28, 2022
5dc098a
impl all state read functions
svyatonik Nov 28, 2022
29dc969
Merge branch 'master' into signed-extension-to-refund-relayer-at-targ…
svyatonik Nov 28, 2022
e581c4d
fix typos from review
svyatonik Nov 29, 2022
d067da3
renamed extension + reference issue from TODO
svyatonik Nov 29, 2022
8ab7c9f
tests for pre-dispaytch
svyatonik Nov 29, 2022
da8699e
renamed extension source file
svyatonik Nov 29, 2022
d2596ab
tests for post-dispatch
svyatonik Nov 29, 2022
3f5e08d
abstract fee calculation
svyatonik Nov 30, 2022
7d31015
clippy
svyatonik Nov 30, 2022
3258a8e
actually fix clippy
svyatonik Nov 30, 2022
ecf5f7c
Merge branch 'master' into signed-extension-to-refund-relayer-at-targ…
svyatonik Nov 30, 2022
9b3b5f3
Update bin/runtime-common/src/refund_relayer_extension.rs
svyatonik Dec 9, 2022
557fce8
Update bin/runtime-common/src/refund_relayer_extension.rs
svyatonik Dec 9, 2022
16686ee
Update bin/runtime-common/src/refund_relayer_extension.rs
svyatonik Dec 9, 2022
054d4e8
Update bin/runtime-common/src/refund_relayer_extension.rs
svyatonik Dec 9, 2022
948db62
Merge branch 'master' into signed-extension-to-refund-relayer-at-targ…
svyatonik Dec 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bin/millau/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pallet-sudo = { git = "https://github.com/paritytech/substrate", branch = "maste
pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
Expand Down Expand Up @@ -113,6 +114,7 @@ std = [
"pallet-timestamp/std",
"pallet-transaction-payment-rpc-runtime-api/std",
"pallet-transaction-payment/std",
"pallet-utility/std",
"pallet-xcm/std",
"scale-info/std",
"sp-api/std",
Expand Down
8 changes: 8 additions & 0 deletions bin/millau/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,13 @@ impl pallet_bridge_parachains::Config<WithWestendParachainsInstance> for Runtime
type MaxParaHeadSize = MaxWestendParaHeadSize;
}

impl pallet_utility::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}

construct_runtime!(
pub enum Runtime where
Block = Block,
Expand All @@ -573,6 +580,7 @@ construct_runtime!(
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Sudo: pallet_sudo::{Pallet, Call, Config<T>, Storage, Event<T>},
Utility: pallet_utility,

// Must be before session.
Aura: pallet_aura::{Pallet, Config<T>},
Expand Down
4 changes: 4 additions & 0 deletions bin/runtime-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ bp-runtime = { path = "../../primitives/runtime", 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/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
Expand Down Expand Up @@ -61,6 +63,8 @@ std = [
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-bridge-parachains/std",
"pallet-bridge-relayers/std",
"pallet-transaction-payment/std",
"pallet-xcm/std",
"scale-info/std",
"sp-api/std",
Expand Down
1 change: 1 addition & 0 deletions bin/runtime-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use xcm::v3::NetworkId;

pub mod messages;
pub mod messages_api;
pub mod messages_batch_extension;
pub mod messages_benchmarking;
pub mod messages_extension;
pub mod parachains_benchmarking;
Expand Down
297 changes: 297 additions & 0 deletions bin/runtime-common/src/messages_batch_extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.

// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

//! Signed extension that refunds relayer if he has delivered some new messages.
//! It also refunds transacation 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 bp_messages::{LaneId, MessageNonce};
use bp_polkadot_core::parachains::ParaId;
use codec::{Decode, Encode};
use frame_support::{
dispatch::{DispatchInfo, Dispatchable, PostDispatchInfo},
RuntimeDebugNoBound,
};
use pallet_bridge_parachains::RelayBlockNumber;
use pallet_transaction_payment::OnChargeTransaction;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{DispatchInfoOf, PostDispatchInfoOf, SignedExtension, Zero},
transaction_validity::{TransactionValidity, TransactionValidityError, ValidTransaction},
DispatchResult, FixedPointOperand,
};
use sp_std::marker::PhantomData;

// TODO: is it possible to impl it for several bridges at once? Like what we have in
// `BridgeRejectObsoleteHeadersAndMessages`? If it is hard to do now - just submit an issue

#[derive(Decode, Encode, RuntimeDebugNoBound, TypeInfo)]
#[scale_info(skip_type_params(Runtime, GrandpaInstance, ParachainsInstance, MessagesInstance))]
pub struct RefundRelayerForMessagesDeliveryFromParachain<
Runtime,
GrandpaInstance,
ParachainsInstance,
MessagesInstance,
>(PhantomData<(Runtime, GrandpaInstance, ParachainsInstance, MessagesInstance)>);

impl<Runtime, GrandpaInstance, ParachainsInstance, MessagesInstance> Clone
for RefundRelayerForMessagesDeliveryFromParachain<
Runtime,
GrandpaInstance,
ParachainsInstance,
MessagesInstance,
>
{
fn clone(&self) -> Self {
RefundRelayerForMessagesDeliveryFromParachain(PhantomData)
}
}

impl<Runtime, GrandpaInstance, ParachainsInstance, MessagesInstance> Eq
for RefundRelayerForMessagesDeliveryFromParachain<
Runtime,
GrandpaInstance,
ParachainsInstance,
MessagesInstance,
>
{
}

impl<Runtime, GrandpaInstance, ParachainsInstance, MessagesInstance> PartialEq
for RefundRelayerForMessagesDeliveryFromParachain<
Runtime,
GrandpaInstance,
ParachainsInstance,
MessagesInstance,
>
{
fn eq(&self, _other: &Self) -> bool {
true
}
}

/// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`.
#[derive(PartialEq)]
pub struct PreDispatchData<AccountId> {
/// Transaction submitter (relayer) account.
pub relayer: AccountId,
/// Type of the call.
pub call_type: CallType,
}

/// Type of the call that the extension recognizing.
#[derive(Clone, Copy, PartialEq)]
pub enum CallType {
/// Relay chain finality + parachain finality + message delivery calls.
AllFinalityAndDelivery(ExpectedRelayChainState, ExpectedParachainState, MessagesState),
/// Parachain finality + message delivery calls.
ParachainFinalityAndDelivery(ExpectedParachainState, MessagesState),
/// Standalone message delivery call.
Delivery(MessagesState),
}

impl CallType {
/// Returns the pre-dispatch messages pallet state.
fn pre_dispatch_messages_state(&self) -> MessagesState {
match *self {
Self::AllFinalityAndDelivery(_, _, messages_state) => messages_state,
Self::ParachainFinalityAndDelivery(_, messages_state) => messages_state,
Self::Delivery(messages_state) => messages_state,
}
}
}

/// Expected post-dispatch state of the relay chain pallet.
#[derive(Clone, Copy, PartialEq)]
pub struct ExpectedRelayChainState {
/// Best known relay chain block number.
pub best_block_number: RelayBlockNumber,
}

/// Expected post-dispatch state of the parachain pallet.
#[derive(Clone, Copy, PartialEq)]
pub struct ExpectedParachainState {
/// Parachain identifier.
pub para: ParaId,
/// At which relay block the parachain head has been updated?
pub at_relay_block_number: RelayBlockNumber,
}

/// Pre-dispatch state of messages pallet.
///
/// This struct is for pre-dispatch state of the pallet, not the expected post-dispatch state.
/// That's because message delivery transaction may deliver some of messages that it brings.
/// If this happens, we consider it "helpful" and refund its cost. If transaction fails to
/// deliver at least one message, it is considered wrong and is not refunded.
#[derive(Clone, Copy, PartialEq)]
pub struct MessagesState {
/// Message lane identifier.
pub lane: LaneId,
/// Best delivered message nonce.
pub last_delivered_nonce: MessageNonce,
}

// without this typedef rustfmt fails with internal err
type BalanceOf<Runtime> =
<<Runtime as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
Runtime,
>>::Balance;

impl<Runtime, GrandpaInstance, ParachainsInstance, MessagesInstance> SignedExtension
for RefundRelayerForMessagesDeliveryFromParachain<
Runtime,
GrandpaInstance,
ParachainsInstance,
MessagesInstance,
> where
Runtime: 'static
+ Send
+ Sync
+ frame_system::Config
+ pallet_transaction_payment::Config
+ pallet_bridge_grandpa::Config<GrandpaInstance>
+ pallet_bridge_parachains::Config<ParachainsInstance>
+ pallet_bridge_messages::Config<MessagesInstance>
+ pallet_bridge_relayers::Config<Reward = BalanceOf<Runtime>>,
GrandpaInstance: 'static + Send + Sync,
ParachainsInstance: 'static + Send + Sync,
MessagesInstance: 'static + Send + Sync,
<Runtime as frame_system::Config>::RuntimeCall:
Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: FixedPointOperand,
{
const IDENTIFIER: &'static str = "RefundRelayerForMessagesDeliveryFromParachain";
type AccountId = Runtime::AccountId;
type Call = Runtime::RuntimeCall;
type AdditionalSigned = ();
type Pre = PreDispatchData<Runtime::AccountId>;

fn additional_signed(&self) -> Result<(), TransactionValidityError> {
Ok(())
}

fn validate(
&self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
Ok(ValidTransaction::default())
}

fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_post_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
// TODO: for every call from the batch - call the `BridgesExtension` to ensure that every
// call transaction brings something new and reject obsolete transactions
unimplemented!("TODO") // TODO: return actual call type
}

fn post_dispatch(
pre: Option<Self::Pre>,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
len: usize,
result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
// we never refund anything if that is not bridge transaction or if it is a bridge
// transaction that we do not support here
let (relayer, call_type) = match pre {
Some(pre) => (pre.relayer, pre.call_type),
None => return Ok(()),
};

// we never refund anything if transaction has failed
if result.is_err() {
return Ok(())
}

// check if relay chain state has been updated
if let CallType::AllFinalityAndDelivery(expected_relay_chain_state, _, _) = call_type {
let actual_relay_chain_state = relay_chain_state::<Runtime, GrandpaInstance>();
if actual_relay_chain_state != expected_relay_chain_state {
// we only refund relayer if all calls have updated chain state
return Ok(())
}

// there's a conflict between how bridge GRANDPA pallet works and the
// `AllFinalityAndDelivery` transaction. If relay cahin 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.
}

// check if parachain state has been updated
match call_type {
CallType::AllFinalityAndDelivery(_, expected_parachain_state, _) |
CallType::ParachainFinalityAndDelivery(expected_parachain_state, _) => {
let actual_parachain_state = parachain_state::<Runtime, ParachainsInstance>();
if expected_parachain_state != actual_parachain_state {
// we only refund relayer if all calls have updated chain state
return Ok(())
}
},
_ => (),
}

// check if messages have been delivered
let actual_messages_state = messages_state::<Runtime, MessagesInstance>();
let pre_dispatch_messages_state = call_type.pre_dispatch_messages_state();
if actual_messages_state == pre_dispatch_messages_state {
// we only refund relayer if all calls have updated chain state
return Ok(())
}

// regarding the tip - refund that happens here (at this side of the bridge) isn't the whole
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an important thing - we don't refund the whole transaction cost here. Tip is not included in refund. Reasoning is in the code

// relayer compensation. He'll receive some amount at the other side of the bridge. It shall
// (in theory) cover the tip here. 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();

// compute the relayer reward
let reward = pallet_transaction_payment::Pallet::<Runtime>::compute_actual_fee(
len as _, info, post_info, tip,
);

// finally - regiater reward in relayers pallet
pallet_bridge_relayers::Pallet::<Runtime>::register_relayer_reward(&relayer, reward);

Ok(())
}
}

/// Returns relay chain state that we are interested in.
fn relay_chain_state<Runtime, GrandpaInstance>() -> ExpectedRelayChainState {
unimplemented!("TODO")
}

/// Returns parachain state that we are interested in.
fn parachain_state<Runtime, ParachainsInstance>() -> ExpectedParachainState {
unimplemented!("TODO")
}

/// Returns messages state that we are interested in.
fn messages_state<Runtime, MessagesInstance>() -> MessagesState {
unimplemented!("TODO")
}
5 changes: 5 additions & 0 deletions modules/grandpa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,11 @@ pub mod pallet {
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get the best finalized block number.
pub fn best_finalized_number() -> Option<BridgedBlockNumber<T, I>> {
BestFinalized::<T, I>::get().map(|(number, _)| number)
}

/// Get the best finalized header the pallet knows of.
pub fn best_finalized() -> Option<BridgedHeader<T, I>> {
let (_, hash) = <BestFinalized<T, I>>::get()?;
Expand Down
Loading