From 69c7c52de630219c27a41c332e8f9e758ead8855 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi <shawntabrizi@gmail.com> Date: Sat, 27 Mar 2021 21:16:10 +0100 Subject: [PATCH] Handle Onboarding in Current Lease Period (#2731) * Lease out current period and trigger onboard * Add test for trigger_onboard * patch and add benchmark * finish benchmarks * Use result instead of panic for test_registrar * nits --- polkadot/runtime/common/src/mock.rs | 68 ++++++---- polkadot/runtime/common/src/slots.rs | 189 ++++++++++++++++++++++----- polkadot/runtime/rococo/src/lib.rs | 2 +- 3 files changed, 199 insertions(+), 60 deletions(-) diff --git a/polkadot/runtime/common/src/mock.rs b/polkadot/runtime/common/src/mock.rs index 93f639ae8a0..79ae6014f93 100644 --- a/polkadot/runtime/common/src/mock.rs +++ b/polkadot/runtime/common/src/mock.rs @@ -19,7 +19,7 @@ use std::{cell::RefCell, collections::HashMap}; use parity_scale_codec::{Encode, Decode}; use sp_runtime::traits::SaturatedConversion; -use frame_support::dispatch::DispatchResult; +use frame_support::dispatch::{DispatchError, DispatchResult}; use primitives::v1::{HeadData, ValidationCode, Id as ParaId}; use crate::traits::Registrar; @@ -57,18 +57,21 @@ impl<T: frame_system::Config> Registrar for TestRegistrar<T> { PARACHAINS.with(|x| { let parachains = x.borrow_mut(); match parachains.binary_search(&id) { - Ok(_) => panic!("Already Parachain"), - Err(_) => {}, + Ok(_) => Err(DispatchError::Other("Already Parachain")), + Err(_) => Ok(()), } - }); + })?; // Should not be parathread, then make it. PARATHREADS.with(|x| { let mut parathreads = x.borrow_mut(); match parathreads.binary_search(&id) { - Ok(_) => panic!("Already Parathread"), - Err(i) => parathreads.insert(i, id), + Ok(_) => Err(DispatchError::Other("Already Parathread")), + Err(i) => { + parathreads.insert(i, id); + Ok(()) + }, } - }); + })?; MANAGERS.with(|x| x.borrow_mut().insert(id, manager.encode())); Ok(()) } @@ -76,66 +79,77 @@ impl<T: frame_system::Config> Registrar for TestRegistrar<T> { fn deregister(id: ParaId) -> DispatchResult { // Should not be parachain. PARACHAINS.with(|x| { - let mut parachains = x.borrow_mut(); + let parachains = x.borrow_mut(); match parachains.binary_search(&id) { - Ok(i) => { - parachains.remove(i); - }, - Err(_) => {}, + Ok(_) => Err(DispatchError::Other("cannot deregister parachain")), + Err(_) => Ok(()), } - }); + })?; // Remove from parathread. PARATHREADS.with(|x| { let mut parathreads = x.borrow_mut(); match parathreads.binary_search(&id) { Ok(i) => { parathreads.remove(i); + Ok(()) }, - Err(_) => {}, + Err(_) => Err(DispatchError::Other("not parathread, so cannot `deregister`")), } - }); + })?; MANAGERS.with(|x| x.borrow_mut().remove(&id)); Ok(()) } fn make_parachain(id: ParaId) -> DispatchResult { - OPERATIONS.with(|x| x.borrow_mut().push((id, frame_system::Pallet::<T>::block_number().saturated_into(), true))); PARATHREADS.with(|x| { let mut parathreads = x.borrow_mut(); match parathreads.binary_search(&id) { Ok(i) => { parathreads.remove(i); + Ok(()) }, - Err(_) => panic!("not parathread, so cannot `make_parachain`"), + Err(_) => Err(DispatchError::Other("not parathread, so cannot `make_parachain`")), } - }); + })?; PARACHAINS.with(|x| { let mut parachains = x.borrow_mut(); match parachains.binary_search(&id) { - Ok(_) => {}, - Err(i) => parachains.insert(i, id), + Ok(_) => Err(DispatchError::Other("already parachain, so cannot `make_parachain`")), + Err(i) => { + parachains.insert(i, id); + Ok(()) + }, } - }); + })?; + OPERATIONS.with(|x| x.borrow_mut().push( + (id, frame_system::Pallet::<T>::block_number().saturated_into(), true) + )); Ok(()) } fn make_parathread(id: ParaId) -> DispatchResult { - OPERATIONS.with(|x| x.borrow_mut().push((id, frame_system::Pallet::<T>::block_number().saturated_into(), false))); PARACHAINS.with(|x| { let mut parachains = x.borrow_mut(); match parachains.binary_search(&id) { Ok(i) => { parachains.remove(i); + Ok(()) }, - Err(_) => panic!("not parachain, so cannot `make_parathread`"), + Err(_) => Err(DispatchError::Other("not parachain, so cannot `make_parathread`")), } - }); + })?; PARATHREADS.with(|x| { let mut parathreads = x.borrow_mut(); match parathreads.binary_search(&id) { - Ok(_) => {}, - Err(i) => parathreads.insert(i, id), + Ok(_) => Err(DispatchError::Other("already parathread, so cannot `make_parathread`")), + Err(i) => { + parathreads.insert(i, id); + Ok(()) + }, } - }); + })?; + OPERATIONS.with(|x| x.borrow_mut().push( + (id, frame_system::Pallet::<T>::block_number().saturated_into(), false) + )); Ok(()) } diff --git a/polkadot/runtime/common/src/slots.rs b/polkadot/runtime/common/src/slots.rs index b38c36e9022..768c5ff957d 100644 --- a/polkadot/runtime/common/src/slots.rs +++ b/polkadot/runtime/common/src/slots.rs @@ -28,7 +28,7 @@ use frame_support::{ traits::{Currency, ReservableCurrency, Get}, weights::Weight, }; use primitives::v1::Id as ParaId; -use frame_system::ensure_root; +use frame_system::{ensure_signed, ensure_root}; use crate::traits::{Leaser, LeaseError, Registrar}; type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; @@ -37,12 +37,16 @@ type LeasePeriodOf<T> = <T as frame_system::Config>::BlockNumber; pub trait WeightInfo { fn force_lease() -> Weight; fn manage_lease_period_start(c: u32, t: u32) -> Weight; + fn clear_all_leases() -> Weight; + fn trigger_onboard() -> Weight; } pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn force_lease() -> Weight { 0 } fn manage_lease_period_start(_c: u32, _t:u32) -> Weight { 0 } + fn clear_all_leases() -> Weight { 0 } + fn trigger_onboard() -> Weight { 0 } } /// The module's configuration trait. @@ -173,10 +177,8 @@ decl_module! { /// Clear all leases for a Para Id, refunding any deposits back to the original owners. /// /// Can only be called by the Root origin. - #[weight = 0] // TODO: Benchmarks - fn clear_all_leases(origin, - para: ParaId, - ) -> DispatchResult { + #[weight = T::WeightInfo::clear_all_leases()] + fn clear_all_leases(origin, para: ParaId) -> DispatchResult { ensure_root(origin)?; let deposits = Self::all_deposits_held(para); @@ -189,6 +191,31 @@ decl_module! { Leases::<T>::remove(para); Ok(()) } + + /// Try to onboard a parachain that has a lease for the current lease period. + /// + /// This function can be useful if there was some state issue with a para that should + /// have onboarded, but was unable to. As long as they have a lease period, we can + /// let them onboard from here. + /// + /// Origin must be signed, but can be called by anyone. + #[weight = T::WeightInfo::trigger_onboard()] + fn trigger_onboard(origin, para: ParaId) -> DispatchResult { + let _ = ensure_signed(origin)?; + let leases = Leases::<T>::get(para); + match leases.first() { + // If the first element in leases is present, then it has a lease! + // We can try to onboard it. + Some(Some(_lease_info)) => { + T::Registrar::make_parachain(para)? + }, + // Otherwise, it does not have a lease. + Some(None) | None => { + return Err(Error::<T>::ParaNotOnboarding.into()); + } + }; + Ok(()) + } } } @@ -323,10 +350,11 @@ impl<T: Config> Leaser for Module<T> { period_begin: Self::LeasePeriod, period_count: Self::LeasePeriod, ) -> Result<(), LeaseError> { + let current_lease_period = Self::lease_period_index(); // Finally, we update the deposit held so it is `amount` for the new lease period // indices that were won in the auction. let offset = period_begin - .checked_sub(&Self::lease_period_index()) + .checked_sub(¤t_lease_period) .and_then(|x| x.checked_into::<usize>()) .ok_or(LeaseError::AlreadyEnded)?; @@ -376,6 +404,14 @@ impl<T: Config> Leaser for Module<T> { } let reserved = maybe_additional.unwrap_or_default(); + + // Check if current lease period is same as period begin, and onboard them directly. + // This will allow us to support onboarding new parachains in the middle of a lease period. + if current_lease_period == period_begin { + // Best effort. Not much we can do if this fails. + let _ = T::Registrar::make_parachain(para); + } + Self::deposit_event( RawEvent::Leased(para, leaser.clone(), period_begin, period_count, reserved, amount) ); @@ -417,7 +453,7 @@ mod tests { use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use frame_support::{ - parameter_types, assert_ok, + parameter_types, assert_ok, assert_noop, traits::{OnInitialize, OnFinalize} }; use pallet_balances; @@ -537,7 +573,7 @@ mod tests { assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default())); - assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_ok()); + assert_ok!(Slots::lease_out(1.into(), &1, 1, 1, 1)); assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); @@ -563,8 +599,8 @@ mod tests { assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default())); - assert!(Slots::lease_out(1.into(), &1, 6, 1, 1).is_ok()); - assert!(Slots::lease_out(1.into(), &1, 4, 3, 1).is_ok()); + assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1)); + assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1)); run_to_block(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); @@ -734,6 +770,62 @@ mod tests { assert!(Leases::<Test>::get(ParaId::from(1)).is_empty()); }); } + + #[test] + fn lease_out_current_lease_period() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default())); + assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(2), Default::default(), Default::default())); + + run_to_block(20); + assert_eq!(Slots::lease_period_index(), 2); + // Can't lease from the past + assert!(Slots::lease_out(1.into(), &1, 1, 1, 1).is_err()); + // Lease in the current period triggers onboarding + assert_ok!(Slots::lease_out(1.into(), &1, 1, 2, 1)); + // Lease in the future doesn't + assert_ok!(Slots::lease_out(2.into(), &1, 1, 3, 1)); + + assert_eq!(TestRegistrar::<Test>::operations(), vec![ + (1.into(), 20, true), + ]); + }); + } + + #[test] + fn trigger_onboard_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(1), Default::default(), Default::default())); + assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(2), Default::default(), Default::default())); + assert_ok!(TestRegistrar::<Test>::register(1, ParaId::from(3), Default::default(), Default::default())); + + // We will directly manipulate leases to emulate some kind of failure in the system. + // Para 1 will have no leases + // Para 2 will have a lease period in the current index + Leases::<Test>::insert(ParaId::from(2), vec![Some((0, 0))]); + // Para 3 will have a lease period in a future index + Leases::<Test>::insert(ParaId::from(3), vec![None, None, Some((0, 0))]); + + // Para 1 should fail cause they don't have any leases + assert_noop!(Slots::trigger_onboard(Origin::signed(1), 1.into()), Error::<Test>::ParaNotOnboarding); + + // Para 2 should succeed + assert_ok!(Slots::trigger_onboard(Origin::signed(1), 2.into())); + + // Para 3 should fail cause their lease is in the future + assert_noop!(Slots::trigger_onboard(Origin::signed(1), 3.into()), Error::<Test>::ParaNotOnboarding); + + // Trying Para 2 again should fail cause they are not currently a parathread + assert!(Slots::trigger_onboard(Origin::signed(1), 2.into()).is_err()); + + assert_eq!(TestRegistrar::<Test>::operations(), vec![ + (2.into(), 1, true), + ]); + }); + } } #[cfg(feature = "runtime-benchmarks")] @@ -743,7 +835,7 @@ mod benchmarking { use frame_support::assert_ok; use sp_runtime::traits::Bounded; - use frame_benchmarking::{benchmarks, account}; + use frame_benchmarking::{benchmarks, account, whitelisted_caller, impl_benchmark_test_suite}; use crate::slots::Module as Slots; @@ -774,7 +866,7 @@ mod benchmarking { let leaser: T::AccountId = account("leaser", 0, 0); T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value()); let amount = T::Currency::minimum_balance(); - let period_begin = 0u32.into(); + let period_begin = 69u32.into(); let period_count = 3u32.into(); }: _(RawOrigin::Root, para, leaser.clone(), amount, period_begin, period_count) verify { @@ -787,12 +879,18 @@ mod benchmarking { let c in 1 .. 100; let t in 1 .. 100; - let period_begin = 0u32.into(); - let period_count = 3u32.into(); + let period_begin = 1u32.into(); + let period_count = 4u32.into(); + + // Make T parathreads + let paras_info = (0..t).map(|i| { + register_a_parathread::<T>(i) + }).collect::<Vec<_>>(); + + T::Registrar::execute_pending_transitions(); // T parathread are upgrading to parachains - for i in 0 .. t { - let (para, leaser) = register_a_parathread::<T>(i); + for (para, leaser) in paras_info { let amount = T::Currency::minimum_balance(); Slots::<T>::force_lease(RawOrigin::Root.into(), para, leaser, amount, period_begin, period_count)?; @@ -827,26 +925,53 @@ mod benchmarking { assert!(T::Registrar::is_parathread(ParaId::from(i))); } } - } - #[cfg(test)] - mod tests { - use super::*; - use crate::integration_tests::{new_test_ext, Test}; - use frame_support::assert_ok; + // Assume that at most 8 people have deposits for leases on a parachain. + // This would cover at least 4 years of leases in the worst case scenario. + clear_all_leases { + let max_people = 8; + let (para, _) = register_a_parathread::<T>(1); - #[test] - fn force_lease_benchmark() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_force_lease::<Test>()); - }); + for i in 0 .. max_people { + let leaser = account("lease_deposit", i, 0); + let amount = T::Currency::minimum_balance(); + T::Currency::make_free_balance_be(&leaser, BalanceOf::<T>::max_value()); + + // Average slot has 4 lease periods. + let period_count: LeasePeriodOf<T> = 4u32.into(); + let period_begin = period_count * i.into(); + Slots::<T>::force_lease(RawOrigin::Root.into(), para, leaser, amount, period_begin, period_count)?; + } + + for i in 0 .. max_people { + let leaser = account("lease_deposit", i, 0); + assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance()); + } + + }: _(RawOrigin::Root, para) + verify { + for i in 0 .. max_people { + let leaser = account("lease_deposit", i, 0); + assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into()); + } } - #[test] - fn manage_lease_period_start_benchmark() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_manage_lease_period_start::<Test>()); - }); + trigger_onboard { + // get a parachain into a bad state where they did not onboard + let (para, _) = register_a_parathread::<T>(1); + Leases::<T>::insert(para, vec![Some((T::AccountId::default(), BalanceOf::<T>::default()))]); + assert!(T::Registrar::is_parathread(para)); + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), para) + verify { + T::Registrar::execute_pending_transitions(); + assert!(T::Registrar::is_parachain(para)); } } + + impl_benchmark_test_suite!( + Slots, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 33e1d585028..14c988b552a 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -627,7 +627,7 @@ impl Randomness<Hash, BlockNumber> for ParentHashRandomness { } parameter_types! { - pub const EndingPeriod: BlockNumber = 15 * MINUTES; + pub const EndingPeriod: BlockNumber = 1 * HOURS; pub const SampleLength: BlockNumber = 1; } -- GitLab