Skip to content

Commit

Permalink
done with RefundRelayerForMessagesDeliveryFromParachain::post_dispatch
Browse files Browse the repository at this point in the history
  • Loading branch information
svyatonik committed Nov 25, 2022
1 parent 20f90aa commit dc35853
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 94 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

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
303 changes: 231 additions & 72 deletions bin/runtime-common/src/messages_batch_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,166 @@
//! 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::Dispatchable, Parameter, RuntimeDebug};
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},
traits::{DispatchInfoOf, PostDispatchInfoOf, SignedExtension, Zero},
transaction_validity::{TransactionValidity, TransactionValidityError, ValidTransaction},
DispatchResult,
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(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct RefundRelayerForMessagesDeliveryFromParachain<AccountId, Call>(
PhantomData<(AccountId, Call)>,
);
#[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,
}

impl<AccountId, Call> SignedExtension
for RefundRelayerForMessagesDeliveryFromParachain<AccountId, Call>
where
AccountId: 'static + Parameter + Send + Sync,
Call: 'static + Dispatchable + Parameter + Send + Sync,
// 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 = AccountId;
type Call = Call;
type AccountId = Runtime::AccountId;
type Call = Runtime::RuntimeCall;
type AdditionalSigned = ();
type Pre = ();
type Pre = PreDispatchData<Runtime::AccountId>;

fn additional_signed(&self) -> Result<(), TransactionValidityError> {
Ok(())
Expand All @@ -69,70 +200,98 @@ where
_call: &Self::Call,
_post_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<(), TransactionValidityError> {
) -> 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
Ok(())
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,
pre: Option<Self::Pre>,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
len: usize,
result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
// TODO:
// if batch matches!(relay-chain-finality-call, parachain-finality-call,
// deliver-messages-call) then:
//
// 0) ensure that the `result` is `Ok(_)`
//
// 1) ensure that all calls are dependent (relaychain -> parachain of this relaychain and
// proof is using new relaychain header -> messages of this parachain and proof is using new
// parachain head). Otherwise - no refund!!!
//
// 2) ensure that the parachain finality transaction is for single parachain (our special
// case). Otherwise - no refund!!!
//
// 3) check result of every call - e.g. parachain finality call succeeds even if it is not
// updating anything. But we are only interesting in refunding calls that have succeeded. If
// at least one call has not succeeded - no refund!!!
//
// 4) now we may refund using `pallet-bridge-relayers`. The sum of refund is
// `pallet_transaction_payment::Pallet::<Runtime>::compute_actual_fee(len as u32, info,
// post_info, tip)`

// TODO:
// if batch matches!(parachain-finality-call, deliver-messages-call) then:
//
// 0) ensure that the `result` is `Ok(_)`
//
// 1) ensure that all calls are dependent (parachain -> messages of this parachain and proof
// is using new parachain head). Otherwise - no refund!!!
//
// 2) ensure that the parachain finality transaction is for single parachain (our special
// case). Otherwise - no refund!!!
//
// 3) check result of every call - e.g. parachain finality call succeeds even if it is not
// updating anything. But we are only interesting in refunding calls that have succeeded. If
// at least one call has not succeeded - no refund!!!
//
// 4) now we may refund using
// `pallet-bridge-relayers`. The sum of refund is
// `pallet_transaction_payment::Pallet::<Runtime>::compute_actual_fee(len as u32, info,
// post_info, tip)`

// TODO: if the call is just deliver-message-call then:
//
// 0) ensure that the `result` is `Ok(_)`
//
// 1) check that at least some messages were accepted. Otherwise - no refund!!!;
//
// 2) now we may refund using `pallet-bridge-relayers`. The sum of refund is
// `pallet_transaction_payment::Pallet::<Runtime>::compute_actual_fee(len as u32, info,
// post_info, tip)`
// 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
// 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
2 changes: 2 additions & 0 deletions modules/relayers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch =
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 }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }

[dev-dependencies]
Expand All @@ -44,6 +45,7 @@ std = [
"pallet-bridge-messages/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
Expand Down
Loading

0 comments on commit dc35853

Please sign in to comment.