Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • parity/mirrors/polkadot-sdk
1 result
Show changes
Showing
with 3050 additions and 1804 deletions
......@@ -17,8 +17,8 @@
//! Helpers for generating message storage proofs, that are used by tests and by benchmarks.
use bp_messages::{
storage_keys, ChainWithMessages, InboundLaneData, LaneId, MessageKey, MessageNonce,
MessagePayload, OutboundLaneData,
storage_keys, ChainWithMessages, InboundLaneData, MessageKey, MessageNonce, MessagePayload,
OutboundLaneData,
};
use bp_runtime::{
grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf,
......@@ -47,7 +47,11 @@ pub fn encode_lane_data(d: &OutboundLaneData) -> Vec<u8> {
///
/// Returns state trie root and nodes with prepared messages.
#[allow(clippy::too_many_arguments)]
pub fn prepare_messages_storage_proof<BridgedChain: Chain, ThisChain: ChainWithMessages>(
pub fn prepare_messages_storage_proof<
BridgedChain: Chain,
ThisChain: ChainWithMessages,
LaneId: Encode + Copy,
>(
lane: LaneId,
message_nonces: RangeInclusive<MessageNonce>,
outbound_lane_data: Option<OutboundLaneData>,
......@@ -132,7 +136,11 @@ where
/// Prepare storage proof of given messages delivery.
///
/// Returns state trie root and nodes with prepared messages.
pub fn prepare_message_delivery_storage_proof<BridgedChain: Chain, ThisChain: ChainWithMessages>(
pub fn prepare_message_delivery_storage_proof<
BridgedChain: Chain,
ThisChain: ChainWithMessages,
LaneId: Encode,
>(
lane: LaneId,
inbound_lane_data: InboundLaneData<AccountIdOf<ThisChain>>,
proof_params: UnverifiedStorageProofParams,
......
......@@ -35,15 +35,16 @@ use bp_messages::{
DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof,
MessageDispatch,
},
ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, Message, MessageKey,
MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState,
ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState,
Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
};
use bp_runtime::{
messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams,
};
use codec::{Decode, Encode};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::{
derive_impl, parameter_types,
derive_impl,
weights::{constants::RocksDbWeight, Weight},
};
use scale_info::TypeInfo;
......@@ -57,7 +58,7 @@ use std::{collections::VecDeque, ops::RangeInclusive};
pub type AccountId = u64;
pub type Balance = u64;
#[derive(Decode, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
#[derive(Decode, DecodeWithMemTracking, Encode, Clone, Debug, PartialEq, Eq, TypeInfo)]
pub struct TestPayload {
/// Field that may be used to identify messages.
pub id: u64,
......@@ -183,12 +184,6 @@ impl pallet_bridge_grandpa::Config for TestRuntime {
type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
parameter_types! {
pub const MaxMessagesToPruneAtOnce: u64 = 10;
pub const TestBridgedChainId: bp_runtime::ChainId = *b"test";
pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID, TEST_LANE_ID_2];
}
/// weights of messages pallet calls we use in tests.
pub type TestWeightInfo = ();
......@@ -200,13 +195,11 @@ impl Config for TestRuntime {
type BridgedChain = BridgedChain;
type BridgedHeaderChain = BridgedChainGrandpa;
type ActiveOutboundLanes = ActiveOutboundLanes;
type OutboundPayload = TestPayload;
type InboundPayload = TestPayload;
type DeliveryPayments = TestDeliveryPayments;
type LaneId = TestLaneIdType;
type DeliveryPayments = TestDeliveryPayments;
type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments;
type OnMessagesDelivered = TestOnMessagesDelivered;
......@@ -215,13 +208,13 @@ impl Config for TestRuntime {
#[cfg(feature = "runtime-benchmarks")]
impl crate::benchmarking::Config<()> for TestRuntime {
fn bench_lane_id() -> LaneId {
TEST_LANE_ID
fn bench_lane_id() -> Self::LaneId {
test_lane_id()
}
fn prepare_message_proof(
params: crate::benchmarking::MessageProofParams,
) -> (FromBridgedChainMessagesProof<BridgedHeaderHash>, Weight) {
params: crate::benchmarking::MessageProofParams<Self::LaneId>,
) -> (FromBridgedChainMessagesProof<BridgedHeaderHash, Self::LaneId>, Weight) {
use bp_runtime::RangeInclusiveExt;
let dispatch_weight =
......@@ -236,8 +229,8 @@ impl crate::benchmarking::Config<()> for TestRuntime {
}
fn prepare_message_delivery_proof(
params: crate::benchmarking::MessageDeliveryProofParams<AccountId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
params: crate::benchmarking::MessageDeliveryProofParams<AccountId, Self::LaneId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, Self::LaneId> {
// in mock run we only care about benchmarks correctness, not the benchmark results
// => ignore size related arguments
prepare_messages_delivery_proof(params.lane, params.inbound_lane_data)
......@@ -266,14 +259,22 @@ pub const TEST_RELAYER_B: AccountId = 101;
/// Account id of additional test relayer - C.
pub const TEST_RELAYER_C: AccountId = 102;
/// Lane identifier type used for tests.
pub type TestLaneIdType = HashedLaneId;
/// Lane that we're using in tests.
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 1]);
pub fn test_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 2).unwrap()
}
/// Secondary lane that we're using in tests.
pub const TEST_LANE_ID_2: LaneId = LaneId([0, 0, 0, 2]);
/// Lane that is completely unknown to our runtime.
pub fn unknown_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 3).unwrap()
}
/// Inactive outbound lane.
pub const TEST_LANE_ID_3: LaneId = LaneId([0, 0, 0, 3]);
/// Lane that is registered, but it is closed.
pub fn closed_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 4).unwrap()
}
/// Regular message payload.
pub const REGULAR_PAYLOAD: TestPayload = message_payload(0, 50);
......@@ -318,11 +319,11 @@ impl TestDeliveryConfirmationPayments {
}
}
impl DeliveryConfirmationPayments<AccountId> for TestDeliveryConfirmationPayments {
impl DeliveryConfirmationPayments<AccountId, TestLaneIdType> for TestDeliveryConfirmationPayments {
type Error = &'static str;
fn pay_reward(
_lane_id: LaneId,
_lane_id: TestLaneIdType,
messages_relayers: VecDeque<UnrewardedRelayer<AccountId>>,
_confirmation_relayer: &AccountId,
received_range: &RangeInclusive<MessageNonce>,
......@@ -343,22 +344,33 @@ impl DeliveryConfirmationPayments<AccountId> for TestDeliveryConfirmationPayment
pub struct TestMessageDispatch;
impl TestMessageDispatch {
pub fn deactivate() {
frame_support::storage::unhashed::put(b"TestMessageDispatch.IsCongested", &true)
pub fn deactivate(lane: TestLaneIdType) {
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1;
for _ in 1..=latest_received_nonce {
Self::emulate_enqueued_message(lane);
}
}
pub fn emulate_enqueued_message(lane: TestLaneIdType) {
let key = (b"dispatched", lane).encode();
let dispatched = frame_support::storage::unhashed::get_or_default::<MessageNonce>(&key[..]);
frame_support::storage::unhashed::put(&key[..], &(dispatched + 1));
}
}
impl MessageDispatch for TestMessageDispatch {
type DispatchPayload = TestPayload;
type DispatchLevelResult = TestDispatchLevelResult;
type LaneId = TestLaneIdType;
fn is_active() -> bool {
!frame_support::storage::unhashed::get_or_default::<bool>(
b"TestMessageDispatch.IsCongested",
)
fn is_active(lane: Self::LaneId) -> bool {
frame_support::storage::unhashed::get_or_default::<MessageNonce>(
&(b"dispatched", lane).encode()[..],
) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
}
fn dispatch_weight(message: &mut DispatchMessage<TestPayload>) -> Weight {
fn dispatch_weight(message: &mut DispatchMessage<TestPayload, Self::LaneId>) -> Weight {
match message.data.payload.as_ref() {
Ok(payload) => payload.declared_weight,
Err(_) => Weight::zero(),
......@@ -366,10 +378,13 @@ impl MessageDispatch for TestMessageDispatch {
}
fn dispatch(
message: DispatchMessage<TestPayload>,
message: DispatchMessage<TestPayload, Self::LaneId>,
) -> MessageDispatchResult<TestDispatchLevelResult> {
match message.data.payload.as_ref() {
Ok(payload) => payload.dispatch_result.clone(),
Ok(payload) => {
Self::emulate_enqueued_message(message.key.lane_id);
payload.dispatch_result.clone()
},
Err(_) => dispatch_result(0),
}
}
......@@ -379,13 +394,13 @@ impl MessageDispatch for TestMessageDispatch {
pub struct TestOnMessagesDelivered;
impl TestOnMessagesDelivered {
pub fn call_arguments() -> Option<(LaneId, MessageNonce)> {
pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> {
frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered")
}
}
impl OnMessagesDelivered for TestOnMessagesDelivered {
fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) {
impl OnMessagesDelivered<TestLaneIdType> for TestOnMessagesDelivered {
fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) {
frame_support::storage::unhashed::put(
b"TestOnMessagesDelivered.OnMessagesDelivered",
&(lane, enqueued_messages),
......@@ -394,8 +409,8 @@ impl OnMessagesDelivered for TestOnMessagesDelivered {
}
/// Return test lane message with given nonce and payload.
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message {
Message { key: MessageKey { lane_id: TEST_LANE_ID, nonce }, payload: payload.encode() }
pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message<TestLaneIdType> {
Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() }
}
/// Return valid outbound message data, constructed from given payload.
......@@ -438,23 +453,38 @@ pub fn unrewarded_relayer(
}
/// Returns unrewarded relayers state at given lane.
pub fn inbound_unrewarded_relayers_state(lane: bp_messages::LaneId) -> UnrewardedRelayersState {
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).0;
pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState {
let inbound_lane_data = crate::InboundLanes::<TestRuntime, ()>::get(lane).unwrap().0;
UnrewardedRelayersState::from(&inbound_lane_data)
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<TestRuntime> { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] }
.assimilate_storage(&mut t)
.unwrap();
pallet_balances::GenesisConfig::<TestRuntime> {
balances: vec![(ENDOWED_ACCOUNT, 1_000_000)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
sp_io::TestExternalities::new(t)
}
/// Run pallet test.
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
new_test_ext().execute_with(test)
new_test_ext().execute_with(|| {
crate::InboundLanes::<TestRuntime, ()>::insert(test_lane_id(), InboundLaneData::opened());
crate::OutboundLanes::<TestRuntime, ()>::insert(test_lane_id(), OutboundLaneData::opened());
crate::InboundLanes::<TestRuntime, ()>::insert(
closed_lane_id(),
InboundLaneData { state: LaneState::Closed, ..Default::default() },
);
crate::OutboundLanes::<TestRuntime, ()>::insert(
closed_lane_id(),
OutboundLaneData { state: LaneState::Closed, ..Default::default() },
);
test()
})
}
/// Prepare valid storage proof for given messages and insert appropriate header to the
......@@ -463,24 +493,25 @@ pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
/// Since this function changes the runtime storage, you can't "inline" it in the
/// `asset_noop` macro calls.
pub fn prepare_messages_proof(
messages: Vec<Message>,
messages: Vec<Message<TestLaneIdType>>,
outbound_lane_data: Option<OutboundLaneData>,
) -> Box<FromBridgedChainMessagesProof<BridgedHeaderHash>> {
) -> Box<FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>> {
// first - let's generate storage proof
let lane = messages.first().unwrap().key.lane_id;
let nonces_start = messages.first().unwrap().key.nonce;
let nonces_end = messages.last().unwrap().key.nonce;
let (storage_root, storage_proof) = prepare_messages_storage_proof::<BridgedChain, ThisChain>(
TEST_LANE_ID,
nonces_start..=nonces_end,
outbound_lane_data,
UnverifiedStorageProofParams::default(),
|nonce| messages[(nonce - nonces_start) as usize].payload.clone(),
encode_all_messages,
encode_lane_data,
false,
false,
);
let (storage_root, storage_proof) =
prepare_messages_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
lane,
nonces_start..=nonces_end,
outbound_lane_data,
UnverifiedStorageProofParams::default(),
|nonce| messages[(nonce - nonces_start) as usize].payload.clone(),
encode_all_messages,
encode_lane_data,
false,
false,
);
// let's now insert bridged chain header into the storage
let bridged_header_hash = Default::default();
......@@ -489,7 +520,7 @@ pub fn prepare_messages_proof(
StoredHeaderData { number: 0, state_root: storage_root },
);
Box::new(FromBridgedChainMessagesProof::<BridgedHeaderHash> {
Box::new(FromBridgedChainMessagesProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
......@@ -504,12 +535,12 @@ pub fn prepare_messages_proof(
/// Since this function changes the runtime storage, you can't "inline" it in the
/// `asset_noop` macro calls.
pub fn prepare_messages_delivery_proof(
lane: LaneId,
lane: TestLaneIdType,
inbound_lane_data: InboundLaneData<AccountId>,
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
) -> FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType> {
// first - let's generate storage proof
let (storage_root, storage_proof) =
prepare_message_delivery_storage_proof::<BridgedChain, ThisChain>(
prepare_message_delivery_storage_proof::<BridgedChain, ThisChain, TestLaneIdType>(
lane,
inbound_lane_data,
UnverifiedStorageProofParams::default(),
......@@ -522,7 +553,7 @@ pub fn prepare_messages_delivery_proof(
StoredHeaderData { number: 0, state_root: storage_root },
);
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash> {
FromBridgedChainMessagesDeliveryProof::<BridgedHeaderHash, TestLaneIdType> {
bridged_header_hash,
storage_proof,
lane,
......
......@@ -17,20 +17,20 @@
//! Pallet-level tests.
use crate::{
outbound_lane,
active_outbound_lane,
lanes_manager::RuntimeInboundLaneStorage,
outbound_lane::ReceptionConfirmationError,
tests::mock::{self, RuntimeEvent as TestEvent, *},
tests::mock::{RuntimeEvent as TestEvent, *},
weights_ext::WeightInfoExt,
Call, Config, Error, Event, InboundLanes, MaybeOutboundLanesCount, OutboundLanes,
OutboundMessages, Pallet, PalletOperatingMode, PalletOwner, RuntimeInboundLaneStorage,
StoredInboundLaneData,
Call, Config, Error, Event, InboundLanes, LanesManagerError, OutboundLanes, OutboundMessages,
Pallet, PalletOperatingMode, PalletOwner, StoredInboundLaneData,
};
use bp_messages::{
source_chain::{FromBridgedChainMessagesDeliveryProof, MessagesBridge},
target_chain::FromBridgedChainMessagesProof,
target_chain::{FromBridgedChainMessagesProof, MessageDispatch},
BridgeMessagesCall, ChainWithMessages, DeliveredMessages, InboundLaneData,
InboundMessageDetails, LaneId, MessageKey, MessageNonce, MessagesOperatingMode,
InboundMessageDetails, LaneIdType, LaneState, MessageKey, MessageNonce, MessagesOperatingMode,
OutboundLaneData, OutboundMessageDetails, UnrewardedRelayer, UnrewardedRelayersState,
VerificationError,
};
......@@ -38,25 +38,23 @@ use bp_runtime::{BasicOperatingMode, PreComputedSize, RangeInclusiveExt, Size};
use bp_test_utils::generate_owned_bridge_module_tests;
use codec::Encode;
use frame_support::{
assert_noop, assert_ok,
assert_err, assert_noop, assert_ok,
dispatch::Pays,
storage::generator::{StorageMap, StorageValue},
traits::Hooks,
weights::Weight,
};
use frame_system::{EventRecord, Pallet as System, Phase};
use sp_core::Get;
use sp_runtime::DispatchError;
use sp_runtime::{BoundedVec, DispatchError};
fn get_ready_for_events() {
System::<TestRuntime>::set_block_number(1);
System::<TestRuntime>::reset_events();
}
fn send_regular_message(lane_id: LaneId) {
fn send_regular_message(lane_id: TestLaneIdType) {
get_ready_for_events();
let outbound_lane = outbound_lane::<TestRuntime, ()>(lane_id);
let outbound_lane = active_outbound_lane::<TestRuntime, ()>(lane_id).unwrap();
let message_nonce = outbound_lane.data().latest_generated_nonce + 1;
let prev_enqueued_messages = outbound_lane.data().queued_messages().saturating_len();
let valid_message = Pallet::<TestRuntime, ()>::validate_message(lane_id, &REGULAR_PAYLOAD)
......@@ -69,7 +67,10 @@ fn send_regular_message(lane_id: LaneId) {
System::<TestRuntime>::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: TestEvent::Messages(Event::MessageAccepted { lane_id, nonce: message_nonce }),
event: TestEvent::Messages(Event::MessageAccepted {
lane_id: lane_id.into(),
nonce: message_nonce
}),
topics: vec![],
}],
);
......@@ -82,8 +83,9 @@ fn receive_messages_delivery_proof() {
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1),
prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 1,
relayers: vec![UnrewardedRelayer {
relayer: 0,
......@@ -99,13 +101,14 @@ fn receive_messages_delivery_proof() {
last_delivered_nonce: 1,
},
));
assert_ok!(Pallet::<TestRuntime>::do_try_state());
assert_eq!(
System::<TestRuntime>::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: TestEvent::Messages(Event::MessagesDelivered {
lane_id: TEST_LANE_ID,
lane_id: test_lane_id().into(),
messages: DeliveredMessages::new(1),
}),
topics: vec![],
......@@ -117,14 +120,14 @@ fn receive_messages_delivery_proof() {
fn pallet_rejects_transactions_if_halted() {
run_test(|| {
// send message first to be able to check that delivery_proof fails later
send_regular_message(TEST_LANE_ID);
send_regular_message(test_lane_id());
PalletOperatingMode::<TestRuntime, ()>::put(MessagesOperatingMode::Basic(
BasicOperatingMode::Halted,
));
assert_noop!(
Pallet::<TestRuntime, ()>::validate_message(TEST_LANE_ID, &REGULAR_PAYLOAD),
Pallet::<TestRuntime, ()>::validate_message(test_lane_id(), &REGULAR_PAYLOAD),
Error::<TestRuntime, ()>::NotOperatingNormally,
);
......@@ -141,8 +144,9 @@ fn pallet_rejects_transactions_if_halted() {
);
let delivery_proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 1,
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(),
},
......@@ -160,13 +164,14 @@ fn pallet_rejects_transactions_if_halted() {
),
Error::<TestRuntime, ()>::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted),
);
assert_ok!(Pallet::<TestRuntime>::do_try_state());
});
}
#[test]
fn receive_messages_fails_if_dispatcher_is_inactive() {
run_test(|| {
TestMessageDispatch::deactivate();
TestMessageDispatch::deactivate(test_lane_id());
let proof = prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None);
assert_noop!(
Pallet::<TestRuntime>::receive_messages_proof(
......@@ -176,7 +181,7 @@ fn receive_messages_fails_if_dispatcher_is_inactive() {
1,
REGULAR_PAYLOAD.declared_weight,
),
Error::<TestRuntime, ()>::MessageDispatchInactive,
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::LaneDispatcherInactive),
);
});
}
......@@ -185,14 +190,14 @@ fn receive_messages_fails_if_dispatcher_is_inactive() {
fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() {
run_test(|| {
// send message first to be able to check that delivery_proof fails later
send_regular_message(TEST_LANE_ID);
send_regular_message(test_lane_id());
PalletOperatingMode::<TestRuntime, ()>::put(
MessagesOperatingMode::RejectingOutboundMessages,
);
assert_noop!(
Pallet::<TestRuntime, ()>::validate_message(TEST_LANE_ID, &REGULAR_PAYLOAD),
Pallet::<TestRuntime, ()>::validate_message(test_lane_id(), &REGULAR_PAYLOAD),
Error::<TestRuntime, ()>::NotOperatingNormally,
);
......@@ -207,8 +212,9 @@ fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() {
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1),
prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 1,
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(),
},
......@@ -220,13 +226,14 @@ fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() {
last_delivered_nonce: 1,
},
));
assert_ok!(Pallet::<TestRuntime>::do_try_state());
});
}
#[test]
fn send_message_works() {
run_test(|| {
send_regular_message(TEST_LANE_ID);
send_regular_message(test_lane_id());
});
}
......@@ -241,7 +248,7 @@ fn send_message_rejects_too_large_message() {
.extra
.extend_from_slice(&vec![0u8; max_outbound_payload_size as usize]);
assert_noop!(
Pallet::<TestRuntime, ()>::validate_message(TEST_LANE_ID, &message_payload.clone(),),
Pallet::<TestRuntime, ()>::validate_message(test_lane_id(), &message_payload.clone(),),
Error::<TestRuntime, ()>::MessageRejectedByPallet(VerificationError::MessageTooLarge),
);
......@@ -252,7 +259,7 @@ fn send_message_rejects_too_large_message() {
assert_eq!(message_payload.encoded_size() as u32, max_outbound_payload_size);
let valid_message =
Pallet::<TestRuntime, ()>::validate_message(TEST_LANE_ID, &message_payload)
Pallet::<TestRuntime, ()>::validate_message(test_lane_id(), &message_payload)
.expect("validate_message has failed");
Pallet::<TestRuntime, ()>::send_message(valid_message);
})
......@@ -269,7 +276,13 @@ fn receive_messages_proof_works() {
REGULAR_PAYLOAD.declared_weight,
));
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).0.last_delivered_nonce(), 1);
assert_eq!(
InboundLanes::<TestRuntime>::get(test_lane_id())
.unwrap()
.0
.last_delivered_nonce(),
1
);
assert!(TestDeliveryPayments::is_reward_paid(1));
});
......@@ -280,8 +293,9 @@ fn receive_messages_proof_updates_confirmed_message_nonce() {
run_test(|| {
// say we have received 10 messages && last confirmed message is 8
InboundLanes::<TestRuntime, ()>::insert(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 8,
relayers: vec![
unrewarded_relayer(9, 9, TEST_RELAYER_A),
......@@ -291,7 +305,7 @@ fn receive_messages_proof_updates_confirmed_message_nonce() {
},
);
assert_eq!(
inbound_unrewarded_relayers_state(TEST_LANE_ID),
inbound_unrewarded_relayers_state(test_lane_id()),
UnrewardedRelayersState {
unrewarded_relayer_entries: 2,
messages_in_oldest_entry: 1,
......@@ -313,8 +327,9 @@ fn receive_messages_proof_updates_confirmed_message_nonce() {
));
assert_eq!(
InboundLanes::<TestRuntime>::get(TEST_LANE_ID).0,
InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap().0,
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 9,
relayers: vec![
unrewarded_relayer(10, 10, TEST_RELAYER_B),
......@@ -324,7 +339,7 @@ fn receive_messages_proof_updates_confirmed_message_nonce() {
},
);
assert_eq!(
inbound_unrewarded_relayers_state(TEST_LANE_ID),
inbound_unrewarded_relayers_state(test_lane_id()),
UnrewardedRelayersState {
unrewarded_relayer_entries: 2,
messages_in_oldest_entry: 1,
......@@ -335,6 +350,86 @@ fn receive_messages_proof_updates_confirmed_message_nonce() {
});
}
#[test]
fn receive_messages_proof_fails_when_dispatcher_is_inactive() {
run_test(|| {
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1;
for _ in 1..=latest_received_nonce {
TestMessageDispatch::emulate_enqueued_message(test_lane_id());
}
assert!(!TestMessageDispatch::is_active(test_lane_id()));
InboundLanes::<TestRuntime, ()>::insert(
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: latest_received_nonce,
relayers: vec![].into(),
},
);
// try to delvier next message - it should fail because dispatcher is in "suspended" state
// at the beginning of the call
let messages_proof =
prepare_messages_proof(vec![message(latest_received_nonce + 1, REGULAR_PAYLOAD)], None);
assert_noop!(
Pallet::<TestRuntime>::receive_messages_proof(
RuntimeOrigin::signed(1),
TEST_RELAYER_A,
messages_proof,
1,
REGULAR_PAYLOAD.declared_weight,
),
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::LaneDispatcherInactive)
);
assert!(!TestMessageDispatch::is_active(test_lane_id()));
});
}
#[test]
fn receive_messages_succeeds_when_dispatcher_becomes_inactive_in_the_middle_of_transaction() {
run_test(|| {
// "enqueue" enough (to deactivate dispatcher) messages at dispatcher
let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX / 2;
for _ in 1..=latest_received_nonce {
TestMessageDispatch::emulate_enqueued_message(test_lane_id());
}
assert!(TestMessageDispatch::is_active(test_lane_id()));
InboundLanes::<TestRuntime, ()>::insert(
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: latest_received_nonce,
relayers: vec![].into(),
},
);
// try to delvier next `BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` messages
// - it will lead to dispatcher deactivation, but the transaction shall not fail and all
// messages must be delivered
let messages_begin = latest_received_nonce + 1;
let messages_end =
messages_begin + BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
let messages_range = messages_begin..messages_end;
let messages_count = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
assert_ok!(Pallet::<TestRuntime>::receive_messages_proof(
RuntimeOrigin::signed(1),
TEST_RELAYER_A,
prepare_messages_proof(
messages_range.map(|nonce| message(nonce, REGULAR_PAYLOAD)).collect(),
None,
),
messages_count as _,
REGULAR_PAYLOAD.declared_weight * messages_count,
),);
assert_eq!(
inbound_unrewarded_relayers_state(test_lane_id()).last_delivered_nonce,
messages_end - 1,
);
assert!(!TestMessageDispatch::is_active(test_lane_id()));
});
}
#[test]
fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enough() {
run_test(|| {
......@@ -352,7 +447,10 @@ fn receive_messages_proof_does_not_accept_message_if_dispatch_weight_is_not_enou
),
Error::<TestRuntime, ()>::InsufficientDispatchWeight
);
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 0);
assert_eq!(
InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap().last_delivered_nonce(),
0
);
});
}
......@@ -395,22 +493,64 @@ fn receive_messages_proof_rejects_proof_with_too_many_messages() {
#[test]
fn receive_messages_delivery_proof_works() {
run_test(|| {
send_regular_message(TEST_LANE_ID);
assert_eq!(
OutboundLanes::<TestRuntime, ()>::get(test_lane_id())
.unwrap()
.latest_received_nonce,
0,
);
assert_eq!(
OutboundLanes::<TestRuntime, ()>::get(test_lane_id())
.unwrap()
.oldest_unpruned_nonce,
1,
);
send_regular_message(test_lane_id());
receive_messages_delivery_proof();
assert_eq!(OutboundLanes::<TestRuntime, ()>::get(TEST_LANE_ID).latest_received_nonce, 1,);
assert_eq!(
OutboundLanes::<TestRuntime, ()>::get(test_lane_id())
.unwrap()
.latest_received_nonce,
1,
);
assert_eq!(
OutboundLanes::<TestRuntime, ()>::get(test_lane_id())
.unwrap()
.oldest_unpruned_nonce,
2,
);
});
}
#[test]
fn receive_messages_delivery_proof_works_on_closed_outbound_lanes() {
run_test(|| {
send_regular_message(test_lane_id());
active_outbound_lane::<TestRuntime, ()>(test_lane_id())
.unwrap()
.set_state(LaneState::Closed);
receive_messages_delivery_proof();
assert_eq!(
OutboundLanes::<TestRuntime, ()>::get(test_lane_id())
.unwrap()
.latest_received_nonce,
1,
);
});
}
#[test]
fn receive_messages_delivery_proof_rewards_relayers() {
run_test(|| {
send_regular_message(TEST_LANE_ID);
send_regular_message(TEST_LANE_ID);
send_regular_message(test_lane_id());
send_regular_message(test_lane_id());
// this reports delivery of message 1 => reward is paid to TEST_RELAYER_A
let single_message_delivery_proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(),
..Default::default()
......@@ -428,6 +568,7 @@ fn receive_messages_delivery_proof_rewards_relayers() {
},
);
assert_ok!(result);
assert_ok!(Pallet::<TestRuntime>::do_try_state());
assert_eq!(
result.unwrap().actual_weight.unwrap(),
TestWeightInfo::receive_messages_delivery_proof_weight(
......@@ -445,7 +586,7 @@ fn receive_messages_delivery_proof_rewards_relayers() {
// this reports delivery of both message 1 and message 2 => reward is paid only to
// TEST_RELAYER_B
let two_messages_delivery_proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
relayers: vec![
unrewarded_relayer(1, 1, TEST_RELAYER_A),
......@@ -467,6 +608,7 @@ fn receive_messages_delivery_proof_rewards_relayers() {
},
);
assert_ok!(result);
assert_ok!(Pallet::<TestRuntime>::do_try_state());
// even though the pre-dispatch weight was for two messages, the actual weight is
// for single message only
assert_eq!(
......@@ -482,15 +624,15 @@ fn receive_messages_delivery_proof_rewards_relayers() {
);
assert!(!TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_A, 1));
assert!(TestDeliveryConfirmationPayments::is_reward_paid(TEST_RELAYER_B, 1));
assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((TEST_LANE_ID, 0)));
assert_eq!(TestOnMessagesDelivered::call_arguments(), Some((test_lane_id(), 0)));
});
}
#[test]
fn receive_messages_delivery_proof_rejects_invalid_proof() {
run_test(|| {
let mut proof = prepare_messages_delivery_proof(TEST_LANE_ID, Default::default());
proof.lane = bp_messages::LaneId([42, 42, 42, 42]);
let mut proof = prepare_messages_delivery_proof(test_lane_id(), Default::default());
proof.lane = TestLaneIdType::try_new(42, 84).unwrap();
assert_noop!(
Pallet::<TestRuntime>::receive_messages_delivery_proof(
......@@ -508,7 +650,7 @@ fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_i
run_test(|| {
// when number of relayers entries is invalid
let proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
relayers: vec![
unrewarded_relayer(1, 1, TEST_RELAYER_A),
......@@ -534,7 +676,7 @@ fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_i
// when number of messages is invalid
let proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
relayers: vec![
unrewarded_relayer(1, 1, TEST_RELAYER_A),
......@@ -560,7 +702,7 @@ fn receive_messages_delivery_proof_rejects_proof_if_declared_relayers_state_is_i
// when last delivered nonce is invalid
let proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
relayers: vec![
unrewarded_relayer(1, 1, TEST_RELAYER_A),
......@@ -601,7 +743,10 @@ fn receive_messages_accepts_single_message_with_invalid_payload() {
* improperly encoded) */
),);
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 1,);
assert_eq!(
InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap().last_delivered_nonce(),
1,
);
});
}
......@@ -622,7 +767,10 @@ fn receive_messages_accepts_batch_with_message_with_invalid_payload() {
REGULAR_PAYLOAD.declared_weight + REGULAR_PAYLOAD.declared_weight,
),);
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 3,);
assert_eq!(
InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap().last_delivered_nonce(),
3,
);
});
}
......@@ -645,7 +793,10 @@ fn actual_dispatch_weight_does_not_overflow() {
),
Error::<TestRuntime, ()>::InsufficientDispatchWeight
);
assert_eq!(InboundLanes::<TestRuntime>::get(TEST_LANE_ID).last_delivered_nonce(), 0);
assert_eq!(
InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap().last_delivered_nonce(),
0
);
});
}
......@@ -722,8 +873,9 @@ fn proof_size_refund_from_receive_messages_proof_works() {
REGULAR_PAYLOAD.declared_weight,
);
InboundLanes::<TestRuntime>::insert(
TEST_LANE_ID,
test_lane_id(),
StoredInboundLaneData(InboundLaneData {
state: LaneState::Opened,
relayers: vec![
UnrewardedRelayer {
relayer: 42,
......@@ -750,8 +902,9 @@ fn proof_size_refund_from_receive_messages_proof_works() {
// if count of unrewarded relayer entries is less than maximal, then some `proof_size`
// must be refunded
InboundLanes::<TestRuntime>::insert(
TEST_LANE_ID,
test_lane_id(),
StoredInboundLaneData(InboundLaneData {
state: LaneState::Opened,
relayers: vec![
UnrewardedRelayer {
relayer: 42,
......@@ -787,7 +940,7 @@ fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messa
{
run_test(|| {
// send message first to be able to check that delivery_proof fails later
send_regular_message(TEST_LANE_ID);
send_regular_message(test_lane_id());
// 1) InboundLaneData declares that the `last_confirmed_nonce` is 1;
// 2) InboundLaneData has no entries => `InboundLaneData::last_delivered_nonce()` returns
......@@ -796,8 +949,12 @@ fn receive_messages_delivery_proof_rejects_proof_if_trying_to_confirm_more_messa
// 4) so the number of declared messages (see `UnrewardedRelayersState`) is `0` and numer of
// actually confirmed messages is `1`.
let proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
InboundLaneData { last_confirmed_nonce: 1, relayers: Default::default() },
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 1,
relayers: Default::default(),
},
);
assert_noop!(
Pallet::<TestRuntime>::receive_messages_delivery_proof(
......@@ -821,20 +978,20 @@ fn storage_keys_computed_properly() {
assert_eq!(
OutboundMessages::<TestRuntime>::storage_map_final_key(MessageKey {
lane_id: TEST_LANE_ID,
lane_id: test_lane_id(),
nonce: 42
}),
bp_messages::storage_keys::message_key("Messages", &TEST_LANE_ID, 42).0,
bp_messages::storage_keys::message_key("Messages", &test_lane_id(), 42).0,
);
assert_eq!(
OutboundLanes::<TestRuntime>::storage_map_final_key(TEST_LANE_ID),
bp_messages::storage_keys::outbound_lane_data_key("Messages", &TEST_LANE_ID).0,
OutboundLanes::<TestRuntime>::storage_map_final_key(test_lane_id()),
bp_messages::storage_keys::outbound_lane_data_key("Messages", &test_lane_id()).0,
);
assert_eq!(
InboundLanes::<TestRuntime>::storage_map_final_key(TEST_LANE_ID),
bp_messages::storage_keys::inbound_lane_data_key("Messages", &TEST_LANE_ID).0,
InboundLanes::<TestRuntime>::storage_map_final_key(test_lane_id()),
bp_messages::storage_keys::inbound_lane_data_key("Messages", &test_lane_id()).0,
);
}
......@@ -843,7 +1000,7 @@ fn inbound_message_details_works() {
run_test(|| {
assert_eq!(
Pallet::<TestRuntime>::inbound_message_data(
TEST_LANE_ID,
test_lane_id(),
REGULAR_PAYLOAD.encode(),
OutboundMessageDetails { nonce: 0, dispatch_weight: Weight::zero(), size: 0 },
),
......@@ -852,147 +1009,15 @@ fn inbound_message_details_works() {
});
}
#[test]
fn on_idle_callback_respects_remaining_weight() {
run_test(|| {
send_regular_message(TEST_LANE_ID);
send_regular_message(TEST_LANE_ID);
send_regular_message(TEST_LANE_ID);
send_regular_message(TEST_LANE_ID);
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1),
prepare_messages_delivery_proof(
TEST_LANE_ID,
InboundLaneData {
last_confirmed_nonce: 4,
relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)].into(),
},
),
UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 4,
total_messages: 4,
last_delivered_nonce: 4,
},
));
// all 4 messages may be pruned now
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().latest_received_nonce, 4);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 1);
System::<TestRuntime>::set_block_number(2);
// if passed wight is too low to do anything
let dbw = DbWeight::get();
assert_eq!(Pallet::<TestRuntime, ()>::on_idle(0, dbw.reads_writes(1, 1)), Weight::zero(),);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 1);
// if passed wight is enough to prune single message
assert_eq!(
Pallet::<TestRuntime, ()>::on_idle(0, dbw.reads_writes(1, 2)),
dbw.reads_writes(1, 2),
);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 2);
// if passed wight is enough to prune two more messages
assert_eq!(
Pallet::<TestRuntime, ()>::on_idle(0, dbw.reads_writes(1, 3)),
dbw.reads_writes(1, 3),
);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 4);
// if passed wight is enough to prune many messages
assert_eq!(
Pallet::<TestRuntime, ()>::on_idle(0, dbw.reads_writes(100, 100)),
dbw.reads_writes(1, 2),
);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 5);
});
}
#[test]
fn on_idle_callback_is_rotating_lanes_to_prune() {
run_test(|| {
// send + receive confirmation for lane 1
send_regular_message(TEST_LANE_ID);
receive_messages_delivery_proof();
// send + receive confirmation for lane 2
send_regular_message(TEST_LANE_ID_2);
assert_ok!(Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1),
prepare_messages_delivery_proof(
TEST_LANE_ID_2,
InboundLaneData {
last_confirmed_nonce: 1,
relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(),
},
),
UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1,
last_delivered_nonce: 1,
},
));
// nothing is pruned yet
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().latest_received_nonce, 1);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 1);
assert_eq!(
outbound_lane::<TestRuntime, ()>(TEST_LANE_ID_2).data().latest_received_nonce,
1
);
assert_eq!(
outbound_lane::<TestRuntime, ()>(TEST_LANE_ID_2).data().oldest_unpruned_nonce,
1
);
// in block#2.on_idle lane messages of lane 1 are pruned
let dbw = DbWeight::get();
System::<TestRuntime>::set_block_number(2);
assert_eq!(
Pallet::<TestRuntime, ()>::on_idle(0, dbw.reads_writes(100, 100)),
dbw.reads_writes(1, 2),
);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 2);
assert_eq!(
outbound_lane::<TestRuntime, ()>(TEST_LANE_ID_2).data().oldest_unpruned_nonce,
1
);
// in block#3.on_idle lane messages of lane 2 are pruned
System::<TestRuntime>::set_block_number(3);
assert_eq!(
Pallet::<TestRuntime, ()>::on_idle(0, dbw.reads_writes(100, 100)),
dbw.reads_writes(1, 2),
);
assert_eq!(outbound_lane::<TestRuntime, ()>(TEST_LANE_ID).data().oldest_unpruned_nonce, 2);
assert_eq!(
outbound_lane::<TestRuntime, ()>(TEST_LANE_ID_2).data().oldest_unpruned_nonce,
2
);
});
}
#[test]
fn outbound_message_from_unconfigured_lane_is_rejected() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime, ()>::validate_message(TEST_LANE_ID_3, &REGULAR_PAYLOAD,),
Error::<TestRuntime, ()>::InactiveOutboundLane,
);
});
}
#[test]
fn test_bridge_messages_call_is_correctly_defined() {
run_test(|| {
let account_id = 1;
let message_proof = prepare_messages_proof(vec![message(1, REGULAR_PAYLOAD)], None);
let message_delivery_proof = prepare_messages_delivery_proof(
TEST_LANE_ID,
test_lane_id(),
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 1,
relayers: vec![UnrewardedRelayer {
relayer: 0,
......@@ -1016,8 +1041,8 @@ fn test_bridge_messages_call_is_correctly_defined() {
};
let indirect_receive_messages_proof_call = BridgeMessagesCall::<
AccountId,
FromBridgedChainMessagesProof<BridgedHeaderHash>,
FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash>,
FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>,
FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType>,
>::receive_messages_proof {
relayer_id_at_bridged_chain: account_id,
proof: *message_proof,
......@@ -1036,8 +1061,8 @@ fn test_bridge_messages_call_is_correctly_defined() {
};
let indirect_receive_messages_delivery_proof_call = BridgeMessagesCall::<
AccountId,
FromBridgedChainMessagesProof<BridgedHeaderHash>,
FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash>,
FromBridgedChainMessagesProof<BridgedHeaderHash, TestLaneIdType>,
FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash, TestLaneIdType>,
>::receive_messages_delivery_proof {
proof: message_delivery_proof,
relayers_state: unrewarded_relayer_state,
......@@ -1062,12 +1087,12 @@ fn inbound_storage_extra_proof_size_bytes_works() {
fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage<TestRuntime, ()> {
RuntimeInboundLaneStorage {
lane_id: Default::default(),
cached_data: Some(InboundLaneData {
lane_id: TestLaneIdType::try_new(1, 2).unwrap(),
cached_data: InboundLaneData {
state: LaneState::Opened,
relayers: vec![relayer_entry(); relayer_entries].into(),
last_confirmed_nonce: 0,
}),
_phantom: Default::default(),
},
}
}
......@@ -1092,9 +1117,116 @@ fn inbound_storage_extra_proof_size_bytes_works() {
}
#[test]
fn maybe_outbound_lanes_count_returns_correct_value() {
assert_eq!(
MaybeOutboundLanesCount::<TestRuntime, ()>::get(),
Some(mock::ActiveOutboundLanes::get().len() as u32)
);
fn send_messages_fails_if_outbound_lane_is_not_opened() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime, ()>::validate_message(unknown_lane_id(), &REGULAR_PAYLOAD),
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownOutboundLane),
);
assert_noop!(
Pallet::<TestRuntime, ()>::validate_message(closed_lane_id(), &REGULAR_PAYLOAD),
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::ClosedOutboundLane),
);
});
}
#[test]
fn receive_messages_proof_fails_if_inbound_lane_is_not_opened() {
run_test(|| {
let mut message = message(1, REGULAR_PAYLOAD);
message.key.lane_id = unknown_lane_id();
let proof = prepare_messages_proof(vec![message.clone()], None);
assert_noop!(
Pallet::<TestRuntime>::receive_messages_proof(
RuntimeOrigin::signed(1),
TEST_RELAYER_A,
proof,
1,
REGULAR_PAYLOAD.declared_weight,
),
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownInboundLane),
);
message.key.lane_id = closed_lane_id();
let proof = prepare_messages_proof(vec![message], None);
assert_noop!(
Pallet::<TestRuntime>::receive_messages_proof(
RuntimeOrigin::signed(1),
TEST_RELAYER_A,
proof,
1,
REGULAR_PAYLOAD.declared_weight,
),
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::ClosedInboundLane),
);
});
}
#[test]
fn receive_messages_delivery_proof_fails_if_outbound_lane_is_unknown() {
run_test(|| {
let make_proof = |lane: TestLaneIdType| {
prepare_messages_delivery_proof(
lane,
InboundLaneData {
state: LaneState::Opened,
last_confirmed_nonce: 1,
relayers: vec![UnrewardedRelayer {
relayer: 0,
messages: DeliveredMessages::new(1),
}]
.into(),
},
)
};
let proof = make_proof(unknown_lane_id());
assert_noop!(
Pallet::<TestRuntime>::receive_messages_delivery_proof(
RuntimeOrigin::signed(1),
proof,
UnrewardedRelayersState {
unrewarded_relayer_entries: 1,
messages_in_oldest_entry: 1,
total_messages: 1,
last_delivered_nonce: 1,
},
),
Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownOutboundLane),
);
});
}
#[test]
fn do_try_state_for_outbound_lanes_works() {
run_test(|| {
let lane_id = test_lane_id();
// setup delivered nonce 1
OutboundLanes::<TestRuntime>::insert(
lane_id,
OutboundLaneData {
state: LaneState::Opened,
oldest_unpruned_nonce: 2,
latest_received_nonce: 1,
latest_generated_nonce: 0,
},
);
// store message for nonce 1
OutboundMessages::<TestRuntime>::insert(
MessageKey { lane_id, nonce: 1 },
BoundedVec::default(),
);
assert_err!(
Pallet::<TestRuntime>::do_try_state(),
sp_runtime::TryRuntimeError::Other("Found unpruned lanes!")
);
// remove message for nonce 1
OutboundMessages::<TestRuntime>::remove(MessageKey { lane_id, nonce: 1 });
assert_ok!(Pallet::<TestRuntime>::do_try_state());
})
}
......@@ -14,10 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockHash, RelayBlockNumber};
use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockNumber};
use bp_header_chain::HeaderChain;
use bp_parachains::BestParaHeadHash;
use bp_polkadot_core::parachains::{ParaHash, ParaId};
use bp_parachains::{BestParaHeadHash, SubmitParachainHeadsInfo};
use bp_runtime::{HeaderId, OwnedBridgeModule};
use frame_support::{
dispatch::CallableCallFor,
......@@ -30,21 +29,6 @@ use sp_runtime::{
RuntimeDebug,
};
/// Info about a `SubmitParachainHeads` call which tries to update a single parachain.
#[derive(PartialEq, RuntimeDebug)]
pub struct SubmitParachainHeadsInfo {
/// Number and hash of the finalized relay block that has been used to prove parachain
/// finality.
pub at_relay_block: HeaderId<RelayBlockHash, RelayBlockNumber>,
/// Parachain identifier.
pub para_id: ParaId,
/// Hash of the bundled parachain head.
pub para_head_hash: ParaHash,
/// If `true`, then the call must be free (assuming that everything else is valid) to
/// be treated as valid.
pub is_free_execution_expected: bool,
}
/// Verified `SubmitParachainHeadsInfo`.
#[derive(PartialEq, RuntimeDebug)]
pub struct VerifiedSubmitParachainHeadsInfo {
......
......@@ -28,7 +28,10 @@ pub use weights::WeightInfo;
pub use weights_ext::WeightInfoExt;
use bp_header_chain::{HeaderChain, HeaderChainError};
use bp_parachains::{ParaInfo, ParaStoredHeaderData};
use bp_parachains::{
ParaInfo, ParaStoredHeaderData, RelayBlockHash, RelayBlockHasher, RelayBlockNumber,
SubmitParachainHeadsInfo,
};
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
use bp_runtime::{Chain, HashOf, HeaderId, HeaderIdOf, Parachain};
use frame_support::{dispatch::PostDispatchInfo, DefaultNoBound};
......@@ -61,13 +64,6 @@ mod proofs;
/// The target that will be used when publishing logs related to this pallet.
pub const LOG_TARGET: &str = "runtime::bridge-parachains";
/// Block hash of the bridged relay chain.
pub type RelayBlockHash = bp_polkadot_core::Hash;
/// Block number of the bridged relay chain.
pub type RelayBlockNumber = bp_polkadot_core::BlockNumber;
/// Hasher of the bridged relay chain.
pub type RelayBlockHasher = bp_polkadot_core::Hasher;
/// Artifacts of the parachains head update.
struct UpdateParachainHeadArtifacts {
/// New best head of the parachain.
......@@ -739,7 +735,8 @@ pub mod pallet {
/// Initial pallet owner.
pub owner: Option<T::AccountId>,
/// Dummy marker.
pub phantom: sp_std::marker::PhantomData<I>,
#[serde(skip)]
pub _phantom: sp_std::marker::PhantomData<I>,
}
#[pallet::genesis_build]
......
......@@ -16,56 +16,80 @@ log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
# Bridge dependencies
bp-header-chain = { workspace = true }
bp-messages = { workspace = true }
bp-relayers = { workspace = true }
bp-runtime = { workspace = true }
pallet-bridge-grandpa = { workspace = true }
pallet-bridge-messages = { workspace = true }
pallet-bridge-parachains = { workspace = true }
# Substrate Dependencies
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-transaction-payment = { workspace = true }
sp-arithmetic = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
[dev-dependencies]
bp-runtime = { workspace = true, default-features = true }
bp-parachains = { workspace = true }
bp-polkadot-core = { workspace = true }
bp-runtime = { workspace = true }
bp-test-utils = { workspace = true }
pallet-balances = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
pallet-utility = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[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-test-utils/std",
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"pallet-bridge-grandpa/std",
"pallet-bridge-messages/std",
"pallet-bridge-parachains/std",
"pallet-transaction-payment/std",
"pallet-utility/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/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-transaction-payment/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-bridge-grandpa/try-runtime",
"pallet-bridge-messages/try-runtime",
"pallet-bridge-parachains/try-runtime",
"pallet-transaction-payment/try-runtime",
"pallet-utility/try-runtime",
"sp-runtime/try-runtime",
]
integrity-test = []
......@@ -20,9 +20,8 @@
use crate::*;
use bp_messages::LaneId;
use bp_relayers::RewardsAccountOwner;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_benchmarking::v2::*;
use frame_support::{assert_ok, weights::Weight};
use frame_system::RawOrigin;
use sp_runtime::traits::One;
......@@ -30,102 +29,170 @@ use sp_runtime::traits::One;
const REWARD_AMOUNT: u32 = u32::MAX;
/// Pallet we're benchmarking here.
pub struct Pallet<T: Config>(crate::Pallet<T>);
pub struct Pallet<T: Config<I>, I: 'static = ()>(crate::Pallet<T, I>);
/// Trait that must be implemented by runtime.
pub trait Config: crate::Config {
pub trait Config<I: 'static = ()>: crate::Config<I> {
/// `T::Reward` to use in benchmarks.
fn bench_reward() -> Self::Reward;
/// Prepare environment for paying given reward for serving given lane.
fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Self::Reward);
fn prepare_rewards_account(
reward_kind: Self::Reward,
reward: Self::RewardBalance,
) -> Option<BeneficiaryOf<Self, I>>;
/// Give enough balance to given account.
fn deposit_account(account: Self::AccountId, balance: Self::Reward);
fn deposit_account(account: Self::AccountId, balance: Self::Balance);
}
benchmarks! {
// Benchmark `claim_rewards` call.
claim_rewards {
let lane = LaneId([0, 0, 0, 0]);
let account_params =
RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain);
fn assert_last_event<T: Config<I>, I: 'static>(
generic_event: <T as pallet::Config<I>>::RuntimeEvent,
) {
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
}
#[instance_benchmarks(
where
BeneficiaryOf<T, I>: From<<T as frame_system::Config>::AccountId>,
)]
mod benchmarks {
use super::*;
#[benchmark]
fn claim_rewards() {
let relayer: T::AccountId = whitelisted_caller();
let reward = T::Reward::from(REWARD_AMOUNT);
let reward_kind = T::bench_reward();
let reward_balance = T::RewardBalance::from(REWARD_AMOUNT);
let _ = T::prepare_rewards_account(reward_kind, reward_balance);
RelayerRewards::<T, I>::insert(&relayer, reward_kind, reward_balance);
#[extrinsic_call]
_(RawOrigin::Signed(relayer.clone()), reward_kind);
T::prepare_rewards_account(account_params, reward);
RelayerRewards::<T>::insert(&relayer, account_params, reward);
}: _(RawOrigin::Signed(relayer), account_params)
verify {
// we can't check anything here, because `PaymentProcedure` is responsible for
// payment logic, so we assume that if call has succeeded, the procedure has
// also completed successfully
assert_last_event::<T, I>(
Event::RewardPaid {
relayer: relayer.clone(),
reward_kind,
reward_balance,
beneficiary: relayer.into(),
}
.into(),
);
}
// Benchmark `register` call.
register {
#[benchmark]
fn claim_rewards_to() -> Result<(), BenchmarkError> {
let relayer: T::AccountId = whitelisted_caller();
let reward_kind = T::bench_reward();
let reward_balance = T::RewardBalance::from(REWARD_AMOUNT);
let Some(alternative_beneficiary) = T::prepare_rewards_account(reward_kind, reward_balance)
else {
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)));
};
RelayerRewards::<T, I>::insert(&relayer, reward_kind, reward_balance);
#[extrinsic_call]
_(RawOrigin::Signed(relayer.clone()), reward_kind, alternative_beneficiary.clone());
// we can't check anything here, because `PaymentProcedure` is responsible for
// payment logic, so we assume that if call has succeeded, the procedure has
// also completed successfully
assert_last_event::<T, I>(
Event::RewardPaid {
relayer: relayer.clone(),
reward_kind,
reward_balance,
beneficiary: alternative_beneficiary,
}
.into(),
);
Ok(())
}
#[benchmark]
fn register() {
let relayer: T::AccountId = whitelisted_caller();
let valid_till = frame_system::Pallet::<T>::block_number()
.saturating_add(crate::Pallet::<T>::required_registration_lease())
.saturating_add(crate::Pallet::<T, I>::required_registration_lease())
.saturating_add(One::one())
.saturating_add(One::one());
T::deposit_account(relayer.clone(), crate::Pallet::<T, I>::required_stake());
#[extrinsic_call]
_(RawOrigin::Signed(relayer.clone()), valid_till);
T::deposit_account(relayer.clone(), crate::Pallet::<T>::required_stake());
}: _(RawOrigin::Signed(relayer.clone()), valid_till)
verify {
assert!(crate::Pallet::<T>::is_registration_active(&relayer));
assert!(crate::Pallet::<T, I>::is_registration_active(&relayer));
}
// Benchmark `deregister` call.
deregister {
#[benchmark]
fn deregister() {
let relayer: T::AccountId = whitelisted_caller();
let valid_till = frame_system::Pallet::<T>::block_number()
.saturating_add(crate::Pallet::<T>::required_registration_lease())
.saturating_add(crate::Pallet::<T, I>::required_registration_lease())
.saturating_add(One::one())
.saturating_add(One::one());
T::deposit_account(relayer.clone(), crate::Pallet::<T>::required_stake());
crate::Pallet::<T>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap();
T::deposit_account(relayer.clone(), crate::Pallet::<T, I>::required_stake());
crate::Pallet::<T, I>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till)
.unwrap();
frame_system::Pallet::<T>::set_block_number(valid_till.saturating_add(One::one()));
}: _(RawOrigin::Signed(relayer.clone()))
verify {
assert!(!crate::Pallet::<T>::is_registration_active(&relayer));
#[extrinsic_call]
_(RawOrigin::Signed(relayer.clone()));
assert!(!crate::Pallet::<T, I>::is_registration_active(&relayer));
}
// Benchmark `slash_and_deregister` method of the pallet. We are adding this weight to
// the weight of message delivery call if `RefundBridgedParachainMessages` signed extension
// the weight of message delivery call if `BridgeRelayersTransactionExtension` signed extension
// is deployed at runtime level.
slash_and_deregister {
#[benchmark]
fn slash_and_deregister() {
// prepare and register relayer account
let relayer: T::AccountId = whitelisted_caller();
let valid_till = frame_system::Pallet::<T>::block_number()
.saturating_add(crate::Pallet::<T>::required_registration_lease())
.saturating_add(crate::Pallet::<T, I>::required_registration_lease())
.saturating_add(One::one())
.saturating_add(One::one());
T::deposit_account(relayer.clone(), crate::Pallet::<T>::required_stake());
crate::Pallet::<T>::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap();
T::deposit_account(relayer.clone(), crate::Pallet::<T, I>::required_stake());
assert_ok!(crate::Pallet::<T, I>::register(
RawOrigin::Signed(relayer.clone()).into(),
valid_till
));
// create slash destination account
let lane = LaneId([0, 0, 0, 0]);
let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain);
T::prepare_rewards_account(slash_destination, Zero::zero());
}: {
crate::Pallet::<T>::slash_and_deregister(&relayer, slash_destination.into())
}
verify {
assert!(!crate::Pallet::<T>::is_registration_active(&relayer));
let slash_destination: T::AccountId = whitelisted_caller();
T::deposit_account(slash_destination.clone(), Zero::zero());
#[block]
{
crate::Pallet::<T, I>::slash_and_deregister(
&relayer,
bp_relayers::ExplicitOrAccountParams::Explicit::<_, ()>(slash_destination),
);
}
assert!(!crate::Pallet::<T, I>::is_registration_active(&relayer));
}
// Benchmark `register_relayer_reward` method of the pallet. We are adding this weight to
// the weight of message delivery call if `RefundBridgedParachainMessages` signed extension
// the weight of message delivery call if `BridgeRelayersTransactionExtension` signed extension
// is deployed at runtime level.
register_relayer_reward {
let lane = LaneId([0, 0, 0, 0]);
#[benchmark]
fn register_relayer_reward() {
let reward_kind = T::bench_reward();
let relayer: T::AccountId = whitelisted_caller();
let account_params =
RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain);
}: {
crate::Pallet::<T>::register_relayer_reward(account_params, &relayer, One::one());
}
verify {
assert_eq!(RelayerRewards::<T>::get(relayer, &account_params), Some(One::one()));
#[block]
{
crate::Pallet::<T, I>::register_relayer_reward(reward_kind, &relayer, One::one());
}
assert_eq!(RelayerRewards::<T, I>::get(relayer, &reward_kind), Some(One::one()));
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime)
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime);
}
// 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/>.
//! Adapter that allows using `pallet-bridge-relayers` as a signed extension in the
//! bridge with remote GRANDPA chain.
use crate::{
extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig, LOG_TARGET,
};
use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
use bp_runtime::{Chain, StaticStrProvider};
use core::marker::PhantomData;
use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
use frame_system::Config as SystemConfig;
use pallet_bridge_grandpa::{
CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
SubmitFinalityProofHelper,
};
use pallet_bridge_messages::{
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
};
use sp_runtime::{
traits::{Dispatchable, Get},
transaction_validity::{TransactionPriority, TransactionValidityError},
Saturating,
};
/// Adapter to be used in signed extension configuration, when bridging with remote
/// chains that are using GRANDPA finality.
pub struct WithGrandpaChainExtensionConfig<
// signed extension identifier
IdProvider,
// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
// uses `BridgeGrandpaConfig<BridgeGrandpaPalletInstance>` to receive messages and
// confirmations from the remote chain.
Runtime,
// batch call unpacker
BatchCallUnpacker,
// instance of the `pallet-bridge-grandpa`, tracked by this extension
BridgeGrandpaPalletInstance,
// instance of BridgedChain `pallet-bridge-messages`, tracked by this extension
BridgeMessagesPalletInstance,
// instance of `pallet-bridge-relayers`, tracked by this extension
BridgeRelayersPalletInstance,
// message delivery transaction priority boost for every additional message
PriorityBoostPerMessage,
>(
PhantomData<(
IdProvider,
Runtime,
BatchCallUnpacker,
BridgeGrandpaPalletInstance,
BridgeMessagesPalletInstance,
BridgeRelayersPalletInstance,
PriorityBoostPerMessage,
)>,
);
impl<ID, R, BCU, GI, MI, RI, P> ExtensionConfig
for WithGrandpaChainExtensionConfig<ID, R, BCU, GI, MI, RI, P>
where
ID: StaticStrProvider,
R: BridgeRelayersConfig<RI>
+ BridgeMessagesConfig<MI, BridgedChain = pallet_bridge_grandpa::BridgedChain<R, GI>>
+ BridgeGrandpaConfig<GI>,
BCU: BatchCallUnpacker<R>,
GI: 'static,
MI: 'static,
RI: 'static,
P: Get<TransactionPriority>,
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ BridgeGrandpaCallSubtype<R, GI>
+ BridgeMessagesCallSubType<R, MI>,
{
type IdProvider = ID;
type Runtime = R;
type BridgeMessagesPalletInstance = MI;
type BridgeRelayersPalletInstance = RI;
type PriorityBoostPerMessage = P;
type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber<R, GI>;
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
fn parse_and_check_for_obsolete_call(
call: &R::RuntimeCall,
) -> Result<
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
TransactionValidityError,
> {
let calls = BCU::unpack(call, 2);
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());
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(ExtensionCallInfo::RelayFinalityAndMsgs(relay_finality_call, msgs_call)),
(1, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
_ => None,
})
}
fn check_obsolete_parsed_call(
call: &R::RuntimeCall,
) -> Result<&R::RuntimeCall, TransactionValidityError> {
call.check_obsolete_submit_finality_proof()?;
call.check_obsolete_call()?;
Ok(call)
}
fn check_call_result(
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
call_data: &mut ExtensionCallData,
relayer: &R::AccountId,
) -> bool {
verify_submit_finality_proof_succeeded::<Self, GI>(call_info, call_data, relayer) &&
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
}
}
/// If the batch call contains the GRANDPA chain state update call, verify that it
/// has been successful.
///
/// Only returns false when GRANDPA chain state update call has failed.
pub(crate) fn verify_submit_finality_proof_succeeded<C, GI>(
call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
call_data: &mut ExtensionCallData,
relayer: &<C::Runtime as SystemConfig>::AccountId,
) -> bool
where
C: ExtensionConfig,
GI: 'static,
C::Runtime: BridgeGrandpaConfig<GI>,
<C::Runtime as BridgeGrandpaConfig<GI>>::BridgedChain:
Chain<BlockNumber = C::RemoteGrandpaChainBlockNumber>,
{
let Some(finality_proof_info) = call_info.submit_finality_proof_info() else { return true };
if !SubmitFinalityProofHelper::<C::Runtime, GI>::was_successful(
finality_proof_info.block_number,
) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: LOG_TARGET,
"{}.{:?}: relayer {:?} has submitted invalid GRANDPA chain finality proof",
C::IdProvider::STR,
call_info.messages_call_info().lane_id(),
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
call_data.extra_weight.saturating_accrue(finality_proof_info.extra_weight);
call_data.extra_size.saturating_accrue(finality_proof_info.extra_size);
true
}
// 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/>.
//! Adapter that allows using `pallet-bridge-relayers` as a signed extension in the
//! bridge with any remote chain. This adapter does not refund any finality transactions.
use crate::{extension::verify_messages_call_succeeded, Config as BridgeRelayersConfig};
use bp_relayers::{ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
use bp_runtime::StaticStrProvider;
use core::marker::PhantomData;
use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
use pallet_bridge_messages::{
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
};
use sp_runtime::{
traits::{Dispatchable, Get},
transaction_validity::{TransactionPriority, TransactionValidityError},
};
/// Transaction extension that refunds a relayer for standalone messages delivery and confirmation
/// transactions. Finality transactions are not refunded.
pub struct WithMessagesExtensionConfig<
IdProvider,
Runtime,
BridgeMessagesPalletInstance,
BridgeRelayersPalletInstance,
PriorityBoostPerMessage,
>(
PhantomData<(
// signed extension identifier
IdProvider,
// runtime with `pallet-bridge-messages` pallet deployed
Runtime,
// instance of BridgedChain `pallet-bridge-messages`, tracked by this extension
BridgeMessagesPalletInstance,
// instance of `pallet-bridge-relayers`, tracked by this extension
BridgeRelayersPalletInstance,
// message delivery transaction priority boost for every additional message
PriorityBoostPerMessage,
)>,
);
impl<ID, R, MI, RI, P> ExtensionConfig for WithMessagesExtensionConfig<ID, R, MI, RI, P>
where
ID: StaticStrProvider,
R: BridgeRelayersConfig<RI> + BridgeMessagesConfig<MI>,
MI: 'static,
RI: 'static,
P: Get<TransactionPriority>,
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ BridgeMessagesCallSubType<R, MI>,
{
type IdProvider = ID;
type Runtime = R;
type BridgeMessagesPalletInstance = MI;
type BridgeRelayersPalletInstance = RI;
type PriorityBoostPerMessage = P;
type RemoteGrandpaChainBlockNumber = ();
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
fn parse_and_check_for_obsolete_call(
call: &R::RuntimeCall,
) -> Result<
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
TransactionValidityError,
> {
let call = Self::check_obsolete_parsed_call(call)?;
Ok(call.call_info().map(ExtensionCallInfo::Msgs))
}
fn check_obsolete_parsed_call(
call: &R::RuntimeCall,
) -> Result<&R::RuntimeCall, TransactionValidityError> {
call.check_obsolete_call()?;
Ok(call)
}
fn check_call_result(
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
call_data: &mut ExtensionCallData,
relayer: &R::AccountId,
) -> bool {
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
}
}
......@@ -14,307 +14,207 @@
// 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 transaction cost if the transaction is an `utility.batchAll()`
//! with calls that are: delivering new message and all necessary underlying headers
//! (parachain or relay chain).
use crate::messages_call_ext::{
CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType,
//! Signed extension, built around `pallet-bridge-relayers`. It is able to:
//!
//! - refund the cost of successful message delivery and confirmation transactions to the submitter
//! by registering corresponding reward in the pallet;
//!
//! - bump priority of messages delivery and confirmation transactions, signed by the registered
//! relayers.
use crate::{Config as RelayersConfig, Pallet as RelayersPallet, WeightInfoExt, LOG_TARGET};
use bp_messages::{ChainWithMessages, MessageNonce};
use bp_relayers::{
ExplicitOrAccountParams, ExtensionCallData, ExtensionCallInfo, ExtensionConfig,
RewardsAccountOwner, RewardsAccountParams,
};
use bp_messages::{ChainWithMessages, LaneId, MessageNonce};
use bp_relayers::{ExplicitOrAccountParams, RewardsAccountOwner, RewardsAccountParams};
use bp_runtime::{Chain, Parachain, RangeInclusiveExt, StaticStrProvider};
use codec::{Codec, Decode, Encode};
use bp_runtime::{Chain, RangeInclusiveExt, StaticStrProvider};
use codec::{Decode, DecodeWithMemTracking, Encode};
use core::{fmt::Debug, marker::PhantomData};
use frame_support::{
dispatch::{CallableCallFor, DispatchInfo, PostDispatchInfo},
traits::IsSubType,
dispatch::{DispatchInfo, PostDispatchInfo},
pallet_prelude::TransactionSource,
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 frame_system::Config as SystemConfig;
use pallet_bridge_messages::{
CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig, LaneIdOf,
};
use pallet_bridge_relayers::{
Config as RelayersConfig, Pallet as RelayersPallet, WeightInfoExt as _,
use pallet_transaction_payment::{
Config as TransactionPaymentConfig, OnChargeTransaction, Pallet as TransactionPaymentPallet,
};
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::{DispatchInfoOf, Dispatchable, Get, PostDispatchInfoOf, SignedExtension, Zero},
transaction_validity::{
TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransactionBuilder,
traits::{
AsSystemOriginSigner, DispatchInfoOf, Dispatchable, PostDispatchInfoOf,
TransactionExtension, ValidateResult, Zero,
},
DispatchResult, FixedPointOperand, RuntimeDebug,
transaction_validity::{InvalidTransaction, TransactionValidityError, ValidTransactionBuilder},
DispatchResult, 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 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 BridgedChain: Parachain;
}
/// 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 BridgedChain = Para;
}
pub use grandpa_adapter::WithGrandpaChainExtensionConfig;
pub use messages_adapter::WithMessagesExtensionConfig;
pub use parachain_adapter::WithParachainExtensionConfig;
pub use priority::*;
/// 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>;
}
mod grandpa_adapter;
mod messages_adapter;
mod parachain_adapter;
mod priority;
/// 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`.
/// Data that is crafted in `validate`, passed to `prepare` and used at `post_dispatch` method.
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct PreDispatchData<AccountId> {
pub struct PreDispatchData<
AccountId,
RemoteGrandpaChainBlockNumber: Debug,
LaneId: Clone + Copy + Debug,
> {
/// Transaction submitter (relayer) account.
relayer: AccountId,
/// Type of the call.
call_info: CallInfo,
call_info: ExtensionCallInfo<RemoteGrandpaChainBlockNumber, LaneId>,
}
/// 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
impl<AccountId, RemoteGrandpaChainBlockNumber: Debug, LaneId: Clone + Copy + Debug>
PreDispatchData<AccountId, RemoteGrandpaChainBlockNumber, LaneId>
{
/// Returns mutable reference to `finality_target` sent to the
/// `SubmitFinalityProof` call.
#[cfg(test)]
fn submit_finality_proof_info_mut(
pub 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),
) -> Option<&mut bp_header_chain::SubmitFinalityProofInfo<RemoteGrandpaChainBlockNumber>> {
match self.call_info {
ExtensionCallInfo::AllFinalityAndMsgs(ref mut info, _, _) => Some(info),
ExtensionCallInfo::RelayFinalityAndMsgs(ref mut 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> {
pub enum RelayerAccountAction<AccountId, RewardBalance, LaneId> {
/// Do nothing with relayer account.
None,
/// Reward the relayer.
Reward(AccountId, RewardsAccountParams, Reward),
Reward(AccountId, RewardsAccountParams<LaneId>, RewardBalance),
/// Slash the relayer.
Slash(AccountId, RewardsAccountParams),
Slash(AccountId, RewardsAccountParams<LaneId>),
}
/// Everything common among our refund signed extensions.
pub trait RefundSignedExtension:
'static + Clone + Codec + sp_std::fmt::Debug + Default + Eq + PartialEq + Send + Sync + TypeInfo
/// A signed extension, built around `pallet-bridge-relayers`.
///
/// It may be incorporated into runtime to refund relayers for submitting correct
/// message delivery and confirmation transactions, optionally batched with required
/// finality proofs.
///
/// (Works only with `pallet-bridge-messages` and `RewardsAccountParams` as the `Reward`)
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
DecodeWithMemTracking,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[scale_info(skip_type_params(Runtime, Config, LaneId))]
pub struct BridgeRelayersTransactionExtension<Runtime, Config>(PhantomData<(Runtime, Config)>);
impl<R, C> BridgeRelayersTransactionExtension<R, C>
where
Self: 'static + Send + Sync,
R: RelayersConfig<C::BridgeRelayersPalletInstance>
+ BridgeMessagesConfig<C::BridgeMessagesPalletInstance>
+ TransactionPaymentConfig,
C: ExtensionConfig<Runtime = R>,
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<R::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
<R as TransactionPaymentConfig>::OnChargeTransaction: OnChargeTransaction<R>,
<R as RelayersConfig<C::BridgeRelayersPalletInstance>>::Reward:
From<RewardsAccountParams<C::LaneId>>,
<R as RelayersConfig<C::BridgeRelayersPalletInstance>>::RewardBalance: From<
<<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance,
>,
C::LaneId: From<LaneIdOf<R, C::BridgeMessagesPalletInstance>>,
{
/// 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;
/// 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(
parsed_call: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
) -> Option<MessageNonce> {
// we only boost priority of message delivery transactions
if !parsed_call.is_receive_messages_proof_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 = <R as BridgeMessagesConfig<C::BridgeMessagesPalletInstance>>::BridgedChain
::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
return None
}
Some(bundled_messages)
}
/// 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>>>>,
pre: Option<PreDispatchData<R::AccountId, C::RemoteGrandpaChainBlockNumber, C::LaneId>>,
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;
) -> RelayerAccountAction<R::AccountId, R::RewardBalance, C::LaneId> {
// 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),
Some(pre) => (pre.relayer, pre.call_info),
_ => return RelayerAccountAction::None,
};
// now we know that the relayer either needs to be rewarded, or slashed
// now we know that the call is supported and we may need to reward or slash relayer
// => 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,
>>::BridgedChain::ID,
if call_info.is_receive_messages_proof_call() {
RewardsAccountOwner::ThisChain
} else {
RewardsAccountOwner::BridgedChain
},
);
let lane_id = call_info.messages_call_info().lane_id();
let reward_account_params = RewardsAccountParams::new(
lane_id,
<R as BridgeMessagesConfig<C::BridgeMessagesPalletInstance>>::BridgedChain::ID,
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:
// there are a 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 may_slash_relayer = Self::bundled_messages_for_priority_boost(&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);
......@@ -322,37 +222,19 @@ pub trait RefundSignedExtension:
// 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(),
target: LOG_TARGET,
"{}.{:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
Self::IDENTIFIER,
lane_id,
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,
) {
// check whether the call has succeeded
let mut call_data = ExtensionCallData::default();
if !C::check_call_result(&call_info, &mut call_data, &relayer) {
return slash_relayer_if_delivery_result
}
......@@ -364,654 +246,297 @@ pub trait RefundSignedExtension:
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 post_info_len = len.saturating_sub(call_data.extra_size as usize);
let mut post_info_weight = post_info
.actual_weight
.unwrap_or(info.total_weight())
.saturating_sub(call_data.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(),
<R as RelayersConfig<C::BridgeRelayersPalletInstance>>::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);
let refund = Self::compute_refund(info, &post_info, post_info_len, tip);
// we can finally reward relayer
RelayerAccountAction::Reward(relayer, reward_account_params, refund)
RelayerAccountAction::Reward(relayer, reward_account_params, refund.into())
}
/// 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,
>>::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
if bundled_messages > max_unconfirmed_messages_in_confirmation_tx {
return None
}
Some(bundled_messages)
/// Compute refund for the successful relayer transaction
fn compute_refund(
info: &DispatchInfo,
post_info: &PostDispatchInfo,
len: usize,
tip: <<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance,
) -> <<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance
{
TransactionPaymentPallet::<R>::compute_actual_fee(len as _, info, post_info, tip)
}
}
/// Adapter that allow implementing `sp_runtime::traits::SignedExtension` for any
/// `RefundSignedExtension`.
#[derive(
DefaultNoBound,
CloneNoBound,
Decode,
Encode,
EqNoBound,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
pub struct RefundSignedExtensionAdapter<T: RefundSignedExtension>(T);
impl<T: RefundSignedExtension> SignedExtension for RefundSignedExtensionAdapter<T>
impl<R, C> TransactionExtension<R::RuntimeCall> for BridgeRelayersTransactionExtension<R, C>
where
CallOf<T::Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ MessagesCallSubType<T::Runtime, <T::Msgs as RefundableMessagesLaneId>::Instance>,
Self: 'static + Send + Sync,
R: RelayersConfig<C::BridgeRelayersPalletInstance>
+ BridgeMessagesConfig<C::BridgeMessagesPalletInstance>
+ TransactionPaymentConfig,
C: ExtensionConfig<Runtime = R>,
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<R::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
<R as TransactionPaymentConfig>::OnChargeTransaction: OnChargeTransaction<R>,
<R as RelayersConfig<C::BridgeRelayersPalletInstance>>::Reward:
From<RewardsAccountParams<C::LaneId>>,
<R as RelayersConfig<C::BridgeRelayersPalletInstance>>::RewardBalance: From<
<<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance,
>,
C::LaneId: From<LaneIdOf<R, C::BridgeMessagesPalletInstance>>,
{
const IDENTIFIER: &'static str = T::Id::STR;
type AccountId = AccountIdOf<T::Runtime>;
type Call = CallOf<T::Runtime>;
type AdditionalSigned = ();
type Pre = Option<PreDispatchData<AccountIdOf<T::Runtime>>>;
const IDENTIFIER: &'static str = C::IdProvider::STR;
type Implicit = ();
type Pre = Option<PreDispatchData<R::AccountId, C::RemoteGrandpaChainBlockNumber, C::LaneId>>;
type Val = Self::Pre;
fn additional_signed(&self) -> Result<(), TransactionValidityError> {
Ok(())
fn weight(&self, _call: &R::RuntimeCall) -> Weight {
Weight::zero()
}
fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
origin: <R::RuntimeCall as Dispatchable>::RuntimeOrigin,
call: &R::RuntimeCall,
_info: &DispatchInfoOf<R::RuntimeCall>,
_len: usize,
) -> TransactionValidity {
// 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)?;
_self_implicit: Self::Implicit,
_inherited_implication: &impl Encode,
_source: TransactionSource,
) -> ValidateResult<Self::Val, R::RuntimeCall> {
// Prepare relevant data for `prepare`
let parsed_call = match C::parse_and_check_for_obsolete_call(call)? {
Some(parsed_call) => parsed_call,
None => return Ok((Default::default(), None, origin)),
};
// Those calls are only for signed transactions.
let relayer = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
let data = PreDispatchData { relayer: relayer.clone(), call_info: parsed_call };
// the following code just plays with transaction priority and never returns an error
// the following code just plays with transaction priority
// we only boost priority of presumably correct message delivery transactions
let bundled_messages = match T::bundled_messages_for_priority_boost(parsed_call.as_ref()) {
let bundled_messages = match Self::bundled_messages_for_priority_boost(&data.call_info) {
Some(bundled_messages) => bundled_messages,
None => return Ok(Default::default()),
None => return Ok((Default::default(), Some(data), origin)),
};
// we only boost priority if relayer has staked required balance
if !RelayersPallet::<T::Runtime>::is_registration_active(who) {
return Ok(Default::default())
if !RelayersPallet::<R, C::BridgeRelayersPalletInstance>::is_registration_active(
&data.relayer,
) {
return Ok((Default::default(), Some(data), origin))
}
// compute priority boost
let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::<
T::Priority,
>(bundled_messages);
let priority_boost =
priority::compute_priority_boost::<C::PriorityBoostPerMessage>(bundled_messages);
let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
log::trace!(
target: "runtime::bridge",
"{} via {:?} has boosted priority of message delivery transaction \
target: LOG_TARGET,
"{}.{:?}: has boosted priority of message delivery transaction \
of relayer {:?}: {} messages -> {} priority",
Self::IDENTIFIER,
<T::Msgs as RefundableMessagesLaneId>::Id::get(),
who,
data.call_info.messages_call_info().lane_id(),
data.relayer,
bundled_messages,
priority_boost,
);
valid_transaction.build()
let validity = valid_transaction.build()?;
Ok((validity, Some(data), origin))
}
fn pre_dispatch(
fn prepare(
self,
who: &Self::AccountId,
call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
val: Self::Val,
_origin: &<R::RuntimeCall as Dispatchable>::RuntimeOrigin,
_call: &R::RuntimeCall,
_info: &DispatchInfoOf<R::RuntimeCall>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
// this is a relevant piece of `validate` that we need here (in `pre_dispatch`)
let parsed_call = T::parse_and_check_for_obsolete_call(call)?;
Ok(parsed_call.map(|call_info| {
Ok(val.inspect(|data| {
log::trace!(
target: "runtime::bridge",
"{} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
target: LOG_TARGET,
"{}.{:?}: parsed bridge transaction in prepare: {:?}",
Self::IDENTIFIER,
<T::Msgs as RefundableMessagesLaneId>::Id::get(),
call_info,
data.call_info.messages_call_info().lane_id(),
data.call_info,
);
PreDispatchData { relayer: who.clone(), call_info }
}))
}
fn post_dispatch(
pre: Option<Self::Pre>,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
fn post_dispatch_details(
pre: Self::Pre,
info: &DispatchInfoOf<R::RuntimeCall>,
post_info: &PostDispatchInfoOf<R::RuntimeCall>,
len: usize,
result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
let call_result = T::analyze_call_result(pre, info, post_info, len, result);
) -> Result<Weight, TransactionValidityError> {
let lane_id = pre.as_ref().map(|p| p.call_info.messages_call_info().lane_id());
let call_result = Self::analyze_call_result(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,
RelayersPallet::<R, C::BridgeRelayersPalletInstance>::register_relayer_reward(
reward_account.into(),
&relayer,
reward,
);
log::trace!(
target: "runtime::bridge",
"{} via {:?} has registered reward: {:?} for {:?}",
target: LOG_TARGET,
"{}.{:?}: has registered reward: {:?} for {:?}",
Self::IDENTIFIER,
<T::Msgs as RefundableMessagesLaneId>::Id::get(),
lane_id,
reward,
relayer,
);
},
RelayerAccountAction::Slash(relayer, slash_account) =>
RelayersPallet::<T::Runtime>::slash_and_deregister(
RelayersPallet::<R, C::BridgeRelayersPalletInstance>::slash_and_deregister(
&relayer,
ExplicitOrAccountParams::Params(slash_account),
),
}
Ok(())
}
}
/// Signed 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> RefundSignedExtension
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::BridgedChain::PARACHAIN_ID));
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_grandpa_call_successful =
RefundBridgedGrandpaMessages::<
Runtime,
Runtime::BridgesGrandpaPalletInstance,
Msgs,
Refund,
Priority,
Id,
>::additional_call_result_check(relayer, call_info, extra_weight, extra_size);
if !is_grandpa_call_successful {
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::BridgedChain::PARACHAIN_ID,
Msgs::Id::get(),
relayer,
);
return false
}
}
true
}
}
/// Signed 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> RefundSignedExtension
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
Ok(Weight::zero())
}
}
/// 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> RefundSignedExtension
for RefundBridgedMessages<Runtime, Msgs, Refund, Priority, Id>
/// Verify that the messages pallet call, supported by extension has succeeded.
pub(crate) fn verify_messages_call_succeeded<C>(
call_info: &ExtensionCallInfo<
C::RemoteGrandpaChainBlockNumber,
LaneIdOf<C::Runtime, C::BridgeMessagesPalletInstance>,
>,
_call_data: &mut ExtensionCallData,
relayer: &<C::Runtime as SystemConfig>::AccountId,
) -> bool
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>,
C: ExtensionConfig,
C::Runtime: BridgeMessagesConfig<C::BridgeMessagesPalletInstance>,
{
type Runtime = Runtime;
type Msgs = Msgs;
type Refund = Refund;
type Priority = Priority;
type Id = Id;
let messages_call = call_info.messages_call_info();
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)
if !MessagesCallHelper::<C::Runtime, C::BridgeMessagesPalletInstance>::was_successful(
messages_call,
) {
log::trace!(
target: LOG_TARGET,
"{}.{:?}: relayer {:?} has submitted invalid messages call",
C::IdProvider::STR,
call_info.messages_call_info().lane_id(),
relayer,
);
return false
}
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
}
true
}
#[cfg(test)]
pub(crate) mod tests {
mod tests {
use super::*;
use crate::{
messages_call_ext::{
BaseMessagesProofInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
UnrewardedRelayerOccupation,
},
mock::*,
};
use bp_header_chain::StoredHeaderDataBuilder;
use crate::mock::*;
use bp_header_chain::{StoredHeaderDataBuilder, SubmitFinalityProofInfo};
use bp_messages::{
source_chain::FromBridgedChainMessagesDeliveryProof,
target_chain::FromBridgedChainMessagesProof, DeliveredMessages, InboundLaneData,
MessageNonce, MessagesOperatingMode, OutboundLaneData, UnrewardedRelayer,
UnrewardedRelayersState,
target_chain::FromBridgedChainMessagesProof, BaseMessagesProofInfo, DeliveredMessages,
InboundLaneData, MessageNonce, MessagesCallInfo, MessagesOperatingMode, OutboundLaneData,
ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayer,
UnrewardedRelayerOccupation, UnrewardedRelayersState,
};
use bp_parachains::{BestParaHeadHash, ParaInfo};
use bp_parachains::{BestParaHeadHash, ParaInfo, SubmitParachainHeadsInfo};
use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
use bp_runtime::{BasicOperatingMode, HeaderId};
use bp_relayers::RuntimeWithUtilityPallet;
use bp_runtime::{BasicOperatingMode, HeaderId, Parachain};
use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID};
use frame_support::{
__private::sp_tracing,
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 pallet_bridge_parachains::{Call as ParachainsCall, Pallet as ParachainsPallet};
use pallet_utility::Call as UtilityCall;
use sp_runtime::{
traits::{ConstU64, Header as HeaderT},
transaction_validity::{InvalidTransaction, ValidTransaction},
traits::{ConstU64, DispatchTransaction, Header as HeaderT},
transaction_validity::{
InvalidTransaction, TransactionSource::External, TransactionValidity, ValidTransaction,
},
DispatchError,
};
parameter_types! {
pub TestLaneId: LaneId = TEST_LANE_ID;
pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
TestParachain: u32 = BridgedUnderlyingParachain::PARACHAIN_ID;
pub MsgProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
test_lane_id(),
TEST_BRIDGED_CHAIN_ID,
RewardsAccountOwner::ThisChain,
);
pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new(
TEST_LANE_ID,
pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams<TestLaneIdType> = RewardsAccountParams::new(
test_lane_id(),
TEST_BRIDGED_CHAIN_ID,
RewardsAccountOwner::BridgedChain,
);
}
bp_runtime::generate_static_str_provider!(TestGrandpaExtension);
bp_runtime::generate_static_str_provider!(TestExtension);
bp_runtime::generate_static_str_provider!(TestMessagesExtension);
type TestMessagesExtensionProvider = RefundBridgedMessages<
type TestGrandpaExtensionConfig = grandpa_adapter::WithGrandpaChainExtensionConfig<
StrTestGrandpaExtension,
TestRuntime,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
RuntimeWithUtilityPallet<TestRuntime>,
(),
(),
(),
ConstU64<1>,
StrTestExtension,
>;
type TestMessagesExtension = RefundSignedExtensionAdapter<TestMessagesExtensionProvider>;
type TestGrandpaExtensionProvider = RefundBridgedGrandpaMessages<
type TestGrandpaExtension =
BridgeRelayersTransactionExtension<TestRuntime, TestGrandpaExtensionConfig>;
type TestExtensionConfig = parachain_adapter::WithParachainExtensionConfig<
StrTestExtension,
TestRuntime,
RuntimeWithUtilityPallet<TestRuntime>,
(),
(),
(),
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
ConstU64<1>,
StrTestExtension,
>;
type TestGrandpaExtension = RefundSignedExtensionAdapter<TestGrandpaExtensionProvider>;
type TestExtensionProvider = RefundBridgedParachainMessages<
type TestExtension = BridgeRelayersTransactionExtension<TestRuntime, TestExtensionConfig>;
type TestMessagesExtensionConfig = messages_adapter::WithMessagesExtensionConfig<
StrTestMessagesExtension,
TestRuntime,
RefundableParachain<(), BridgedUnderlyingParachain>,
RefundableMessagesLane<(), TestLaneId>,
ActualFeeRefund<TestRuntime>,
(),
(),
ConstU64<1>,
StrTestExtension,
>;
type TestExtension = RefundSignedExtensionAdapter<TestExtensionProvider>;
type TestMessagesExtension =
BridgeRelayersTransactionExtension<TestRuntime, TestMessagesExtensionConfig>;
fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
let test_stake: ThisChainBalance = TestStake::get();
let test_stake: ThisChainBalance = Stake::get();
ExistentialDeposit::get().saturating_add(test_stake * 100)
}
......@@ -1026,7 +551,7 @@ pub(crate) mod tests {
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
}
pub fn relayer_account_at_this_chain() -> ThisChainAccountId {
fn relayer_account_at_this_chain() -> ThisChainAccountId {
0
}
......@@ -1034,13 +559,13 @@ pub(crate) mod tests {
0
}
pub fn initialize_environment(
best_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
fn initialize_environment(
best_relay_header_number: BridgedChainBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
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());
let best_relay_header = HeaderId(best_relay_header_number, BridgedChainHash::default());
pallet_bridge_grandpa::CurrentAuthoritySet::<TestRuntime>::put(
StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(),
);
......@@ -1050,7 +575,7 @@ pub(crate) mod tests {
bp_test_utils::test_header::<BridgedChainHeader>(0).build(),
);
let para_id = ParaId(BridgedUnderlyingParachain::PARACHAIN_ID);
let para_id = ParaId(TestParachain::get());
let para_info = ParaInfo {
best_head_hash: BestParaHeadHash {
at_relay_block_number: parachain_head_at_relay_header_number,
......@@ -1060,7 +585,7 @@ pub(crate) mod tests {
};
pallet_bridge_parachains::ParasInfo::<TestRuntime>::insert(para_id, para_info);
let lane_id = TestLaneId::get();
let lane_id = test_lane_id();
let in_lane_data =
InboundLaneData { last_confirmed_nonce: best_message, ..Default::default() };
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
......@@ -1078,7 +603,7 @@ pub(crate) mod tests {
.unwrap();
}
fn submit_relay_header_call(relay_header_number: RelayBlockNumber) -> RuntimeCall {
fn submit_relay_header_call(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
......@@ -1094,7 +619,7 @@ pub(crate) mod tests {
})
}
pub fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall {
fn submit_relay_header_call_ex(relay_header_number: BridgedChainBlockNumber) -> RuntimeCall {
let relay_header = BridgedChainHeader::new(
relay_header_number,
Default::default(),
......@@ -1113,12 +638,12 @@ pub(crate) mod tests {
}
fn submit_parachain_head_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
parachains: vec![(
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
ParaId(TestParachain::get()),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
......@@ -1126,12 +651,12 @@ pub(crate) mod tests {
}
pub fn submit_parachain_head_call_ex(
parachain_head_at_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
) -> RuntimeCall {
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads_ex {
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
at_relay_block: (parachain_head_at_relay_header_number, BridgedChainHash::default()),
parachains: vec![(
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
ParaId(TestParachain::get()),
[parachain_head_at_relay_header_number as u8; 32].into(),
)],
parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
......@@ -1145,10 +670,11 @@ pub(crate) mod tests {
proof: Box::new(FromBridgedChainMessagesProof {
bridged_header_hash: Default::default(),
storage_proof: Default::default(),
lane: TestLaneId::get(),
lane: test_lane_id(),
nonces_start: pallet_bridge_messages::InboundLanes::<TestRuntime>::get(
TEST_LANE_ID,
test_lane_id(),
)
.unwrap()
.last_delivered_nonce() +
1,
nonces_end: best_message,
......@@ -1163,7 +689,7 @@ pub(crate) mod tests {
proof: FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: Default::default(),
storage_proof: Default::default(),
lane: TestLaneId::get(),
lane: test_lane_id(),
},
relayers_state: UnrewardedRelayersState {
last_delivered_nonce: best_message,
......@@ -1173,7 +699,7 @@ pub(crate) mod tests {
}
fn parachain_finality_and_delivery_batch_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1185,7 +711,7 @@ pub(crate) mod tests {
}
fn parachain_finality_and_confirmation_batch_call(
parachain_head_at_relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1197,7 +723,7 @@ pub(crate) mod tests {
}
fn relay_finality_and_delivery_batch_call(
relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1209,7 +735,7 @@ pub(crate) mod tests {
}
fn relay_finality_and_delivery_batch_call_ex(
relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1221,7 +747,7 @@ pub(crate) mod tests {
}
fn relay_finality_and_confirmation_batch_call(
relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1233,7 +759,7 @@ pub(crate) mod tests {
}
fn relay_finality_and_confirmation_batch_call_ex(
relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1245,8 +771,8 @@ pub(crate) mod tests {
}
fn all_finality_and_delivery_batch_call(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1259,8 +785,8 @@ pub(crate) mod tests {
}
fn all_finality_and_delivery_batch_call_ex(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1273,8 +799,8 @@ pub(crate) mod tests {
}
fn all_finality_and_confirmation_batch_call(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1287,8 +813,8 @@ pub(crate) mod tests {
}
fn all_finality_and_confirmation_batch_call_ex(
relay_header_number: RelayBlockNumber,
parachain_head_at_relay_header_number: RelayBlockNumber,
relay_header_number: BridgedChainBlockNumber,
parachain_head_at_relay_header_number: BridgedChainBlockNumber,
best_message: MessageNonce,
) -> RuntimeCall {
RuntimeCall::Utility(UtilityCall::batch_all {
......@@ -1300,10 +826,11 @@ pub(crate) mod tests {
})
}
fn all_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn all_finality_pre_dispatch_data(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::AllFinalityAndMsgs(
call_info: ExtensionCallInfo::AllFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
......@@ -1314,38 +841,40 @@ pub(crate) mod tests {
},
SubmitParachainHeadsInfo {
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
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,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots:
BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
free_message_slots:
BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
},
}),
),
}
}
fn all_finality_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
#[cfg(test)]
fn all_finality_pre_dispatch_data_ex(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
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.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
data
}
fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn all_finality_confirmation_pre_dispatch_data(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::AllFinalityAndMsgs(
call_info: ExtensionCallInfo::AllFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
......@@ -1356,13 +885,13 @@ pub(crate) mod tests {
},
SubmitParachainHeadsInfo {
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
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,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
......@@ -1371,17 +900,18 @@ pub(crate) mod tests {
}
}
fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
fn all_finality_confirmation_pre_dispatch_data_ex(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
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.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
data
}
fn relay_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn relay_finality_pre_dispatch_data(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::RelayFinalityAndMsgs(
call_info: ExtensionCallInfo::RelayFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
......@@ -1392,32 +922,33 @@ pub(crate) mod tests {
},
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots:
BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
free_message_slots:
BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
},
}),
),
}
}
fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
fn relay_finality_pre_dispatch_data_ex(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
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.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
data
}
fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn relay_finality_confirmation_pre_dispatch_data(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::RelayFinalityAndMsgs(
call_info: ExtensionCallInfo::RelayFinalityAndMsgs(
SubmitFinalityProofInfo {
block_number: 200,
current_set_id: None,
......@@ -1428,7 +959,7 @@ pub(crate) mod tests {
},
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
......@@ -1437,53 +968,55 @@ pub(crate) mod tests {
}
}
fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData<ThisChainAccountId> {
fn relay_finality_confirmation_pre_dispatch_data_ex(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
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.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID);
data
}
fn parachain_finality_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn parachain_finality_pre_dispatch_data(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
call_info: ExtensionCallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
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,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots:
BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
free_message_slots:
BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
},
}),
),
}
}
fn parachain_finality_confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn parachain_finality_confirmation_pre_dispatch_data(
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::ParachainFinalityAndMsgs(
call_info: ExtensionCallInfo::ParachainFinalityAndMsgs(
SubmitParachainHeadsInfo {
at_relay_block: HeaderId(200, [0u8; 32].into()),
para_id: ParaId(BridgedUnderlyingParachain::PARACHAIN_ID),
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,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
......@@ -1492,33 +1025,35 @@ pub(crate) mod tests {
}
}
fn delivery_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn delivery_pre_dispatch_data<RemoteGrandpaChainBlockNumber: Debug>(
) -> PreDispatchData<ThisChainAccountId, RemoteGrandpaChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof(
call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof(
ReceiveMessagesProofInfo {
base: BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
},
unrewarded_relayers: UnrewardedRelayerOccupation {
free_relayer_slots:
BridgedUnderlyingChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX,
free_message_slots:
BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
},
},
)),
}
}
fn confirmation_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
fn confirmation_pre_dispatch_data<RemoteGrandpaChainBlockNumber: Debug>(
) -> PreDispatchData<ThisChainAccountId, RemoteGrandpaChainBlockNumber, TestLaneIdType> {
PreDispatchData {
relayer: relayer_account_at_this_chain(),
call_info: CallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof(
call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof(
ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
lane_id: TEST_LANE_ID,
lane_id: test_lane_id(),
bundled_range: 101..=200,
best_stored_nonce: 100,
}),
......@@ -1527,14 +1062,18 @@ pub(crate) mod tests {
}
fn set_bundled_range_end(
mut pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
mut pre_dispatch_data: PreDispatchData<
ThisChainAccountId,
BridgedChainBlockNumber,
TestLaneIdType,
>,
end: MessageNonce,
) -> PreDispatchData<ThisChainAccountId> {
) -> PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType> {
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,
ExtensionCallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
ExtensionCallInfo::RelayFinalityAndMsgs(_, ref mut info) => info,
ExtensionCallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
ExtensionCallInfo::Msgs(ref mut info) => info,
};
if let MessagesCallInfo::ReceiveMessagesProof(ref mut msg_info) = msg_info {
......@@ -1545,21 +1084,45 @@ pub(crate) mod tests {
}
fn run_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestExtension =
RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
let extension: TestExtension = BridgeRelayersTransactionExtension(PhantomData);
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
External,
0,
)
.map(|t| t.0)
}
fn run_grandpa_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestGrandpaExtension =
RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
let extension: TestGrandpaExtension = BridgeRelayersTransactionExtension(PhantomData);
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
External,
0,
)
.map(|t| t.0)
}
fn run_messages_validate(call: RuntimeCall) -> TransactionValidity {
let extension: TestMessagesExtension =
RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
let extension: TestMessagesExtension = BridgeRelayersTransactionExtension(PhantomData);
extension
.validate_only(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
External,
0,
)
.map(|t| t.0)
}
fn ignore_priority(tx: TransactionValidity) -> TransactionValidity {
......@@ -1571,34 +1134,66 @@ pub(crate) mod tests {
fn run_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestExtension =
RefundSignedExtensionAdapter(RefundBridgedParachainMessages(PhantomData));
extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
) -> Result<
Option<PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType>>,
TransactionValidityError,
> {
sp_tracing::try_init_simple();
let extension: TestExtension = BridgeRelayersTransactionExtension(PhantomData);
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
0,
)
.map(|(pre, _)| pre)
}
fn run_grandpa_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestGrandpaExtension =
RefundSignedExtensionAdapter(RefundBridgedGrandpaMessages(PhantomData));
extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
) -> Result<
Option<PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType>>,
TransactionValidityError,
> {
let extension: TestGrandpaExtension = BridgeRelayersTransactionExtension(PhantomData);
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
0,
)
.map(|(pre, _)| pre)
}
fn run_messages_pre_dispatch(
call: RuntimeCall,
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
let extension: TestMessagesExtension =
RefundSignedExtensionAdapter(RefundBridgedMessages(PhantomData));
extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
) -> Result<
Option<PreDispatchData<ThisChainAccountId, (), TestLaneIdType>>,
TransactionValidityError,
> {
let extension: TestMessagesExtension = BridgeRelayersTransactionExtension(PhantomData);
extension
.validate_and_prepare(
Some(relayer_account_at_this_chain()).into(),
&call,
&DispatchInfo::default(),
0,
0,
)
.map(|(pre, _)| pre)
}
fn dispatch_info() -> DispatchInfo {
DispatchInfo {
weight: Weight::from_parts(
call_weight: Weight::from_parts(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
0,
),
extension_weight: Weight::zero(),
class: frame_support::dispatch::DispatchClass::Normal,
pays_fee: frame_support::dispatch::Pays::Yes,
}
......@@ -1609,24 +1204,26 @@ pub(crate) mod tests {
}
fn run_post_dispatch(
pre_dispatch_data: Option<PreDispatchData<ThisChainAccountId>>,
pre_dispatch_data: Option<
PreDispatchData<ThisChainAccountId, BridgedChainBlockNumber, TestLaneIdType>,
>,
dispatch_result: DispatchResult,
) {
let post_dispatch_result = TestExtension::post_dispatch(
Some(pre_dispatch_data),
let post_dispatch_result = TestExtension::post_dispatch_details(
pre_dispatch_data,
&dispatch_info(),
&post_dispatch_info(),
1024,
&dispatch_result,
);
assert_eq!(post_dispatch_result, Ok(()));
assert_eq!(post_dispatch_result, Ok(Weight::zero()));
}
fn expected_delivery_reward() -> ThisChainBalance {
fn expected_delivery_reward() -> RewardBalance {
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));
Some(dispatch_info().call_weight.saturating_sub(extra_weight));
pallet_transaction_payment::Pallet::<TestRuntime>::compute_actual_fee(
1024,
&dispatch_info(),
......@@ -1635,7 +1232,7 @@ pub(crate) mod tests {
)
}
fn expected_confirmation_reward() -> ThisChainBalance {
fn expected_confirmation_reward() -> RewardBalance {
pallet_transaction_payment::Pallet::<TestRuntime>::compute_actual_fee(
1024,
&dispatch_info(),
......@@ -1651,46 +1248,28 @@ pub(crate) mod tests {
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!(run_validate(message_delivery_call(200)), Ok(Default::default()),);
assert_eq!(
ignore_priority(run_validate(message_confirmation_call(200))),
run_validate(parachain_finality_and_delivery_batch_call(200, 200)),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_messages_validate(message_confirmation_call(200))),
run_validate(all_finality_and_delivery_batch_call(200, 200, 200)),
Ok(Default::default()),
);
// message confirmation validation is passing
assert_eq!(
ignore_priority(run_validate(parachain_finality_and_confirmation_batch_call(
200, 200
))),
ignore_priority(run_validate(message_confirmation_call(200))),
Ok(Default::default()),
);
assert_eq!(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
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_ex(
ignore_priority(run_validate(all_finality_and_confirmation_batch_call(
200, 200, 200
))),
Ok(Default::default()),
......@@ -1706,28 +1285,25 @@ pub(crate) mod tests {
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_delivery =
run_validate(message_delivery_call(200)).unwrap().priority;
let priority_of_200_messages_delivery =
run_validate(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
);
}
let priority_of_100_messages_confirmation =
run_validate(message_confirmation_call(200)).unwrap().priority;
let priority_of_200_messages_confirmation =
run_validate(message_confirmation_call(300)).unwrap().priority;
assert_eq!(
priority_of_100_messages_confirmation,
priority_of_200_messages_confirmation
);
});
}
......@@ -1739,26 +1315,23 @@ pub(crate) mod tests {
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 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
))
.unwrap()
.priority;
let priority_of_more_than_max_messages_delivery = f(message_delivery_call(
100 + BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1,
))
.unwrap()
.priority;
let priority_of_max_messages_delivery = run_validate(message_delivery_call(
100 + BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
))
.unwrap()
.priority;
let priority_of_more_than_max_messages_delivery = run_validate(message_delivery_call(
100 + BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 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,
);
}
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,
);
});
}
......@@ -1775,15 +1348,10 @@ pub(crate) mod tests {
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))),
......@@ -1800,24 +1368,12 @@ pub(crate) mod tests {
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()),
);
});
}
......@@ -2103,13 +1659,10 @@ pub(crate) mod tests {
let call = RuntimeCall::Utility(UtilityCall::batch_all {
calls: vec![
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
at_relay_block: (100, RelayBlockHash::default()),
at_relay_block: (100, BridgedChainHash::default()),
parachains: vec![
(ParaId(BridgedUnderlyingParachain::PARACHAIN_ID), [1u8; 32].into()),
(
ParaId(BridgedUnderlyingParachain::PARACHAIN_ID + 1),
[1u8; 32].into(),
),
(ParaId(TestParachain::get()), [1u8; 32].into()),
(ParaId(TestParachain::get() + 1), [1u8; 32].into()),
],
parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
}),
......@@ -2230,7 +1783,7 @@ pub(crate) mod tests {
initialize_environment(200, 200, 200);
let mut dispatch_info = dispatch_info();
dispatch_info.weight = Weight::from_parts(
dispatch_info.call_weight = Weight::from_parts(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2,
0,
);
......@@ -2250,7 +1803,7 @@ pub(crate) mod tests {
// 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, ..) => {
ExtensionCallInfo::AllFinalityAndMsgs(ref mut info, ..) => {
info.extra_weight.set_ref_time(
frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND,
);
......@@ -2356,7 +1909,7 @@ pub(crate) mod tests {
let delivery_rewards_account_balance =
Balances::free_balance(delivery_rewards_account());
let test_stake: ThisChainBalance = TestStake::get();
let test_stake: ThisChainBalance = Stake::get();
Balances::set_balance(
&relayer_account_at_this_chain(),
ExistentialDeposit::get() + test_stake * 10,
......@@ -2426,11 +1979,15 @@ pub(crate) mod tests {
}
fn run_analyze_call_result(
pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
pre_dispatch_data: PreDispatchData<
ThisChainAccountId,
BridgedChainBlockNumber,
TestLaneIdType,
>,
dispatch_result: DispatchResult,
) -> RelayerAccountAction<ThisChainAccountId, ThisChainBalance> {
TestExtensionProvider::analyze_call_result(
Some(Some(pre_dispatch_data)),
) -> RelayerAccountAction<ThisChainAccountId, RewardBalance, TestLaneIdType> {
TestExtension::analyze_call_result(
Some(pre_dispatch_data),
&dispatch_info(),
&post_dispatch_info(),
1024,
......@@ -2494,41 +2051,29 @@ pub(crate) mod tests {
}
#[test]
fn messages_ext_only_parses_standalone_transactions() {
fn grandpa_ext_only_parses_valid_batches() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
TestGrandpaExtensionConfig::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(
TestGrandpaExtensionConfig::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(
TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
&parachain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
......@@ -2536,43 +2081,31 @@ pub(crate) mod tests {
// parachain + message confirmation call batch is ignored
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
&parachain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is ignored
// relay + message delivery call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
TestGrandpaExtensionConfig::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),
Ok(Some(relay_finality_pre_dispatch_data().call_info)),
);
// relay + message confirmation call batch is ignored
// relay + message confirmation call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
TestGrandpaExtensionConfig::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),
Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)),
);
// message delivery call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
......@@ -2580,7 +2113,7 @@ pub(crate) mod tests {
// message confirmation call batch is accepted
assert_eq!(
TestMessagesExtensionProvider::parse_and_check_for_obsolete_call(
TestGrandpaExtensionConfig::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
......@@ -2589,66 +2122,19 @@ pub(crate) mod tests {
}
#[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() {
fn messages_ext_only_parses_standalone_transactions() {
run_test(|| {
initialize_environment(100, 100, 100);
// relay + parachain + message delivery calls batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::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(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&all_finality_and_delivery_batch_call_ex(200, 200, 200)
),
Ok(None),
......@@ -2656,13 +2142,13 @@ pub(crate) mod tests {
// relay + parachain + message confirmation calls batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::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(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&all_finality_and_confirmation_batch_call_ex(200, 200, 200)
),
Ok(None),
......@@ -2670,7 +2156,7 @@ pub(crate) mod tests {
// parachain + message delivery call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&parachain_finality_and_delivery_batch_call(200, 200)
),
Ok(None),
......@@ -2678,43 +2164,43 @@ pub(crate) mod tests {
// parachain + message confirmation call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&parachain_finality_and_confirmation_batch_call(200, 200)
),
Ok(None),
);
// relay + message delivery call batch is accepted
// relay + message delivery call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&relay_finality_and_delivery_batch_call(200, 200)
),
Ok(Some(relay_finality_pre_dispatch_data().call_info)),
Ok(None),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::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)),
Ok(None),
);
// relay + message confirmation call batch is accepted
// relay + message confirmation call batch is ignored
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&relay_finality_and_confirmation_batch_call(200, 200)
),
Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)),
Ok(None),
);
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::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)),
Ok(None),
);
// message delivery call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&message_delivery_call(200)
),
Ok(Some(delivery_pre_dispatch_data().call_info)),
......@@ -2722,7 +2208,7 @@ pub(crate) mod tests {
// message confirmation call batch is accepted
assert_eq!(
TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call(
TestMessagesExtensionConfig::parse_and_check_for_obsolete_call(
&message_confirmation_call(200)
),
Ok(Some(confirmation_pre_dispatch_data().call_info)),
......@@ -2730,6 +2216,53 @@ pub(crate) mod tests {
});
}
#[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_rejects_batch_with_obsolete_relay_chain_header() {
run_test(|| {
......@@ -2874,7 +2407,7 @@ pub(crate) mod tests {
fn does_not_panic_on_boosting_priority_of_empty_message_delivery_transaction() {
run_test(|| {
let best_delivered_message =
BridgedUnderlyingChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
BridgedUnderlyingParachain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX;
initialize_environment(100, 100, best_delivered_message);
// register relayer so it gets priority boost
......@@ -2882,7 +2415,7 @@ pub(crate) mod tests {
.unwrap();
// allow empty message delivery transactions
let lane_id = TestLaneId::get();
let lane_id = test_lane_id();
let in_lane_data = InboundLaneData {
last_confirmed_nonce: 0,
relayers: vec![UnrewardedRelayer {
......@@ -2890,6 +2423,7 @@ pub(crate) mod tests {
messages: DeliveredMessages { begin: 1, end: best_delivered_message },
}]
.into(),
..Default::default()
};
pallet_bridge_messages::InboundLanes::<TestRuntime>::insert(lane_id, in_lane_data);
......
// 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/>.
//! Adapter that allows using `pallet-bridge-relayers` as a signed extension in the
//! bridge with remote parachain.
use crate::{
extension::{
grandpa_adapter::verify_submit_finality_proof_succeeded, verify_messages_call_succeeded,
},
Config as BridgeRelayersConfig, LOG_TARGET,
};
use bp_relayers::{BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig};
use bp_runtime::{Parachain, StaticStrProvider};
use core::marker::PhantomData;
use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
use frame_system::Config as SystemConfig;
use pallet_bridge_grandpa::{
CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig,
};
use pallet_bridge_messages::{
CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf,
};
use pallet_bridge_parachains::{
CallSubType as BridgeParachainsCallSubtype, Config as BridgeParachainsConfig,
SubmitParachainHeadsHelper,
};
use sp_runtime::{
traits::{Dispatchable, Get},
transaction_validity::{TransactionPriority, TransactionValidityError},
};
/// Adapter to be used in signed extension configuration, when bridging with remote parachains.
pub struct WithParachainExtensionConfig<
// signed extension identifier
IdProvider,
// runtime that implements `BridgeMessagesConfig<BridgeMessagesPalletInstance>`, which
// uses `BridgeParachainsConfig<BridgeParachainsPalletInstance>` to receive messages and
// confirmations from the remote chain.
Runtime,
// batch call unpacker
BatchCallUnpacker,
// instance of the `pallet-bridge-parachains`, tracked by this extension
BridgeParachainsPalletInstance,
// instance of BridgedChain `pallet-bridge-messages`, tracked by this extension
BridgeMessagesPalletInstance,
// instance of `pallet-bridge-relayers`, tracked by this extension
BridgeRelayersPalletInstance,
// message delivery transaction priority boost for every additional message
PriorityBoostPerMessage,
>(
PhantomData<(
IdProvider,
Runtime,
BatchCallUnpacker,
BridgeParachainsPalletInstance,
BridgeMessagesPalletInstance,
BridgeRelayersPalletInstance,
PriorityBoostPerMessage,
)>,
);
impl<ID, R, BCU, PI, MI, RI, P> ExtensionConfig
for WithParachainExtensionConfig<ID, R, BCU, PI, MI, RI, P>
where
ID: StaticStrProvider,
R: BridgeRelayersConfig<RI>
+ BridgeMessagesConfig<MI>
+ BridgeParachainsConfig<PI>
+ BridgeGrandpaConfig<R::BridgesGrandpaPalletInstance>,
BCU: BatchCallUnpacker<R>,
PI: 'static,
MI: 'static,
RI: 'static,
P: Get<TransactionPriority>,
R::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ BridgeGrandpaCallSubtype<R, R::BridgesGrandpaPalletInstance>
+ BridgeParachainsCallSubtype<R, PI>
+ BridgeMessagesCallSubType<R, MI>,
<R as BridgeMessagesConfig<MI>>::BridgedChain: Parachain,
{
type IdProvider = ID;
type Runtime = R;
type BridgeMessagesPalletInstance = MI;
type BridgeRelayersPalletInstance = RI;
type PriorityBoostPerMessage = P;
type RemoteGrandpaChainBlockNumber =
pallet_bridge_grandpa::BridgedBlockNumber<R, R::BridgesGrandpaPalletInstance>;
type LaneId = LaneIdOf<R, Self::BridgeMessagesPalletInstance>;
fn parse_and_check_for_obsolete_call(
call: &R::RuntimeCall,
) -> Result<
Option<ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>>,
TransactionValidityError,
> {
let calls = BCU::unpack(call, 3);
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());
let para_finality_call = calls.next().transpose()?.and_then(|c| {
let r = c.submit_parachain_heads_info_for(
<R as BridgeMessagesConfig<Self::BridgeMessagesPalletInstance>>::BridgedChain::PARACHAIN_ID,
);
r
});
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(ExtensionCallInfo::AllFinalityAndMsgs(
relay_finality_call,
para_finality_call,
msgs_call,
)),
(2, None, Some(para_finality_call), Some(msgs_call)) =>
Some(ExtensionCallInfo::ParachainFinalityAndMsgs(para_finality_call, msgs_call)),
(1, None, None, Some(msgs_call)) => Some(ExtensionCallInfo::Msgs(msgs_call)),
_ => None,
})
}
fn check_obsolete_parsed_call(
call: &R::RuntimeCall,
) -> Result<&R::RuntimeCall, TransactionValidityError> {
call.check_obsolete_submit_finality_proof()?;
call.check_obsolete_submit_parachain_heads()?;
call.check_obsolete_call()?;
Ok(call)
}
fn check_call_result(
call_info: &ExtensionCallInfo<Self::RemoteGrandpaChainBlockNumber, Self::LaneId>,
call_data: &mut ExtensionCallData,
relayer: &R::AccountId,
) -> bool {
verify_submit_finality_proof_succeeded::<Self, R::BridgesGrandpaPalletInstance>(
call_info, call_data, relayer,
) && verify_submit_parachain_head_succeeded::<Self, PI>(call_info, call_data, relayer) &&
verify_messages_call_succeeded::<Self>(call_info, call_data, relayer)
}
}
/// If the batch call contains the parachain state update call, verify that it
/// has been successful.
///
/// Only returns false when parachain state update call has failed.
pub(crate) fn verify_submit_parachain_head_succeeded<C, PI>(
call_info: &ExtensionCallInfo<C::RemoteGrandpaChainBlockNumber, C::LaneId>,
_call_data: &mut ExtensionCallData,
relayer: &<C::Runtime as SystemConfig>::AccountId,
) -> bool
where
C: ExtensionConfig,
PI: 'static,
C::Runtime: BridgeParachainsConfig<PI>,
{
let Some(para_proof_info) = call_info.submit_parachain_heads_info() else { return true };
if !SubmitParachainHeadsHelper::<C::Runtime, PI>::was_successful(para_proof_info) {
// we only refund relayer if all calls have updated chain state
log::trace!(
target: LOG_TARGET,
"{}.{:?}: relayer {:?} has submitted invalid parachain finality proof",
C::IdProvider::STR,
call_info.messages_call_info().lane_id(),
relayer,
);
return false
}
true
}
......@@ -50,7 +50,6 @@ mod integrity_tests {}
#[cfg(feature = "integrity-test")]
mod integrity_tests {
use super::{compute_priority_boost, ItemCount};
use crate::extensions::refund_relayer_extension::RefundableParachainId;
use bp_messages::MessageNonce;
use bp_runtime::PreComputedSize;
......@@ -207,14 +206,17 @@ mod integrity_tests {
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(tx_call_size);
let transaction_weight = Runtime::WeightInfo::submit_finality_proof_weight(
let transaction_weight = <Runtime as ::pallet_bridge_grandpa::Config<
GrandpaInstance,
>>::WeightInfo::submit_finality_proof_weight(
Runtime::BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1,
Runtime::BridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
);
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
call_weight: transaction_weight,
extension_weight: Default::default(),
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
......@@ -239,12 +241,18 @@ mod integrity_tests {
/// almost the same priority if we'll add `tip_boost_per_header` tip to the `TX1`. We want
/// to be sure that if we add plain `PriorityBoostPerHeader` priority to `TX1`, the priority
/// will be close to `TX2` as well.
pub fn ensure_priority_boost_is_sane<Runtime, RefundableParachain, PriorityBoostPerHeader>(
pub fn ensure_priority_boost_is_sane<
Runtime,
ParachainsInstance,
Para,
PriorityBoostPerHeader,
>(
tip_boost_per_header: BalanceOf<Runtime>,
) where
Runtime: pallet_transaction_payment::Config
+ pallet_bridge_parachains::Config<RefundableParachain::Instance>,
RefundableParachain: RefundableParachainId,
+ pallet_bridge_parachains::Config<ParachainsInstance>,
ParachainsInstance: 'static,
Para: Parachain,
PriorityBoostPerHeader: Get<TransactionPriority>,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
......@@ -263,7 +271,8 @@ mod integrity_tests {
|_n_headers, tip| {
estimate_parachain_header_submit_transaction_priority::<
Runtime,
RefundableParachain,
ParachainsInstance,
Para,
>(tip)
},
);
......@@ -271,13 +280,18 @@ mod integrity_tests {
/// Estimate parachain header delivery transaction priority.
#[cfg(feature = "integrity-test")]
fn estimate_parachain_header_submit_transaction_priority<Runtime, RefundableParachain>(
fn estimate_parachain_header_submit_transaction_priority<
Runtime,
ParachainsInstance,
Para,
>(
tip: BalanceOf<Runtime>,
) -> TransactionPriority
where
Runtime: pallet_transaction_payment::Config
+ pallet_bridge_parachains::Config<RefundableParachain::Instance>,
RefundableParachain: RefundableParachainId,
+ pallet_bridge_parachains::Config<ParachainsInstance>,
ParachainsInstance: 'static,
Para: Parachain,
Runtime::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<Runtime>: Send + Sync + FixedPointOperand,
{
......@@ -287,14 +301,14 @@ mod integrity_tests {
let base_tx_size = 512;
// let's say we are relaying largest parachain headers and proof takes some more bytes
let tx_call_size = <Runtime as pallet_bridge_parachains::Config<
RefundableParachain::Instance,
ParachainsInstance,
>>::WeightInfo::expected_extra_storage_proof_size()
.saturating_add(RefundableParachain::BridgedChain::MAX_HEADER_SIZE);
.saturating_add(Para::MAX_HEADER_SIZE);
// finally we are able to estimate transaction size and weight
let transaction_size = base_tx_size.saturating_add(tx_call_size);
let transaction_weight = <Runtime as pallet_bridge_parachains::Config<
RefundableParachain::Instance,
ParachainsInstance,
>>::WeightInfo::submit_parachain_heads_weight(
Runtime::DbWeight::get(),
&PreComputedSize(transaction_size as _),
......@@ -304,7 +318,8 @@ mod integrity_tests {
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
call_weight: transaction_weight,
extension_weight: Default::default(),
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
......@@ -374,20 +389,27 @@ mod integrity_tests {
// 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::WeightInfo::message_dispatch_weight(estimated_message_size);
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::WeightInfo::expected_extra_storage_proof_size()
.saturating_mul(2)
.saturating_div(3)
.saturating_add(estimated_message_size)
.saturating_mul(messages as _);
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::WeightInfo::receive_messages_proof_weight(
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),
......@@ -395,7 +417,8 @@ mod integrity_tests {
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::get_priority(
&DispatchInfo {
weight: transaction_weight,
call_weight: transaction_weight,
extension_weight: Default::default(),
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
},
......
......@@ -18,31 +18,34 @@
//! coordinate relations between relayers.
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
use bp_relayers::{
ExplicitOrAccountParams, PaymentProcedure, Registration, RelayerRewardsKeyProvider,
RewardsAccountParams, StakeAndSlash,
};
extern crate alloc;
pub use bp_relayers::RewardLedger;
use bp_relayers::{PaymentProcedure, Registration, RelayerRewardsKeyProvider, StakeAndSlash};
use bp_runtime::StorageDoubleMapKeyProvider;
use frame_support::fail;
use core::marker::PhantomData;
use frame_support::{fail, traits::tokens::Balance};
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
use sp_runtime::{traits::CheckedSub, Saturating};
use sp_std::marker::PhantomData;
use sp_runtime::{
traits::{CheckedSub, IdentifyAccount},
Saturating,
};
pub use pallet::*;
pub use payment_adapter::DeliveryConfirmationPaymentsAdapter;
pub use payment_adapter::{DeliveryConfirmationPaymentsAdapter, PayRewardFromAccount};
pub use stake_adapter::StakeAndSlashNamed;
pub use weights::WeightInfo;
pub use weights_ext::WeightInfoExt;
pub mod benchmarking;
mod mock;
mod payment_adapter;
mod stake_adapter;
mod weights_ext;
pub mod benchmarking;
pub mod extension;
pub mod migration;
pub mod weights;
/// The target that will be used when publishing logs related to this pallet.
......@@ -55,62 +58,63 @@ pub mod pallet {
use frame_system::pallet_prelude::*;
/// `RelayerRewardsKeyProvider` for given configuration.
type RelayerRewardsKeyProviderOf<T> =
RelayerRewardsKeyProvider<<T as frame_system::Config>::AccountId, <T as Config>::Reward>;
type RelayerRewardsKeyProviderOf<T, I> = RelayerRewardsKeyProvider<
<T as frame_system::Config>::AccountId,
<T as Config<I>>::Reward,
<T as Config<I>>::RewardBalance,
>;
/// Shortcut to alternative beneficiary type for `Config::PaymentProcedure`.
pub type BeneficiaryOf<T, I> = <<T as Config<I>>::PaymentProcedure as PaymentProcedure<
<T as frame_system::Config>::AccountId,
<T as Config<I>>::Reward,
<T as Config<I>>::RewardBalance,
>>::Beneficiary;
#[pallet::config]
pub trait Config: frame_system::Config {
pub trait Config<I: 'static = ()>: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type of relayer reward.
type Reward: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen;
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type of relayer reward balance.
type RewardBalance: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen;
/// Reward discriminator type. The pallet can collect different types of rewards for a
/// single account, so `Reward` is used as the second key in the `RelayerRewards` double
/// map.
///
/// For example, rewards for different bridges can be stored, where `Reward` is
/// implemented as an enum representing each bridge.
type Reward: Parameter + MaxEncodedLen + Send + Sync + Copy + Clone;
/// Pay rewards scheme.
type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward>;
type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward, Self::RewardBalance>;
/// Stake and slash scheme.
type StakeAndSlash: StakeAndSlash<Self::AccountId, BlockNumberFor<Self>, Self::Reward>;
type StakeAndSlash: StakeAndSlash<Self::AccountId, BlockNumberFor<Self>, Self::Balance>;
/// Type for representing balance of an account used for `T::StakeAndSlash`.
type Balance: Balance;
/// Pallet call weights.
type WeightInfo: WeightInfoExt;
}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::storage_version(migration::STORAGE_VERSION)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::call]
impl<T: Config> Pallet<T> {
impl<T: Config<I>, I: 'static> Pallet<T, I>
where
BeneficiaryOf<T, I>: From<<T as frame_system::Config>::AccountId>,
{
/// Claim accumulated rewards.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::claim_rewards())]
pub fn claim_rewards(
origin: OriginFor<T>,
rewards_account_params: RewardsAccountParams,
) -> DispatchResult {
pub fn claim_rewards(origin: OriginFor<T>, reward_kind: T::Reward) -> DispatchResult {
let relayer = ensure_signed(origin)?;
RelayerRewards::<T>::try_mutate_exists(
&relayer,
rewards_account_params,
|maybe_reward| -> DispatchResult {
let reward = maybe_reward.take().ok_or(Error::<T>::NoRewardForRelayer)?;
T::PaymentProcedure::pay_reward(&relayer, rewards_account_params, reward)
.map_err(|e| {
log::trace!(
target: LOG_TARGET,
"Failed to pay {:?} rewards to {:?}: {:?}",
rewards_account_params,
relayer,
e,
);
Error::<T>::FailedToPayReward
})?;
Self::deposit_event(Event::<T>::RewardPaid {
relayer: relayer.clone(),
rewards_account_params,
reward,
});
Ok(())
},
)
Self::do_claim_rewards(relayer.clone(), reward_kind, relayer.into())
}
/// Register relayer or update its registration.
......@@ -125,53 +129,57 @@ pub mod pallet {
// than the `RequiredRegistrationLease`
let lease = valid_till.saturating_sub(frame_system::Pallet::<T>::block_number());
ensure!(
lease > Pallet::<T>::required_registration_lease(),
Error::<T>::InvalidRegistrationLease
lease > Self::required_registration_lease(),
Error::<T, I>::InvalidRegistrationLease
);
RegisteredRelayers::<T>::try_mutate(&relayer, |maybe_registration| -> DispatchResult {
let mut registration = maybe_registration
.unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() });
// new `valid_till` must be larger (or equal) than the old one
ensure!(
valid_till >= registration.valid_till,
Error::<T>::CannotReduceRegistrationLease,
);
registration.valid_till = valid_till;
// regarding stake, there are three options:
// - if relayer stake is larger than required stake, we may do unreserve
// - if relayer stake equals to required stake, we do nothing
// - if relayer stake is smaller than required stake, we do additional reserve
let required_stake = Pallet::<T>::required_stake();
if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) {
Self::do_unreserve(&relayer, to_unreserve)?;
} else if let Some(to_reserve) = required_stake.checked_sub(&registration.stake) {
T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| {
log::trace!(
target: LOG_TARGET,
"Failed to reserve {:?} on relayer {:?} account: {:?}",
to_reserve,
relayer,
e,
);
RegisteredRelayers::<T, I>::try_mutate(
&relayer,
|maybe_registration| -> DispatchResult {
let mut registration = maybe_registration
.unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() });
// new `valid_till` must be larger (or equal) than the old one
ensure!(
valid_till >= registration.valid_till,
Error::<T, I>::CannotReduceRegistrationLease,
);
registration.valid_till = valid_till;
// regarding stake, there are three options:
// - if relayer stake is larger than required stake, we may do unreserve
// - if relayer stake equals to required stake, we do nothing
// - if relayer stake is smaller than required stake, we do additional reserve
let required_stake = Self::required_stake();
if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) {
Self::do_unreserve(&relayer, to_unreserve)?;
} else if let Some(to_reserve) = required_stake.checked_sub(&registration.stake)
{
T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| {
log::trace!(
target: LOG_TARGET,
"Failed to reserve {:?} on relayer {:?} account: {:?}",
to_reserve,
relayer,
e,
);
Error::<T>::FailedToReserve
})?;
}
registration.stake = required_stake;
Error::<T, I>::FailedToReserve
})?;
}
registration.stake = required_stake;
log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer);
Self::deposit_event(Event::<T>::RegistrationUpdated {
relayer: relayer.clone(),
registration,
});
log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer);
Self::deposit_event(Event::<T, I>::RegistrationUpdated {
relayer: relayer.clone(),
registration,
});
*maybe_registration = Some(registration);
*maybe_registration = Some(registration);
Ok(())
})
Ok(())
},
)
}
/// `Deregister` relayer.
......@@ -183,34 +191,115 @@ pub mod pallet {
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
let relayer = ensure_signed(origin)?;
RegisteredRelayers::<T>::try_mutate(&relayer, |maybe_registration| -> DispatchResult {
let registration = match maybe_registration.take() {
Some(registration) => registration,
None => fail!(Error::<T>::NotRegistered),
};
RegisteredRelayers::<T, I>::try_mutate(
&relayer,
|maybe_registration| -> DispatchResult {
let registration = match maybe_registration.take() {
Some(registration) => registration,
None => fail!(Error::<T, I>::NotRegistered),
};
// we can't deregister until `valid_till + 1`
ensure!(
registration.valid_till < frame_system::Pallet::<T>::block_number(),
Error::<T, I>::RegistrationIsStillActive,
);
// we can't deregister until `valid_till + 1`
ensure!(
registration.valid_till < frame_system::Pallet::<T>::block_number(),
Error::<T>::RegistrationIsStillActive,
);
// if stake is non-zero, we should do unreserve
if !registration.stake.is_zero() {
Self::do_unreserve(&relayer, registration.stake)?;
}
// if stake is non-zero, we should do unreserve
if !registration.stake.is_zero() {
Self::do_unreserve(&relayer, registration.stake)?;
}
log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer);
Self::deposit_event(Event::<T, I>::Deregistered { relayer: relayer.clone() });
log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer);
Self::deposit_event(Event::<T>::Deregistered { relayer: relayer.clone() });
*maybe_registration = None;
*maybe_registration = None;
Ok(())
},
)
}
/// Claim accumulated rewards and send them to the alternative beneficiary.
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::claim_rewards_to())]
pub fn claim_rewards_to(
origin: OriginFor<T>,
reward_kind: T::Reward,
beneficiary: BeneficiaryOf<T, I>,
) -> DispatchResult {
let relayer = ensure_signed(origin)?;
Ok(())
})
Self::do_claim_rewards(relayer, reward_kind, beneficiary)
}
}
impl<T: Config> Pallet<T> {
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Relayers that have reserved some of their balance to get free priority boost
/// for their message delivery transactions.
pub fn registered_relayer(
relayer: &T::AccountId,
) -> Option<Registration<BlockNumberFor<T>, T::Balance>> {
RegisteredRelayers::<T, I>::get(relayer)
}
/// Map of the relayer => accumulated reward.
pub fn relayer_reward<EncodeLikeAccountId, EncodeLikeReward>(
key1: EncodeLikeAccountId,
key2: EncodeLikeReward,
) -> Option<<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Value>
where
EncodeLikeAccountId: codec::EncodeLike<
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Key1,
>,
EncodeLikeReward: codec::EncodeLike<
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Key2,
>,
{
RelayerRewards::<T, I>::get(key1, key2)
}
fn do_claim_rewards(
relayer: T::AccountId,
reward_kind: T::Reward,
beneficiary: BeneficiaryOf<T, I>,
) -> DispatchResult {
RelayerRewards::<T, I>::try_mutate_exists(
&relayer,
reward_kind,
|maybe_reward| -> DispatchResult {
let reward_balance =
maybe_reward.take().ok_or(Error::<T, I>::NoRewardForRelayer)?;
T::PaymentProcedure::pay_reward(
&relayer,
reward_kind,
reward_balance,
beneficiary.clone(),
)
.map_err(|e| {
log::error!(
target: LOG_TARGET,
"Failed to pay ({:?} / {:?}) rewards to {:?}(beneficiary: {:?}), error: {:?}",
reward_kind,
reward_balance,
relayer,
beneficiary,
e,
);
Error::<T, I>::FailedToPayReward
})?;
Self::deposit_event(Event::<T, I>::RewardPaid {
relayer: relayer.clone(),
reward_kind,
reward_balance,
beneficiary,
});
Ok(())
},
)
}
/// Returns true if given relayer registration is active at current block.
///
/// This call respects both `RequiredStake` and `RequiredRegistrationLease`, meaning that
......@@ -224,7 +313,7 @@ pub mod pallet {
// registration is inactive if relayer stake is less than required
if registration.stake < Self::required_stake() {
return false
return false;
}
// registration is inactive if it ends soon
......@@ -232,7 +321,7 @@ pub mod pallet {
.valid_till
.saturating_sub(frame_system::Pallet::<T>::block_number());
if remaining_lease <= Self::required_registration_lease() {
return false
return false;
}
true
......@@ -243,9 +332,9 @@ pub mod pallet {
/// It may fail inside, but error is swallowed and we only log it.
pub fn slash_and_deregister(
relayer: &T::AccountId,
slash_destination: ExplicitOrAccountParams<T::AccountId>,
slash_destination: impl IdentifyAccount<AccountId = T::AccountId>,
) {
let registration = match RegisteredRelayers::<T>::take(relayer) {
let registration = match RegisteredRelayers::<T, I>::take(relayer) {
Some(registration) => registration,
None => {
log::trace!(
......@@ -254,13 +343,14 @@ pub mod pallet {
relayer,
);
return
return;
},
};
let slash_destination = slash_destination.into_account();
match T::StakeAndSlash::repatriate_reserved(
relayer,
slash_destination.clone(),
&slash_destination,
registration.stake,
) {
Ok(failed_to_slash) if failed_to_slash.is_zero() => {
......@@ -286,8 +376,8 @@ pub mod pallet {
Err(e) => {
// TODO: document this. Where?
// it may fail if there's no beneficiary account. For us it means that this
// account must exists before we'll deploy the bridge
// it may fail if there's no beneficiary account. For us, it means that this
// account must exist before we'll deploy the bridge
log::debug!(
target: crate::LOG_TARGET,
"Failed to slash relayer account {:?}: {:?}. Maybe beneficiary account doesn't exist? \
......@@ -300,37 +390,43 @@ pub mod pallet {
);
},
}
Self::deposit_event(Event::<T, I>::SlashedAndDeregistered {
relayer: relayer.clone(),
registration,
});
}
/// Register reward for given relayer.
pub fn register_relayer_reward(
rewards_account_params: RewardsAccountParams,
pub(crate) fn register_relayer_reward(
reward_kind: T::Reward,
relayer: &T::AccountId,
reward: T::Reward,
reward_balance: T::RewardBalance,
) {
if reward.is_zero() {
return
if reward_balance.is_zero() {
return;
}
RelayerRewards::<T>::mutate(
RelayerRewards::<T, I>::mutate(
relayer,
rewards_account_params,
|old_reward: &mut Option<T::Reward>| {
let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward);
reward_kind,
|old_reward: &mut Option<T::RewardBalance>| {
let new_reward =
old_reward.unwrap_or_else(Zero::zero).saturating_add(reward_balance);
*old_reward = Some(new_reward);
log::trace!(
target: crate::LOG_TARGET,
"Relayer {:?} can now claim reward for serving payer {:?}: {:?}",
relayer,
rewards_account_params,
reward_kind,
new_reward,
);
Self::deposit_event(Event::<T>::RewardRegistered {
Self::deposit_event(Event::<T, I>::RewardRegistered {
relayer: relayer.clone(),
rewards_account_params,
reward,
reward_kind,
reward_balance,
});
},
);
......@@ -341,21 +437,21 @@ pub mod pallet {
<T::StakeAndSlash as StakeAndSlash<
T::AccountId,
BlockNumberFor<T>,
T::Reward,
T::Balance,
>>::RequiredRegistrationLease::get()
}
/// Return required stake.
pub(crate) fn required_stake() -> T::Reward {
pub(crate) fn required_stake() -> T::Balance {
<T::StakeAndSlash as StakeAndSlash<
T::AccountId,
BlockNumberFor<T>,
T::Reward,
T::Balance,
>>::RequiredStake::get()
}
/// `Unreserve` given amount on relayer account.
fn do_unreserve(relayer: &T::AccountId, amount: T::Reward) -> DispatchResult {
fn do_unreserve(relayer: &T::AccountId, amount: T::Balance) -> DispatchResult {
let failed_to_unreserve = T::StakeAndSlash::unreserve(relayer, amount);
if !failed_to_unreserve.is_zero() {
log::trace!(
......@@ -366,7 +462,7 @@ pub mod pallet {
relayer,
);
fail!(Error::<T>::FailedToUnreserve)
fail!(Error::<T, I>::FailedToUnreserve)
}
Ok(())
......@@ -375,31 +471,33 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
pub enum Event<T: Config<I>, I: 'static = ()> {
/// Relayer reward has been registered and may be claimed later.
RewardRegistered {
/// Relayer account that can claim reward.
relayer: T::AccountId,
/// Relayer can claim reward from this account.
rewards_account_params: RewardsAccountParams,
/// Relayer can claim this kind of reward.
reward_kind: T::Reward,
/// Reward amount.
reward: T::Reward,
reward_balance: T::RewardBalance,
},
/// Reward has been paid to the relayer.
RewardPaid {
/// Relayer account that has been rewarded.
relayer: T::AccountId,
/// Relayer has received reward from this account.
rewards_account_params: RewardsAccountParams,
/// Relayer has received reward of this kind.
reward_kind: T::Reward,
/// Reward amount.
reward: T::Reward,
reward_balance: T::RewardBalance,
/// Beneficiary.
beneficiary: BeneficiaryOf<T, I>,
},
/// Relayer registration has been added or updated.
RegistrationUpdated {
/// Relayer account that has been registered.
relayer: T::AccountId,
/// Relayer registration.
registration: Registration<BlockNumberFor<T>, T::Reward>,
registration: Registration<BlockNumberFor<T>, T::Balance>,
},
/// Relayer has been `deregistered`.
Deregistered {
......@@ -411,12 +509,12 @@ pub mod pallet {
/// Relayer account that has been `deregistered`.
relayer: T::AccountId,
/// Registration that was removed.
registration: Registration<BlockNumberFor<T>, T::Reward>,
registration: Registration<BlockNumberFor<T>, T::Balance>,
},
}
#[pallet::error]
pub enum Error<T> {
pub enum Error<T, I = ()> {
/// No reward can be claimed by given relayer.
NoRewardForRelayer,
/// Reward payment procedure has failed.
......@@ -438,14 +536,13 @@ pub mod pallet {
/// Map of the relayer => accumulated reward.
#[pallet::storage]
#[pallet::getter(fn relayer_reward)]
pub type RelayerRewards<T: Config> = StorageDoubleMap<
pub type RelayerRewards<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Hasher1,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Key1,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Hasher2,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Key2,
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Value,
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Hasher1,
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Key1,
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Hasher2,
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Key2,
<RelayerRewardsKeyProviderOf<T, I> as StorageDoubleMapKeyProvider>::Value,
OptionQuery,
>;
......@@ -456,28 +553,35 @@ pub mod pallet {
/// priority and will be rejected (without significant tip) in case if registered
/// relayer is present.
#[pallet::storage]
#[pallet::getter(fn registered_relayer)]
pub type RegisteredRelayers<T: Config> = StorageMap<
pub type RegisteredRelayers<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
Registration<BlockNumberFor<T>, T::Reward>,
Registration<BlockNumberFor<T>, T::Balance>,
OptionQuery,
>;
}
/// Implementation of `RewardLedger` for the pallet.
impl<T: Config<I>, I: 'static, Reward, RewardBalance>
RewardLedger<T::AccountId, Reward, RewardBalance> for Pallet<T, I>
where
Reward: Into<T::Reward>,
RewardBalance: Into<T::RewardBalance>,
{
fn register_reward(relayer: &T::AccountId, reward: Reward, reward_balance: RewardBalance) {
Self::register_relayer_reward(reward.into(), relayer, reward_balance.into());
}
}
#[cfg(test)]
mod tests {
use super::*;
use mock::{RuntimeEvent as TestEvent, *};
use crate::Event::{RewardPaid, RewardRegistered};
use bp_messages::LaneId;
use bp_relayers::RewardsAccountOwner;
use frame_support::{
assert_noop, assert_ok,
traits::fungible::{Inspect, Mutate},
};
use bp_messages::{HashedLaneId, LaneIdType};
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate};
use frame_system::{EventRecord, Pallet as System, Phase};
use sp_runtime::DispatchError;
......@@ -492,7 +596,7 @@ mod tests {
get_ready_for_events();
Pallet::<TestRuntime>::register_relayer_reward(
TEST_REWARDS_ACCOUNT_PARAMS,
test_reward_account_param(),
&REGULAR_RELAYER,
100,
);
......@@ -502,10 +606,10 @@ mod tests {
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(RewardRegistered {
event: TestEvent::BridgeRelayers(Event::RewardRegistered {
relayer: REGULAR_RELAYER,
rewards_account_params: TEST_REWARDS_ACCOUNT_PARAMS,
reward: 100
reward_kind: test_reward_account_param(),
reward_balance: 100
}),
topics: vec![],
}),
......@@ -513,13 +617,51 @@ mod tests {
});
}
#[test]
fn slash_and_deregister_works() {
run_test(|| {
get_ready_for_events();
// register
assert_ok!(Pallet::<TestRuntime>::register(
RuntimeOrigin::signed(REGISTER_RELAYER),
150,
));
// check if registered
let registration =
Pallet::<TestRuntime>::registered_relayer(&REGISTER_RELAYER).unwrap();
assert_eq!(registration, Registration { valid_till: 150, stake: Stake::get() });
// slash and deregister
let slash_destination = RewardsAccountParams::new(
HashedLaneId::try_new(1, 2).unwrap(),
*b"test",
RewardsAccountOwner::ThisChain,
);
let slash_destination = bp_relayers::ExplicitOrAccountParams::Params(slash_destination);
Pallet::<TestRuntime>::slash_and_deregister(&REGISTER_RELAYER, slash_destination);
// check if event emitted
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::BridgeRelayers(Event::SlashedAndDeregistered {
relayer: REGISTER_RELAYER,
registration,
}),
topics: vec![],
})
)
});
}
#[test]
fn root_cant_claim_anything() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::root(),
TEST_REWARDS_ACCOUNT_PARAMS
test_reward_account_param()
),
DispatchError::BadOrigin,
);
......@@ -532,7 +674,7 @@ mod tests {
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_REWARDS_ACCOUNT_PARAMS
test_reward_account_param()
),
Error::<TestRuntime>::NoRewardForRelayer,
);
......@@ -544,13 +686,13 @@ mod tests {
run_test(|| {
RelayerRewards::<TestRuntime>::insert(
FAILING_RELAYER,
TEST_REWARDS_ACCOUNT_PARAMS,
test_reward_account_param(),
100,
);
assert_noop!(
Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(FAILING_RELAYER),
TEST_REWARDS_ACCOUNT_PARAMS
test_reward_account_param()
),
Error::<TestRuntime>::FailedToPayReward,
);
......@@ -564,15 +706,15 @@ mod tests {
RelayerRewards::<TestRuntime>::insert(
REGULAR_RELAYER,
TEST_REWARDS_ACCOUNT_PARAMS,
test_reward_account_param(),
100,
);
assert_ok!(Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_REWARDS_ACCOUNT_PARAMS
test_reward_account_param()
));
assert_eq!(
RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER, TEST_REWARDS_ACCOUNT_PARAMS),
RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER, test_reward_account_param()),
None
);
......@@ -581,10 +723,11 @@ mod tests {
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(RewardPaid {
event: TestEvent::BridgeRelayers(Event::RewardPaid {
relayer: REGULAR_RELAYER,
rewards_account_params: TEST_REWARDS_ACCOUNT_PARAMS,
reward: 100
reward_kind: test_reward_account_param(),
reward_balance: 100,
beneficiary: REGULAR_RELAYER,
}),
topics: vec![],
}),
......@@ -593,40 +736,39 @@ mod tests {
}
#[test]
fn pay_reward_from_account_actually_pays_reward() {
type Balances = pallet_balances::Pallet<TestRuntime>;
type PayLaneRewardFromAccount = bp_relayers::PayRewardFromAccount<Balances, AccountId>;
fn relayer_can_claim_reward_to() {
run_test(|| {
let in_lane_0 = RewardsAccountParams::new(
LaneId([0, 0, 0, 0]),
*b"test",
RewardsAccountOwner::ThisChain,
get_ready_for_events();
RelayerRewards::<TestRuntime>::insert(
REGULAR_RELAYER,
test_reward_account_param(),
100,
);
let out_lane_1 = RewardsAccountParams::new(
LaneId([0, 0, 0, 1]),
*b"test",
RewardsAccountOwner::BridgedChain,
assert_ok!(Pallet::<TestRuntime>::claim_rewards_to(
RuntimeOrigin::signed(REGULAR_RELAYER),
test_reward_account_param(),
REGULAR_RELAYER2,
));
assert_eq!(
RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER, test_reward_account_param()),
None
);
let in_lane0_rewards_account = PayLaneRewardFromAccount::rewards_account(in_lane_0);
let out_lane1_rewards_account = PayLaneRewardFromAccount::rewards_account(out_lane_1);
Balances::mint_into(&in_lane0_rewards_account, 100).unwrap();
Balances::mint_into(&out_lane1_rewards_account, 100).unwrap();
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
assert_eq!(Balances::balance(&1), 0);
PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100).unwrap();
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
assert_eq!(Balances::balance(&1), 100);
PayLaneRewardFromAccount::pay_reward(&1, out_lane_1, 100).unwrap();
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
assert_eq!(Balances::balance(&1), 200);
// Check if the `RewardPaid` event was emitted.
assert_eq!(
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::BridgeRelayers(Event::RewardPaid {
relayer: REGULAR_RELAYER,
reward_kind: test_reward_account_param(),
reward_balance: 100,
beneficiary: REGULAR_RELAYER2,
}),
topics: vec![],
}),
);
});
}
......@@ -668,7 +810,7 @@ mod tests {
));
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
assert_eq!(
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
Pallet::<TestRuntime>::registered_relayer(&REGISTER_RELAYER),
Some(Registration { valid_till: 150, stake: Stake::get() }),
);
......@@ -676,7 +818,7 @@ mod tests {
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::RegistrationUpdated {
event: TestEvent::BridgeRelayers(Event::RegistrationUpdated {
relayer: REGISTER_RELAYER,
registration: Registration { valid_till: 150, stake: Stake::get() },
}),
......@@ -736,7 +878,7 @@ mod tests {
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + 1);
assert_eq!(
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
Pallet::<TestRuntime>::registered_relayer(&REGISTER_RELAYER),
Some(Registration { valid_till: 150, stake: Stake::get() }),
);
......@@ -744,7 +886,7 @@ mod tests {
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::RegistrationUpdated {
event: TestEvent::BridgeRelayers(Event::RegistrationUpdated {
relayer: REGISTER_RELAYER,
registration: Registration { valid_till: 150, stake: Stake::get() }
}),
......@@ -800,7 +942,7 @@ mod tests {
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance - 1);
assert_eq!(
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
Pallet::<TestRuntime>::registered_relayer(&REGISTER_RELAYER),
Some(Registration { valid_till: 150, stake: Stake::get() }),
);
......@@ -808,7 +950,7 @@ mod tests {
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::RegistrationUpdated {
event: TestEvent::BridgeRelayers(Event::RegistrationUpdated {
relayer: REGISTER_RELAYER,
registration: Registration { valid_till: 150, stake: Stake::get() }
}),
......@@ -870,7 +1012,9 @@ mod tests {
System::<TestRuntime>::events().last(),
Some(&EventRecord {
phase: Phase::Initialization,
event: TestEvent::Relayers(Event::Deregistered { relayer: REGISTER_RELAYER }),
event: TestEvent::BridgeRelayers(Event::Deregistered {
relayer: REGISTER_RELAYER
}),
topics: vec![],
}),
);
......
// 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/>.
//! A module that is responsible for migration of storage.
use alloc::vec::Vec;
use frame_support::{
traits::{Get, StorageVersion},
weights::Weight,
};
/// The in-code storage version.
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
/// This module contains data structures that are valid for the initial state of `0`.
/// (used with v1 migration).
pub mod v0 {
use crate::{Config, Pallet};
use bp_relayers::RewardsAccountOwner;
use bp_runtime::{ChainId, StorageDoubleMapKeyProvider};
use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen};
use core::marker::PhantomData;
use frame_support::{pallet_prelude::OptionQuery, Blake2_128Concat, Identity};
use scale_info::TypeInfo;
use sp_runtime::traits::AccountIdConversion;
/// Structure used to identify the account that pays a reward to the relayer.
#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
pub struct RewardsAccountParams<LaneId> {
/// lane_id
pub lane_id: LaneId,
/// bridged_chain_id
pub bridged_chain_id: ChainId,
/// owner
pub owner: RewardsAccountOwner,
}
impl<LaneId: Decode + Encode> RewardsAccountParams<LaneId> {
/// Create a new instance of `RewardsAccountParams`.
pub const fn new(
lane_id: LaneId,
bridged_chain_id: ChainId,
owner: RewardsAccountOwner,
) -> Self {
Self { lane_id, bridged_chain_id, owner }
}
}
impl<LaneId> sp_runtime::TypeId for RewardsAccountParams<LaneId> {
const TYPE_ID: [u8; 4] = *b"brap";
}
pub(crate) struct RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>(
PhantomData<(AccountId, RewardBalance, LaneId)>,
);
impl<AccountId, RewardBalance, LaneId> StorageDoubleMapKeyProvider
for RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>
where
AccountId: 'static + Codec + EncodeLike + Send + Sync,
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
LaneId: Codec + EncodeLike + Send + Sync,
{
const MAP_NAME: &'static str = "RelayerRewards";
type Hasher1 = Blake2_128Concat;
type Key1 = AccountId;
type Hasher2 = Identity;
type Key2 = RewardsAccountParams<LaneId>;
type Value = RewardBalance;
}
pub(crate) type RelayerRewardsKeyProviderOf<T, I, LaneId> = RelayerRewardsKeyProvider<
<T as frame_system::Config>::AccountId,
<T as Config<I>>::RewardBalance,
LaneId,
>;
#[frame_support::storage_alias]
pub(crate) type RelayerRewards<T: Config<I>, I: 'static, LaneId> = StorageDoubleMap<
Pallet<T, I>,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher1,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key1,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher2,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key2,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Value,
OptionQuery,
>;
/// Reward account generator for `v0`.
pub struct PayRewardFromAccount<Account, LaneId>(PhantomData<(Account, LaneId)>);
impl<Account, LaneId> PayRewardFromAccount<Account, LaneId>
where
Account: Decode + Encode,
LaneId: Decode + Encode,
{
/// Return account that pays rewards based on the provided parameters.
pub fn rewards_account(params: RewardsAccountParams<LaneId>) -> Account {
params.into_sub_account_truncating(b"rewards-account")
}
}
}
/// This migration updates `RelayerRewards` where `RewardsAccountParams` was used as the key with
/// `lane_id` as the first attribute, which affects `into_sub_account_truncating`. We are migrating
/// this key to use the new `RewardsAccountParams` where `lane_id` is the last attribute.
pub mod v1 {
use super::*;
use crate::{Config, Pallet};
use bp_messages::LaneIdType;
use bp_relayers::RewardsAccountParams;
use bp_runtime::StorageDoubleMapKeyProvider;
use codec::{Codec, EncodeLike};
use core::marker::PhantomData;
use frame_support::{
pallet_prelude::OptionQuery, traits::UncheckedOnRuntimeUpgrade, Blake2_128Concat, Identity,
};
use sp_arithmetic::traits::Zero;
pub(crate) struct RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>(
PhantomData<(AccountId, RewardBalance, LaneId)>,
);
impl<AccountId, RewardBalance, LaneId> StorageDoubleMapKeyProvider
for RelayerRewardsKeyProvider<AccountId, RewardBalance, LaneId>
where
AccountId: 'static + Codec + EncodeLike + Send + Sync,
RewardBalance: 'static + Codec + EncodeLike + Send + Sync,
LaneId: Codec + EncodeLike + Send + Sync,
{
const MAP_NAME: &'static str = "RelayerRewards";
type Hasher1 = Blake2_128Concat;
type Key1 = AccountId;
type Hasher2 = Identity;
type Key2 = v1::RewardsAccountParams<LaneId>;
type Value = RewardBalance;
}
pub(crate) type RelayerRewardsKeyProviderOf<T, I, LaneId> = RelayerRewardsKeyProvider<
<T as frame_system::Config>::AccountId,
<T as Config<I>>::RewardBalance,
LaneId,
>;
#[frame_support::storage_alias]
pub(crate) type RelayerRewards<T: Config<I>, I: 'static, LaneId> = StorageDoubleMap<
Pallet<T, I>,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher1,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key1,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Hasher2,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Key2,
<RelayerRewardsKeyProviderOf<T, I, LaneId> as StorageDoubleMapKeyProvider>::Value,
OptionQuery,
>;
// Copy of `Pallet::<T, I>::register_relayer_reward` compatible with v1.
fn register_relayer_reward_for_v1<
T: Config<I>,
I: 'static,
LaneId: LaneIdType + Send + Sync,
>(
rewards_account_params: v1::RewardsAccountParams<LaneId>,
relayer: &T::AccountId,
reward_balance: T::RewardBalance,
) {
use sp_runtime::Saturating;
if reward_balance.is_zero() {
return
}
v1::RelayerRewards::<T, I, LaneId>::mutate(
relayer,
rewards_account_params,
|old_reward: &mut Option<T::RewardBalance>| {
let new_reward =
old_reward.unwrap_or_else(Zero::zero).saturating_add(reward_balance);
*old_reward = Some(new_reward);
log::trace!(
target: crate::LOG_TARGET,
"Relayer {:?} can now claim reward for serving payer {:?}: {:?}",
relayer,
rewards_account_params,
new_reward,
);
},
);
}
/// Migrates the pallet storage to v1.
pub struct UncheckedMigrationV0ToV1<T, I, LaneId>(PhantomData<(T, I, LaneId)>);
#[cfg(feature = "try-runtime")]
const LOG_TARGET: &str = "runtime::bridge-relayers-migration";
impl<T: Config<I>, I: 'static, LaneId: LaneIdType + Send + Sync> UncheckedOnRuntimeUpgrade
for UncheckedMigrationV0ToV1<T, I, LaneId>
{
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
// list all rewards (we cannot do this as one step because of `drain` limitation)
let mut rewards_to_migrate =
Vec::with_capacity(v0::RelayerRewards::<T, I, LaneId>::iter().count());
for (key1, key2, reward) in v0::RelayerRewards::<T, I, LaneId>::drain() {
rewards_to_migrate.push((key1, key2, reward));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
}
// re-register rewards with new format of `RewardsAccountParams`.
for (key1, key2, reward) in rewards_to_migrate {
// expand old key
let v0::RewardsAccountParams { owner, lane_id, bridged_chain_id } = key2;
// re-register reward
register_relayer_reward_for_v1::<T, I, LaneId>(
v1::RewardsAccountParams::new(lane_id, bridged_chain_id, owner),
&key1,
reward,
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
}
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
use codec::Encode;
use frame_support::BoundedBTreeMap;
use sp_runtime::traits::ConstU32;
// collect actual rewards
let mut rewards: BoundedBTreeMap<
(T::AccountId, LaneId),
T::RewardBalance,
ConstU32<{ u32::MAX }>,
> = BoundedBTreeMap::new();
for (key1, key2, reward) in v0::RelayerRewards::<T, I, LaneId>::iter() {
log::info!(target: LOG_TARGET, "Reward to migrate: {key1:?}::{key2:?} - {reward:?}");
rewards = rewards
.try_mutate(|inner| {
inner
.entry((key1.clone(), key2.lane_id))
.and_modify(|value| *value += reward)
.or_insert(reward);
})
.unwrap();
}
log::info!(target: LOG_TARGET, "Found total rewards to migrate: {rewards:?}");
Ok(rewards.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
use codec::Decode;
use frame_support::BoundedBTreeMap;
use sp_runtime::traits::ConstU32;
let rewards_before: BoundedBTreeMap<
(T::AccountId, LaneId),
T::RewardBalance,
ConstU32<{ u32::MAX }>,
> = Decode::decode(&mut &state[..]).unwrap();
// collect migrated rewards
let mut rewards_after: BoundedBTreeMap<
(T::AccountId, LaneId),
T::RewardBalance,
ConstU32<{ u32::MAX }>,
> = BoundedBTreeMap::new();
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::iter() {
log::info!(target: LOG_TARGET, "Migrated rewards: {key1:?}::{key2:?} - {reward:?}");
rewards_after = rewards_after
.try_mutate(|inner| {
inner
.entry((key1.clone(), *key2.lane_id()))
.and_modify(|value| *value += reward)
.or_insert(reward);
})
.unwrap();
}
log::info!(target: LOG_TARGET, "Found total migrated rewards: {rewards_after:?}");
frame_support::ensure!(
rewards_before == rewards_after,
"The rewards were not migrated correctly!."
);
log::info!(target: LOG_TARGET, "migrated all.");
Ok(())
}
}
/// [`UncheckedMigrationV0ToV1`] wrapped in a
/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
/// migration is only performed when on-chain version is 0.
pub type MigrationToV1<T, I, LaneId> = frame_support::migrations::VersionedMigration<
0,
1,
UncheckedMigrationV0ToV1<T, I, LaneId>,
Pallet<T, I>,
<T as frame_system::Config>::DbWeight,
>;
}
/// The pallet in version 1 only supported rewards collected under the key of
/// `RewardsAccountParams`. This migration essentially converts existing `RewardsAccountParams` keys
/// to the generic type `T::Reward`.
pub mod v2 {
use super::*;
#[cfg(feature = "try-runtime")]
use crate::RelayerRewards;
use crate::{Config, Pallet};
use bp_messages::LaneIdType;
use bp_relayers::RewardsAccountParams;
use core::marker::PhantomData;
use frame_support::traits::UncheckedOnRuntimeUpgrade;
/// Migrates the pallet storage to v2.
pub struct UncheckedMigrationV1ToV2<T, I, LaneId>(PhantomData<(T, I, LaneId)>);
#[cfg(feature = "try-runtime")]
const LOG_TARGET: &str = "runtime::bridge-relayers-migration";
impl<T: Config<I>, I: 'static, LaneId: LaneIdType + Send + Sync> UncheckedOnRuntimeUpgrade
for UncheckedMigrationV1ToV2<T, I, LaneId>
where
<T as Config<I>>::Reward: From<RewardsAccountParams<LaneId>>,
{
fn on_runtime_upgrade() -> Weight {
let mut weight = T::DbWeight::get().reads(1);
// list all rewards (we cannot do this as one step because of `drain` limitation)
let mut rewards_to_migrate =
Vec::with_capacity(v1::RelayerRewards::<T, I, LaneId>::iter().count());
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::drain() {
rewards_to_migrate.push((key1, key2, reward));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
}
// re-register rewards with new format.
for (key1, key2, reward) in rewards_to_migrate {
// convert old key to the new
let new_key2: T::Reward = key2.into();
// re-register reward (drained above)
Pallet::<T, I>::register_relayer_reward(new_key2, &key1, reward);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
}
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
use codec::Encode;
use frame_support::BoundedBTreeMap;
use sp_runtime::traits::ConstU32;
// collect actual rewards
let mut rewards: BoundedBTreeMap<
(T::AccountId, Vec<u8>),
T::RewardBalance,
ConstU32<{ u32::MAX }>,
> = BoundedBTreeMap::new();
for (key1, key2, reward) in v1::RelayerRewards::<T, I, LaneId>::iter() {
let new_key2: T::Reward = key2.into();
log::info!(target: LOG_TARGET, "Reward to migrate: {key1:?}::{key2:?}->{new_key2:?} - {reward:?}");
rewards = rewards
.try_mutate(|inner| {
inner
.entry((key1.clone(), new_key2.encode()))
.and_modify(|value| *value += reward)
.or_insert(reward);
})
.unwrap();
}
log::info!(target: LOG_TARGET, "Found total rewards to migrate: {rewards:?}");
Ok(rewards.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
use codec::{Decode, Encode};
use frame_support::BoundedBTreeMap;
use sp_runtime::traits::ConstU32;
let rewards_before: BoundedBTreeMap<
(T::AccountId, Vec<u8>),
T::RewardBalance,
ConstU32<{ u32::MAX }>,
> = Decode::decode(&mut &state[..]).unwrap();
// collect migrated rewards
let mut rewards_after: BoundedBTreeMap<
(T::AccountId, Vec<u8>),
T::RewardBalance,
ConstU32<{ u32::MAX }>,
> = BoundedBTreeMap::new();
for (key1, key2, reward) in v2::RelayerRewards::<T, I>::iter() {
log::info!(target: LOG_TARGET, "Migrated rewards: {key1:?}::{key2:?} - {reward:?}");
rewards_after = rewards_after
.try_mutate(|inner| {
inner
.entry((key1.clone(), key2.encode()))
.and_modify(|value| *value += reward)
.or_insert(reward);
})
.unwrap();
}
log::info!(target: LOG_TARGET, "Found total migrated rewards: {rewards_after:?}");
frame_support::ensure!(
rewards_before == rewards_after,
"The rewards were not migrated correctly!."
);
log::info!(target: LOG_TARGET, "migrated all.");
Ok(())
}
}
/// [`UncheckedMigrationV1ToV2`] wrapped in a
/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
/// migration is only performed when on-chain version is 1.
pub type MigrationToV2<T, I, LaneId> = frame_support::migrations::VersionedMigration<
1,
2,
UncheckedMigrationV1ToV2<T, I, LaneId>,
Pallet<T, I>,
<T as frame_system::Config>::DbWeight,
>;
}
......@@ -18,51 +18,193 @@
use crate as pallet_bridge_relayers;
use bp_messages::LaneId;
use bp_header_chain::ChainWithGrandpa;
use bp_messages::{
target_chain::{DispatchMessage, MessageDispatch},
ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce,
};
use bp_parachains::SingleParaStoredHeaderDataBuilder;
use bp_relayers::{
PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams,
};
use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, Parachain};
use codec::Encode;
use frame_support::{
derive_impl, parameter_types, traits::fungible::Mutate, weights::RuntimeDbWeight,
derive_impl, parameter_types,
traits::fungible::Mutate,
weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight},
};
use pallet_transaction_payment::Multiplier;
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, ConstU32, ConstU64, ConstU8},
BuildStorage, FixedPointNumber, Perquintill, StateVersion,
};
use sp_runtime::BuildStorage;
pub type AccountId = u64;
pub type Balance = u64;
pub type BlockNumber = u64;
/// Account identifier at `ThisChain`.
pub type ThisChainAccountId = u64;
/// Balance at `ThisChain`.
pub type ThisChainBalance = u64;
/// Block number at `ThisChain`.
pub type ThisChainBlockNumber = u32;
/// Hash at `ThisChain`.
pub type ThisChainHash = H256;
/// Hasher at `ThisChain`.
pub type ThisChainHasher = BlakeTwo256;
/// Header of `ThisChain`.
pub type ThisChainHeader = sp_runtime::generic::Header<ThisChainBlockNumber, ThisChainHasher>;
/// Block of `ThisChain`.
pub type ThisChainBlock = frame_system::mocking::MockBlockU32<TestRuntime>;
/// Account identifier at the `BridgedChain`.
pub type BridgedChainAccountId = u128;
/// Balance at the `BridgedChain`.
pub type BridgedChainBalance = u128;
/// Block number at the `BridgedChain`.
pub type BridgedChainBlockNumber = u32;
/// Hash at the `BridgedChain`.
pub type BridgedChainHash = H256;
/// Hasher at the `BridgedChain`.
pub type BridgedChainHasher = BlakeTwo256;
/// Header of the `BridgedChain`.
pub type BridgedChainHeader =
sp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
/// Bridged chain id used in tests.
pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg";
/// Maximal extrinsic size at the `BridgedChain`.
pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024;
/// Lane identifier type used for tests.
pub type TestLaneIdType = HashedLaneId;
/// Lane that we're using in tests.
pub fn test_lane_id() -> TestLaneIdType {
TestLaneIdType::try_new(1, 2).unwrap()
}
/// Reward measurement type.
pub type RewardBalance = u64;
/// Underlying chain of `ThisChain`.
pub struct ThisUnderlyingChain;
impl Chain for ThisUnderlyingChain {
const ID: ChainId = *b"tuch";
type BlockNumber = ThisChainBlockNumber;
type Hash = ThisChainHash;
type Hasher = ThisChainHasher;
type Header = ThisChainHeader;
type AccountId = ThisChainAccountId;
type Balance = ThisChainBalance;
type Nonce = u32;
type Signature = sp_runtime::MultiSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl ChainWithMessages for ThisUnderlyingChain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
}
/// Underlying chain of `BridgedChain`.
pub struct BridgedUnderlyingParachain;
impl Chain for BridgedUnderlyingParachain {
const ID: ChainId = TEST_BRIDGED_CHAIN_ID;
type BlockNumber = BridgedChainBlockNumber;
type Hash = BridgedChainHash;
type Hasher = BridgedChainHasher;
type Header = BridgedChainHeader;
type AccountId = BridgedChainAccountId;
type Balance = BridgedChainBalance;
type Nonce = u32;
type Signature = sp_runtime::MultiSignature;
const STATE_VERSION: StateVersion = StateVersion::V1;
fn max_extrinsic_size() -> u32 {
BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE
}
fn max_extrinsic_weight() -> Weight {
Weight::zero()
}
}
impl ChainWithGrandpa for BridgedUnderlyingParachain {
const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "";
const MAX_AUTHORITIES_COUNT: u32 = 16;
const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 8;
const MAX_MANDATORY_HEADER_SIZE: u32 = 256;
const AVERAGE_HEADER_SIZE: u32 = 64;
}
impl ChainWithMessages for BridgedUnderlyingParachain {
const WITH_CHAIN_MESSAGES_PALLET_NAME: &'static str = "";
const MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX: MessageNonce = 16;
const MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX: MessageNonce = 1000;
}
impl Parachain for BridgedUnderlyingParachain {
const PARACHAIN_ID: u32 = 42;
const MAX_HEADER_SIZE: u32 = 1_024;
}
pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
AccountId,
BlockNumber,
ThisChainAccountId,
ThisChainBlockNumber,
Balances,
ReserveId,
Stake,
Lease,
>;
type Block = frame_system::mocking::MockBlock<TestRuntime>;
frame_support::construct_runtime! {
pub enum TestRuntime
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Event<T>},
Relayers: pallet_bridge_relayers::{Pallet, Call, Event<T>},
System: frame_system,
Utility: pallet_utility,
Balances: pallet_balances,
TransactionPayment: pallet_transaction_payment,
BridgeRelayers: pallet_bridge_relayers,
BridgeGrandpa: pallet_bridge_grandpa,
BridgeParachains: pallet_bridge_parachains,
BridgeMessages: pallet_bridge_messages,
}
}
parameter_types! {
pub const BridgedParasPalletName: &'static str = "Paras";
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
pub const ExistentialDeposit: Balance = 1;
pub const ExistentialDeposit: ThisChainBalance = 1;
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
pub const Stake: Balance = 1_000;
pub const Lease: BlockNumber = 8;
pub const Stake: ThisChainBalance = 1_000;
pub const Lease: ThisChainBlockNumber = 8;
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
pub const TransactionBaseFee: ThisChainBalance = 0;
pub const TransactionByteFee: ThisChainBalance = 1;
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000);
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000u128);
pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value();
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for TestRuntime {
type Block = Block;
type AccountData = pallet_balances::AccountData<Balance>;
type Block = ThisChainBlock;
// TODO: remove when https://github.com/paritytech/polkadot-sdk/pull/4543 merged
type BlockHashCount = ConstU32<10>;
type AccountData = pallet_balances::AccountData<ThisChainBalance>;
type DbWeight = DbWeight;
}
......@@ -72,58 +214,148 @@ impl pallet_balances::Config for TestRuntime {
type AccountStore = System;
}
impl pallet_utility::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}
#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)]
impl pallet_transaction_payment::Config for TestRuntime {
type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter<Balances, ()>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = IdentityFee<ThisChainBalance>;
type LengthToFee = ConstantMultiplier<ThisChainBalance, TransactionByteFee>;
type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment<
TestRuntime,
TargetBlockFullness,
AdjustmentVariable,
MinimumMultiplier,
MaximumMultiplier,
>;
type RuntimeEvent = RuntimeEvent;
}
impl pallet_bridge_grandpa::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgedChain = BridgedUnderlyingParachain;
type MaxFreeHeadersPerBlock = ConstU32<4>;
type FreeHeadersInterval = ConstU32<1_024>;
type HeadersToKeep = ConstU32<8>;
type WeightInfo = pallet_bridge_grandpa::weights::BridgeWeight<TestRuntime>;
}
impl pallet_bridge_parachains::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type BridgesGrandpaPalletInstance = ();
type ParasPalletName = BridgedParasPalletName;
type ParaStoredHeaderDataBuilder =
SingleParaStoredHeaderDataBuilder<BridgedUnderlyingParachain>;
type HeadsToKeep = ConstU32<8>;
type MaxParaHeadDataSize = ConstU32<1024>;
type WeightInfo = pallet_bridge_parachains::weights::BridgeWeight<TestRuntime>;
}
impl pallet_bridge_messages::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = pallet_bridge_messages::weights::BridgeWeight<TestRuntime>;
type OutboundPayload = Vec<u8>;
type InboundPayload = Vec<u8>;
type LaneId = TestLaneIdType;
type DeliveryPayments = ();
type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter<
TestRuntime,
(),
(),
ConstU64<100_000>,
>;
type OnMessagesDelivered = ();
type MessageDispatch = DummyMessageDispatch;
type ThisChain = ThisUnderlyingChain;
type BridgedChain = BridgedUnderlyingParachain;
type BridgedHeaderChain = BridgeGrandpa;
}
impl pallet_bridge_relayers::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
type Reward = Balance;
type RewardBalance = RewardBalance;
type Reward = RewardsAccountParams<pallet_bridge_messages::LaneIdOf<TestRuntime, ()>>;
type PaymentProcedure = TestPaymentProcedure;
type StakeAndSlash = TestStakeAndSlash;
type Balance = ThisChainBalance;
type WeightInfo = ();
}
#[cfg(feature = "runtime-benchmarks")]
impl pallet_bridge_relayers::benchmarking::Config for TestRuntime {
fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Balance) {
let rewards_account =
bp_relayers::PayRewardFromAccount::<Balances, AccountId>::rewards_account(
account_params,
);
Self::deposit_account(rewards_account, reward);
fn bench_reward() -> Self::Reward {
RewardsAccountParams::new(
TestLaneIdType::default(),
*b"test",
RewardsAccountOwner::ThisChain,
)
}
fn deposit_account(account: Self::AccountId, balance: Self::Reward) {
Balances::mint_into(&account, balance.saturating_add(ExistentialDeposit::get())).unwrap();
fn prepare_rewards_account(
account_params: RewardsAccountParams<TestLaneIdType>,
reward: Self::RewardBalance,
) -> Option<ThisChainAccountId> {
let rewards_account = PayRewardFromAccount::<
Balances,
ThisChainAccountId,
TestLaneIdType,
RewardBalance,
>::rewards_account(account_params);
Self::deposit_account(rewards_account, reward.into());
Some(REGULAR_RELAYER2)
}
}
/// Message lane that we're using in tests.
pub const TEST_REWARDS_ACCOUNT_PARAMS: RewardsAccountParams =
RewardsAccountParams::new(LaneId([0, 0, 0, 0]), *b"test", RewardsAccountOwner::ThisChain);
fn deposit_account(account: Self::AccountId, balance: Self::Balance) {
frame_support::assert_ok!(Balances::mint_into(
&account,
balance.saturating_add(ExistentialDeposit::get())
));
}
}
/// Regular relayer that may receive rewards.
pub const REGULAR_RELAYER: AccountId = 1;
pub const REGULAR_RELAYER: ThisChainAccountId = 1;
/// Regular relayer that may receive rewards.
pub const REGULAR_RELAYER2: ThisChainAccountId = 3;
/// Relayer that can't receive rewards.
pub const FAILING_RELAYER: AccountId = 2;
pub const FAILING_RELAYER: ThisChainAccountId = 2;
/// Relayer that is able to register.
pub const REGISTER_RELAYER: AccountId = 42;
pub const REGISTER_RELAYER: ThisChainAccountId = 42;
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
pub struct TestPaymentProcedure;
impl TestPaymentProcedure {
pub fn rewards_account(params: RewardsAccountParams) -> AccountId {
PayRewardFromAccount::<(), AccountId>::rewards_account(params)
pub fn rewards_account(params: RewardsAccountParams<TestLaneIdType>) -> ThisChainAccountId {
PayRewardFromAccount::<(), ThisChainAccountId, TestLaneIdType, RewardBalance>::rewards_account(
params,
)
}
}
impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
impl PaymentProcedure<ThisChainAccountId, RewardsAccountParams<TestLaneIdType>, RewardBalance>
for TestPaymentProcedure
{
type Error = ();
type Beneficiary = ThisChainAccountId;
fn pay_reward(
relayer: &AccountId,
_lane_id: RewardsAccountParams,
_reward: Balance,
relayer: &ThisChainAccountId,
_reward_kind: RewardsAccountParams<TestLaneIdType>,
_reward: RewardBalance,
_beneficiary: Self::Beneficiary,
) -> Result<(), Self::Error> {
match *relayer {
FAILING_RELAYER => Err(()),
......@@ -132,6 +364,47 @@ impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
}
}
/// Dummy message dispatcher.
pub struct DummyMessageDispatch;
impl DummyMessageDispatch {
pub fn deactivate(lane: TestLaneIdType) {
frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false);
}
}
impl MessageDispatch for DummyMessageDispatch {
type DispatchPayload = Vec<u8>;
type DispatchLevelResult = ();
type LaneId = TestLaneIdType;
fn is_active(lane: Self::LaneId) -> bool {
frame_support::storage::unhashed::take::<bool>(&(b"inactive", lane).encode()[..]) !=
Some(false)
}
fn dispatch_weight(
_message: &mut DispatchMessage<Self::DispatchPayload, Self::LaneId>,
) -> Weight {
Weight::zero()
}
fn dispatch(
_: DispatchMessage<Self::DispatchPayload, Self::LaneId>,
) -> MessageDispatchResult<Self::DispatchLevelResult> {
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
}
}
/// Reward account params that we are using in tests.
pub fn test_reward_account_param() -> RewardsAccountParams<TestLaneIdType> {
RewardsAccountParams::new(
TestLaneIdType::try_new(1, 2).unwrap(),
*b"test",
RewardsAccountOwner::ThisChain,
)
}
/// Return test externalities to use in tests.
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::<TestRuntime>::default().build_storage().unwrap();
......
......@@ -14,37 +14,43 @@
// 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/>.
//! Code that allows relayers pallet to be used as a payment mechanism for the messages pallet.
//! Code that allows relayers pallet to be used as a payment mechanism for
//! the `pallet-bridge-messages` pallet using `RewardsAccountParams`.
use crate::{Config, Pallet};
use alloc::collections::vec_deque::VecDeque;
use bp_messages::{
source_chain::{DeliveryConfirmationPayments, RelayersRewards},
LaneId, MessageNonce,
MessageNonce,
};
pub use bp_relayers::PayRewardFromAccount;
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
use bp_runtime::Chain;
use core::{marker::PhantomData, ops::RangeInclusive};
use frame_support::{sp_runtime::SaturatedConversion, traits::Get};
use pallet_bridge_messages::LaneIdOf;
use sp_arithmetic::traits::{Saturating, Zero};
use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
/// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism
/// for the messages pallet.
pub struct DeliveryConfirmationPaymentsAdapter<T, MI, DeliveryReward>(
PhantomData<(T, MI, DeliveryReward)>,
/// for the `pallet-bridge-messages` pallet and using `RewardsAccountParams`.
pub struct DeliveryConfirmationPaymentsAdapter<T, MI, RI, DeliveryReward>(
PhantomData<(T, MI, RI, DeliveryReward)>,
);
impl<T, MI, DeliveryReward> DeliveryConfirmationPayments<T::AccountId>
for DeliveryConfirmationPaymentsAdapter<T, MI, DeliveryReward>
impl<T, MI, RI, DeliveryReward> DeliveryConfirmationPayments<T::AccountId, LaneIdOf<T, MI>>
for DeliveryConfirmationPaymentsAdapter<T, MI, RI, DeliveryReward>
where
T: Config + pallet_bridge_messages::Config<MI>,
T: Config<RI> + pallet_bridge_messages::Config<MI>,
MI: 'static,
DeliveryReward: Get<T::Reward>,
RI: 'static,
DeliveryReward: Get<T::RewardBalance>,
<T as Config<RI>>::Reward: From<RewardsAccountParams<LaneIdOf<T, MI>>>,
{
type Error = &'static str;
fn pay_reward(
lane_id: LaneId,
lane_id: LaneIdOf<T, MI>,
messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>,
confirmation_relayer: &T::AccountId,
received_range: &RangeInclusive<bp_messages::MessageNonce>,
......@@ -53,7 +59,7 @@ where
bp_messages::calc_relayers_rewards::<T::AccountId>(messages_relayers, received_range);
let rewarded_relayers = relayers_rewards.len();
register_relayers_rewards::<T>(
register_relayers_rewards::<T, RI, MI>(
confirmation_relayer,
relayers_rewards,
RewardsAccountParams::new(
......@@ -69,21 +75,28 @@ where
}
// Update rewards to given relayers, optionally rewarding confirmation relayer.
fn register_relayers_rewards<T: Config>(
fn register_relayers_rewards<
T: Config<RI> + pallet_bridge_messages::Config<MI>,
RI: 'static,
MI: 'static,
>(
confirmation_relayer: &T::AccountId,
relayers_rewards: RelayersRewards<T::AccountId>,
lane_id: RewardsAccountParams,
delivery_fee: T::Reward,
) {
lane_id: RewardsAccountParams<LaneIdOf<T, MI>>,
delivery_fee: T::RewardBalance,
) where
<T as Config<RI>>::Reward: From<RewardsAccountParams<LaneIdOf<T, MI>>>,
{
// reward every relayer except `confirmation_relayer`
let mut confirmation_relayer_reward = T::Reward::zero();
let mut confirmation_relayer_reward = T::RewardBalance::zero();
for (relayer, messages) in relayers_rewards {
// sane runtime configurations guarantee that the number of messages will be below
// `u32::MAX`
let relayer_reward = T::Reward::saturated_from(messages).saturating_mul(delivery_fee);
let relayer_reward =
T::RewardBalance::saturated_from(messages).saturating_mul(delivery_fee);
if relayer != *confirmation_relayer {
Pallet::<T>::register_relayer_reward(lane_id, &relayer, relayer_reward);
Pallet::<T, RI>::register_relayer_reward(lane_id.into(), &relayer, relayer_reward);
} else {
confirmation_relayer_reward =
confirmation_relayer_reward.saturating_add(relayer_reward);
......@@ -91,8 +104,8 @@ fn register_relayers_rewards<T: Config>(
}
// finally - pay reward to confirmation relayer
Pallet::<T>::register_relayer_reward(
lane_id,
Pallet::<T, RI>::register_relayer_reward(
lane_id.into(),
confirmation_relayer,
confirmation_relayer_reward,
);
......@@ -102,31 +115,37 @@ fn register_relayers_rewards<T: Config>(
mod tests {
use super::*;
use crate::{mock::*, RelayerRewards};
const RELAYER_1: AccountId = 1;
const RELAYER_2: AccountId = 2;
const RELAYER_3: AccountId = 3;
fn relayers_rewards() -> RelayersRewards<AccountId> {
use bp_messages::LaneIdType;
use bp_relayers::PaymentProcedure;
use frame_support::{
assert_ok,
traits::fungible::{Inspect, Mutate},
};
const RELAYER_1: ThisChainAccountId = 1;
const RELAYER_2: ThisChainAccountId = 2;
const RELAYER_3: ThisChainAccountId = 3;
fn relayers_rewards() -> RelayersRewards<ThisChainAccountId> {
vec![(RELAYER_1, 2), (RELAYER_2, 3)].into_iter().collect()
}
#[test]
fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() {
run_test(|| {
register_relayers_rewards::<TestRuntime>(
register_relayers_rewards::<TestRuntime, (), ()>(
&RELAYER_2,
relayers_rewards(),
TEST_REWARDS_ACCOUNT_PARAMS,
test_reward_account_param(),
50,
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_REWARDS_ACCOUNT_PARAMS),
RelayerRewards::<TestRuntime>::get(RELAYER_1, test_reward_account_param()),
Some(100)
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_REWARDS_ACCOUNT_PARAMS),
RelayerRewards::<TestRuntime>::get(RELAYER_2, test_reward_account_param()),
Some(150)
);
});
......@@ -135,25 +154,73 @@ mod tests {
#[test]
fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() {
run_test(|| {
register_relayers_rewards::<TestRuntime>(
register_relayers_rewards::<TestRuntime, (), ()>(
&RELAYER_3,
relayers_rewards(),
TEST_REWARDS_ACCOUNT_PARAMS,
test_reward_account_param(),
50,
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_REWARDS_ACCOUNT_PARAMS),
RelayerRewards::<TestRuntime>::get(RELAYER_1, test_reward_account_param()),
Some(100)
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_REWARDS_ACCOUNT_PARAMS),
RelayerRewards::<TestRuntime>::get(RELAYER_2, test_reward_account_param()),
Some(150)
);
assert_eq!(
RelayerRewards::<TestRuntime>::get(RELAYER_3, TEST_REWARDS_ACCOUNT_PARAMS),
RelayerRewards::<TestRuntime>::get(RELAYER_3, test_reward_account_param()),
None
);
});
}
#[test]
fn pay_reward_from_account_actually_pays_reward() {
type Balances = pallet_balances::Pallet<TestRuntime>;
type PayLaneRewardFromAccount =
PayRewardFromAccount<Balances, ThisChainAccountId, TestLaneIdType, RewardBalance>;
run_test(|| {
let in_lane_0 = RewardsAccountParams::new(
TestLaneIdType::try_new(1, 2).unwrap(),
*b"test",
RewardsAccountOwner::ThisChain,
);
let out_lane_1 = RewardsAccountParams::new(
TestLaneIdType::try_new(1, 3).unwrap(),
*b"test",
RewardsAccountOwner::BridgedChain,
);
let in_lane0_rewards_account = PayLaneRewardFromAccount::rewards_account(in_lane_0);
let out_lane1_rewards_account = PayLaneRewardFromAccount::rewards_account(out_lane_1);
assert_ok!(Balances::mint_into(&in_lane0_rewards_account, 200));
assert_ok!(Balances::mint_into(&out_lane1_rewards_account, 100));
assert_eq!(Balances::balance(&in_lane0_rewards_account), 200);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
assert_eq!(Balances::balance(&1), 0);
assert_eq!(Balances::balance(&2), 0);
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100, 1_u64));
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 100);
assert_eq!(Balances::balance(&1), 100);
assert_eq!(Balances::balance(&2), 0);
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, out_lane_1, 100, 1_u64));
assert_eq!(Balances::balance(&in_lane0_rewards_account), 100);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
assert_eq!(Balances::balance(&1), 200);
assert_eq!(Balances::balance(&2), 0);
assert_ok!(PayLaneRewardFromAccount::pay_reward(&1, in_lane_0, 100, 2_u64));
assert_eq!(Balances::balance(&in_lane0_rewards_account), 0);
assert_eq!(Balances::balance(&out_lane1_rewards_account), 0);
assert_eq!(Balances::balance(&1), 200);
assert_eq!(Balances::balance(&2), 100);
});
}
}
......@@ -17,11 +17,11 @@
//! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash`
//! mechanism of the relayers pallet.
use bp_relayers::{ExplicitOrAccountParams, PayRewardFromAccount, StakeAndSlash};
use bp_relayers::StakeAndSlash;
use codec::Codec;
use core::{fmt::Debug, marker::PhantomData};
use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency};
use sp_runtime::{traits::Get, DispatchError, DispatchResult};
use sp_std::{fmt::Debug, marker::PhantomData};
/// `StakeAndSlash` that works with `NamedReservableCurrency` and uses named
/// reservations.
......@@ -55,18 +55,13 @@ where
fn repatriate_reserved(
relayer: &AccountId,
beneficiary: ExplicitOrAccountParams<AccountId>,
beneficiary: &AccountId,
amount: Currency::Balance,
) -> Result<Currency::Balance, DispatchError> {
let beneficiary_account = match beneficiary {
ExplicitOrAccountParams::Explicit(account) => account,
ExplicitOrAccountParams::Params(params) =>
PayRewardFromAccount::<(), AccountId>::rewards_account(params),
};
Currency::repatriate_reserved_named(
&ReserveId::get(),
relayer,
&beneficiary_account,
&beneficiary,
amount,
BalanceStatus::Free,
)
......@@ -77,10 +72,12 @@ where
mod tests {
use super::*;
use crate::mock::*;
use bp_relayers::ExplicitOrAccountParams;
use frame_support::traits::fungible::Mutate;
use sp_runtime::traits::IdentifyAccount;
fn test_stake() -> Balance {
fn test_stake() -> ThisChainBalance {
Stake::get()
}
......@@ -130,7 +127,7 @@ mod tests {
#[test]
fn repatriate_reserved_works() {
run_test(|| {
let beneficiary = TEST_REWARDS_ACCOUNT_PARAMS;
let beneficiary = test_reward_account_param();
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
let mut expected_balance = ExistentialDeposit::get();
......@@ -139,7 +136,7 @@ mod tests {
assert_eq!(
TestStakeAndSlash::repatriate_reserved(
&1,
ExplicitOrAccountParams::Params(beneficiary),
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
test_stake()
),
Ok(test_stake())
......@@ -155,7 +152,7 @@ mod tests {
assert_eq!(
TestStakeAndSlash::repatriate_reserved(
&2,
ExplicitOrAccountParams::Params(beneficiary),
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
test_stake()
),
Ok(test_stake() - test_stake() / 3)
......@@ -171,7 +168,7 @@ mod tests {
assert_eq!(
TestStakeAndSlash::repatriate_reserved(
&3,
ExplicitOrAccountParams::Params(beneficiary),
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
test_stake()
),
Ok(0)
......@@ -186,14 +183,14 @@ mod tests {
#[test]
fn repatriate_reserved_doesnt_work_when_beneficiary_account_is_missing() {
run_test(|| {
let beneficiary = TEST_REWARDS_ACCOUNT_PARAMS;
let beneficiary = test_reward_account_param();
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
Balances::mint_into(&3, test_stake() * 2).unwrap();
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
assert!(TestStakeAndSlash::repatriate_reserved(
&3,
ExplicitOrAccountParams::Params(beneficiary),
&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
test_stake()
)
.is_err());
......
......@@ -42,15 +42,16 @@
#![allow(unused_imports)]
#![allow(missing_docs)]
use core::marker::PhantomData;
use frame_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_bridge_relayers.
pub trait WeightInfo {
fn claim_rewards() -> Weight;
fn claim_rewards_to() -> Weight;
fn register() -> Weight;
fn deregister() -> Weight;
fn slash_and_deregister() -> Weight;
......@@ -85,6 +86,29 @@ impl<T: frame_system::Config> WeightInfo for BridgeWeight<T> {
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
/// mode: MaxEncodedLen)
///
/// Storage: Balances TotalIssuance (r:1 w:0)
///
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
/// MaxEncodedLen)
///
/// Storage: System Account (r:1 w:1)
///
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
/// MaxEncodedLen)
fn claim_rewards_to() -> Weight {
// Proof Size summary in bytes:
// Measured: `294`
// Estimated: `8592`
// Minimum execution time: 77_614 nanoseconds.
Weight::from_parts(79_987_000, 8592)
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
///
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
......@@ -184,6 +208,29 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: BridgeRelayers RelayerRewards (r:1 w:1)
///
/// Proof: BridgeRelayers RelayerRewards (max_values: None, max_size: Some(65), added: 2540,
/// mode: MaxEncodedLen)
///
/// Storage: Balances TotalIssuance (r:1 w:0)
///
/// Proof: Balances TotalIssuance (max_values: Some(1), max_size: Some(8), added: 503, mode:
/// MaxEncodedLen)
///
/// Storage: System Account (r:1 w:1)
///
/// Proof: System Account (max_values: None, max_size: Some(104), added: 2579, mode:
/// MaxEncodedLen)
fn claim_rewards_to() -> Weight {
// Proof Size summary in bytes:
// Measured: `294`
// Estimated: `8592`
// Minimum execution time: 77_614 nanoseconds.
Weight::from_parts(79_987_000, 8592)
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: BridgeRelayers RegisteredRelayers (r:1 w:1)
///
/// Proof: BridgeRelayers RegisteredRelayers (max_values: None, max_size: Some(64), added: 2539,
......
......@@ -23,13 +23,13 @@ use frame_support::pallet_prelude::Weight;
/// Extended weight info.
pub trait WeightInfoExt: WeightInfo {
/// Returns weight, that needs to be added to the pre-dispatch weight of message delivery call,
/// if `RefundBridgedParachainMessages` signed extension is deployed at runtime level.
/// if `BridgeRelayersTransactionExtension` signed extension is deployed at runtime level.
fn receive_messages_proof_overhead_from_runtime() -> Weight {
Self::slash_and_deregister().max(Self::register_relayer_reward())
}
/// Returns weight, that needs to be added to the pre-dispatch weight of message delivery
/// confirmation call, if `RefundBridgedParachainMessages` signed extension is deployed at
/// confirmation call, if `BridgeRelayersTransactionExtension` signed extension is deployed at
/// runtime level.
fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
Self::register_relayer_reward()
......
......@@ -16,11 +16,9 @@ log = { workspace = true }
scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true }
# Bridge dependencies
bp-xcm-bridge-hub-router = { workspace = true }
# Substrate Dependencies
frame-benchmarking = { optional = true, workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
......@@ -29,7 +27,6 @@ sp-runtime = { workspace = true }
sp-std = { workspace = true }
# Polkadot Dependencies
xcm = { workspace = true }
xcm-builder = { workspace = true }
......@@ -59,6 +56,7 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
......