Newer
Older
T::WeightInfo::service_queue_base()
.saturating_add(T::WeightInfo::ready_ring_unknit()),
)
.is_err()
{
return (false, None)
}
let mut book_state = BookStateFor::<T>::get(&origin);
let mut total_processed = 0;
if T::QueuePausedQuery::is_paused(&origin) {
let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone());
return (false, next_ready)
}
while book_state.end > book_state.begin {
let (processed, status) =
Self::service_page(&origin, &mut book_state, weight, overweight_limit);
total_processed.saturating_accrue(processed);
match status {
// Store the page progress and do not go to the next one.
Bailed | NoProgress => break,
// Go to the next page if this one is at the end.
};
book_state.begin.saturating_inc();
}
let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone());
if book_state.begin >= book_state.end {
// No longer ready - unknit.
if let Some(neighbours) = book_state.ready_neighbours.take() {
Self::ready_ring_unknit(&origin, neighbours);
} else if total_processed > 0 {
defensive!("Freshly processed queue must have been ready");
}
}
BookStateFor::<T>::insert(&origin, &book_state);
if total_processed > 0 {
T::QueueChangeHandler::on_queue_changed(origin, book_state.into());
}
(total_processed > 0, next_ready)
}
/// Service as many messages of a page as possible.
///
/// Returns how many messages were processed and the page's status.
fn service_page(
origin: &MessageOriginOf<T>,
book_state: &mut BookStateOf<T>,
weight: &mut WeightMeter,
overweight_limit: Weight,
) -> (u32, PageExecutionStatus) {
use PageExecutionStatus::*;
if weight
.try_consume(
T::WeightInfo::service_page_base_completion()
.max(T::WeightInfo::service_page_base_no_completion()),
)
.is_err()
{
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
return (0, Bailed)
}
let page_index = book_state.begin;
let mut page = match Pages::<T>::get(origin, page_index) {
Some(p) => p,
None => {
defensive!("message-queue: referenced page not found");
return (0, NoMore)
},
};
let mut total_processed = 0;
// Execute as many messages as possible.
let status = loop {
use ItemExecutionStatus::*;
match Self::service_page_item(
origin,
page_index,
book_state,
&mut page,
weight,
overweight_limit,
) {
Bailed => break PageExecutionStatus::Bailed,
NoItem => break PageExecutionStatus::NoMore,
NoProgress => break PageExecutionStatus::NoProgress,
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
// Keep going as long as we make progress...
Executed(true) => total_processed.saturating_inc(),
Executed(false) => (),
}
};
if page.is_complete() {
debug_assert!(status != Bailed, "we never bail if a page became complete");
Pages::<T>::remove(origin, page_index);
debug_assert!(book_state.count > 0, "completing a page implies there are pages");
book_state.count.saturating_dec();
} else {
Pages::<T>::insert(origin, page_index, page);
}
(total_processed, status)
}
/// Execute the next message of a page.
pub(crate) fn service_page_item(
origin: &MessageOriginOf<T>,
page_index: PageIndex,
book_state: &mut BookStateOf<T>,
page: &mut PageOf<T>,
weight: &mut WeightMeter,
overweight_limit: Weight,
) -> ItemExecutionStatus {
// This ugly pre-checking is needed for the invariant
// "we never bail if a page became complete".
if page.is_complete() {
return ItemExecutionStatus::NoItem
}
if weight.try_consume(T::WeightInfo::service_page_item()).is_err() {
return ItemExecutionStatus::Bailed
}
let payload = &match page.peek_first() {
Some(m) => m,
None => return ItemExecutionStatus::NoItem,
}[..];
use MessageExecutionStatus::*;
let is_processed = match Self::process_message_payload(
origin.clone(),
page_index,
page.first_index,
weight,
overweight_limit,
) {
InsufficientWeight => return ItemExecutionStatus::Bailed,
Unprocessable { permanent: false } => return ItemExecutionStatus::NoProgress,
Processed | Unprocessable { permanent: true } => true,
Overweight => false,
};
if is_processed {
book_state.message_count.saturating_dec();
book_state.size.saturating_reduce(payload.len() as u64);
}
page.skip_first(is_processed);
ItemExecutionStatus::Executed(is_processed)
}
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
/// Ensure the correctness of state of this pallet.
///
/// # Assumptions-
///
/// If `serviceHead` points to a ready Queue, then BookState of that Queue has:
///
/// * `message_count` > 0
/// * `size` > 0
/// * `end` > `begin`
/// * Some(ready_neighbours)
/// * If `ready_neighbours.next` == self.origin, then `ready_neighbours.prev` == self.origin
/// (only queue in ring)
///
/// For Pages(begin to end-1) in BookState:
///
/// * `remaining` > 0
/// * `remaining_size` > 0
/// * `first` <= `last`
/// * Every page can be decoded into peek_* functions
#[cfg(any(test, feature = "try-runtime"))]
pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
// Checking memory corruption for BookStateFor
ensure!(
BookStateFor::<T>::iter_keys().count() == BookStateFor::<T>::iter_values().count(),
"Memory Corruption in BookStateFor"
);
// Checking memory corruption for Pages
ensure!(
Pages::<T>::iter_keys().count() == Pages::<T>::iter_values().count(),
"Memory Corruption in Pages"
);
// No state to check
if ServiceHead::<T>::get().is_none() {
return Ok(())
}
//loop around this origin
let starting_origin = ServiceHead::<T>::get().unwrap();
while let Some(head) = Self::bump_service_head(&mut WeightMeter::new()) {
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
ensure!(
BookStateFor::<T>::contains_key(&head),
"Service head must point to an existing book"
);
let head_book_state = BookStateFor::<T>::get(&head);
ensure!(
head_book_state.message_count > 0,
"There must be some messages if in ReadyRing"
);
ensure!(head_book_state.size > 0, "There must be some message size if in ReadyRing");
ensure!(
head_book_state.end > head_book_state.begin,
"End > Begin if unprocessed messages exists"
);
ensure!(
head_book_state.ready_neighbours.is_some(),
"There must be neighbours if in ReadyRing"
);
if head_book_state.ready_neighbours.as_ref().unwrap().next == head {
ensure!(
head_book_state.ready_neighbours.as_ref().unwrap().prev == head,
"Can only happen if only queue in ReadyRing"
);
}
for page_index in head_book_state.begin..head_book_state.end {
let page = Pages::<T>::get(&head, page_index).unwrap();
let remaining_messages = page.remaining;
let mut counted_remaining_messages = 0;
ensure!(
remaining_messages > 0.into(),
"These must be some messages that have not been processed yet!"
);
for i in 0..u32::MAX {
if let Some((_, processed, _)) = page.peek_index(i as usize) {
if !processed {
counted_remaining_messages += 1;
}
} else {
break
}
}
ensure!(
remaining_messages == counted_remaining_messages.into(),
"Memory Corruption"
);
}
if head_book_state.ready_neighbours.as_ref().unwrap().next == starting_origin {
break
}
}
Ok(())
}
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
/// Print the pages in each queue and the messages in each page.
///
/// Processed messages are prefixed with a `*` and the current `begin`ning page with a `>`.
///
/// # Example output
///
/// ```text
/// queue Here:
/// page 0: []
/// > page 1: []
/// page 2: ["\0weight=4", "\0c", ]
/// page 3: ["\0bigbig 1", ]
/// page 4: ["\0bigbig 2", ]
/// page 5: ["\0bigbig 3", ]
/// ```
#[cfg(feature = "std")]
pub fn debug_info() -> String {
let mut info = String::new();
for (origin, book_state) in BookStateFor::<T>::iter() {
let mut queue = format!("queue {:?}:\n", &origin);
let mut pages = Pages::<T>::iter_prefix(&origin).collect::<Vec<_>>();
pages.sort_by(|(a, _), (b, _)| a.cmp(b));
for (page_index, mut page) in pages.into_iter() {
let page_info = if book_state.begin == page_index { ">" } else { " " };
let mut page_info = format!(
"{} page {} ({:?} first, {:?} last, {:?} remain): [ ",
page_info, page_index, page.first, page.last, page.remaining
);
for i in 0..u32::MAX {
if let Some((_, processed, message)) =
page.peek_index(i.try_into().expect("std-only code"))
{
let msg = String::from_utf8_lossy(message);
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
if processed {
page_info.push('*');
}
page_info.push_str(&format!("{:?}, ", msg));
page.skip_first(true);
} else {
break
}
}
page_info.push_str("]\n");
queue.push_str(&page_info);
}
info.push_str(&queue);
}
info
}
/// Process a single message.
///
/// The base weight of this function needs to be accounted for by the caller. `weight` is the
/// remaining weight to process the message. `overweight_limit` is the maximum weight that a
/// message can ever consume. Messages above this limit are marked as permanently overweight.
fn process_message_payload(
origin: MessageOriginOf<T>,
page_index: PageIndex,
message_index: T::Size,
message: &[u8],
meter: &mut WeightMeter,
overweight_limit: Weight,
) -> MessageExecutionStatus {
let hash = sp_io::hashing::blake2_256(message);
use ProcessMessageError::*;
let prev_consumed = meter.consumed();
let mut id = hash;
match T::MessageProcessor::process_message(message, origin.clone(), meter, &mut id) {
Err(Overweight(w)) if w.any_gt(overweight_limit) => {
// Permanently overweight.
Self::deposit_event(Event::<T>::OverweightEnqueued {
origin,
page_index,
message_index,
});
MessageExecutionStatus::Overweight
},
Err(Overweight(_)) => {
// Temporarily overweight - save progress and stop processing this
// queue.
MessageExecutionStatus::InsufficientWeight
},
Err(Yield) => {
// Processing should be reattempted later.
MessageExecutionStatus::Unprocessable { permanent: false }
},
Err(error @ BadFormat | error @ Corrupt | error @ Unsupported) => {
Self::deposit_event(Event::<T>::ProcessingFailed { id, origin, error });
MessageExecutionStatus::Unprocessable { permanent: true }
Ok(success) => {
let weight_used = meter.consumed().saturating_sub(prev_consumed);
Self::deposit_event(Event::<T>::Processed { id, origin, weight_used, success });
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
MessageExecutionStatus::Processed
},
}
}
}
/// Provides a [`sp_core::Get`] to access the `MEL` of a [`codec::MaxEncodedLen`] type.
pub struct MaxEncodedLenOf<T>(sp_std::marker::PhantomData<T>);
impl<T: MaxEncodedLen> Get<u32> for MaxEncodedLenOf<T> {
fn get() -> u32 {
T::max_encoded_len() as u32
}
}
/// Calculates the maximum message length and exposed it through the [`codec::MaxEncodedLen`] trait.
pub struct MaxMessageLen<Origin, Size, HeapSize>(
sp_std::marker::PhantomData<(Origin, Size, HeapSize)>,
);
impl<Origin: MaxEncodedLen, Size: MaxEncodedLen + Into<u32>, HeapSize: Get<Size>> Get<u32>
for MaxMessageLen<Origin, Size, HeapSize>
{
fn get() -> u32 {
(HeapSize::get().into()).saturating_sub(ItemHeader::<Size>::max_encoded_len() as u32)
}
}
/// The maximal message length.
pub type MaxMessageLenOf<T> =
MaxMessageLen<MessageOriginOf<T>, <T as Config>::Size, <T as Config>::HeapSize>;
/// The maximal encoded origin length.
pub type MaxOriginLenOf<T> = MaxEncodedLenOf<MessageOriginOf<T>>;
/// The `MessageOrigin` of this pallet.
pub type MessageOriginOf<T> = <<T as Config>::MessageProcessor as ProcessMessage>::Origin;
/// The maximal heap size of a page.
pub type HeapSizeU32Of<T> = IntoU32<<T as Config>::HeapSize, <T as Config>::Size>;
/// The [`Page`] of this pallet.
pub type PageOf<T> = Page<<T as Config>::Size, <T as Config>::HeapSize>;
/// The [`BookState`] of this pallet.
pub type BookStateOf<T> = BookState<MessageOriginOf<T>>;
/// Converts a [`sp_core::Get`] with returns a type that can be cast into an `u32` into a `Get`
/// which returns an `u32`.
pub struct IntoU32<T, O>(sp_std::marker::PhantomData<(T, O)>);
impl<T: Get<O>, O: Into<u32>> Get<u32> for IntoU32<T, O> {
fn get() -> u32 {
T::get().into()
}
}
impl<T: Config> ServiceQueues for Pallet<T> {
type OverweightMessageAddress = (MessageOriginOf<T>, PageIndex, T::Size);
fn service_queues(weight_limit: Weight) -> Weight {
// Get the maximum weight that processing a single message may take:
let max_weight = Self::max_message_weight(weight_limit).unwrap_or_else(|| {
defensive!("Not enough weight to service a single message.");
Weight::zero()
});
let mut next = match Self::bump_service_head(&mut weight) {
Some(h) => h,
None => return weight.consumed(),
};
// The last queue that did not make any progress.
// The loop aborts as soon as it arrives at this queue again without making any progress
// on other queues in between.
let mut last_no_progress = None;
loop {
let (progressed, n) = Self::service_queue(next.clone(), &mut weight, max_weight);
next = match n {
Some(n) =>
if !progressed {
if last_no_progress == Some(n.clone()) {
break
}
if last_no_progress.is_none() {
last_no_progress = Some(next.clone())
}
n
} else {
last_no_progress = None;
n
},
None => break,
}
}
}
/// Execute a single overweight message.
///
/// The weight limit must be enough for `execute_overweight` and the message execution itself.
fn execute_overweight(
weight_limit: Weight,
(message_origin, page, index): Self::OverweightMessageAddress,
) -> Result<Weight, ExecuteOverweightError> {
if weight
.try_consume(
T::WeightInfo::execute_overweight_page_removed()
.max(T::WeightInfo::execute_overweight_page_updated()),
)
.is_err()
{
return Err(ExecuteOverweightError::InsufficientWeight)
}
Pallet::<T>::do_execute_overweight(message_origin, page, index, weight.remaining()).map_err(
|e| match e {
Error::<T>::InsufficientWeight => ExecuteOverweightError::InsufficientWeight,
Error::<T>::AlreadyProcessed => ExecuteOverweightError::AlreadyProcessed,
Error::<T>::QueuePaused => ExecuteOverweightError::QueuePaused,
Error::<T>::NoPage | Error::<T>::NoMessage | Error::<T>::Queued =>
ExecuteOverweightError::NotFound,
_ => ExecuteOverweightError::Other,
},
)
}
}
impl<T: Config> EnqueueMessage<MessageOriginOf<T>> for Pallet<T> {
type MaxMessageLen =
MaxMessageLen<<T::MessageProcessor as ProcessMessage>::Origin, T::Size, T::HeapSize>;
fn enqueue_message(
message: BoundedSlice<u8, Self::MaxMessageLen>,
origin: <T::MessageProcessor as ProcessMessage>::Origin,
) {
Self::do_enqueue_message(&origin, message);
let book_state = BookStateFor::<T>::get(&origin);
T::QueueChangeHandler::on_queue_changed(origin, book_state.into());
}
fn enqueue_messages<'a>(
messages: impl Iterator<Item = BoundedSlice<'a, u8, Self::MaxMessageLen>>,
origin: <T::MessageProcessor as ProcessMessage>::Origin,
) {
for message in messages {
Self::do_enqueue_message(&origin, message);
}
let book_state = BookStateFor::<T>::get(&origin);
T::QueueChangeHandler::on_queue_changed(origin, book_state.into());
}
fn sweep_queue(origin: MessageOriginOf<T>) {
if !BookStateFor::<T>::contains_key(&origin) {
return
}
let mut book_state = BookStateFor::<T>::get(&origin);
book_state.begin = book_state.end;
if let Some(neighbours) = book_state.ready_neighbours.take() {
Self::ready_ring_unknit(&origin, neighbours);
}
BookStateFor::<T>::insert(&origin, &book_state);
}
fn footprint(origin: MessageOriginOf<T>) -> QueueFootprint {
BookStateFor::<T>::get(&origin).into()