mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 00:37:57 +00:00
665e3654ce
Fixes https://github.com/paritytech/polkadot-sdk/issues/3884#issuecomment-2026058687 After moving regression tests to benchmarks (https://github.com/paritytech/polkadot-sdk/pull/3741) we don't need to filter tests anymore. --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Co-authored-by: Alin Dima <alin@parity.io> Co-authored-by: Andrei Sandu <andrei-mihail@parity.io> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Javier Viola <363911+pepoviola@users.noreply.github.com> Co-authored-by: Serban Iorga <serban@parity.io> Co-authored-by: Adrian Catangiu <adrian@parity.io> Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com> Co-authored-by: Dastan <88332432+dastansam@users.noreply.github.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Clara van Staden <claravanstaden64@gmail.com> Co-authored-by: Ron <yrong1997@gmail.com> Co-authored-by: Vincent Geddes <vincent@snowfork.com> Co-authored-by: Svyatoslav Nikolsky <svyatonik@gmail.com> Co-authored-by: Bastian Köcher <info@kchr.de>
3038 lines
90 KiB
Rust
3038 lines
90 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! # Scheduler tests.
|
|
|
|
use super::*;
|
|
use crate::mock::{
|
|
logger::{self, Threshold},
|
|
new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *,
|
|
};
|
|
use frame_support::{
|
|
assert_err, assert_noop, assert_ok,
|
|
traits::{Contains, GetStorageVersion, OnInitialize, QueryPreimage, StorePreimage},
|
|
Hashable,
|
|
};
|
|
use sp_runtime::traits::Hash;
|
|
use substrate_test_utils::assert_eq_uvec;
|
|
|
|
#[test]
|
|
#[docify::export]
|
|
fn basic_scheduling_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// Call to schedule
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
|
|
// BaseCallFilter should be implemented to accept `Logger::log` runtime call which is
|
|
// implemented for `BaseFilter` in the mock runtime
|
|
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
|
|
|
// Schedule call to be executed at the 4th block
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap()
|
|
));
|
|
|
|
// `log` runtime call should not have executed yet
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
|
|
run_to_block(4);
|
|
// `log` runtime call should have executed at block 4
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[docify::export]
|
|
fn scheduling_with_preimages_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// Call to schedule
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
|
|
let hash = <Test as frame_system::Config>::Hashing::hash_of(&call);
|
|
let len = call.using_encoded(|x| x.len()) as u32;
|
|
|
|
// Important to use here `Bounded::Lookup` to ensure that that the Scheduler can request the
|
|
// hash from PreImage to dispatch the call
|
|
let hashed = Bounded::Lookup { hash, len };
|
|
|
|
// Schedule call to be executed at block 4 with the PreImage hash
|
|
assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), hashed));
|
|
|
|
// Register preimage on chain
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode()));
|
|
assert!(Preimage::is_requested(&hash));
|
|
|
|
// `log` runtime call should not have executed yet
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
|
|
run_to_block(4);
|
|
// preimage should not have been removed when executed by the scheduler
|
|
assert!(!Preimage::len(&hash).is_some());
|
|
assert!(!Preimage::is_requested(&hash));
|
|
// `log` runtime call should have executed at block 4
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn schedule_after_works() {
|
|
new_test_ext().execute_with(|| {
|
|
run_to_block(2);
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
|
// This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::After(3),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap()
|
|
));
|
|
run_to_block(5);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(6);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn schedule_after_zero_works() {
|
|
new_test_ext().execute_with(|| {
|
|
run_to_block(2);
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::After(0),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap()
|
|
));
|
|
// Will trigger on the next block.
|
|
run_to_block(3);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn periodic_scheduling_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// at #4, every 3 blocks, 3 times.
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
Some((3, 3)),
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(6);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(7);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
run_to_block(9);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
run_to_block(10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_scheduling_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task fails until block 8 is reached
|
|
Threshold::<Test>::put((8, 100));
|
|
// task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// retry 10 times every 3 blocks
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 3));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// task should be retried in block 7
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(6);
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
// task still fails, should be retried in block 10
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(7).is_empty());
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(8);
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(9);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
// finally it should succeed
|
|
run_to_block(10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
run_to_block(11);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(12);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn named_retry_scheduling_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task fails until block 8 is reached
|
|
Threshold::<Test>::put((8, 100));
|
|
// task 42 at #4
|
|
let call = RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
});
|
|
assert_eq!(
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap(),
|
|
(4, 0)
|
|
);
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// retry 10 times every 3 blocks
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 3));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// task should be retried in block 7
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(6);
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
// task still fails, should be retried in block 10
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(7).is_empty());
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(8);
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(9);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
// finally it should succeed
|
|
run_to_block(10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
run_to_block(11);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(12);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_scheduling_multiple_tasks_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task fails until block 8 is reached
|
|
Threshold::<Test>::put((8, 100));
|
|
// task 20 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 20,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
// task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// task 20 will be retried 3 times every block
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1));
|
|
// task 42 will be retried 10 times every 3 blocks
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 1), 10, 3));
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// both tasks fail
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
// 20 is rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(5).len(), 1);
|
|
// 42 is rescheduled for block 7
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 1);
|
|
assert!(logger::log().is_empty());
|
|
// 20 still fails
|
|
run_to_block(5);
|
|
// 20 rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(6).len(), 1);
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 1);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert!(logger::log().is_empty());
|
|
// 20 still fails
|
|
run_to_block(6);
|
|
// rescheduled for next block together with 42
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert!(logger::log().is_empty());
|
|
// both tasks will fail, for 20 it was the last retry so it's dropped
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(7).is_empty());
|
|
assert!(Agenda::<Test>::get(8).is_empty());
|
|
// 42 is rescheduled for block 10
|
|
assert_eq!(Agenda::<Test>::get(10).len(), 1);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(8);
|
|
assert_eq!(Agenda::<Test>::get(10).len(), 1);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(9);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
// 42 runs successfully
|
|
run_to_block(10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
run_to_block(11);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(12);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_scheduling_multiple_named_tasks_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task fails until we reach block 8
|
|
Threshold::<Test>::put((8, 100));
|
|
// task 20 at #4
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[20u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 20,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
// task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[42u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// task 20 will be retried 3 times every block
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [20u8; 32], 3, 1));
|
|
// task 42 will be retried 10 times every 3 block
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 3));
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// both tasks fail
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
// 42 is rescheduled for block 7
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 1);
|
|
// 20 is rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(5).len(), 1);
|
|
assert!(logger::log().is_empty());
|
|
// 20 still fails
|
|
run_to_block(5);
|
|
// 20 rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(6).len(), 1);
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 1);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert!(logger::log().is_empty());
|
|
// 20 still fails
|
|
run_to_block(6);
|
|
// 20 rescheduled for next block together with 42
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert!(logger::log().is_empty());
|
|
// both tasks will fail, for 20 it was the last retry so it's dropped
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(7).is_empty());
|
|
assert!(Agenda::<Test>::get(8).is_empty());
|
|
// 42 is rescheduled for block 10
|
|
assert_eq!(Agenda::<Test>::get(10).len(), 1);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(8);
|
|
assert_eq!(Agenda::<Test>::get(10).len(), 1);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(9);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
// 42 runs successfully
|
|
run_to_block(10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
run_to_block(11);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(12);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_scheduling_with_period_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// tasks fail until we reach block 4 and after we're past block 8
|
|
Threshold::<Test>::put((4, 8));
|
|
// task 42 at #4, every 3 blocks, 6 times
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
Some((3, 6)),
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// 42 will be retried 10 times every 2 blocks
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// 42 runs successfully once, it will run again at block 7
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// nothing changed
|
|
run_to_block(6);
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// 42 runs successfully again, it will run again at block 10
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(7).is_empty());
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
run_to_block(9);
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// 42 has 10 retries left out of a total of 10
|
|
assert_eq!(Retries::<Test>::get((10, 0)).unwrap().remaining, 10);
|
|
// 42 will fail because we're outside the set threshold (block number in `4..8`), so it
|
|
// should be retried in 2 blocks (at block 12)
|
|
run_to_block(10);
|
|
// should be queued for the normal period of 3 blocks
|
|
assert!(Agenda::<Test>::get(13)[0].is_some());
|
|
// should also be queued to be retried in 2 blocks
|
|
assert!(Agenda::<Test>::get(12)[0].is_some());
|
|
// 42 has consumed one retry attempt
|
|
assert_eq!(Retries::<Test>::get((12, 0)).unwrap().remaining, 9);
|
|
assert_eq!(Retries::<Test>::get((13, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// 42 will fail again
|
|
run_to_block(12);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(13)[0].is_some());
|
|
// should be queued to be retried in 2 blocks
|
|
assert!(Agenda::<Test>::get(14)[0].is_some());
|
|
// 42 has consumed another retry attempt
|
|
assert_eq!(Retries::<Test>::get((14, 0)).unwrap().remaining, 8);
|
|
assert_eq!(Retries::<Test>::get((13, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// 42 will fail for the regular periodic run
|
|
run_to_block(13);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(16)[0].is_some());
|
|
// should still be queued to be retried next block
|
|
assert!(Agenda::<Test>::get(14)[0].is_some());
|
|
// 42 consumed another periodic run, which failed, so another retry is queued for block 15
|
|
assert!(Agenda::<Test>::get(16)[0].as_ref().unwrap().maybe_periodic.is_some());
|
|
assert!(Agenda::<Test>::get(15)[0].as_ref().unwrap().maybe_periodic.is_none());
|
|
assert!(Agenda::<Test>::get(14)[0].as_ref().unwrap().maybe_periodic.is_none());
|
|
assert_eq!(Retries::<Test>::iter().count(), 3);
|
|
assert!(Retries::<Test>::get((14, 0)).unwrap().remaining == 8);
|
|
assert!(Retries::<Test>::get((15, 0)).unwrap().remaining == 9);
|
|
assert!(Retries::<Test>::get((16, 0)).unwrap().remaining == 10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// change the threshold to allow the task to succeed
|
|
Threshold::<Test>::put((14, 100));
|
|
// first retry should now succeed
|
|
run_to_block(14);
|
|
assert!(Agenda::<Test>::get(15)[0].as_ref().unwrap().maybe_periodic.is_none());
|
|
assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1);
|
|
assert!(Agenda::<Test>::get(16)[0].is_some());
|
|
assert_eq!(Retries::<Test>::get((15, 0)).unwrap().remaining, 9);
|
|
assert_eq!(Retries::<Test>::get((16, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
// second retry should also succeed
|
|
run_to_block(15);
|
|
assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1);
|
|
assert!(Agenda::<Test>::get(16)[0].is_some());
|
|
assert!(Agenda::<Test>::get(17).is_empty());
|
|
assert_eq!(Retries::<Test>::get((16, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)]
|
|
);
|
|
// normal periodic run on block 16 will succeed
|
|
run_to_block(16);
|
|
// next periodic run at block 19
|
|
assert!(Agenda::<Test>::get(19)[0].is_some());
|
|
assert!(Agenda::<Test>::get(18).is_empty());
|
|
assert!(Agenda::<Test>::get(17).is_empty());
|
|
assert_eq!(Retries::<Test>::get((19, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32)
|
|
]
|
|
);
|
|
// final periodic run on block 19 will succeed
|
|
run_to_block(19);
|
|
// next periodic run at block 19
|
|
assert_eq!(Agenda::<Test>::iter().count(), 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32)
|
|
]
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn named_retry_scheduling_with_period_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// tasks fail until we reach block 4 and after we're past block 8
|
|
Threshold::<Test>::put((4, 8));
|
|
// task 42 at #4, every 3 blocks, 6 times
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[42u8; 32],
|
|
DispatchTime::At(4),
|
|
Some((3, 6)),
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// 42 will be retried 10 times every 2 blocks
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// 42 runs successfully once, it will run again at block 7
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// nothing changed
|
|
run_to_block(6);
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// 42 runs successfully again, it will run again at block 10
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(7).is_empty());
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
run_to_block(9);
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// 42 has 10 retries left out of a total of 10
|
|
assert_eq!(Retries::<Test>::get((10, 0)).unwrap().remaining, 10);
|
|
// 42 will fail because we're outside the set threshold (block number in `4..8`), so it
|
|
// should be retried in 2 blocks (at block 12)
|
|
run_to_block(10);
|
|
// should be queued for the normal period of 3 blocks
|
|
assert!(Agenda::<Test>::get(13)[0].is_some());
|
|
// should also be queued to be retried in 2 blocks
|
|
assert!(Agenda::<Test>::get(12)[0].is_some());
|
|
// 42 has consumed one retry attempt
|
|
assert_eq!(Retries::<Test>::get((12, 0)).unwrap().remaining, 9);
|
|
assert_eq!(Retries::<Test>::get((13, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(Lookup::<Test>::get([42u8; 32]).unwrap(), (13, 0));
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// 42 will fail again
|
|
run_to_block(12);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(13)[0].is_some());
|
|
// should be queued to be retried in 2 blocks
|
|
assert!(Agenda::<Test>::get(14)[0].is_some());
|
|
// 42 has consumed another retry attempt
|
|
assert_eq!(Retries::<Test>::get((14, 0)).unwrap().remaining, 8);
|
|
assert_eq!(Retries::<Test>::get((13, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// 42 will fail for the regular periodic run
|
|
run_to_block(13);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(16)[0].is_some());
|
|
// should still be queued to be retried next block
|
|
assert!(Agenda::<Test>::get(14)[0].is_some());
|
|
// 42 consumed another periodic run, which failed, so another retry is queued for block 15
|
|
assert!(Agenda::<Test>::get(16)[0].as_ref().unwrap().maybe_periodic.is_some());
|
|
assert!(Agenda::<Test>::get(15)[0].as_ref().unwrap().maybe_periodic.is_none());
|
|
assert!(Agenda::<Test>::get(14)[0].as_ref().unwrap().maybe_periodic.is_none());
|
|
assert_eq!(Retries::<Test>::iter().count(), 3);
|
|
assert!(Retries::<Test>::get((14, 0)).unwrap().remaining == 8);
|
|
assert!(Retries::<Test>::get((15, 0)).unwrap().remaining == 9);
|
|
assert!(Retries::<Test>::get((16, 0)).unwrap().remaining == 10);
|
|
assert_eq!(Lookup::<Test>::get([42u8; 32]).unwrap(), (16, 0));
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
// change the threshold to allow the task to succeed
|
|
Threshold::<Test>::put((14, 100));
|
|
// first retry should now succeed
|
|
run_to_block(14);
|
|
assert!(Agenda::<Test>::get(15)[0].as_ref().unwrap().maybe_periodic.is_none());
|
|
assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1);
|
|
assert!(Agenda::<Test>::get(16)[0].is_some());
|
|
assert_eq!(Retries::<Test>::get((15, 0)).unwrap().remaining, 9);
|
|
assert_eq!(Retries::<Test>::get((16, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
// second retry should also succeed
|
|
run_to_block(15);
|
|
assert_eq!(Agenda::<Test>::get(16).iter().filter(|entry| entry.is_some()).count(), 1);
|
|
assert!(Agenda::<Test>::get(16)[0].is_some());
|
|
assert!(Agenda::<Test>::get(17).is_empty());
|
|
assert_eq!(Retries::<Test>::get((16, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(Lookup::<Test>::get([42u8; 32]).unwrap(), (16, 0));
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)]
|
|
);
|
|
// normal periodic run on block 16 will succeed
|
|
run_to_block(16);
|
|
// next periodic run at block 19
|
|
assert!(Agenda::<Test>::get(19)[0].is_some());
|
|
assert!(Agenda::<Test>::get(18).is_empty());
|
|
assert!(Agenda::<Test>::get(17).is_empty());
|
|
assert_eq!(Retries::<Test>::get((19, 0)).unwrap().remaining, 10);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(Lookup::<Test>::get([42u8; 32]).unwrap(), (19, 0));
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32)
|
|
]
|
|
);
|
|
// final periodic run on block 19 will succeed
|
|
run_to_block(19);
|
|
// next periodic run at block 19
|
|
assert_eq!(Agenda::<Test>::iter().count(), 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
assert_eq!(Lookup::<Test>::iter().count(), 0);
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32),
|
|
(root(), 42u32)
|
|
]
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_scheduling_expires() {
|
|
new_test_ext().execute_with(|| {
|
|
// task will fail if we're past block 3
|
|
Threshold::<Test>::put((1, 3));
|
|
// task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// task 42 will be retried 3 times every block
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
// task 42 is scheduled for next block
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// task fails because we're past block 3
|
|
run_to_block(4);
|
|
// task is scheduled for next block
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
assert!(Agenda::<Test>::get(5)[0].is_some());
|
|
// one retry attempt is consumed
|
|
assert_eq!(Retries::<Test>::get((5, 0)).unwrap().remaining, 2);
|
|
assert!(logger::log().is_empty());
|
|
// task fails again
|
|
run_to_block(5);
|
|
// task is scheduled for next block
|
|
assert!(Agenda::<Test>::get(5).is_empty());
|
|
assert!(Agenda::<Test>::get(6)[0].is_some());
|
|
// another retry attempt is consumed
|
|
assert_eq!(Retries::<Test>::get((6, 0)).unwrap().remaining, 1);
|
|
assert!(logger::log().is_empty());
|
|
// task fails again
|
|
run_to_block(6);
|
|
// task is scheduled for next block
|
|
assert!(Agenda::<Test>::get(6).is_empty());
|
|
assert!(Agenda::<Test>::get(7)[0].is_some());
|
|
// another retry attempt is consumed
|
|
assert_eq!(Retries::<Test>::get((7, 0)).unwrap().remaining, 0);
|
|
assert!(logger::log().is_empty());
|
|
// task fails again
|
|
run_to_block(7);
|
|
// task ran out of retries so it gets dropped
|
|
assert_eq!(Agenda::<Test>::iter().count(), 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
assert!(logger::log().is_empty());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn set_retry_bad_origin() {
|
|
new_test_ext().execute_with(|| {
|
|
// task 42 at #4 with account 101 as origin
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
101.into(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// try to change the retry config with a different (non-root) account
|
|
let res: Result<(), DispatchError> =
|
|
Scheduler::set_retry(RuntimeOrigin::signed(102), (4, 0), 10, 2);
|
|
assert_eq!(res, Err(BadOrigin.into()));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn set_named_retry_bad_origin() {
|
|
new_test_ext().execute_with(|| {
|
|
// task 42 at #4 with account 101 as origin
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[42u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
101.into(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// try to change the retry config with a different (non-root) account
|
|
let res: Result<(), DispatchError> =
|
|
Scheduler::set_retry_named(RuntimeOrigin::signed(102), [42u8; 32], 10, 2);
|
|
assert_eq!(res, Err(BadOrigin.into()));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn set_retry_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// make sure the retry configuration was stored
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2));
|
|
assert_eq!(
|
|
Retries::<Test>::get((4, 0)),
|
|
Some(RetryConfig { total_retries: 10, remaining: 10, period: 2 })
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn set_named_retry_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task 42 at #4 with account 101 as origin
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[42u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
// make sure the retry configuration was stored
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2));
|
|
let address = Lookup::<Test>::get([42u8; 32]).unwrap();
|
|
assert_eq!(
|
|
Retries::<Test>::get(address),
|
|
Some(RetryConfig { total_retries: 10, remaining: 10, period: 2 })
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_periodic_full_cycle() {
|
|
new_test_ext().execute_with(|| {
|
|
// tasks fail after we pass block 1000
|
|
Threshold::<Test>::put((1, 1000));
|
|
// task 42 at #4, every 100 blocks, 4 times
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[42u8; 32],
|
|
DispatchTime::At(10),
|
|
Some((100, 4)),
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
// 42 will be retried 2 times every block
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 2, 1));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
run_to_block(9);
|
|
assert!(logger::log().is_empty());
|
|
assert!(Agenda::<Test>::get(10)[0].is_some());
|
|
// 42 runs successfully once, it will run again at block 110
|
|
run_to_block(10);
|
|
assert!(Agenda::<Test>::get(10).is_empty());
|
|
assert!(Agenda::<Test>::get(110)[0].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// nothing changed
|
|
run_to_block(109);
|
|
assert!(Agenda::<Test>::get(110)[0].is_some());
|
|
// original task still has 2 remaining retries
|
|
assert_eq!(Retries::<Test>::get((110, 0)).unwrap().remaining, 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// make 42 fail next block
|
|
Threshold::<Test>::put((1, 2));
|
|
// 42 will fail because we're outside the set threshold (block number in `1..2`), so it
|
|
// should be retried next block (at block 111)
|
|
run_to_block(110);
|
|
// should be queued for the normal period of 100 blocks
|
|
assert!(Agenda::<Test>::get(210)[0].is_some());
|
|
// should also be queued to be retried next block
|
|
assert!(Agenda::<Test>::get(111)[0].is_some());
|
|
// 42 retry clone has consumed one retry attempt
|
|
assert_eq!(Retries::<Test>::get((111, 0)).unwrap().remaining, 1);
|
|
// 42 original task still has the original remaining attempts
|
|
assert_eq!(Retries::<Test>::get((210, 0)).unwrap().remaining, 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// 42 retry will fail again
|
|
run_to_block(111);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(210)[0].is_some());
|
|
// should be queued to be retried next block
|
|
assert!(Agenda::<Test>::get(112)[0].is_some());
|
|
// 42 has consumed another retry attempt
|
|
assert_eq!(Retries::<Test>::get((210, 0)).unwrap().remaining, 2);
|
|
assert_eq!(Retries::<Test>::get((112, 0)).unwrap().remaining, 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// 42 retry will fail again
|
|
run_to_block(112);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(210)[0].is_some());
|
|
// 42 retry clone ran out of retries, must have been evicted
|
|
assert_eq!(Agenda::<Test>::iter().count(), 1);
|
|
|
|
// advance
|
|
run_to_block(209);
|
|
// should still be queued for the normal period
|
|
assert!(Agenda::<Test>::get(210)[0].is_some());
|
|
// 42 retry clone ran out of retries, must have been evicted
|
|
assert_eq!(Agenda::<Test>::iter().count(), 1);
|
|
// 42 should fail again and should spawn another retry clone
|
|
run_to_block(210);
|
|
// should be queued for the normal period of 100 blocks
|
|
assert!(Agenda::<Test>::get(310)[0].is_some());
|
|
// should also be queued to be retried next block
|
|
assert!(Agenda::<Test>::get(211)[0].is_some());
|
|
// 42 retry clone has consumed one retry attempt
|
|
assert_eq!(Retries::<Test>::get((211, 0)).unwrap().remaining, 1);
|
|
// 42 original task still has the original remaining attempts
|
|
assert_eq!(Retries::<Test>::get((310, 0)).unwrap().remaining, 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// make 42 run successfully again
|
|
Threshold::<Test>::put((1, 1000));
|
|
// 42 retry clone should now succeed
|
|
run_to_block(211);
|
|
// should be queued for the normal period of 100 blocks
|
|
assert!(Agenda::<Test>::get(310)[0].is_some());
|
|
// retry was successful, retry task should have been discarded
|
|
assert_eq!(Agenda::<Test>::iter().count(), 1);
|
|
// 42 original task still has the original remaining attempts
|
|
assert_eq!(Retries::<Test>::get((310, 0)).unwrap().remaining, 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
|
|
// fast forward to the last periodic run of 42
|
|
run_to_block(310);
|
|
// 42 was successful, the period ended as this was the 4th scheduled periodic run so 42 must
|
|
// have been discarded
|
|
assert_eq!(Agenda::<Test>::iter().count(), 0);
|
|
// agenda is empty so no retries should exist
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reschedule_works() {
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
|
assert_eq!(
|
|
Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap()
|
|
)
|
|
.unwrap(),
|
|
(4, 0)
|
|
);
|
|
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
|
|
assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0));
|
|
|
|
assert_noop!(
|
|
Scheduler::do_reschedule((6, 0), DispatchTime::At(6)),
|
|
Error::<Test>::RescheduleNoChange
|
|
);
|
|
|
|
run_to_block(4);
|
|
assert!(logger::log().is_empty());
|
|
|
|
run_to_block(6);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reschedule_named_works() {
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
|
assert_eq!(
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap(),
|
|
(4, 0)
|
|
);
|
|
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
|
|
assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0));
|
|
|
|
assert_noop!(
|
|
Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)),
|
|
Error::<Test>::RescheduleNoChange
|
|
);
|
|
|
|
run_to_block(4);
|
|
assert!(logger::log().is_empty());
|
|
|
|
run_to_block(6);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reschedule_named_periodic_works() {
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
assert!(!<Test as frame_system::Config>::BaseCallFilter::contains(&call));
|
|
assert_eq!(
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
Some((3, 3)),
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap(),
|
|
(4, 0)
|
|
);
|
|
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
|
|
assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(5)).unwrap(), (5, 0));
|
|
assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0));
|
|
|
|
run_to_block(5);
|
|
assert!(logger::log().is_empty());
|
|
|
|
run_to_block(6);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
|
|
assert_eq!(
|
|
Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(10)).unwrap(),
|
|
(10, 0)
|
|
);
|
|
|
|
run_to_block(9);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
|
|
run_to_block(10);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]);
|
|
|
|
run_to_block(13);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_named_scheduling_works_with_normal_cancel() {
|
|
new_test_ext().execute_with(|| {
|
|
// at #4.
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
let i = Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32]));
|
|
assert_ok!(Scheduler::do_cancel(None, i));
|
|
run_to_block(100);
|
|
assert!(logger::log().is_empty());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_named_periodic_scheduling_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// at #4, every 3 blocks, 3 times.
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
Some((3, 3)),
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
// same id results in error.
|
|
assert!(Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap(),
|
|
)
|
|
.is_err());
|
|
// different id is ok.
|
|
Scheduler::do_schedule_named(
|
|
[2u8; 32],
|
|
DispatchTime::At(8),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
)
|
|
.unwrap();
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(6);
|
|
assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32]));
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_respects_weight_limits() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
new_test_ext().execute_with(|| {
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 69, weight: max_weight / 3 * 2 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
// 69 and 42 do not fit together
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
run_to_block(5);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn retry_respects_weight_limits() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
new_test_ext().execute_with(|| {
|
|
// schedule 42
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(8),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
// schedule 20 with a call that will fail until we reach block 8
|
|
Threshold::<Test>::put((8, 100));
|
|
let call = RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: max_weight / 3 * 2 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
// set a retry config for 20 for 10 retries every block
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1));
|
|
// 20 should fail and be retried later
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(5)[0].is_some());
|
|
assert!(Agenda::<Test>::get(8)[0].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert!(logger::log().is_empty());
|
|
// 20 still fails but is scheduled next block together with 42
|
|
run_to_block(7);
|
|
assert_eq!(Agenda::<Test>::get(8).len(), 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert!(logger::log().is_empty());
|
|
// 20 and 42 do not fit together
|
|
// 42 is executed as it was first in the queue
|
|
// 20 is still on the 8th block's agenda
|
|
run_to_block(8);
|
|
assert!(Agenda::<Test>::get(8)[0].is_none());
|
|
assert!(Agenda::<Test>::get(8)[1].is_some());
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// 20 is executed and the schedule is cleared
|
|
run_to_block(9);
|
|
assert_eq!(Agenda::<Test>::iter().count(), 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 20u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn try_schedule_retry_respects_weight_limits() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
new_test_ext().execute_with(|| {
|
|
let service_agendas_weight = <Test as Config>::WeightInfo::service_agendas_base();
|
|
let service_agenda_weight = <Test as Config>::WeightInfo::service_agenda_base(
|
|
<Test as Config>::MaxScheduledPerBlock::get(),
|
|
);
|
|
let actual_service_agenda_weight = <Test as Config>::WeightInfo::service_agenda_base(1);
|
|
// Some weight for `service_agenda` will be refunded, so we need to make sure the weight
|
|
// `try_schedule_retry` is going to ask for is greater than this difference, and we take a
|
|
// safety factor of 10 to make sure we're over that limit.
|
|
let meter = WeightMeter::with_limit(
|
|
<Test as Config>::WeightInfo::schedule_retry(
|
|
<Test as Config>::MaxScheduledPerBlock::get(),
|
|
) / 10,
|
|
);
|
|
assert!(meter.can_consume(service_agenda_weight - actual_service_agenda_weight));
|
|
|
|
let reference_call =
|
|
RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: max_weight / 3 * 2 });
|
|
let bounded = <Test as Config>::Preimages::bound(reference_call).unwrap();
|
|
let base_weight = <Test as Config>::WeightInfo::service_task(
|
|
bounded.lookup_len().map(|x| x as usize),
|
|
false,
|
|
false,
|
|
);
|
|
// we make the call cost enough so that all checks have enough weight to run aside from
|
|
// `try_schedule_retry`
|
|
let call_weight = max_weight - service_agendas_weight - service_agenda_weight - base_weight;
|
|
let call = RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: call_weight });
|
|
// schedule 20 with a call that will fail until we reach block 8
|
|
Threshold::<Test>::put((8, 100));
|
|
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
// set a retry config for 20 for 10 retries every block
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1));
|
|
// 20 should fail and, because of insufficient weight, it should not be scheduled again
|
|
run_to_block(4);
|
|
// nothing else should be scheduled
|
|
assert_eq!(Agenda::<Test>::iter().count(), 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
assert_eq!(logger::log(), vec![]);
|
|
// check the `RetryFailed` event happened
|
|
let events = frame_system::Pallet::<Test>::events();
|
|
let system_event: <Test as frame_system::Config>::RuntimeEvent =
|
|
Event::RetryFailed { task: (4, 0), id: None }.into();
|
|
// compare to the last event record
|
|
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
|
|
assert_eq!(event, &system_event);
|
|
});
|
|
}
|
|
|
|
/// Permanently overweight calls are not deleted but also not executed.
|
|
#[test]
|
|
fn scheduler_does_not_delete_permanently_overweight_call() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
new_test_ext().execute_with(|| {
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
// Never executes.
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![]);
|
|
|
|
// Assert the `PermanentlyOverweight` event.
|
|
assert_eq!(
|
|
System::events().last().unwrap().event,
|
|
crate::Event::PermanentlyOverweight { task: (4, 0), id: None }.into(),
|
|
);
|
|
// The call is still in the agenda.
|
|
assert!(Agenda::<Test>::get(4)[0].is_some());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_handles_periodic_failure() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
let max_per_block = <Test as Config>::MaxScheduledPerBlock::get();
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
Some((4, u32::MAX)),
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
));
|
|
// Executes 5 times till block 20.
|
|
run_to_block(20);
|
|
assert_eq!(logger::log().len(), 5);
|
|
|
|
// Block 28 will already be full.
|
|
for _ in 0..max_per_block {
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(28),
|
|
None,
|
|
120,
|
|
root(),
|
|
bound.clone(),
|
|
));
|
|
}
|
|
|
|
// Going to block 24 will emit a `PeriodicFailed` event.
|
|
run_to_block(24);
|
|
assert_eq!(logger::log().len(), 6);
|
|
|
|
assert_eq!(
|
|
System::events().last().unwrap().event,
|
|
crate::Event::PeriodicFailed { task: (24, 0), id: None }.into(),
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_handles_periodic_unavailable_preimage() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 });
|
|
let hash = <Test as frame_system::Config>::Hashing::hash_of(&call);
|
|
let len = call.using_encoded(|x| x.len()) as u32;
|
|
// Important to use here `Bounded::Lookup` to ensure that we request the hash.
|
|
let bound = Bounded::Lookup { hash, len };
|
|
// The preimage isn't requested yet.
|
|
assert!(!Preimage::is_requested(&hash));
|
|
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
Some((4, u32::MAX)),
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
));
|
|
|
|
// The preimage is requested.
|
|
assert!(Preimage::is_requested(&hash));
|
|
|
|
// Note the preimage.
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), call.encode()));
|
|
|
|
// Executes 1 times till block 4.
|
|
run_to_block(4);
|
|
assert_eq!(logger::log().len(), 1);
|
|
|
|
// As the public api doesn't support to remove a noted preimage, we need to first unnote it
|
|
// and then request it again. Basically this should not happen in real life (whatever you
|
|
// call real life;).
|
|
Preimage::unnote(&hash);
|
|
Preimage::request(&hash);
|
|
|
|
// Does not ever execute again.
|
|
run_to_block(100);
|
|
assert_eq!(logger::log().len(), 1);
|
|
|
|
// The preimage is not requested anymore.
|
|
assert!(!Preimage::is_requested(&hash));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_respects_priority_ordering() {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
new_test_ext().execute_with(|| {
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
1,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 69, weight: max_weight / 3 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
0,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_respects_priority_ordering_with_soft_deadlines() {
|
|
new_test_ext().execute_with(|| {
|
|
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 5 * 2 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
255,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 69, weight: max_weight / 5 * 2 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
let call = RuntimeCall::Logger(LoggerCall::log { i: 2600, weight: max_weight / 5 * 4 });
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
126,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
|
|
// 2600 does not fit with 69 or 42, but has higher priority, so will go through
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 2600u32)]);
|
|
// 69 and 42 fit together
|
|
run_to_block(5);
|
|
assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn on_initialize_weight_is_correct() {
|
|
new_test_ext().execute_with(|| {
|
|
let call_weight = Weight::from_parts(25, 0);
|
|
|
|
// Named
|
|
let call = RuntimeCall::Logger(LoggerCall::log {
|
|
i: 3,
|
|
weight: call_weight + Weight::from_parts(1, 0),
|
|
});
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(3),
|
|
None,
|
|
255,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
let call = RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: call_weight + Weight::from_parts(2, 0),
|
|
});
|
|
// Anon Periodic
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(2),
|
|
Some((1000, 3)),
|
|
128,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
let call = RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: call_weight + Weight::from_parts(3, 0),
|
|
});
|
|
// Anon
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(2),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
// Named Periodic
|
|
let call = RuntimeCall::Logger(LoggerCall::log {
|
|
i: 2600,
|
|
weight: call_weight + Weight::from_parts(4, 0),
|
|
});
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[2u8; 32],
|
|
DispatchTime::At(1),
|
|
Some((1000, 3)),
|
|
126,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
));
|
|
|
|
// Will include the named periodic only
|
|
assert_eq!(
|
|
Scheduler::on_initialize(1),
|
|
TestWeightInfo::service_agendas_base() +
|
|
TestWeightInfo::service_agenda_base(1) +
|
|
<TestWeightInfo as MarginalWeightInfo>::service_task(None, true, true) +
|
|
TestWeightInfo::execute_dispatch_unsigned() +
|
|
call_weight + Weight::from_parts(4, 0)
|
|
);
|
|
assert_eq!(IncompleteSince::<Test>::get(), None);
|
|
assert_eq!(logger::log(), vec![(root(), 2600u32)]);
|
|
|
|
// Will include anon and anon periodic
|
|
assert_eq!(
|
|
Scheduler::on_initialize(2),
|
|
TestWeightInfo::service_agendas_base() +
|
|
TestWeightInfo::service_agenda_base(2) +
|
|
<TestWeightInfo as MarginalWeightInfo>::service_task(None, false, true) +
|
|
TestWeightInfo::execute_dispatch_unsigned() +
|
|
call_weight + Weight::from_parts(3, 0) +
|
|
<TestWeightInfo as MarginalWeightInfo>::service_task(None, false, false) +
|
|
TestWeightInfo::execute_dispatch_unsigned() +
|
|
call_weight + Weight::from_parts(2, 0)
|
|
);
|
|
assert_eq!(IncompleteSince::<Test>::get(), None);
|
|
assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]);
|
|
|
|
// Will include named only
|
|
assert_eq!(
|
|
Scheduler::on_initialize(3),
|
|
TestWeightInfo::service_agendas_base() +
|
|
TestWeightInfo::service_agenda_base(1) +
|
|
<TestWeightInfo as MarginalWeightInfo>::service_task(None, true, false) +
|
|
TestWeightInfo::execute_dispatch_unsigned() +
|
|
call_weight + Weight::from_parts(1, 0)
|
|
);
|
|
assert_eq!(IncompleteSince::<Test>::get(), None);
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32), (root(), 3u32)]
|
|
);
|
|
|
|
// Will contain none
|
|
let actual_weight = Scheduler::on_initialize(4);
|
|
assert_eq!(
|
|
actual_weight,
|
|
TestWeightInfo::service_agendas_base() + TestWeightInfo::service_agenda_base(0)
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn root_calls_works() {
|
|
new_test_ext().execute_with(|| {
|
|
let call = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
assert_ok!(
|
|
Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 4, None, 127, call,)
|
|
);
|
|
assert_ok!(Scheduler::schedule(RuntimeOrigin::root(), 4, None, 127, call2));
|
|
run_to_block(3);
|
|
// Scheduled calls are in the agenda.
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
assert!(logger::log().is_empty());
|
|
assert_ok!(Scheduler::cancel_named(RuntimeOrigin::root(), [1u8; 32]));
|
|
assert_ok!(Scheduler::cancel(RuntimeOrigin::root(), 4, 1));
|
|
// Scheduled calls are made NONE, so should not effect state
|
|
run_to_block(100);
|
|
assert!(logger::log().is_empty());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn fails_to_schedule_task_in_the_past() {
|
|
new_test_ext().execute_with(|| {
|
|
run_to_block(3);
|
|
|
|
let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
let call3 = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
|
|
assert_noop!(
|
|
Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 2, None, 127, call1),
|
|
Error::<Test>::TargetBlockNumberInPast,
|
|
);
|
|
|
|
assert_noop!(
|
|
Scheduler::schedule(RuntimeOrigin::root(), 2, None, 127, call2),
|
|
Error::<Test>::TargetBlockNumberInPast,
|
|
);
|
|
|
|
assert_noop!(
|
|
Scheduler::schedule(RuntimeOrigin::root(), 3, None, 127, call3),
|
|
Error::<Test>::TargetBlockNumberInPast,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn should_use_origin() {
|
|
new_test_ext().execute_with(|| {
|
|
let call = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
assert_ok!(Scheduler::schedule_named(
|
|
system::RawOrigin::Signed(1).into(),
|
|
[1u8; 32],
|
|
4,
|
|
None,
|
|
127,
|
|
call,
|
|
));
|
|
assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,));
|
|
run_to_block(3);
|
|
// Scheduled calls are in the agenda.
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
assert!(logger::log().is_empty());
|
|
assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), [1u8; 32]));
|
|
assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1));
|
|
// Scheduled calls are made NONE, so should not effect state
|
|
run_to_block(100);
|
|
assert!(logger::log().is_empty());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn should_check_origin() {
|
|
new_test_ext().execute_with(|| {
|
|
let call = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
assert_noop!(
|
|
Scheduler::schedule_named(
|
|
system::RawOrigin::Signed(2).into(),
|
|
[1u8; 32],
|
|
4,
|
|
None,
|
|
127,
|
|
call
|
|
),
|
|
BadOrigin
|
|
);
|
|
assert_noop!(
|
|
Scheduler::schedule(system::RawOrigin::Signed(2).into(), 4, None, 127, call2),
|
|
BadOrigin
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn should_check_origin_for_cancel() {
|
|
new_test_ext().execute_with(|| {
|
|
let call = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0),
|
|
}));
|
|
assert_ok!(Scheduler::schedule_named(
|
|
system::RawOrigin::Signed(1).into(),
|
|
[1u8; 32],
|
|
4,
|
|
None,
|
|
127,
|
|
call,
|
|
));
|
|
assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,));
|
|
run_to_block(3);
|
|
// Scheduled calls are in the agenda.
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
assert!(logger::log().is_empty());
|
|
assert_noop!(
|
|
Scheduler::cancel_named(system::RawOrigin::Signed(2).into(), [1u8; 32]),
|
|
BadOrigin
|
|
);
|
|
assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin);
|
|
assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), [1u8; 32]), BadOrigin);
|
|
assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin);
|
|
run_to_block(5);
|
|
assert_eq!(
|
|
logger::log(),
|
|
vec![
|
|
(system::RawOrigin::Signed(1).into(), 69u32),
|
|
(system::RawOrigin::Signed(1).into(), 42u32)
|
|
]
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_removes_retry_entry() {
|
|
new_test_ext().execute_with(|| {
|
|
// task fails until block 99 is reached
|
|
Threshold::<Test>::put((99, 100));
|
|
// task 20 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 20,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
// named task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// task 20 will be retried 3 times every block
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1));
|
|
// task 42 will be retried 10 times every 3 blocks
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1));
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// both tasks fail
|
|
run_to_block(4);
|
|
assert!(Agenda::<Test>::get(4).is_empty());
|
|
// 42 and 20 are rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(5).len(), 2);
|
|
assert!(logger::log().is_empty());
|
|
// 42 and 20 still fail
|
|
run_to_block(5);
|
|
// 42 and 20 rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(6).len(), 2);
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
assert!(logger::log().is_empty());
|
|
|
|
// even though 42 is being retried, the tasks scheduled for retries are not named
|
|
assert_eq!(Lookup::<Test>::iter().count(), 0);
|
|
assert!(Scheduler::cancel(root().into(), 6, 0).is_ok());
|
|
|
|
// 20 is removed, 42 still fails
|
|
run_to_block(6);
|
|
// 42 rescheduled for next block
|
|
assert_eq!(Agenda::<Test>::get(7).len(), 1);
|
|
// 20's retry entry is removed
|
|
assert!(!Retries::<Test>::contains_key((4, 0)));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
assert!(logger::log().is_empty());
|
|
|
|
assert!(Scheduler::cancel(root().into(), 7, 0).is_ok());
|
|
|
|
// both tasks are canceled, everything is removed now
|
|
run_to_block(7);
|
|
assert!(Agenda::<Test>::get(8).is_empty());
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_retries_works() {
|
|
new_test_ext().execute_with(|| {
|
|
// task fails until block 99 is reached
|
|
Threshold::<Test>::put((99, 100));
|
|
// task 20 at #4
|
|
assert_ok!(Scheduler::do_schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 20,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
// named task 42 at #4
|
|
assert_ok!(Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log {
|
|
i: 42,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap()
|
|
));
|
|
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// task 20 will be retried 3 times every block
|
|
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1));
|
|
// task 42 will be retried 10 times every 3 blocks
|
|
assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1));
|
|
assert_eq!(Retries::<Test>::iter().count(), 2);
|
|
run_to_block(3);
|
|
assert!(logger::log().is_empty());
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 2);
|
|
// cancel the retry config for 20
|
|
assert_ok!(Scheduler::cancel_retry(root().into(), (4, 0)));
|
|
assert_eq!(Retries::<Test>::iter().count(), 1);
|
|
// cancel the retry config for 42
|
|
assert_ok!(Scheduler::cancel_retry_named(root().into(), [1u8; 32]));
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
run_to_block(4);
|
|
// both tasks failed and there are no more retries, so they are evicted
|
|
assert_eq!(Agenda::<Test>::get(4).len(), 0);
|
|
assert_eq!(Retries::<Test>::iter().count(), 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn migration_to_v4_works() {
|
|
new_test_ext().execute_with(|| {
|
|
for i in 0..3u64 {
|
|
let k = i.twox_64_concat();
|
|
let old = vec![
|
|
Some(ScheduledV1 {
|
|
maybe_id: None,
|
|
priority: i as u8 + 10,
|
|
call: RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0),
|
|
}),
|
|
maybe_periodic: None,
|
|
}),
|
|
None,
|
|
Some(ScheduledV1 {
|
|
maybe_id: Some(b"test".to_vec()),
|
|
priority: 123,
|
|
call: RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
}),
|
|
];
|
|
frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old);
|
|
}
|
|
|
|
Scheduler::migrate_v1_to_v4();
|
|
|
|
let mut x = Agenda::<Test>::iter().map(|x| (x.0, x.1.into_inner())).collect::<Vec<_>>();
|
|
x.sort_by_key(|x| x.0);
|
|
let expected = vec![
|
|
(
|
|
0,
|
|
vec![
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: None,
|
|
priority: 10,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: None,
|
|
origin: root(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
None,
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
origin: root(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
],
|
|
),
|
|
(
|
|
1,
|
|
vec![
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: None,
|
|
priority: 11,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: None,
|
|
origin: root(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
None,
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
origin: root(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
],
|
|
),
|
|
(
|
|
2,
|
|
vec![
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: None,
|
|
priority: 12,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: None,
|
|
origin: root(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
None,
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
origin: root(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
],
|
|
),
|
|
];
|
|
for (i, j) in x.iter().zip(expected.iter()) {
|
|
assert_eq!(i.0, j.0);
|
|
for (x, y) in i.1.iter().zip(j.1.iter()) {
|
|
assert_eq!(x, y);
|
|
}
|
|
}
|
|
assert_eq_uvec!(x, expected);
|
|
|
|
assert_eq!(Scheduler::on_chain_storage_version(), 4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_migrate_origin() {
|
|
new_test_ext().execute_with(|| {
|
|
for i in 0..3u64 {
|
|
let k = i.twox_64_concat();
|
|
let old: Vec<Option<Scheduled<[u8; 32], BoundedCallOf<Test>, u64, u32, u64>>> = vec![
|
|
Some(Scheduled {
|
|
maybe_id: None,
|
|
priority: i as u8 + 10,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0),
|
|
}))
|
|
.unwrap(),
|
|
origin: 3u32,
|
|
maybe_periodic: None,
|
|
_phantom: Default::default(),
|
|
}),
|
|
None,
|
|
Some(Scheduled {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
origin: 2u32,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0),
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
_phantom: Default::default(),
|
|
}),
|
|
];
|
|
frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old);
|
|
}
|
|
|
|
impl Into<OriginCaller> for u32 {
|
|
fn into(self) -> OriginCaller {
|
|
match self {
|
|
3u32 => system::RawOrigin::Root.into(),
|
|
2u32 => system::RawOrigin::None.into(),
|
|
101u32 => system::RawOrigin::Signed(101).into(),
|
|
102u32 => system::RawOrigin::Signed(102).into(),
|
|
_ => unreachable!("test make no use of it"),
|
|
}
|
|
}
|
|
}
|
|
|
|
Scheduler::migrate_origin::<u32>();
|
|
|
|
assert_eq_uvec!(
|
|
Agenda::<Test>::iter().map(|x| (x.0, x.1.into_inner())).collect::<Vec<_>>(),
|
|
vec![
|
|
(
|
|
0,
|
|
vec![
|
|
Some(ScheduledOf::<Test> {
|
|
maybe_id: None,
|
|
priority: 10,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0)
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: None,
|
|
origin: system::RawOrigin::Root.into(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
None,
|
|
Some(Scheduled {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
origin: system::RawOrigin::None.into(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
]
|
|
),
|
|
(
|
|
1,
|
|
vec![
|
|
Some(Scheduled {
|
|
maybe_id: None,
|
|
priority: 11,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0)
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: None,
|
|
origin: system::RawOrigin::Root.into(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
None,
|
|
Some(Scheduled {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
origin: system::RawOrigin::None.into(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
]
|
|
),
|
|
(
|
|
2,
|
|
vec![
|
|
Some(Scheduled {
|
|
maybe_id: None,
|
|
priority: 12,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 96,
|
|
weight: Weight::from_parts(100, 0)
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: None,
|
|
origin: system::RawOrigin::Root.into(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
None,
|
|
Some(Scheduled {
|
|
maybe_id: Some(blake2_256(&b"test"[..])),
|
|
priority: 123,
|
|
call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log {
|
|
i: 69,
|
|
weight: Weight::from_parts(10, 0)
|
|
}))
|
|
.unwrap(),
|
|
maybe_periodic: Some((456u64, 10)),
|
|
origin: system::RawOrigin::None.into(),
|
|
_phantom: PhantomData::<u64>::default(),
|
|
}),
|
|
]
|
|
)
|
|
]
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn postponed_named_task_cannot_be_rescheduled() {
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(1000, 0) });
|
|
let hash = <Test as frame_system::Config>::Hashing::hash_of(&call);
|
|
let len = call.using_encoded(|x| x.len()) as u32;
|
|
// Important to use here `Bounded::Lookup` to ensure that we request the hash.
|
|
let hashed = Bounded::Lookup { hash, len };
|
|
let name: [u8; 32] = hash.as_ref().try_into().unwrap();
|
|
|
|
let address = Scheduler::do_schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
hashed.clone(),
|
|
)
|
|
.unwrap();
|
|
assert!(Preimage::is_requested(&hash));
|
|
assert!(Lookup::<Test>::contains_key(name));
|
|
|
|
// Run to a very large block.
|
|
run_to_block(10);
|
|
|
|
// It was not executed.
|
|
assert!(logger::log().is_empty());
|
|
|
|
// Preimage was not available
|
|
assert_eq!(
|
|
System::events().last().unwrap().event,
|
|
crate::Event::CallUnavailable { task: (4, 0), id: Some(name) }.into()
|
|
);
|
|
|
|
// So it should not be requested.
|
|
assert!(!Preimage::is_requested(&hash));
|
|
// Postponing removes the lookup.
|
|
assert!(!Lookup::<Test>::contains_key(name));
|
|
|
|
// The agenda still contains the call.
|
|
let agenda = Agenda::<Test>::iter().collect::<Vec<_>>();
|
|
assert_eq!(agenda.len(), 1);
|
|
assert_eq!(
|
|
agenda[0].1,
|
|
vec![Some(Scheduled {
|
|
maybe_id: Some(name),
|
|
priority: 127,
|
|
call: hashed,
|
|
maybe_periodic: None,
|
|
origin: root().into(),
|
|
_phantom: Default::default(),
|
|
})]
|
|
);
|
|
|
|
// Finally add the preimage.
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode()));
|
|
|
|
run_to_block(1000);
|
|
// It did not execute.
|
|
assert!(logger::log().is_empty());
|
|
assert!(!Preimage::is_requested(&hash));
|
|
|
|
// Manually re-schedule the call by name does not work.
|
|
assert_err!(
|
|
Scheduler::do_reschedule_named(name, DispatchTime::At(1001)),
|
|
Error::<Test>::NotFound
|
|
);
|
|
// Manually re-scheduling the call by address errors.
|
|
assert_err!(
|
|
Scheduler::do_reschedule(address, DispatchTime::At(1001)),
|
|
Error::<Test>::Named
|
|
);
|
|
});
|
|
}
|
|
|
|
/// Using the scheduler as `v3::Anon` works.
|
|
#[test]
|
|
fn scheduler_v3_anon_basic_works() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
|
|
// Schedule a call.
|
|
let _address = <Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
// Executes in block 4.
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// ... but not again.
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_v3_anon_cancel_works() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
|
|
// Schedule a call.
|
|
let address = <Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
// Cancel the call.
|
|
assert_ok!(<Scheduler as Anon<_, _, _>>::cancel(address));
|
|
// It did not get executed.
|
|
run_to_block(100);
|
|
assert!(logger::log().is_empty());
|
|
// Cannot cancel again.
|
|
assert_err!(<Scheduler as Anon<_, _, _>>::cancel(address), DispatchError::Unavailable);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_v3_anon_reschedule_works() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
|
|
// Schedule a call.
|
|
let address = <Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
|
|
// Cannot re-schedule into the same block.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(4)),
|
|
Error::<Test>::RescheduleNoChange
|
|
);
|
|
// Cannot re-schedule into the past.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(3)),
|
|
Error::<Test>::TargetBlockNumberInPast
|
|
);
|
|
// Re-schedule to block 5.
|
|
assert_ok!(<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(5)));
|
|
// Scheduled for block 5.
|
|
run_to_block(4);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(5);
|
|
// Does execute in block 5.
|
|
assert_eq!(logger::log(), vec![(root(), 42)]);
|
|
// Cannot re-schedule executed task.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(10)),
|
|
DispatchError::Unavailable
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_v3_anon_next_schedule_time_works() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
|
|
// Schedule a call.
|
|
let address = <Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
|
|
// Scheduled for block 4.
|
|
assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(address), Ok(4));
|
|
// Block 4 executes it.
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42)]);
|
|
|
|
// It has no dispatch time anymore.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::next_dispatch_time(address),
|
|
DispatchError::Unavailable
|
|
);
|
|
});
|
|
}
|
|
|
|
/// Re-scheduling a task changes its next dispatch time.
|
|
#[test]
|
|
fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
|
|
// Schedule a call.
|
|
let old_address = <Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
|
|
// Scheduled for block 4.
|
|
assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(old_address), Ok(4));
|
|
// Re-schedule to block 5.
|
|
let address =
|
|
<Scheduler as Anon<_, _, _>>::reschedule(old_address, DispatchTime::At(5)).unwrap();
|
|
assert!(address != old_address);
|
|
// Scheduled for block 5.
|
|
assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(address), Ok(5));
|
|
|
|
// Block 4 does nothing.
|
|
run_to_block(4);
|
|
assert!(logger::log().is_empty());
|
|
// Block 5 executes it.
|
|
run_to_block(5);
|
|
assert_eq!(logger::log(), vec![(root(), 42)]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_v3_anon_schedule_agenda_overflows() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
let max: u32 = <Test as Config>::MaxScheduledPerBlock::get();
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
|
|
// Schedule the maximal number allowed per block.
|
|
for _ in 0..max {
|
|
<Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// One more time and it errors.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::schedule(DispatchTime::At(4), None, 127, root(), bound,),
|
|
DispatchError::Exhausted
|
|
);
|
|
|
|
run_to_block(4);
|
|
// All scheduled calls are executed.
|
|
assert_eq!(logger::log().len() as u32, max);
|
|
});
|
|
}
|
|
|
|
/// Cancelling and scheduling does not overflow the agenda but fills holes.
|
|
#[test]
|
|
fn scheduler_v3_anon_cancel_and_schedule_fills_holes() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
let max: u32 = <Test as Config>::MaxScheduledPerBlock::get();
|
|
assert!(max > 3, "This test only makes sense for MaxScheduledPerBlock > 3");
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
let mut addrs = Vec::<_>::default();
|
|
|
|
// Schedule the maximal number allowed per block.
|
|
for _ in 0..max {
|
|
addrs.push(
|
|
<Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap(),
|
|
);
|
|
}
|
|
// Cancel three of them.
|
|
for addr in addrs.into_iter().take(3) {
|
|
<Scheduler as Anon<_, _, _>>::cancel(addr).unwrap();
|
|
}
|
|
// Schedule three new ones.
|
|
for i in 0..3 {
|
|
let (_block, index) = <Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(i, index);
|
|
}
|
|
|
|
run_to_block(4);
|
|
// Maximum number of calls are executed.
|
|
assert_eq!(logger::log().len() as u32, max);
|
|
});
|
|
}
|
|
|
|
/// Re-scheduling does not overflow the agenda but fills holes.
|
|
#[test]
|
|
fn scheduler_v3_anon_reschedule_fills_holes() {
|
|
use frame_support::traits::schedule::v3::Anon;
|
|
let max: u32 = <Test as Config>::MaxScheduledPerBlock::get();
|
|
assert!(max > 3, "pre-condition: This test only makes sense for MaxScheduledPerBlock > 3");
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
let mut addrs = Vec::<_>::default();
|
|
|
|
// Schedule the maximal number allowed per block.
|
|
for _ in 0..max {
|
|
addrs.push(
|
|
<Scheduler as Anon<_, _, _>>::schedule(
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap(),
|
|
);
|
|
}
|
|
let mut new_addrs = Vec::<_>::default();
|
|
// Reversed last three elements of block 4.
|
|
let last_three = addrs.into_iter().rev().take(3).collect::<Vec<_>>();
|
|
// Re-schedule three of them to block 5.
|
|
for addr in last_three.iter().cloned() {
|
|
new_addrs
|
|
.push(<Scheduler as Anon<_, _, _>>::reschedule(addr, DispatchTime::At(5)).unwrap());
|
|
}
|
|
// Re-scheduling them back into block 3 should result in the same addrs.
|
|
for (old, want) in new_addrs.into_iter().zip(last_three.into_iter().rev()) {
|
|
let new = <Scheduler as Anon<_, _, _>>::reschedule(old, DispatchTime::At(4)).unwrap();
|
|
assert_eq!(new, want);
|
|
}
|
|
|
|
run_to_block(4);
|
|
// Maximum number of calls are executed.
|
|
assert_eq!(logger::log().len() as u32, max);
|
|
});
|
|
}
|
|
|
|
/// The scheduler can be used as `v3::Named` trait.
|
|
#[test]
|
|
fn scheduler_v3_named_basic_works() {
|
|
use frame_support::traits::schedule::v3::Named;
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let name = [1u8; 32];
|
|
|
|
// Schedule a call.
|
|
let _address = <Scheduler as Named<_, _, _>>::schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
// Executes in block 4.
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
// ... but not again.
|
|
run_to_block(100);
|
|
assert_eq!(logger::log(), vec![(root(), 42u32)]);
|
|
});
|
|
}
|
|
|
|
/// A named task can be cancelled by its name.
|
|
#[test]
|
|
fn scheduler_v3_named_cancel_named_works() {
|
|
use frame_support::traits::schedule::v3::Named;
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
let name = [1u8; 32];
|
|
|
|
// Schedule a call.
|
|
<Scheduler as Named<_, _, _>>::schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
// Cancel the call by name.
|
|
assert_ok!(<Scheduler as Named<_, _, _>>::cancel_named(name));
|
|
// It did not get executed.
|
|
run_to_block(100);
|
|
assert!(logger::log().is_empty());
|
|
// Cannot cancel again.
|
|
assert_noop!(<Scheduler as Named<_, _, _>>::cancel_named(name), DispatchError::Unavailable);
|
|
});
|
|
}
|
|
|
|
/// A named task can also be cancelled by its address.
|
|
#[test]
|
|
fn scheduler_v3_named_cancel_without_name_works() {
|
|
use frame_support::traits::schedule::v3::{Anon, Named};
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
let name = [1u8; 32];
|
|
|
|
// Schedule a call.
|
|
let address = <Scheduler as Named<_, _, _>>::schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
// Cancel the call by address.
|
|
assert_ok!(<Scheduler as Anon<_, _, _>>::cancel(address));
|
|
// It did not get executed.
|
|
run_to_block(100);
|
|
assert!(logger::log().is_empty());
|
|
// Cannot cancel again.
|
|
assert_err!(<Scheduler as Anon<_, _, _>>::cancel(address), DispatchError::Unavailable);
|
|
});
|
|
}
|
|
|
|
/// A named task can be re-scheduled by its name but not by its address.
|
|
#[test]
|
|
fn scheduler_v3_named_reschedule_named_works() {
|
|
use frame_support::traits::schedule::v3::{Anon, Named};
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let name = [1u8; 32];
|
|
|
|
// Schedule a call.
|
|
let address = <Scheduler as Named<_, _, _>>::schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
|
|
// Cannot re-schedule by address.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(10)),
|
|
Error::<Test>::Named,
|
|
);
|
|
// Cannot re-schedule into the same block.
|
|
assert_noop!(
|
|
<Scheduler as Named<_, _, _>>::reschedule_named(name, DispatchTime::At(4)),
|
|
Error::<Test>::RescheduleNoChange
|
|
);
|
|
// Cannot re-schedule into the past.
|
|
assert_noop!(
|
|
<Scheduler as Named<_, _, _>>::reschedule_named(name, DispatchTime::At(3)),
|
|
Error::<Test>::TargetBlockNumberInPast
|
|
);
|
|
// Re-schedule to block 5.
|
|
assert_ok!(<Scheduler as Named<_, _, _>>::reschedule_named(name, DispatchTime::At(5)));
|
|
// Scheduled for block 5.
|
|
run_to_block(4);
|
|
assert!(logger::log().is_empty());
|
|
run_to_block(5);
|
|
// Does execute in block 5.
|
|
assert_eq!(logger::log(), vec![(root(), 42)]);
|
|
// Cannot re-schedule executed task.
|
|
assert_noop!(
|
|
<Scheduler as Named<_, _, _>>::reschedule_named(name, DispatchTime::At(10)),
|
|
DispatchError::Unavailable
|
|
);
|
|
// Also not by address.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::reschedule(address, DispatchTime::At(10)),
|
|
DispatchError::Unavailable
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn scheduler_v3_named_next_schedule_time_works() {
|
|
use frame_support::traits::schedule::v3::{Anon, Named};
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let bound = Preimage::bound(call).unwrap();
|
|
let name = [1u8; 32];
|
|
|
|
// Schedule a call.
|
|
let address = <Scheduler as Named<_, _, _>>::schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
|
|
run_to_block(3);
|
|
// Did not execute till block 3.
|
|
assert!(logger::log().is_empty());
|
|
|
|
// Scheduled for block 4.
|
|
assert_eq!(<Scheduler as Named<_, _, _>>::next_dispatch_time(name), Ok(4));
|
|
// Also works by address.
|
|
assert_eq!(<Scheduler as Anon<_, _, _>>::next_dispatch_time(address), Ok(4));
|
|
// Block 4 executes it.
|
|
run_to_block(4);
|
|
assert_eq!(logger::log(), vec![(root(), 42)]);
|
|
|
|
// It has no dispatch time anymore.
|
|
assert_noop!(
|
|
<Scheduler as Named<_, _, _>>::next_dispatch_time(name),
|
|
DispatchError::Unavailable
|
|
);
|
|
// Also not by address.
|
|
assert_noop!(
|
|
<Scheduler as Anon<_, _, _>>::next_dispatch_time(address),
|
|
DispatchError::Unavailable
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_last_task_removes_agenda() {
|
|
new_test_ext().execute_with(|| {
|
|
let when = 4;
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let address = Scheduler::do_schedule(
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call.clone()).unwrap(),
|
|
)
|
|
.unwrap();
|
|
let address2 = Scheduler::do_schedule(
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
// two tasks at agenda.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
assert_ok!(Scheduler::do_cancel(None, address));
|
|
// still two tasks at agenda, `None` and `Some`.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
// cancel last task from `when` agenda.
|
|
assert_ok!(Scheduler::do_cancel(None, address2));
|
|
// if all tasks `None`, agenda fully removed.
|
|
assert!(Agenda::<Test>::get(when).len() == 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_named_last_task_removes_agenda() {
|
|
new_test_ext().execute_with(|| {
|
|
let when = 4;
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call.clone()).unwrap(),
|
|
)
|
|
.unwrap();
|
|
Scheduler::do_schedule_named(
|
|
[2u8; 32],
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
// two tasks at agenda.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
assert_ok!(Scheduler::do_cancel_named(None, [2u8; 32]));
|
|
// removes trailing `None` and leaves one task.
|
|
assert!(Agenda::<Test>::get(when).len() == 1);
|
|
// cancel last task from `when` agenda.
|
|
assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32]));
|
|
// if all tasks `None`, agenda fully removed.
|
|
assert!(Agenda::<Test>::get(when).len() == 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reschedule_last_task_removes_agenda() {
|
|
new_test_ext().execute_with(|| {
|
|
let when = 4;
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let address = Scheduler::do_schedule(
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call.clone()).unwrap(),
|
|
)
|
|
.unwrap();
|
|
let address2 = Scheduler::do_schedule(
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
// two tasks at agenda.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
assert_ok!(Scheduler::do_cancel(None, address));
|
|
// still two tasks at agenda, `None` and `Some`.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
// reschedule last task from `when` agenda.
|
|
assert_eq!(
|
|
Scheduler::do_reschedule(address2, DispatchTime::At(when + 1)).unwrap(),
|
|
(when + 1, 0)
|
|
);
|
|
// if all tasks `None`, agenda fully removed.
|
|
assert!(Agenda::<Test>::get(when).len() == 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn reschedule_named_last_task_removes_agenda() {
|
|
new_test_ext().execute_with(|| {
|
|
let when = 4;
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
Scheduler::do_schedule_named(
|
|
[1u8; 32],
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call.clone()).unwrap(),
|
|
)
|
|
.unwrap();
|
|
Scheduler::do_schedule_named(
|
|
[2u8; 32],
|
|
DispatchTime::At(when),
|
|
None,
|
|
127,
|
|
root(),
|
|
Preimage::bound(call).unwrap(),
|
|
)
|
|
.unwrap();
|
|
// two tasks at agenda.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32]));
|
|
// still two tasks at agenda, `None` and `Some`.
|
|
assert!(Agenda::<Test>::get(when).len() == 2);
|
|
// reschedule last task from `when` agenda.
|
|
assert_eq!(
|
|
Scheduler::do_reschedule_named([2u8; 32], DispatchTime::At(when + 1)).unwrap(),
|
|
(when + 1, 0)
|
|
);
|
|
// if all tasks `None`, agenda fully removed.
|
|
assert!(Agenda::<Test>::get(when).len() == 0);
|
|
});
|
|
}
|
|
|
|
/// Ensures that an unavailable call sends an event.
|
|
#[test]
|
|
fn unavailable_call_is_detected() {
|
|
use frame_support::traits::schedule::v3::Named;
|
|
|
|
new_test_ext().execute_with(|| {
|
|
let call =
|
|
RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) });
|
|
let hash = <Test as frame_system::Config>::Hashing::hash_of(&call);
|
|
let len = call.using_encoded(|x| x.len()) as u32;
|
|
// Important to use here `Bounded::Lookup` to ensure that we request the hash.
|
|
let bound = Bounded::Lookup { hash, len };
|
|
|
|
let name = [1u8; 32];
|
|
|
|
// Schedule a call.
|
|
let _address = <Scheduler as Named<_, _, _>>::schedule_named(
|
|
name,
|
|
DispatchTime::At(4),
|
|
None,
|
|
127,
|
|
root(),
|
|
bound.clone(),
|
|
)
|
|
.unwrap();
|
|
|
|
// Ensure the preimage isn't available
|
|
assert!(!Preimage::have(&bound));
|
|
// But we have requested it
|
|
assert!(Preimage::is_requested(&hash));
|
|
|
|
// Executes in block 4.
|
|
run_to_block(4);
|
|
|
|
assert_eq!(
|
|
System::events().last().unwrap().event,
|
|
crate::Event::CallUnavailable { task: (4, 0), id: Some(name) }.into()
|
|
);
|
|
// It should not be requested anymore.
|
|
assert!(!Preimage::is_requested(&hash));
|
|
});
|
|
}
|