mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 19:21:13 +00:00
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:
committed by
GitHub
parent
f0b99dd2f2
commit
3ba8fdfc11
@@ -39,7 +39,7 @@ use self::{
|
||||
use frame_benchmarking::{benchmarks, account, whitelisted_caller};
|
||||
use frame_system::{Module as System, RawOrigin};
|
||||
use parity_wasm::elements::{Instruction, ValueType, BlockType};
|
||||
use sp_runtime::traits::{Hash, Bounded};
|
||||
use sp_runtime::traits::{Hash, Bounded, Zero};
|
||||
use sp_std::{default::Default, convert::{TryInto}, vec::Vec, vec};
|
||||
use pallet_contracts_primitives::RentProjection;
|
||||
|
||||
@@ -209,37 +209,52 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Contract` that was evicted after accumulating some storage.
|
||||
/// A `Contract` that contains some storage items.
|
||||
///
|
||||
/// This is used to benchmark contract resurrection.
|
||||
struct Tombstone<T: Config> {
|
||||
/// This is used to benchmark contract destruction and resurection. Those operations'
|
||||
/// weight depend on the amount of storage accumulated.
|
||||
struct ContractWithStorage<T: Config> {
|
||||
/// The contract that was evicted.
|
||||
contract: Contract<T>,
|
||||
/// The storage the contract held when it was avicted.
|
||||
storage: Vec<(StorageKey, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl<T: Config> Tombstone<T>
|
||||
impl<T: Config> ContractWithStorage<T>
|
||||
where
|
||||
T: Config,
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
{
|
||||
/// Create and evict a new contract with the supplied storage item count and size each.
|
||||
/// Same as [`Self::with_code`] but with dummy contract code.
|
||||
fn new(stor_num: u32, stor_size: u32) -> Result<Self, &'static str> {
|
||||
let contract = Contract::<T>::new(WasmModule::dummy(), vec![], Endow::CollectRent)?;
|
||||
Self::with_code(WasmModule::dummy(), stor_num, stor_size)
|
||||
}
|
||||
|
||||
/// Create and evict a new contract with the supplied storage item count and size each.
|
||||
fn with_code(code: WasmModule<T>, stor_num: u32, stor_size: u32) -> Result<Self, &'static str> {
|
||||
let contract = Contract::<T>::new(code, vec![], Endow::CollectRent)?;
|
||||
let storage_items = create_storage::<T>(stor_num, stor_size)?;
|
||||
contract.store(&storage_items)?;
|
||||
System::<T>::set_block_number(
|
||||
contract.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.into()
|
||||
);
|
||||
Rent::<T>::collect(&contract.account_id);
|
||||
contract.ensure_tombstone()?;
|
||||
|
||||
Ok(Tombstone {
|
||||
Ok(Self {
|
||||
contract,
|
||||
storage: storage_items,
|
||||
})
|
||||
}
|
||||
|
||||
/// Increase the system block number so that this contract is eligible for eviction.
|
||||
fn set_block_num_for_eviction(&self) -> Result<(), &'static str> {
|
||||
System::<T>::set_block_number(
|
||||
self.contract.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Evict this contract.
|
||||
fn evict(&mut self) -> Result<(), &'static str> {
|
||||
self.set_block_num_for_eviction()?;
|
||||
Rent::<T>::snitch_contract_should_be_evicted(&self.contract.account_id, Zero::zero())?;
|
||||
self.contract.ensure_tombstone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate `stor_num` storage items. Each has the size `stor_size`.
|
||||
@@ -270,6 +285,30 @@ benchmarks! {
|
||||
_ {
|
||||
}
|
||||
|
||||
// The base weight without any actual work performed apart from the setup costs.
|
||||
on_initialize {}: {
|
||||
Storage::<T>::process_deletion_queue_batch(Weight::max_value())
|
||||
}
|
||||
|
||||
on_initialize_per_trie_key {
|
||||
let k in 0..1024;
|
||||
let instance = ContractWithStorage::<T>::new(k, T::MaxValueSize::get())?;
|
||||
Storage::<T>::queue_trie_for_deletion(&instance.contract.alive_info()?)?;
|
||||
}: {
|
||||
Storage::<T>::process_deletion_queue_batch(Weight::max_value())
|
||||
}
|
||||
|
||||
on_initialize_per_queue_item {
|
||||
let q in 0..1024.min(T::DeletionQueueDepth::get());
|
||||
for i in 0 .. q {
|
||||
let instance = Contract::<T>::with_index(i, WasmModule::dummy(), vec![], Endow::Max)?;
|
||||
Storage::<T>::queue_trie_for_deletion(&instance.alive_info()?)?;
|
||||
ContractInfoOf::<T>::remove(instance.account_id);
|
||||
}
|
||||
}: {
|
||||
Storage::<T>::process_deletion_queue_batch(Weight::max_value())
|
||||
}
|
||||
|
||||
// This extrinsic is pretty much constant as it is only a simple setter.
|
||||
update_schedule {
|
||||
let schedule = Schedule {
|
||||
@@ -650,7 +689,8 @@ benchmarks! {
|
||||
// Restore just moves the trie id from origin to destination and therefore
|
||||
// does not depend on the size of the destination contract. However, to not
|
||||
// trigger any edge case we won't use an empty contract as destination.
|
||||
let tombstone = Tombstone::<T>::new(10, T::MaxValueSize::get())?;
|
||||
let mut tombstone = ContractWithStorage::<T>::new(10, T::MaxValueSize::get())?;
|
||||
tombstone.evict()?;
|
||||
|
||||
let dest = tombstone.contract.account_id.encode();
|
||||
let dest_len = dest.len();
|
||||
@@ -723,7 +763,8 @@ benchmarks! {
|
||||
|
||||
seal_restore_to_per_delta {
|
||||
let d in 0 .. API_BENCHMARK_BATCHES;
|
||||
let tombstone = Tombstone::<T>::new(0, 0)?;
|
||||
let mut tombstone = ContractWithStorage::<T>::new(0, 0)?;
|
||||
tombstone.evict()?;
|
||||
let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?;
|
||||
|
||||
let dest = tombstone.contract.account_id.encode();
|
||||
@@ -2368,7 +2409,20 @@ benchmarks! {
|
||||
#[extra]
|
||||
print_schedule {
|
||||
#[cfg(feature = "std")]
|
||||
println!("{:#?}", Schedule::<T>::default());
|
||||
{
|
||||
let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) -
|
||||
T::WeightInfo::on_initialize_per_trie_key(0);
|
||||
let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1) -
|
||||
T::WeightInfo::on_initialize_per_queue_item(0);
|
||||
let weight_limit = T::DeletionWeightLimit::get();
|
||||
let queue_depth: u64 = T::DeletionQueueDepth::get().into();
|
||||
println!("{:#?}", Schedule::<T>::default());
|
||||
println!("###############################################");
|
||||
println!("Lazy deletion throughput per block (empty queue, full queue): {}, {}",
|
||||
weight_limit / weight_per_key,
|
||||
(weight_limit - weight_per_queue_item * queue_depth) / weight_per_key,
|
||||
);
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
return Err("Run this bench with a native runtime in order to see the schedule.");
|
||||
}: {}
|
||||
@@ -2394,6 +2448,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
create_test!(on_initialize);
|
||||
create_test!(on_initialize_per_trie_key);
|
||||
create_test!(on_initialize_per_queue_item);
|
||||
|
||||
create_test!(update_schedule);
|
||||
create_test!(put_code);
|
||||
create_test!(instantiate);
|
||||
|
||||
Reference in New Issue
Block a user