contracts: Lazy storage removal (#7740)

* Do not evict a contract from within a call stack

We don't want to trigger contract eviction automatically when
a contract is called. This is because those changes can be
reverted due to how storage transactions are used at the moment.
More Information:
https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324

It can be re-introduced once the linked issue is resolved. In the meantime
`claim_surcharge` must be called to evict a contract.

* Lazily delete storage in on_initialize instead of when removing the contract

* Add missing documentation of new error

* Make Module::claim_surcharge public

It being the only dispatchable that is private is an oversight.

* review: Add final newline

* review: Simplify assert statement

* Add test that checks that partial remove of a contract works

* Premote warning to error

* Added missing docs for seal_terminate

* Lazy deletion should only take AVERAGE_ON_INITIALIZE_RATIO of the block

* Added informational about the lazy deletion throughput

* Avoid lazy deletion in case the block is already full

* Prevent queue decoding in case of an already full block

* Add test that checks that on_initialize honors block limits
This commit is contained in:
Alexander Theißen
2021-01-04 13:35:57 +01:00
committed by GitHub
parent f0b99dd2f2
commit 3ba8fdfc11
9 changed files with 1348 additions and 525 deletions
+50 -6
View File
@@ -123,7 +123,7 @@ use frame_support::{
dispatch::{DispatchResult, DispatchResultWithPostInfo},
traits::{OnUnbalanced, Currency, Get, Time, Randomness},
};
use frame_system::{ensure_signed, ensure_root};
use frame_system::{ensure_signed, ensure_root, Module as System};
use pallet_contracts_primitives::{
RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult, ExecResult,
};
@@ -325,6 +325,12 @@ pub trait Config: frame_system::Config {
/// Type that allows the runtime authors to add new host functions for a contract to call.
type ChainExtension: chain_extension::ChainExtension;
/// The maximum number of tries that can be queued for deletion.
type DeletionQueueDepth: Get<u32>;
/// The maximum amount of weight that can be consumed per block for lazy trie removal.
type DeletionWeightLimit: Get<Weight>;
}
decl_error! {
@@ -396,6 +402,17 @@ decl_error! {
/// in this error. Note that this usually shouldn't happen as deploying such contracts
/// is rejected.
NoChainExtension,
/// Removal of a contract failed because the deletion queue is full.
///
/// This can happen when either calling [`Module::claim_surcharge`] or `seal_terminate`.
/// The queue is filled by deleting contracts and emptied by a fixed amount each block.
/// Trying again during another block is the only way to resolve this issue.
DeletionQueueFull,
/// A contract could not be evicted because it has enough balance to pay rent.
///
/// This can be returned from [`Module::claim_surcharge`] because the target
/// contract has enough balance to pay for its rent.
ContractNotEvictable,
}
}
@@ -449,8 +466,24 @@ decl_module! {
/// The maximum size of a storage value in bytes. A reasonable default is 16 KiB.
const MaxValueSize: u32 = T::MaxValueSize::get();
/// The maximum number of tries that can be queued for deletion.
const DeletionQueueDepth: u32 = T::DeletionQueueDepth::get();
/// The maximum amount of weight that can be consumed per block for lazy trie removal.
const DeletionWeightLimit: Weight = T::DeletionWeightLimit::get();
fn deposit_event() = default;
fn on_initialize() -> Weight {
// We do not want to go above the block limit and rather avoid lazy deletion
// in that case. This should only happen on runtime upgrades.
let weight_limit = T::BlockWeights::get().max_block
.saturating_sub(System::<T>::block_weight().total())
.min(T::DeletionWeightLimit::get());
Storage::<T>::process_deletion_queue_batch(weight_limit)
.saturating_add(T::WeightInfo::on_initialize())
}
/// Updates the schedule for metering contracts.
///
/// The schedule must have a greater version than the stored schedule.
@@ -549,10 +582,14 @@ decl_module! {
/// Allows block producers to claim a small reward for evicting a contract. If a block producer
/// fails to do so, a regular users will be allowed to claim the reward.
///
/// If contract is not evicted as a result of this call, no actions are taken and
/// the sender is not eligible for the reward.
/// If contract is not evicted as a result of this call, [`Error::ContractNotEvictable`]
/// is returned and the sender is not eligible for the reward.
#[weight = T::WeightInfo::claim_surcharge()]
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
pub fn claim_surcharge(
origin,
dest: T::AccountId,
aux_sender: Option<T::AccountId>
) -> DispatchResult {
let origin = origin.into();
let (signed, rewarded) = match (origin, aux_sender) {
(Ok(frame_system::RawOrigin::Signed(account)), None) => {
@@ -574,8 +611,10 @@ decl_module! {
};
// If poking the contract has lead to eviction of the contract, give out the rewards.
if Rent::<T>::snitch_contract_should_be_evicted(&dest, handicap) {
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get())?;
if Rent::<T>::snitch_contract_should_be_evicted(&dest, handicap)? {
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get()).map(|_| ())
} else {
Err(Error::<T>::ContractNotEvictable.into())
}
}
}
@@ -733,6 +772,11 @@ decl_storage! {
///
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
pub ContractInfoOf: map hasher(twox_64_concat) T::AccountId => Option<ContractInfo<T>>;
/// Evicted contracts that await child trie deletion.
///
/// Child trie deletion is a heavy operation depending on the amount of storage items
/// stored in said trie. Therefore this operation is performed lazily in `on_initialize`.
pub DeletionQueue: Vec<storage::DeletedContract>;
}
}