Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

[Contracts] Overflowing bounded DeletionQueue #13702

Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
31ab537
[Contracts review] Overflowing bounded `DeletionQueue` allows DoS aga…
pgherveou Mar 24, 2023
8a0b92f
wip
pgherveou Mar 24, 2023
10cccfd
wip
pgherveou Mar 24, 2023
329fe92
wip
pgherveou Mar 27, 2023
b549582
wip
pgherveou Mar 27, 2023
71f01f3
wip
pgherveou Mar 27, 2023
aed8821
fix doc
pgherveou Mar 27, 2023
85cca3a
wip
pgherveou Mar 27, 2023
1d27eeb
PR review
pgherveou Mar 27, 2023
f6bcda1
unbreak tests
pgherveou Mar 27, 2023
b4592cc
fixes
pgherveou Mar 28, 2023
2e722bd
update budget computation
pgherveou Mar 28, 2023
2867a3c
PR comment: use BlockWeights::get().max_block
pgherveou Mar 28, 2023
3a17f2b
PR comment: Update queue_trie_for_deletion signature
pgherveou Mar 28, 2023
f7f1f42
PR comment: update deletion budget docstring
pgherveou Mar 28, 2023
20a18c3
PR comment: impl Default with derive(DefaultNoBound)
pgherveou Mar 28, 2023
bd99f93
PR comment: Remove DeletedContract
pgherveou Mar 28, 2023
737a48a
PR comment Add ring_buffer test
pgherveou Mar 28, 2023
2d44b3d
remove missed comment
pgherveou Mar 28, 2023
b0bbbcf
misc comments
pgherveou Mar 28, 2023
d4600e0
contracts: add sr25519_recover
pgherveou Mar 27, 2023
7002c46
Merge branch 'master' into pg/contracts-review-overflowing-bounded-de…
pgherveou Mar 28, 2023
c30bf2b
Revert "contracts: add sr25519_recover"
pgherveou Mar 28, 2023
c17e0e6
".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts
Mar 28, 2023
1880b62
PR comments update print_schedule
pgherveou Mar 29, 2023
9fc9db6
Update frame/contracts/src/benchmarking/mod.rs
pgherveou Mar 29, 2023
c0a7523
Update frame/contracts/src/storage.rs
pgherveou Mar 29, 2023
efafd7d
Update frame/contracts/src/storage.rs
pgherveou Mar 29, 2023
717151e
rm temporary fixes
pgherveou Mar 29, 2023
2da3648
fix extra ;
pgherveou Mar 29, 2023
5751764
Update frame/contracts/src/storage.rs
pgherveou Mar 30, 2023
b31603e
Update frame/contracts/src/storage.rs
pgherveou Mar 30, 2023
e269ad4
Update frame/contracts/src/lib.rs
pgherveou Mar 30, 2023
2f1d4fa
Update frame/contracts/src/lib.rs
pgherveou Mar 30, 2023
0b985aa
Support stable rust for compiling the runtime (#13580)
bkchr Mar 29, 2023
dedef69
github PR commit fixes
pgherveou Mar 30, 2023
6b31f97
Revert "Support stable rust for compiling the runtime (#13580)"
pgherveou Mar 30, 2023
4cec441
Restore DeletionQueueMap
pgherveou Mar 30, 2023
092d51c
fix namings
pgherveou Mar 30, 2023
6df076f
PR comment
pgherveou Mar 30, 2023
9d277cd
move comments
pgherveou Mar 30, 2023
5da9750
Update frame/contracts/src/storage.rs
pgherveou Mar 30, 2023
1e5eaaf
Update frame/contracts/src/storage.rs
pgherveou Mar 30, 2023
cec7e08
fixes
pgherveou Mar 30, 2023
d028fdc
Merge remote-tracking branch 'origin/master' into pg/contracts-review…
Mar 30, 2023
a7e45c9
Merge remote-tracking branch 'origin/master' into pg/contracts-review…
Mar 30, 2023
ef324ec
Merge remote-tracking branch 'origin/master' into pg/contracts-review…
Mar 30, 2023
7d39202
Merge remote-tracking branch 'origin/master' into pg/contracts-review…
Mar 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1198,13 +1198,6 @@ impl pallet_tips::Config for Runtime {
parameter_types! {
pub const DepositPerItem: Balance = deposit(1, 0);
pub const DepositPerByte: Balance = deposit(0, 1);
pub const DeletionQueueDepth: u32 = 128;
// The lazy deletion runs inside on_initialize.
pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get()
.per_class
.get(DispatchClass::Normal)
.max_total
.unwrap_or(RuntimeBlockWeights::get().max_block);
pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
}

Expand All @@ -1227,8 +1220,6 @@ impl pallet_contracts::Config for Runtime {
type WeightPrice = pallet_transaction_payment::Pallet<Self>;
type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
type ChainExtension = ();
type DeletionQueueDepth = DeletionQueueDepth;
type DeletionWeightLimit = DeletionWeightLimit;
type Schedule = Schedule;
type AddressGenerator = pallet_contracts::DefaultAddressGenerator;
type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
Expand Down
26 changes: 5 additions & 21 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,19 +214,7 @@ benchmarks! {
on_initialize_per_trie_key {
let k in 0..1024;
let instance = Contract::<T>::with_storage(WasmModule::dummy(), k, T::Schedule::get().limits.payload_len)?;
instance.info()?.queue_trie_for_deletion()?;
}: {
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
}

#[pov_mode = Measured]
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![])?;
instance.info()?.queue_trie_for_deletion()?;
ContractInfoOf::<T>::remove(instance.account_id);
}
instance.info()?.queue_trie_for_deletion();
}: {
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
}
Expand Down Expand Up @@ -3020,16 +3008,12 @@ benchmarks! {
print_schedule {
#[cfg(feature = "std")]
{
let weight_limit = T::DeletionWeightLimit::get();
let max_queue_depth = T::DeletionQueueDepth::get() as usize;
let empty_queue_throughput = ContractInfo::<T>::deletion_budget(0, weight_limit);
let full_queue_throughput = ContractInfo::<T>::deletion_budget(max_queue_depth, weight_limit);
let max_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
let (weight_per_key, key_budget) = ContractInfo::<T>::deletion_budget(max_weight);
println!("{:#?}", Schedule::<T>::default());
println!("###############################################");
println!("Lazy deletion weight per key: {}", empty_queue_throughput.0);
println!("Lazy deletion throughput per block (empty queue, full queue): {}, {}",
empty_queue_throughput.1, full_queue_throughput.1,
);
println!("Lazy deletion weight per key: {weight_per_key}");
println!("Lazy deletion throughput per block: {key_budget}");
}
#[cfg(not(feature = "std"))]
Err("Run this bench with a native runtime in order to see the schedule.")?;
Expand Down
2 changes: 1 addition & 1 deletion frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ where
T::Currency::reducible_balance(&frame.account_id, Expendable, Polite),
ExistenceRequirement::AllowDeath,
)?;
info.queue_trie_for_deletion()?;
info.queue_trie_for_deletion();
ContractInfoOf::<T>::remove(&frame.account_id);
E::remove_user(info.code_hash);
Contracts::<T>::deposit_event(
Expand Down
65 changes: 9 additions & 56 deletions frame/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ mod tests;
use crate::{
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
gas::GasMeter,
storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract},
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
weights::WeightInfo,
};
Expand Down Expand Up @@ -245,33 +245,6 @@ pub mod pallet {
/// memory usage of your runtime.
type CallStack: Array<Item = Frame<Self>>;

/// The maximum number of contracts that can be pending for deletion.
///
/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible
/// immediately, but the deletion of the storage items it has accumulated is performed
/// later. The contract is put into the deletion queue. This defines how many
/// contracts can be queued up at the same time. If that limit is reached `seal_terminate`
/// will fail. The action must be retried in a later block in that case.
///
/// The reasons for limiting the queue depth are:
///
/// 1. The queue is in storage in order to be persistent between blocks. We want to limit
/// the amount of storage that can be consumed.
/// 2. The queue is stored in a vector and needs to be decoded as a whole when reading
/// it at the end of each block. Longer queues take more weight to decode and hence
/// limit the amount of items that can be deleted per block.
#[pallet::constant]
type DeletionQueueDepth: Get<u32>;

/// The maximum amount of weight that can be consumed per block for lazy trie removal.
///
/// The amount of weight that is dedicated per block to work on the deletion queue. Larger
/// values allow more trie keys to be deleted in each block but reduce the amount of
/// weight that is left for transactions. See [`Self::DeletionQueueDepth`] for more
/// information about the deletion queue.
#[pallet::constant]
type DeletionWeightLimit: Get<Weight>;

/// The amount of balance a caller has to pay for each byte of storage.
///
/// # Note
Expand Down Expand Up @@ -329,25 +302,6 @@ pub mod pallet {
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
}

fn on_initialize(_block: T::BlockNumber) -> Weight {
// We want to process the deletion_queue in the on_idle hook. Only in the case
// that the queue length has reached its maximal depth, we process it here.
let max_len = T::DeletionQueueDepth::get() as usize;
let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0);
if queue_len >= max_len {
// 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());
ContractInfo::<T>::process_deletion_queue_batch(weight_limit)
.saturating_add(T::WeightInfo::on_process_deletion_queue_batch())
} else {
T::WeightInfo::on_process_deletion_queue_batch()
}
}

fn integrity_test() {
// Total runtime memory is expected to have 128Mb upper limit
const MAX_RUNTIME_MEM: u32 = 1024 * 1024 * 128;
Expand Down Expand Up @@ -860,12 +814,6 @@ pub mod pallet {
/// 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 calling `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 with the same AccountId already exists.
DuplicateContract,
/// A contract self destructed in its constructor.
Expand Down Expand Up @@ -949,10 +897,15 @@ pub mod pallet {
/// 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`.
/// stored in said trie. Therefore this operation is performed lazily in `on_idle`.
#[pallet::storage]
pub(crate) type DeletionQueue<T: Config> = StorageMap<_, Twox64Concat, u32, TrieId>;

/// A pair of monotonic counters used to track the latest contract marked for deletion
/// and the latest deleted contract in queue.
#[pallet::storage]
pub(crate) type DeletionQueue<T: Config> =
StorageValue<_, BoundedVec<DeletedContract, T::DeletionQueueDepth>, ValueQuery>;
pub(crate) type DeletionQueueCounter<T: Config> =
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;
}

/// Context of a contract invocation.
Expand Down
Loading