mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 02:51:01 +00:00
Reintroduce msg dispatch status reporting (#2027)
* Use an actual Result inside MessageDispatchResult We need this in order to distinguish between Ok and Err * Revert #1660 * Fixes + simplifications * Implement review suggestions
This commit is contained in:
committed by
Bastian Köcher
parent
4d29753f73
commit
3b968a2aba
@@ -242,7 +242,7 @@ mod tests {
|
|||||||
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
||||||
LaneId, MessageKey,
|
LaneId, MessageKey,
|
||||||
};
|
};
|
||||||
use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchResult;
|
use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchError;
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use pallet_bridge_messages::OutboundLanes;
|
use pallet_bridge_messages::OutboundLanes;
|
||||||
use xcm_executor::XcmExecutor;
|
use xcm_executor::XcmExecutor;
|
||||||
@@ -352,8 +352,8 @@ mod tests {
|
|||||||
let dispatch_result =
|
let dispatch_result =
|
||||||
FromRialtoMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
FromRialtoMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
dispatch_result.dispatch_level_result,
|
dispatch_result.dispatch_result,
|
||||||
XcmBlobMessageDispatchResult::NotDispatched(_),
|
Err(XcmBlobMessageDispatchError::NotDispatched(_)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,8 +366,8 @@ mod tests {
|
|||||||
let dispatch_result =
|
let dispatch_result =
|
||||||
FromRialtoMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
FromRialtoMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
dispatch_result.dispatch_level_result,
|
dispatch_result.dispatch_result,
|
||||||
XcmBlobMessageDispatchResult::NotDispatched(_),
|
Err(XcmBlobMessageDispatchError::NotDispatched(_)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -853,7 +853,7 @@ mod tests {
|
|||||||
LaneId, MessageKey,
|
LaneId, MessageKey,
|
||||||
};
|
};
|
||||||
use bridge_runtime_common::{
|
use bridge_runtime_common::{
|
||||||
integrity::check_additional_signed, messages_xcm_extension::XcmBlobMessageDispatchResult,
|
integrity::check_additional_signed, messages_xcm_extension::XcmBlobMessageDispatchError,
|
||||||
};
|
};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use pallet_bridge_messages::OutboundLanes;
|
use pallet_bridge_messages::OutboundLanes;
|
||||||
@@ -928,8 +928,8 @@ mod tests {
|
|||||||
let dispatch_result =
|
let dispatch_result =
|
||||||
FromMillauMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
FromMillauMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
dispatch_result.dispatch_level_result,
|
dispatch_result.dispatch_result,
|
||||||
XcmBlobMessageDispatchResult::NotDispatched(_),
|
Err(XcmBlobMessageDispatchError::NotDispatched(_)),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ mod tests {
|
|||||||
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch},
|
||||||
LaneId, MessageKey,
|
LaneId, MessageKey,
|
||||||
};
|
};
|
||||||
use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchResult;
|
use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchError;
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use pallet_bridge_messages::OutboundLanes;
|
use pallet_bridge_messages::OutboundLanes;
|
||||||
use xcm_executor::XcmExecutor;
|
use xcm_executor::XcmExecutor;
|
||||||
@@ -269,8 +269,8 @@ mod tests {
|
|||||||
let dispatch_result =
|
let dispatch_result =
|
||||||
FromMillauMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
FromMillauMessageDispatch::dispatch(&AccountId::from([0u8; 32]), incoming_message);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
dispatch_result.dispatch_level_result,
|
dispatch_result.dispatch_result,
|
||||||
XcmBlobMessageDispatchResult::NotDispatched(_),
|
Err(XcmBlobMessageDispatchError::NotDispatched(_)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,8 +307,10 @@ pub fn check_message_lane_weights<C: Chain, T: frame_system::Config>(
|
|||||||
messages::target::maximal_incoming_message_dispatch_weight(C::max_extrinsic_weight()),
|
messages::target::maximal_incoming_message_dispatch_weight(C::max_extrinsic_weight()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let max_incoming_inbound_lane_data_proof_size =
|
let max_incoming_inbound_lane_data_proof_size = InboundLaneData::<()>::encoded_size_hint_u32(
|
||||||
InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _);
|
this_chain_max_unrewarded_relayers as _,
|
||||||
|
this_chain_max_unconfirmed_messages as _,
|
||||||
|
);
|
||||||
pallet_bridge_messages::ensure_able_to_receive_confirmation::<Weights<T>>(
|
pallet_bridge_messages::ensure_able_to_receive_confirmation::<Weights<T>>(
|
||||||
C::max_extrinsic_size(),
|
C::max_extrinsic_size(),
|
||||||
C::max_extrinsic_weight(),
|
C::max_extrinsic_weight(),
|
||||||
|
|||||||
@@ -39,9 +39,8 @@ pub type XcmAsPlainPayload = sp_std::prelude::Vec<u8>;
|
|||||||
|
|
||||||
/// Message dispatch result type for single message
|
/// Message dispatch result type for single message
|
||||||
#[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)]
|
#[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)]
|
||||||
pub enum XcmBlobMessageDispatchResult {
|
pub enum XcmBlobMessageDispatchError {
|
||||||
InvalidPayload,
|
InvalidPayload,
|
||||||
Dispatched,
|
|
||||||
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
|
NotDispatched(#[codec(skip)] Option<DispatchBlobError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ impl<
|
|||||||
for XcmBlobMessageDispatch<SourceBridgeHubChain, TargetBridgeHubChain, BlobDispatcher, Weights>
|
for XcmBlobMessageDispatch<SourceBridgeHubChain, TargetBridgeHubChain, BlobDispatcher, Weights>
|
||||||
{
|
{
|
||||||
type DispatchPayload = XcmAsPlainPayload;
|
type DispatchPayload = XcmAsPlainPayload;
|
||||||
type DispatchLevelResult = XcmBlobMessageDispatchResult;
|
type DispatchError = XcmBlobMessageDispatchError;
|
||||||
|
|
||||||
fn dispatch_weight(message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
|
fn dispatch_weight(message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
|
||||||
match message.data.payload {
|
match message.data.payload {
|
||||||
@@ -80,7 +79,7 @@ impl<
|
|||||||
fn dispatch(
|
fn dispatch(
|
||||||
_relayer_account: &AccountIdOf<SourceBridgeHubChain>,
|
_relayer_account: &AccountIdOf<SourceBridgeHubChain>,
|
||||||
message: DispatchMessage<Self::DispatchPayload>,
|
message: DispatchMessage<Self::DispatchPayload>,
|
||||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
) -> MessageDispatchResult<Self::DispatchError> {
|
||||||
let payload = match message.data.payload {
|
let payload = match message.data.payload {
|
||||||
Ok(payload) => payload,
|
Ok(payload) => payload,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -92,7 +91,7 @@ impl<
|
|||||||
);
|
);
|
||||||
return MessageDispatchResult {
|
return MessageDispatchResult {
|
||||||
unspent_weight: Weight::zero(),
|
unspent_weight: Weight::zero(),
|
||||||
dispatch_level_result: XcmBlobMessageDispatchResult::InvalidPayload,
|
dispatch_result: Err(XcmBlobMessageDispatchError::InvalidPayload),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -103,7 +102,7 @@ impl<
|
|||||||
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}",
|
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob was ok - message_nonce: {:?}",
|
||||||
message.key.nonce
|
message.key.nonce
|
||||||
);
|
);
|
||||||
XcmBlobMessageDispatchResult::Dispatched
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
@@ -111,10 +110,13 @@ impl<
|
|||||||
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}",
|
"[XcmBlobMessageDispatch] DispatchBlob::dispatch_blob failed, error: {:?} - message_nonce: {:?}",
|
||||||
e, message.key.nonce
|
e, message.key.nonce
|
||||||
);
|
);
|
||||||
XcmBlobMessageDispatchResult::NotDispatched(Some(e))
|
Err(XcmBlobMessageDispatchError::NotDispatched(Some(e)))
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result }
|
MessageDispatchResult {
|
||||||
|
unspent_weight: Weight::zero(),
|
||||||
|
dispatch_result: dispatch_level_result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitvec = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
|
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false }
|
||||||
log = { version = "0.4.17", default-features = false }
|
log = { version = "0.4.17", default-features = false }
|
||||||
num-traits = { version = "0.2", default-features = false }
|
num-traits = { version = "0.2", default-features = false }
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ benchmarks_instance_pallet! {
|
|||||||
inbound_lane_data: InboundLaneData {
|
inbound_lane_data: InboundLaneData {
|
||||||
relayers: vec![UnrewardedRelayer {
|
relayers: vec![UnrewardedRelayer {
|
||||||
relayer: relayer_id.clone(),
|
relayer: relayer_id.clone(),
|
||||||
messages: DeliveredMessages::new(1),
|
messages: DeliveredMessages::new(1, true),
|
||||||
}].into_iter().collect(),
|
}].into_iter().collect(),
|
||||||
last_confirmed_nonce: 0,
|
last_confirmed_nonce: 0,
|
||||||
},
|
},
|
||||||
@@ -333,8 +333,8 @@ benchmarks_instance_pallet! {
|
|||||||
total_messages: 2,
|
total_messages: 2,
|
||||||
last_delivered_nonce: 2,
|
last_delivered_nonce: 2,
|
||||||
};
|
};
|
||||||
let mut delivered_messages = DeliveredMessages::new(1);
|
let mut delivered_messages = DeliveredMessages::new(1, true);
|
||||||
delivered_messages.note_dispatched_message();
|
delivered_messages.note_dispatched_message(true);
|
||||||
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
|
||||||
lane: T::bench_lane_id(),
|
lane: T::bench_lane_id(),
|
||||||
inbound_lane_data: InboundLaneData {
|
inbound_lane_data: InboundLaneData {
|
||||||
@@ -379,11 +379,11 @@ benchmarks_instance_pallet! {
|
|||||||
relayers: vec![
|
relayers: vec![
|
||||||
UnrewardedRelayer {
|
UnrewardedRelayer {
|
||||||
relayer: relayer1_id.clone(),
|
relayer: relayer1_id.clone(),
|
||||||
messages: DeliveredMessages::new(1),
|
messages: DeliveredMessages::new(1, true),
|
||||||
},
|
},
|
||||||
UnrewardedRelayer {
|
UnrewardedRelayer {
|
||||||
relayer: relayer2_id.clone(),
|
relayer: relayer2_id.clone(),
|
||||||
messages: DeliveredMessages::new(2),
|
messages: DeliveredMessages::new(2, true),
|
||||||
},
|
},
|
||||||
].into_iter().collect(),
|
].into_iter().collect(),
|
||||||
last_confirmed_nonce: 0,
|
last_confirmed_nonce: 0,
|
||||||
@@ -451,7 +451,7 @@ fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
|
|||||||
inbound_lane_storage.set_data(InboundLaneData {
|
inbound_lane_storage.set_data(InboundLaneData {
|
||||||
relayers: vec![UnrewardedRelayer {
|
relayers: vec![UnrewardedRelayer {
|
||||||
relayer: T::bridged_relayer_id(),
|
relayer: T::bridged_relayer_id(),
|
||||||
messages: DeliveredMessages::new(nonce),
|
messages: DeliveredMessages::new(nonce, true),
|
||||||
}]
|
}]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ impl<T: Config<I>, I: 'static> MaxEncodedLen for StoredInboundLaneData<T, I> {
|
|||||||
fn max_encoded_len() -> usize {
|
fn max_encoded_len() -> usize {
|
||||||
InboundLaneData::<T::InboundRelayer>::encoded_size_hint(
|
InboundLaneData::<T::InboundRelayer>::encoded_size_hint(
|
||||||
T::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize,
|
T::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize,
|
||||||
|
T::MaxUnconfirmedMessagesAtInboundLane::get() as usize,
|
||||||
)
|
)
|
||||||
.unwrap_or(usize::MAX)
|
.unwrap_or(usize::MAX)
|
||||||
}
|
}
|
||||||
@@ -154,6 +155,9 @@ impl<S: InboundLaneStorage> InboundLane<S> {
|
|||||||
// overlap.
|
// overlap.
|
||||||
match data.relayers.front_mut() {
|
match data.relayers.front_mut() {
|
||||||
Some(entry) if entry.messages.begin < new_confirmed_nonce => {
|
Some(entry) if entry.messages.begin < new_confirmed_nonce => {
|
||||||
|
entry.messages.dispatch_results = entry.messages.dispatch_results
|
||||||
|
[(new_confirmed_nonce + 1 - entry.messages.begin) as usize..]
|
||||||
|
.to_bitvec();
|
||||||
entry.messages.begin = new_confirmed_nonce + 1;
|
entry.messages.begin = new_confirmed_nonce + 1;
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
@@ -170,7 +174,7 @@ impl<S: InboundLaneStorage> InboundLane<S> {
|
|||||||
relayer_at_this_chain: &AccountId,
|
relayer_at_this_chain: &AccountId,
|
||||||
nonce: MessageNonce,
|
nonce: MessageNonce,
|
||||||
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
|
message_data: DispatchMessageData<Dispatch::DispatchPayload>,
|
||||||
) -> ReceivalResult<Dispatch::DispatchLevelResult> {
|
) -> ReceivalResult<Dispatch::DispatchError> {
|
||||||
let mut data = self.storage.data();
|
let mut data = self.storage.data();
|
||||||
let is_correct_message = nonce == data.last_delivered_nonce() + 1;
|
let is_correct_message = nonce == data.last_delivered_nonce() + 1;
|
||||||
if !is_correct_message {
|
if !is_correct_message {
|
||||||
@@ -198,19 +202,20 @@ impl<S: InboundLaneStorage> InboundLane<S> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// now let's update inbound lane storage
|
// now let's update inbound lane storage
|
||||||
let push_new = match data.relayers.back_mut() {
|
match data.relayers.back_mut() {
|
||||||
Some(entry) if entry.relayer == *relayer_at_bridged_chain => {
|
Some(entry) if entry.relayer == *relayer_at_bridged_chain => {
|
||||||
entry.messages.note_dispatched_message();
|
entry.messages.note_dispatched_message(dispatch_result.dispatch_result.is_ok());
|
||||||
false
|
},
|
||||||
|
_ => {
|
||||||
|
data.relayers.push_back(UnrewardedRelayer {
|
||||||
|
relayer: relayer_at_bridged_chain.clone(),
|
||||||
|
messages: DeliveredMessages::new(
|
||||||
|
nonce,
|
||||||
|
dispatch_result.dispatch_result.is_ok(),
|
||||||
|
),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
_ => true,
|
|
||||||
};
|
};
|
||||||
if push_new {
|
|
||||||
data.relayers.push_back(UnrewardedRelayer {
|
|
||||||
relayer: (*relayer_at_bridged_chain).clone(),
|
|
||||||
messages: DeliveredMessages::new(nonce),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.storage.set_data(data);
|
self.storage.set_data(data);
|
||||||
|
|
||||||
ReceivalResult::Dispatched(dispatch_result)
|
ReceivalResult::Dispatched(dispatch_result)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ use bp_messages::{
|
|||||||
use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, Size};
|
use bp_runtime::{BasicOperatingMode, ChainId, OwnedBridgeModule, Size};
|
||||||
use codec::{Decode, Encode, MaxEncodedLen};
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get};
|
use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get};
|
||||||
use sp_runtime::traits::UniqueSaturatedFrom;
|
use sp_runtime::{traits::UniqueSaturatedFrom, SaturatedConversion};
|
||||||
use sp_std::{cell::RefCell, marker::PhantomData, prelude::*};
|
use sp_std::{cell::RefCell, marker::PhantomData, prelude::*};
|
||||||
|
|
||||||
mod inbound_lane;
|
mod inbound_lane;
|
||||||
@@ -527,7 +527,7 @@ pub mod pallet {
|
|||||||
MessagesReceived(
|
MessagesReceived(
|
||||||
Vec<
|
Vec<
|
||||||
ReceivedMessages<
|
ReceivedMessages<
|
||||||
<T::MessageDispatch as MessageDispatch<T::AccountId>>::DispatchLevelResult,
|
<T::MessageDispatch as MessageDispatch<T::AccountId>>::DispatchError,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
),
|
),
|
||||||
@@ -807,15 +807,19 @@ impl<T: Config<I>, I: 'static> RuntimeInboundLaneStorage<T, I> {
|
|||||||
/// `receive_messages_proof` call, because the actual inbound lane state is smaller than the
|
/// `receive_messages_proof` call, because the actual inbound lane state is smaller than the
|
||||||
/// maximal configured.
|
/// maximal configured.
|
||||||
///
|
///
|
||||||
/// Maximal inbound lane state set size is configured by the
|
/// Maximal inbound lane state size is computed using the
|
||||||
/// `MaxUnrewardedRelayerEntriesAtInboundLane` constant from the pallet configuration. The PoV
|
/// `MaxUnrewardedRelayerEntriesAtInboundLane` and `MaxUnconfirmedMessagesAtInboundLane`
|
||||||
/// of the call includes the maximal size of inbound lane state. If the actual size is smaller,
|
/// constants from the pallet configuration. The PoV of the call includes the maximal size
|
||||||
/// we may subtract extra bytes from this component.
|
/// of the inbound lane state. If the actual size is smaller, we may subtract extra bytes
|
||||||
|
/// from this component.
|
||||||
pub fn extra_proof_size_bytes(&self) -> u64 {
|
pub fn extra_proof_size_bytes(&self) -> u64 {
|
||||||
let max_encoded_len = StoredInboundLaneData::<T, I>::max_encoded_len();
|
let max_encoded_len = StoredInboundLaneData::<T, I>::max_encoded_len();
|
||||||
let relayers_count = self.data().relayers.len();
|
let relayers_count = self.data().relayers.len();
|
||||||
|
let messages_count = self.data().relayers.iter().fold(0usize, |sum, relayer| {
|
||||||
|
sum.saturating_add(relayer.messages.total_messages().saturated_into::<usize>())
|
||||||
|
});
|
||||||
let actual_encoded_len =
|
let actual_encoded_len =
|
||||||
InboundLaneData::<T::InboundRelayer>::encoded_size_hint(relayers_count)
|
InboundLaneData::<T::InboundRelayer>::encoded_size_hint(relayers_count, messages_count)
|
||||||
.unwrap_or(usize::MAX);
|
.unwrap_or(usize::MAX);
|
||||||
max_encoded_len.saturating_sub(actual_encoded_len) as _
|
max_encoded_len.saturating_sub(actual_encoded_len) as _
|
||||||
}
|
}
|
||||||
@@ -946,6 +950,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use frame_system::{EventRecord, Pallet as System, Phase};
|
use frame_system::{EventRecord, Pallet as System, Phase};
|
||||||
use sp_runtime::DispatchError;
|
use sp_runtime::DispatchError;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
fn get_ready_for_events() {
|
fn get_ready_for_events() {
|
||||||
System::<TestRuntime>::set_block_number(1);
|
System::<TestRuntime>::set_block_number(1);
|
||||||
@@ -1003,7 +1008,7 @@ mod tests {
|
|||||||
last_confirmed_nonce: 1,
|
last_confirmed_nonce: 1,
|
||||||
relayers: vec![UnrewardedRelayer {
|
relayers: vec![UnrewardedRelayer {
|
||||||
relayer: 0,
|
relayer: 0,
|
||||||
messages: DeliveredMessages::new(1),
|
messages: DeliveredMessages::new(1, true),
|
||||||
}]
|
}]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -1023,13 +1028,44 @@ mod tests {
|
|||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: TestEvent::Messages(Event::MessagesDelivered {
|
event: TestEvent::Messages(Event::MessagesDelivered {
|
||||||
lane_id: TEST_LANE_ID,
|
lane_id: TEST_LANE_ID,
|
||||||
messages: DeliveredMessages::new(1),
|
messages: DeliveredMessages::new(1, true),
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
}],
|
}],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unrewarded_relayer_entry(msg_count: usize) -> UnrewardedRelayer<TestRelayer> {
|
||||||
|
UnrewardedRelayer {
|
||||||
|
relayer: 42u64,
|
||||||
|
messages: DeliveredMessages {
|
||||||
|
begin: 0,
|
||||||
|
end: msg_count as MessageNonce - 1,
|
||||||
|
dispatch_results: FromIterator::from_iter(vec![true; msg_count]),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unrewarded_relayers_vec(
|
||||||
|
entry_count: usize,
|
||||||
|
msg_count: usize,
|
||||||
|
) -> VecDeque<UnrewardedRelayer<TestRelayer>> {
|
||||||
|
if entry_count > msg_count {
|
||||||
|
panic!("unrewarded_relayers_vec(): expecting msg_count to be >= entry_count");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut unrewarded_relayers = vec![];
|
||||||
|
let mut available_msg_count = msg_count;
|
||||||
|
for _ in 0..entry_count - 1 {
|
||||||
|
unrewarded_relayers
|
||||||
|
.push(unrewarded_relayer_entry(std::cmp::min(1, available_msg_count)));
|
||||||
|
available_msg_count -= 1
|
||||||
|
}
|
||||||
|
unrewarded_relayers.push(unrewarded_relayer_entry(available_msg_count));
|
||||||
|
|
||||||
|
unrewarded_relayers.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pallet_rejects_transactions_if_halted() {
|
fn pallet_rejects_transactions_if_halted() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
@@ -1634,6 +1670,7 @@ mod tests {
|
|||||||
fn proof_size_refund_from_receive_messages_proof_works() {
|
fn proof_size_refund_from_receive_messages_proof_works() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize;
|
let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize;
|
||||||
|
let max_msgs = crate::mock::MaxUnconfirmedMessagesAtInboundLane::get() as usize;
|
||||||
|
|
||||||
// if there's maximal number of unrewarded relayer entries at the inbound lane, then
|
// if there's maximal number of unrewarded relayer entries at the inbound lane, then
|
||||||
// `proof_size` is unchanged in post-dispatch weight
|
// `proof_size` is unchanged in post-dispatch weight
|
||||||
@@ -1648,15 +1685,7 @@ mod tests {
|
|||||||
InboundLanes::<TestRuntime>::insert(
|
InboundLanes::<TestRuntime>::insert(
|
||||||
TEST_LANE_ID,
|
TEST_LANE_ID,
|
||||||
StoredInboundLaneData(InboundLaneData {
|
StoredInboundLaneData(InboundLaneData {
|
||||||
relayers: vec![
|
relayers: unrewarded_relayers_vec(max_entries, max_msgs),
|
||||||
UnrewardedRelayer {
|
|
||||||
relayer: 42,
|
|
||||||
messages: DeliveredMessages { begin: 0, end: 100 }
|
|
||||||
};
|
|
||||||
max_entries
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
last_confirmed_nonce: 0,
|
last_confirmed_nonce: 0,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1677,15 +1706,7 @@ mod tests {
|
|||||||
InboundLanes::<TestRuntime>::insert(
|
InboundLanes::<TestRuntime>::insert(
|
||||||
TEST_LANE_ID,
|
TEST_LANE_ID,
|
||||||
StoredInboundLaneData(InboundLaneData {
|
StoredInboundLaneData(InboundLaneData {
|
||||||
relayers: vec![
|
relayers: unrewarded_relayers_vec(max_entries - 1, max_msgs),
|
||||||
UnrewardedRelayer {
|
|
||||||
relayer: 42,
|
|
||||||
messages: DeliveredMessages { begin: 0, end: 100 }
|
|
||||||
};
|
|
||||||
max_entries - 1
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
last_confirmed_nonce: 0,
|
last_confirmed_nonce: 0,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1701,7 +1722,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
post_dispatch_weight.proof_size() < pre_dispatch_weight.proof_size(),
|
post_dispatch_weight.proof_size() < pre_dispatch_weight.proof_size(),
|
||||||
"Expected post-dispatch PoV {} to be less than pre-dispatch PoV {}",
|
"Expected post-dispatch PoV {} to be < than pre-dispatch PoV {}",
|
||||||
post_dispatch_weight.proof_size(),
|
post_dispatch_weight.proof_size(),
|
||||||
pre_dispatch_weight.proof_size(),
|
pre_dispatch_weight.proof_size(),
|
||||||
);
|
);
|
||||||
@@ -1717,8 +1738,8 @@ mod tests {
|
|||||||
|
|
||||||
// messages 1+2 are confirmed in 1 tx, message 3 in a separate tx
|
// messages 1+2 are confirmed in 1 tx, message 3 in a separate tx
|
||||||
// dispatch of message 2 has failed
|
// dispatch of message 2 has failed
|
||||||
let mut delivered_messages_1_and_2 = DeliveredMessages::new(1);
|
let mut delivered_messages_1_and_2 = DeliveredMessages::new(1, true);
|
||||||
delivered_messages_1_and_2.note_dispatched_message();
|
delivered_messages_1_and_2.note_dispatched_message(true);
|
||||||
let messages_1_and_2_proof = Ok((
|
let messages_1_and_2_proof = Ok((
|
||||||
TEST_LANE_ID,
|
TEST_LANE_ID,
|
||||||
InboundLaneData {
|
InboundLaneData {
|
||||||
@@ -1731,7 +1752,7 @@ mod tests {
|
|||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
let delivered_message_3 = DeliveredMessages::new(3);
|
let delivered_message_3 = DeliveredMessages::new(3, true);
|
||||||
let messages_3_proof = Ok((
|
let messages_3_proof = Ok((
|
||||||
TEST_LANE_ID,
|
TEST_LANE_ID,
|
||||||
InboundLaneData {
|
InboundLaneData {
|
||||||
@@ -2022,7 +2043,7 @@ mod tests {
|
|||||||
last_confirmed_nonce: 1,
|
last_confirmed_nonce: 1,
|
||||||
relayers: vec![UnrewardedRelayer {
|
relayers: vec![UnrewardedRelayer {
|
||||||
relayer: 0,
|
relayer: 0,
|
||||||
messages: DeliveredMessages::new(1),
|
messages: DeliveredMessages::new(1, true),
|
||||||
}]
|
}]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -2082,39 +2103,39 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn inbound_storage_extra_proof_size_bytes_works() {
|
fn inbound_storage_extra_proof_size_bytes_works() {
|
||||||
fn relayer_entry() -> UnrewardedRelayer<TestRelayer> {
|
let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize;
|
||||||
UnrewardedRelayer { relayer: 42u64, messages: DeliveredMessages { begin: 0, end: 100 } }
|
let max_msgs = crate::mock::MaxUnconfirmedMessagesAtInboundLane::get() as usize;
|
||||||
}
|
|
||||||
|
|
||||||
fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage<TestRuntime, ()> {
|
fn storage(
|
||||||
|
entry_count: usize,
|
||||||
|
msg_count: usize,
|
||||||
|
) -> RuntimeInboundLaneStorage<TestRuntime, ()> {
|
||||||
RuntimeInboundLaneStorage {
|
RuntimeInboundLaneStorage {
|
||||||
lane_id: Default::default(),
|
lane_id: Default::default(),
|
||||||
cached_data: RefCell::new(Some(InboundLaneData {
|
cached_data: RefCell::new(Some(InboundLaneData {
|
||||||
relayers: vec![relayer_entry(); relayer_entries].into_iter().collect(),
|
relayers: unrewarded_relayers_vec(entry_count, msg_count),
|
||||||
last_confirmed_nonce: 0,
|
last_confirmed_nonce: 0,
|
||||||
})),
|
})),
|
||||||
_phantom: Default::default(),
|
_phantom: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_entries = crate::mock::MaxUnrewardedRelayerEntriesAtInboundLane::get() as usize;
|
|
||||||
|
|
||||||
// when we have exactly `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers
|
// when we have exactly `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers
|
||||||
assert_eq!(storage(max_entries).extra_proof_size_bytes(), 0);
|
assert_eq!(storage(max_entries, max_msgs).extra_proof_size_bytes(), 0);
|
||||||
|
|
||||||
// when we have less than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers
|
// when we have less than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
storage(max_entries - 1).extra_proof_size_bytes(),
|
storage(max_entries - 1, max_msgs).extra_proof_size_bytes(),
|
||||||
relayer_entry().encode().len() as u64
|
unrewarded_relayer_entry(1).encoded_size() as u64
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
storage(max_entries - 2).extra_proof_size_bytes(),
|
storage(max_entries - 2, max_msgs).extra_proof_size_bytes(),
|
||||||
2 * relayer_entry().encode().len() as u64
|
2 * unrewarded_relayer_entry(1).encoded_size() as u64
|
||||||
);
|
);
|
||||||
|
|
||||||
// when we have more than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers
|
// when we have more than `MaxUnrewardedRelayerEntriesAtInboundLane` unrewarded relayers
|
||||||
// (shall not happen in practice)
|
// (shall not happen in practice)
|
||||||
assert_eq!(storage(max_entries + 1).extra_proof_size_bytes(), 0);
|
assert_eq!(storage(max_entries + 1, max_msgs).extra_proof_size_bytes(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
|
||||||
|
use bitvec::prelude::*;
|
||||||
use bp_messages::{
|
use bp_messages::{
|
||||||
calc_relayers_rewards,
|
calc_relayers_rewards,
|
||||||
source_chain::{DeliveryConfirmationPayments, LaneMessageVerifier, TargetHeaderChain},
|
source_chain::{DeliveryConfirmationPayments, LaneMessageVerifier, TargetHeaderChain},
|
||||||
@@ -62,13 +63,13 @@ pub struct TestPayload {
|
|||||||
///
|
///
|
||||||
/// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`,
|
/// Note: in correct code `dispatch_result.unspent_weight` will always be <= `declared_weight`,
|
||||||
/// but for test purposes we'll be making it larger than `declared_weight` sometimes.
|
/// but for test purposes we'll be making it larger than `declared_weight` sometimes.
|
||||||
pub dispatch_result: MessageDispatchResult<TestDispatchLevelResult>,
|
pub dispatch_result: MessageDispatchResult<TestDispatchError>,
|
||||||
/// Extra bytes that affect payload size.
|
/// Extra bytes that affect payload size.
|
||||||
pub extra: Vec<u8>,
|
pub extra: Vec<u8>,
|
||||||
}
|
}
|
||||||
pub type TestMessageFee = u64;
|
pub type TestMessageFee = u64;
|
||||||
pub type TestRelayer = u64;
|
pub type TestRelayer = u64;
|
||||||
pub type TestDispatchLevelResult = ();
|
pub type TestDispatchError = ();
|
||||||
|
|
||||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||||
@@ -412,7 +413,7 @@ pub struct TestMessageDispatch;
|
|||||||
|
|
||||||
impl MessageDispatch<AccountId> for TestMessageDispatch {
|
impl MessageDispatch<AccountId> for TestMessageDispatch {
|
||||||
type DispatchPayload = TestPayload;
|
type DispatchPayload = TestPayload;
|
||||||
type DispatchLevelResult = TestDispatchLevelResult;
|
type DispatchError = TestDispatchError;
|
||||||
|
|
||||||
fn dispatch_weight(message: &mut DispatchMessage<TestPayload>) -> Weight {
|
fn dispatch_weight(message: &mut DispatchMessage<TestPayload>) -> Weight {
|
||||||
match message.data.payload.as_ref() {
|
match message.data.payload.as_ref() {
|
||||||
@@ -424,7 +425,7 @@ impl MessageDispatch<AccountId> for TestMessageDispatch {
|
|||||||
fn dispatch(
|
fn dispatch(
|
||||||
_relayer_account: &AccountId,
|
_relayer_account: &AccountId,
|
||||||
message: DispatchMessage<TestPayload>,
|
message: DispatchMessage<TestPayload>,
|
||||||
) -> MessageDispatchResult<TestDispatchLevelResult> {
|
) -> MessageDispatchResult<TestDispatchError> {
|
||||||
match message.data.payload.as_ref() {
|
match message.data.payload.as_ref() {
|
||||||
Ok(payload) => payload.dispatch_result.clone(),
|
Ok(payload) => payload.dispatch_result.clone(),
|
||||||
Err(_) => dispatch_result(0),
|
Err(_) => dispatch_result(0),
|
||||||
@@ -459,12 +460,10 @@ pub const fn message_payload(id: u64, declared_weight: u64) -> TestPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns message dispatch result with given unspent weight.
|
/// Returns message dispatch result with given unspent weight.
|
||||||
pub const fn dispatch_result(
|
pub const fn dispatch_result(unspent_weight: u64) -> MessageDispatchResult<TestDispatchError> {
|
||||||
unspent_weight: u64,
|
|
||||||
) -> MessageDispatchResult<TestDispatchLevelResult> {
|
|
||||||
MessageDispatchResult {
|
MessageDispatchResult {
|
||||||
unspent_weight: Weight::from_parts(unspent_weight, 0),
|
unspent_weight: Weight::from_parts(unspent_weight, 0),
|
||||||
dispatch_level_result: (),
|
dispatch_result: Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +473,14 @@ pub fn unrewarded_relayer(
|
|||||||
end: MessageNonce,
|
end: MessageNonce,
|
||||||
relayer: TestRelayer,
|
relayer: TestRelayer,
|
||||||
) -> UnrewardedRelayer<TestRelayer> {
|
) -> UnrewardedRelayer<TestRelayer> {
|
||||||
UnrewardedRelayer { relayer, messages: DeliveredMessages { begin, end } }
|
UnrewardedRelayer {
|
||||||
|
relayer,
|
||||||
|
messages: DeliveredMessages {
|
||||||
|
begin,
|
||||||
|
end,
|
||||||
|
dispatch_results: bitvec![u8, Msb0; 1; (end + 1).saturating_sub(begin) as _],
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return test externalities to use in tests.
|
/// Return test externalities to use in tests.
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
|
||||||
|
use bitvec::prelude::*;
|
||||||
use bp_messages::{
|
use bp_messages::{
|
||||||
DeliveredMessages, LaneId, MessageNonce, MessagePayload, OutboundLaneData, UnrewardedRelayer,
|
DeliveredMessages, DispatchResultsBitVec, LaneId, MessageNonce, MessagePayload,
|
||||||
|
OutboundLaneData, UnrewardedRelayer,
|
||||||
};
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
weights::{RuntimeDbWeight, Weight},
|
weights::{RuntimeDbWeight, Weight},
|
||||||
@@ -65,6 +67,9 @@ pub enum ReceivalConfirmationResult {
|
|||||||
/// The unrewarded relayers vec contains non-consecutive entries. May be a result of invalid
|
/// The unrewarded relayers vec contains non-consecutive entries. May be a result of invalid
|
||||||
/// bridged chain storage.
|
/// bridged chain storage.
|
||||||
NonConsecutiveUnrewardedRelayerEntries,
|
NonConsecutiveUnrewardedRelayerEntries,
|
||||||
|
/// The unrewarded relayers vec contains entry with mismatched number of dispatch results. May
|
||||||
|
/// be a result of invalid bridged chain storage.
|
||||||
|
InvalidNumberOfDispatchResults,
|
||||||
/// The chain has more messages that need to be confirmed than there is in the proof.
|
/// The chain has more messages that need to be confirmed than there is in the proof.
|
||||||
TryingToConfirmMoreMessagesThanExpected(MessageNonce),
|
TryingToConfirmMoreMessagesThanExpected(MessageNonce),
|
||||||
}
|
}
|
||||||
@@ -124,9 +129,14 @@ impl<S: OutboundLaneStorage> OutboundLane<S> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = ensure_unrewarded_relayers_are_correct(latest_delivered_nonce, relayers) {
|
let dispatch_results = match extract_dispatch_results(
|
||||||
return e
|
data.latest_received_nonce,
|
||||||
}
|
latest_delivered_nonce,
|
||||||
|
relayers,
|
||||||
|
) {
|
||||||
|
Ok(dispatch_results) => dispatch_results,
|
||||||
|
Err(extract_error) => return extract_error,
|
||||||
|
};
|
||||||
|
|
||||||
let prev_latest_received_nonce = data.latest_received_nonce;
|
let prev_latest_received_nonce = data.latest_received_nonce;
|
||||||
data.latest_received_nonce = latest_delivered_nonce;
|
data.latest_received_nonce = latest_delivered_nonce;
|
||||||
@@ -135,6 +145,7 @@ impl<S: OutboundLaneStorage> OutboundLane<S> {
|
|||||||
ReceivalConfirmationResult::ConfirmedMessages(DeliveredMessages {
|
ReceivalConfirmationResult::ConfirmedMessages(DeliveredMessages {
|
||||||
begin: prev_latest_received_nonce + 1,
|
begin: prev_latest_received_nonce + 1,
|
||||||
end: latest_delivered_nonce,
|
end: latest_delivered_nonce,
|
||||||
|
dispatch_results,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,30 +180,34 @@ impl<S: OutboundLaneStorage> OutboundLane<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies unrewarded relayers vec.
|
/// Extract new dispatch results from the unrewarded relayers vec.
|
||||||
///
|
///
|
||||||
/// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged
|
/// Returns `Err(_)` if unrewarded relayers vec contains invalid data, meaning that the bridged
|
||||||
/// chain has invalid runtime storage.
|
/// chain has invalid runtime storage.
|
||||||
fn ensure_unrewarded_relayers_are_correct<RelayerId>(
|
fn extract_dispatch_results<RelayerId>(
|
||||||
|
prev_latest_received_nonce: MessageNonce,
|
||||||
latest_received_nonce: MessageNonce,
|
latest_received_nonce: MessageNonce,
|
||||||
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
|
relayers: &VecDeque<UnrewardedRelayer<RelayerId>>,
|
||||||
) -> Result<(), ReceivalConfirmationResult> {
|
) -> Result<DispatchResultsBitVec, ReceivalConfirmationResult> {
|
||||||
let mut last_entry_end: Option<MessageNonce> = None;
|
// the only caller of this functions checks that the
|
||||||
|
// prev_latest_received_nonce..=latest_received_nonce is valid, so we're ready to accept
|
||||||
|
// messages in this range => with_capacity call must succeed here or we'll be unable to receive
|
||||||
|
// confirmations at all
|
||||||
|
let mut received_dispatch_result =
|
||||||
|
BitVec::with_capacity((latest_received_nonce - prev_latest_received_nonce + 1) as _);
|
||||||
|
let mut expected_entry_begin = relayers.front().map(|entry| entry.messages.begin);
|
||||||
for entry in relayers {
|
for entry in relayers {
|
||||||
// unrewarded relayer entry must have at least 1 unconfirmed message
|
// unrewarded relayer entry must have at least 1 unconfirmed message
|
||||||
// (guaranteed by the `InboundLane::receive_message()`)
|
// (guaranteed by the `InboundLane::receive_message()`)
|
||||||
if entry.messages.end < entry.messages.begin {
|
if entry.messages.total_messages() == 0 {
|
||||||
return Err(ReceivalConfirmationResult::EmptyUnrewardedRelayerEntry)
|
return Err(ReceivalConfirmationResult::EmptyUnrewardedRelayerEntry)
|
||||||
}
|
}
|
||||||
// every entry must confirm range of messages that follows previous entry range
|
// every entry must confirm range of messages that follows previous entry range
|
||||||
// (guaranteed by the `InboundLane::receive_message()`)
|
// (guaranteed by the `InboundLane::receive_message()`)
|
||||||
if let Some(last_entry_end) = last_entry_end {
|
if expected_entry_begin != Some(entry.messages.begin) {
|
||||||
let expected_entry_begin = last_entry_end.checked_add(1);
|
return Err(ReceivalConfirmationResult::NonConsecutiveUnrewardedRelayerEntries)
|
||||||
if expected_entry_begin != Some(entry.messages.begin) {
|
|
||||||
return Err(ReceivalConfirmationResult::NonConsecutiveUnrewardedRelayerEntries)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
last_entry_end = Some(entry.messages.end);
|
expected_entry_begin = entry.messages.end.checked_add(1);
|
||||||
// entry can't confirm messages larger than `inbound_lane_data.latest_received_nonce()`
|
// entry can't confirm messages larger than `inbound_lane_data.latest_received_nonce()`
|
||||||
// (guaranteed by the `InboundLane::receive_message()`)
|
// (guaranteed by the `InboundLane::receive_message()`)
|
||||||
if entry.messages.end > latest_received_nonce {
|
if entry.messages.end > latest_received_nonce {
|
||||||
@@ -201,9 +216,30 @@ fn ensure_unrewarded_relayers_are_correct<RelayerId>(
|
|||||||
// this is detected now
|
// this is detected now
|
||||||
return Err(ReceivalConfirmationResult::FailedToConfirmFutureMessages)
|
return Err(ReceivalConfirmationResult::FailedToConfirmFutureMessages)
|
||||||
}
|
}
|
||||||
|
// entry must have single dispatch result for every message
|
||||||
|
// (guaranteed by the `InboundLane::receive_message()`)
|
||||||
|
if entry.messages.dispatch_results.len() as MessageNonce != entry.messages.total_messages()
|
||||||
|
{
|
||||||
|
return Err(ReceivalConfirmationResult::InvalidNumberOfDispatchResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we know that the entry is valid
|
||||||
|
// => let's check if it brings new confirmations
|
||||||
|
let new_messages_begin =
|
||||||
|
sp_std::cmp::max(entry.messages.begin, prev_latest_received_nonce + 1);
|
||||||
|
if entry.messages.end < new_messages_begin {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we know that entry brings new confirmations
|
||||||
|
// => let's extract dispatch results
|
||||||
|
received_dispatch_result.extend_from_bitslice(
|
||||||
|
&entry.messages.dispatch_results
|
||||||
|
[(new_messages_begin - entry.messages.begin) as usize..],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(received_dispatch_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -228,7 +264,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn delivered_messages(nonces: RangeInclusive<MessageNonce>) -> DeliveredMessages {
|
fn delivered_messages(nonces: RangeInclusive<MessageNonce>) -> DeliveredMessages {
|
||||||
DeliveredMessages { begin: *nonces.start(), end: *nonces.end() }
|
DeliveredMessages {
|
||||||
|
begin: *nonces.start(),
|
||||||
|
end: *nonces.end(),
|
||||||
|
dispatch_results: bitvec![u8, Msb0; 1; (nonces.end() - nonces.start() + 1) as _],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_3_messages_confirmation_fails(
|
fn assert_3_messages_confirmation_fails(
|
||||||
@@ -361,6 +401,20 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn confirm_delivery_fails_if_number_of_dispatch_results_in_entry_is_invalid() {
|
||||||
|
let mut relayers: VecDeque<_> = unrewarded_relayers(1..=1)
|
||||||
|
.into_iter()
|
||||||
|
.chain(unrewarded_relayers(2..=2).into_iter())
|
||||||
|
.chain(unrewarded_relayers(3..=3).into_iter())
|
||||||
|
.collect();
|
||||||
|
relayers[0].messages.dispatch_results.clear();
|
||||||
|
assert_eq!(
|
||||||
|
assert_3_messages_confirmation_fails(3, &relayers),
|
||||||
|
ReceivalConfirmationResult::InvalidNumberOfDispatchResults,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prune_messages_works() {
|
fn prune_messages_works() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bitvec = { version = "1", default-features = false, features = ["alloc"] }
|
||||||
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive", "bit-vec"] }
|
codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive", "bit-vec"] }
|
||||||
scale-info = { version = "2.5.0", default-features = false, features = ["bit-vec", "derive"] }
|
scale-info = { version = "2.5.0", default-features = false, features = ["bit-vec", "derive"] }
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||||
@@ -28,6 +29,7 @@ hex-literal = "0.4"
|
|||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = [
|
std = [
|
||||||
|
"bitvec/std",
|
||||||
"bp-runtime/std",
|
"bp-runtime/std",
|
||||||
"codec/std",
|
"codec/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
// RuntimeApi generated functions
|
// RuntimeApi generated functions
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
|
use bitvec::prelude::*;
|
||||||
use bp_runtime::{BasicOperatingMode, OperatingMode};
|
use bp_runtime::{BasicOperatingMode, OperatingMode};
|
||||||
use codec::{Decode, Encode, MaxEncodedLen};
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
use frame_support::RuntimeDebug;
|
use frame_support::RuntimeDebug;
|
||||||
@@ -156,18 +157,68 @@ impl<RelayerId> Default for InboundLaneData<RelayerId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<RelayerId> InboundLaneData<RelayerId> {
|
impl<RelayerId> InboundLaneData<RelayerId> {
|
||||||
|
fn dispatch_results_encoded_size_hint(
|
||||||
|
relayers_entries: usize,
|
||||||
|
message_count: usize,
|
||||||
|
) -> Option<usize>
|
||||||
|
where
|
||||||
|
RelayerId: MaxEncodedLen,
|
||||||
|
{
|
||||||
|
// The worst-case scenario for the bitvecs size is the one in which we have as many relayer
|
||||||
|
// entries as possible taking an extra 1 byte slot with just 1 bit of actual information.
|
||||||
|
// For example:
|
||||||
|
// 11111111 1-------
|
||||||
|
// 11111111 1-------
|
||||||
|
// 1-------
|
||||||
|
// 1-------
|
||||||
|
|
||||||
|
// If there are less msgs than relayer entries, in the worst case, each dispatch result
|
||||||
|
// belongs to a different relayer slot. This means 1 byte for the len prefix and 1 byte
|
||||||
|
// for the actual data.
|
||||||
|
if relayers_entries >= message_count {
|
||||||
|
return relayers_entries.checked_add(message_count)
|
||||||
|
}
|
||||||
|
|
||||||
|
let msgs_per_byte = 8;
|
||||||
|
// At the begining each relayer slot has 1 message, using 1 byte
|
||||||
|
let mut num_result_bytes = relayers_entries;
|
||||||
|
// Then we add batches of 8 messages to some relayer slot until there are no more messages.
|
||||||
|
// Each batch takes up 1 more byte.
|
||||||
|
num_result_bytes =
|
||||||
|
num_result_bytes.checked_add((message_count - relayers_entries) / msgs_per_byte)?;
|
||||||
|
|
||||||
|
// The len is stored in a `Compact<u32>`. `Compact<u32>` can store a max value of
|
||||||
|
// 63 on 1 byte, 16383 on 2 bytes, etc.
|
||||||
|
let max_len_per_first_byte = 0b0011_1111;
|
||||||
|
// At the begining each relayer slot uses 1 byte for the len prefix
|
||||||
|
// (each relayer slot contains 1 message)
|
||||||
|
let mut num_len_bytes = relayers_entries;
|
||||||
|
// Then we add batches of 63 messages to as many relayer slots as possible, requiring 2
|
||||||
|
// bytes for the `len` prefix. It's hard to believe that we'll need more than 2 bytes
|
||||||
|
// (more than 16383 messages in 1 relayer slot).
|
||||||
|
num_len_bytes = num_len_bytes.checked_add(sp_std::cmp::min(
|
||||||
|
(message_count - relayers_entries) / max_len_per_first_byte,
|
||||||
|
relayers_entries,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
num_result_bytes.checked_add(num_len_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns approximate size of the struct, given a number of entries in the `relayers` set and
|
/// Returns approximate size of the struct, given a number of entries in the `relayers` set and
|
||||||
/// size of each entry.
|
/// size of each entry.
|
||||||
///
|
///
|
||||||
/// Returns `None` if size overflows `usize` limits.
|
/// Returns `None` if size overflows `usize` limits.
|
||||||
pub fn encoded_size_hint(relayers_entries: usize) -> Option<usize>
|
pub fn encoded_size_hint(relayers_entries: usize, message_count: usize) -> Option<usize>
|
||||||
where
|
where
|
||||||
RelayerId: MaxEncodedLen,
|
RelayerId: MaxEncodedLen,
|
||||||
{
|
{
|
||||||
let message_nonce_size = MessageNonce::max_encoded_len();
|
let message_nonce_size = MessageNonce::max_encoded_len();
|
||||||
let relayer_id_encoded_size = RelayerId::max_encoded_len();
|
let relayer_id_encoded_size = RelayerId::max_encoded_len();
|
||||||
let relayers_entry_size = relayer_id_encoded_size.checked_add(2 * message_nonce_size)?;
|
let relayers_entry_size = relayer_id_encoded_size.checked_add(2 * message_nonce_size)?;
|
||||||
let relayers_size = relayers_entries.checked_mul(relayers_entry_size)?;
|
let relayers_size = relayers_entries.checked_mul(relayers_entry_size)?.checked_add(
|
||||||
|
Self::dispatch_results_encoded_size_hint(relayers_entries, message_count)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
relayers_size.checked_add(message_nonce_size)
|
relayers_size.checked_add(message_nonce_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +226,11 @@ impl<RelayerId> InboundLaneData<RelayerId> {
|
|||||||
/// `relayers` set and the size of each entry.
|
/// `relayers` set and the size of each entry.
|
||||||
///
|
///
|
||||||
/// Returns `u32::MAX` if size overflows `u32` limits.
|
/// Returns `u32::MAX` if size overflows `u32` limits.
|
||||||
pub fn encoded_size_hint_u32(relayers_entries: usize) -> u32
|
pub fn encoded_size_hint_u32(relayers_entries: usize, messages_count: usize) -> u32
|
||||||
where
|
where
|
||||||
RelayerId: MaxEncodedLen,
|
RelayerId: MaxEncodedLen,
|
||||||
{
|
{
|
||||||
Self::encoded_size_hint(relayers_entries)
|
Self::encoded_size_hint(relayers_entries, messages_count)
|
||||||
.and_then(|x| u32::try_from(x).ok())
|
.and_then(|x| u32::try_from(x).ok())
|
||||||
.unwrap_or(u32::MAX)
|
.unwrap_or(u32::MAX)
|
||||||
}
|
}
|
||||||
@@ -219,6 +270,9 @@ pub struct InboundMessageDetails {
|
|||||||
pub dispatch_weight: Weight,
|
pub dispatch_weight: Weight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bit vector of message dispatch results.
|
||||||
|
pub type DispatchResultsBitVec = BitVec<u8, Msb0>;
|
||||||
|
|
||||||
/// Unrewarded relayer entry stored in the inbound lane data.
|
/// Unrewarded relayer entry stored in the inbound lane data.
|
||||||
///
|
///
|
||||||
/// This struct represents a continuous range of messages that have been delivered by the same
|
/// This struct represents a continuous range of messages that have been delivered by the same
|
||||||
@@ -276,13 +330,19 @@ pub struct DeliveredMessages {
|
|||||||
pub begin: MessageNonce,
|
pub begin: MessageNonce,
|
||||||
/// Nonce of the last message that has been delivered (inclusive).
|
/// Nonce of the last message that has been delivered (inclusive).
|
||||||
pub end: MessageNonce,
|
pub end: MessageNonce,
|
||||||
|
/// Dispatch result (`false`/`true`), returned by the message dispatcher for every
|
||||||
|
/// message in the `[begin; end]` range. See `dispatch_result` field of the
|
||||||
|
/// `bp_runtime::messages::MessageDispatchResult` structure for more information.
|
||||||
|
pub dispatch_results: DispatchResultsBitVec,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeliveredMessages {
|
impl DeliveredMessages {
|
||||||
/// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given
|
/// Create new `DeliveredMessages` struct that confirms delivery of single nonce with given
|
||||||
/// dispatch result.
|
/// dispatch result.
|
||||||
pub fn new(nonce: MessageNonce) -> Self {
|
pub fn new(nonce: MessageNonce, dispatch_result: bool) -> Self {
|
||||||
DeliveredMessages { begin: nonce, end: nonce }
|
let mut dispatch_results = BitVec::with_capacity(1);
|
||||||
|
dispatch_results.push(dispatch_result);
|
||||||
|
DeliveredMessages { begin: nonce, end: nonce, dispatch_results }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return total count of delivered messages.
|
/// Return total count of delivered messages.
|
||||||
@@ -295,14 +355,25 @@ impl DeliveredMessages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Note new dispatched message.
|
/// Note new dispatched message.
|
||||||
pub fn note_dispatched_message(&mut self) {
|
pub fn note_dispatched_message(&mut self, dispatch_result: bool) {
|
||||||
self.end += 1;
|
self.end += 1;
|
||||||
|
self.dispatch_results.push(dispatch_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if delivered messages contain message with given nonce.
|
/// Returns true if delivered messages contain message with given nonce.
|
||||||
pub fn contains_message(&self, nonce: MessageNonce) -> bool {
|
pub fn contains_message(&self, nonce: MessageNonce) -> bool {
|
||||||
(self.begin..=self.end).contains(&nonce)
|
(self.begin..=self.end).contains(&nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get dispatch result flag by message nonce.
|
||||||
|
///
|
||||||
|
/// Dispatch result flag must be interpreted using the knowledge of dispatch mechanism
|
||||||
|
/// at the target chain. See `dispatch_result` field of the
|
||||||
|
/// `bp_runtime::messages::MessageDispatchResult` structure for more information.
|
||||||
|
pub fn message_dispatch_result(&self, nonce: MessageNonce) -> Option<bool> {
|
||||||
|
let index = nonce.checked_sub(self.begin)? as usize;
|
||||||
|
self.dispatch_results.get(index).map(|bit| *bit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gist of `InboundLaneData::relayers` field used by runtime APIs.
|
/// Gist of `InboundLaneData::relayers` field used by runtime APIs.
|
||||||
@@ -414,10 +485,10 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
total_unrewarded_messages(
|
total_unrewarded_messages(
|
||||||
&vec![
|
&vec![
|
||||||
UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0) },
|
UnrewardedRelayer { relayer: 1, messages: DeliveredMessages::new(0, true) },
|
||||||
UnrewardedRelayer {
|
UnrewardedRelayer {
|
||||||
relayer: 2,
|
relayer: 2,
|
||||||
messages: DeliveredMessages::new(MessageNonce::MAX)
|
messages: DeliveredMessages::new(MessageNonce::MAX, true)
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -438,12 +509,21 @@ mod tests {
|
|||||||
(13u8, 128u8),
|
(13u8, 128u8),
|
||||||
];
|
];
|
||||||
for (relayer_entries, messages_count) in test_cases {
|
for (relayer_entries, messages_count) in test_cases {
|
||||||
let expected_size = InboundLaneData::<u8>::encoded_size_hint(relayer_entries as _);
|
let expected_size =
|
||||||
|
InboundLaneData::<u8>::encoded_size_hint(relayer_entries as _, messages_count as _);
|
||||||
let actual_size = InboundLaneData {
|
let actual_size = InboundLaneData {
|
||||||
relayers: (1u8..=relayer_entries)
|
relayers: (1u8..=relayer_entries)
|
||||||
.map(|i| UnrewardedRelayer {
|
.map(|i| {
|
||||||
relayer: i,
|
let mut entry = UnrewardedRelayer {
|
||||||
messages: DeliveredMessages::new(i as _),
|
relayer: i,
|
||||||
|
messages: DeliveredMessages::new(i as _, true),
|
||||||
|
};
|
||||||
|
entry.messages.dispatch_results = bitvec![
|
||||||
|
u8, Msb0;
|
||||||
|
1;
|
||||||
|
(messages_count / relayer_entries) as _
|
||||||
|
];
|
||||||
|
entry
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
last_confirmed_nonce: messages_count as _,
|
last_confirmed_nonce: messages_count as _,
|
||||||
@@ -459,13 +539,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn contains_result_works() {
|
fn message_dispatch_result_works() {
|
||||||
let delivered_messages = DeliveredMessages { begin: 100, end: 150 };
|
let delivered_messages =
|
||||||
|
DeliveredMessages { begin: 100, end: 150, dispatch_results: bitvec![u8, Msb0; 1; 151] };
|
||||||
|
|
||||||
assert!(!delivered_messages.contains_message(99));
|
assert!(!delivered_messages.contains_message(99));
|
||||||
assert!(delivered_messages.contains_message(100));
|
assert!(delivered_messages.contains_message(100));
|
||||||
assert!(delivered_messages.contains_message(150));
|
assert!(delivered_messages.contains_message(150));
|
||||||
assert!(!delivered_messages.contains_message(151));
|
assert!(!delivered_messages.contains_message(151));
|
||||||
|
|
||||||
|
assert_eq!(delivered_messages.message_dispatch_result(125), Some(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ pub trait MessageDispatch<AccountId> {
|
|||||||
type DispatchPayload: Decode;
|
type DispatchPayload: Decode;
|
||||||
|
|
||||||
/// Fine-grained result of single message dispatch (for better diagnostic purposes)
|
/// Fine-grained result of single message dispatch (for better diagnostic purposes)
|
||||||
type DispatchLevelResult: Clone + sp_std::fmt::Debug + Eq;
|
type DispatchError: Clone + sp_std::fmt::Debug + Eq;
|
||||||
|
|
||||||
/// Estimate dispatch weight.
|
/// Estimate dispatch weight.
|
||||||
///
|
///
|
||||||
@@ -109,7 +109,7 @@ pub trait MessageDispatch<AccountId> {
|
|||||||
fn dispatch(
|
fn dispatch(
|
||||||
relayer_account: &AccountId,
|
relayer_account: &AccountId,
|
||||||
message: DispatchMessage<Self::DispatchPayload>,
|
message: DispatchMessage<Self::DispatchPayload>,
|
||||||
) -> MessageDispatchResult<Self::DispatchLevelResult>;
|
) -> MessageDispatchResult<Self::DispatchError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manages payments that are happening at the target chain during message delivery transaction.
|
/// Manages payments that are happening at the target chain during message delivery transaction.
|
||||||
@@ -190,7 +190,7 @@ impl<MessagesProof, DispatchPayload: Decode, AccountId> MessageDispatch<AccountI
|
|||||||
for ForbidInboundMessages<MessagesProof, DispatchPayload>
|
for ForbidInboundMessages<MessagesProof, DispatchPayload>
|
||||||
{
|
{
|
||||||
type DispatchPayload = DispatchPayload;
|
type DispatchPayload = DispatchPayload;
|
||||||
type DispatchLevelResult = ();
|
type DispatchError = ();
|
||||||
|
|
||||||
fn dispatch_weight(_message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
|
fn dispatch_weight(_message: &mut DispatchMessage<Self::DispatchPayload>) -> Weight {
|
||||||
Weight::MAX
|
Weight::MAX
|
||||||
@@ -199,7 +199,7 @@ impl<MessagesProof, DispatchPayload: Decode, AccountId> MessageDispatch<AccountI
|
|||||||
fn dispatch(
|
fn dispatch(
|
||||||
_: &AccountId,
|
_: &AccountId,
|
||||||
_: DispatchMessage<Self::DispatchPayload>,
|
_: DispatchMessage<Self::DispatchPayload>,
|
||||||
) -> MessageDispatchResult<Self::DispatchLevelResult> {
|
) -> MessageDispatchResult<Self::DispatchError> {
|
||||||
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () }
|
MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_result: Err(()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use scale_info::TypeInfo;
|
|||||||
|
|
||||||
/// Message dispatch result.
|
/// Message dispatch result.
|
||||||
#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
|
#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)]
|
||||||
pub struct MessageDispatchResult<DispatchLevelResult> {
|
pub struct MessageDispatchResult<DispatchError> {
|
||||||
/// Unspent dispatch weight. This weight that will be deducted from total delivery transaction
|
/// Unspent dispatch weight. This weight that will be deducted from total delivery transaction
|
||||||
/// weight, thus reducing the transaction cost. This shall not be zero in (at least) two cases:
|
/// weight, thus reducing the transaction cost. This shall not be zero in (at least) two cases:
|
||||||
///
|
///
|
||||||
@@ -31,5 +31,5 @@ pub struct MessageDispatchResult<DispatchLevelResult> {
|
|||||||
/// 2) if message has not been dispatched at all.
|
/// 2) if message has not been dispatched at all.
|
||||||
pub unspent_weight: Weight,
|
pub unspent_weight: Weight,
|
||||||
/// Fine-grained result of single message dispatch (for better diagnostic purposes)
|
/// Fine-grained result of single message dispatch (for better diagnostic purposes)
|
||||||
pub dispatch_level_result: DispatchLevelResult,
|
pub dispatch_result: Result<(), DispatchError>,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user