Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a gas-based Storage limit per tx #1142

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5b8860f
A new trait for recording foreign metrics
ahmadkaouk Jul 31, 2023
d692104
Remove Metric trait
ahmadkaouk Aug 2, 2023
523b18c
rename BasicMetric to Metric
ahmadkaouk Aug 2, 2023
b168105
add unit tests and a new MetricError enum
ahmadkaouk Aug 2, 2023
ce75d15
Add ProofSizeMeter
ahmadkaouk Aug 3, 2023
26a85ad
Add ProofSizeMeter to WeightInfo
ahmadkaouk Aug 3, 2023
78273fb
formatting
ahmadkaouk Aug 3, 2023
df4f5cf
Add RefTimeMeter to WeightInfo
ahmadkaouk Aug 3, 2023
1f86da7
add storage meter to WeightInfo
ahmadkaouk Aug 4, 2023
230879a
rename metric to resource
ahmadkaouk Aug 4, 2023
54b7c01
code refactor
ahmadkaouk Aug 7, 2023
d9f04e2
remove unused functions
ahmadkaouk Aug 8, 2023
af358ad
make clippy happy
ahmadkaouk Aug 8, 2023
8fa4dd2
minor improvements
ahmadkaouk Aug 9, 2023
65d8b97
rename meter to resource
ahmadkaouk Aug 9, 2023
6a344db
compute new storage created by SSTORE
ahmadkaouk Aug 10, 2023
dea63f6
revert changes
ahmadkaouk Aug 14, 2023
4c45d4f
compute storage growth for opcodes
ahmadkaouk Aug 17, 2023
cea9817
compute the storage quota per tx
ahmadkaouk Aug 18, 2023
e412643
pin evm temporarily
ahmadkaouk Aug 20, 2023
dbf0370
record storage growth for opcodes
ahmadkaouk Aug 20, 2023
84b487f
update GAS_LIMIT_STORAGE_GROWTH_RATIO for tests
ahmadkaouk Aug 21, 2023
cce7cf5
add support to record storage growth for precompiles
ahmadkaouk Aug 21, 2023
4670129
fix tests
ahmadkaouk Aug 21, 2023
dee68d8
fix formatting
ahmadkaouk Aug 21, 2023
f110333
fix storage growth parameters for tests
ahmadkaouk Aug 22, 2023
332bde7
add rust integration tests
ahmadkaouk Aug 23, 2023
1623f9c
fix typo
ahmadkaouk Aug 23, 2023
19ba629
fix recording storage growth of a special use case of SSTORE
ahmadkaouk Aug 28, 2023
7578175
use saturating add
ahmadkaouk Aug 28, 2023
d0304fe
minor improvements
ahmadkaouk Aug 28, 2023
fb31dac
add license to meter.rs
ahmadkaouk Aug 28, 2023
a6da7ef
fix clippy warnings
ahmadkaouk Aug 28, 2023
4a6ea48
pin evm to master
ahmadkaouk Aug 28, 2023
ba9552d
update evm to the latest commit
ahmadkaouk Aug 29, 2023
f14d3e2
check limit exceedance before updating the usage
ahmadkaouk Sep 12, 2023
ca9e4d3
Use BtreeSet
ahmadkaouk Sep 15, 2023
21bf9d0
Merge remote-tracking branch 'upstream/master' into ahmad-add-storage…
ahmadkaouk Sep 15, 2023
4a343e6
add support for storage growth in precompiles
ahmadkaouk Sep 15, 2023
e72809d
clippy warnings
ahmadkaouk Sep 15, 2023
de7876e
Merge remote-tracking branch 'upstream/master' into ahmad-add-storage…
ahmadkaouk Nov 2, 2023
e46e222
Merge remote-tracking branch 'upstream/master' into ahmad-add-storage…
ahmadkaouk Nov 21, 2023
553984f
update to evm 0.41.0
ahmadkaouk Nov 21, 2023
063ea9d
Update Cargo.toml
ahmadkaouk Nov 21, 2023
74f321c
update Cargo.lock
ahmadkaouk Nov 21, 2023
433a346
Merge branch 'master' into ahmad-add-storage-limit-per-tx
ahmadkaouk Nov 29, 2024
7aaf093
format
ahmadkaouk Nov 29, 2024
ec34143
fix tests
ahmadkaouk Dec 2, 2024
b246fe3
update license information in meter.rs to Apache-2.0
ahmadkaouk Dec 11, 2024
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
2 changes: 2 additions & 0 deletions frame/ethereum/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ impl FindAuthor<H160> for FindAuthorTruncated {

parameter_types! {
pub const TransactionByteFee: u64 = 1;
pub const GasLimitStorageGrowthRatio: u64 = 0;
}

#[derive_impl(pallet_evm::config_preludes::TestDefaultConfig)]
Expand All @@ -96,6 +97,7 @@ impl pallet_evm::Config for Test {
type PrecompilesValue = ();
type Runner = pallet_evm::runner::stack::Runner<Self>;
type FindAuthor = FindAuthorTruncated;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type Timestamp = Timestamp;
}

Expand Down
1 change: 1 addition & 0 deletions frame/evm/precompile/dispatch/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ impl pallet_evm::Config for Test {
type FindAuthor = FindAuthorTruncated;
type SuicideQuickClearLimit = SuicideQuickClearLimit;
type GasLimitPovSizeRatio = ();
type GasLimitStorageGrowthRatio = ();
type Timestamp = Timestamp;
type WeightInfo = ();
}
Expand Down
3 changes: 3 additions & 0 deletions frame/evm/precompile/storage-cleaner/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,12 @@ pub type PCall = StorageCleanerPrecompileCall<Runtime>;

const BLOCK_GAS_LIMIT: u64 = 15_000_000;
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
const MAX_STORAGE_GROWTH: u64 = 400 * 1024;

parameter_types! {
pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT);
pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE);
pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH);
pub WeightPerGas: Weight = Weight::from_parts(20_000, 0);
pub PrecompilesValue: Precompiles<Runtime> = Precompiles::new();
pub SuicideQuickClearLimit: u32 = 0;
Expand All @@ -143,6 +145,7 @@ impl pallet_evm::Config for Runtime {
type FindAuthor = ();
type OnCreate = ();
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type Timestamp = Timestamp;
type WeightInfo = ();
type SuicideQuickClearLimit = SuicideQuickClearLimit;
Expand Down
7 changes: 7 additions & 0 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ pub mod pallet {
/// Define the quick clear limit of storage clearing when a contract suicides. Set to 0 to disable it.
type SuicideQuickClearLimit: Get<u32>;

/// Gas limit storage growth ratio.
type GasLimitStorageGrowthRatio: Get<u64>;

/// Get the timestamp for the current block.
#[pallet::no_default]
type Timestamp: Time;
Expand Down Expand Up @@ -224,11 +227,14 @@ pub mod pallet {

const BLOCK_GAS_LIMIT: u64 = 150_000_000;
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
/// The maximum storage growth per block in bytes.
const MAX_STORAGE_GROWTH: u64 = 400 * 1024;

parameter_types! {
pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT);
pub const ChainId: u64 = 42;
pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE);
pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH);
pub WeightPerGas: Weight = Weight::from_parts(20_000, 0);
pub SuicideQuickClearLimit: u32 = 0;
}
Expand All @@ -251,6 +257,7 @@ pub mod pallet {
type OnCreate = ();
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type SuicideQuickClearLimit = SuicideQuickClearLimit;
type WeightInfo = ();
}
Expand Down
27 changes: 27 additions & 0 deletions frame/evm/src/res/StorageGrowthTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.2;

contract StorageGrowthTest {
mapping(uint256 => uint256) public map;
uint256 foo;
uint256 bar;
uint256 baz;

constructor() {
foo = 1;
bar = 2;
baz = 3;
}

function store() public {
map[0] = 1;
map[1] = 2;
map[2] = 3;
}

function update() public {
foo = 2;
bar = 3;
baz = 4;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
608060405234801561001057600080fd5b506001808190555060028081905550600380819055506101c7806100356000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063975057e714610046578063a2e6204514610050578063b8dda9c71461005a575b600080fd5b61004e61008a565b005b6100586100d6565b005b610074600480360381019061006f919061011d565b6100f0565b6040516100819190610155565b60405180910390f35b6001600080808152602001908152602001600020819055506002600080600181526020019081526020016000208190555060036000806002815260200190815260200160002081905550565b600260018190555060036002819055506004600381905550565b60006020528060005260406000206000915090505481565b6000813590506101178161017a565b92915050565b60006020828403121561012f57600080fd5b600061013d84828501610108565b91505092915050565b61014f81610170565b82525050565b600060208201905061016a6000830184610146565b92915050565b6000819050919050565b61018381610170565b811461018e57600080fd5b5056fea2646970667358221220b25685afab962e465f0b43f6c4de10c82a565f50f6995c1cd444b002bcdd43e964736f6c63430008020033
209 changes: 209 additions & 0 deletions frame/evm/src/runner/meter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// This file is part of Frontier.

// 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.

use alloc::collections::btree_set::BTreeSet;
use evm::{
gasometer::{GasCost, StorageTarget},
Opcode,
};
use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE;
use sp_core::{H160, H256};

/// An error that is returned when the storage limit has been exceeded.
#[derive(Debug, PartialEq)]
pub enum MeterError {
LimitExceeded,
}

/// A meter for tracking the storage growth.
#[derive(Clone)]
pub struct StorageMeter {
usage: u64,
limit: u64,
recorded_new_entries: BTreeSet<(H160, H256)>,
}

impl StorageMeter {
/// Creates a new storage meter with the given limit.
pub fn new(limit: u64) -> Self {
Self {
usage: 0,
limit,
recorded_new_entries: BTreeSet::new(),
}
}

/// Records the given amount of storage usage. The amount is added to the current usage.
/// If the limit is reached, an error is returned.
pub fn record(&mut self, amount: u64) -> Result<(), MeterError> {
let usage = self
.usage
.checked_add(amount)
.ok_or(MeterError::LimitExceeded)?;

if usage > self.limit {
return Err(MeterError::LimitExceeded);
}
self.usage = usage;
Ok(())
}

/// Records the storage growth for the given Opcode.
pub fn record_dynamic_opcode_cost(
&mut self,
_opcode: Opcode,
gas_cost: GasCost,
target: StorageTarget,
) -> Result<(), MeterError> {
if let GasCost::SStore { original, new, .. } = gas_cost {
// Validate if storage growth for the current slot has been accounted for within this transaction.
// Comparing Original and new to determine if a new entry is being created is not sufficient, because
// 'original' updates only at the end of the transaction. So, if a new entry
// is created and updated multiple times within the same transaction, the storage growth is
// accounted for multiple times, because 'original' is always zero for the subsequent updates.
// To avoid this, we keep track of the new entries that are created within the transaction.
let (address, index) = match target {
StorageTarget::Slot(address, index) => (address, index),
_ => return Ok(()),
};
let recorded = self.recorded_new_entries.contains(&(address, index));
if !recorded && original == H256::default() && !new.is_zero() {
self.record(ACCOUNT_STORAGE_PROOF_SIZE)?;
self.recorded_new_entries.insert((address, index));
}
}
Ok(())
}

/// Returns the current usage of storage.
pub fn usage(&self) -> u64 {
self.usage
}

/// Returns the limit of storage.
pub fn limit(&self) -> u64 {
self.limit
}

/// Returns the amount of storage that is available before the limit is reached.
pub fn available(&self) -> u64 {
self.limit.saturating_sub(self.usage)
}

/// Map storage usage to the gas cost.
pub fn storage_to_gas(&self, ratio: u64) -> u64 {
self.usage.saturating_mul(ratio)
}
}
#[cfg(test)]
mod test {
use super::*;

/// Tests the basic functionality of StorageMeter.
#[test]
fn test_basic_functionality() {
let limit = 100;
let mut meter = StorageMeter::new(limit);

assert_eq!(meter.usage(), 0);
assert_eq!(meter.limit(), limit);

let amount = 10;
meter.record(amount).unwrap();
assert_eq!(meter.usage(), amount);
}

/// Tests the behavior of StorageMeter when reaching the limit.
#[test]
fn test_reaching_limit() {
let limit = 100;
let mut meter = StorageMeter::new(limit);

// Approaching the limit without exceeding
meter.record(limit - 1).unwrap();
assert_eq!(meter.usage(), limit - 1);

// Reaching the limit exactly
meter.record(1).unwrap();
assert_eq!(meter.usage(), limit);

// Exceeding the limit
let res = meter.record(1);
assert_eq!(meter.usage(), limit);
assert!(res.is_err());
assert_eq!(res, Err(MeterError::LimitExceeded));
}

/// Tests the record of dynamic opcode cost.
#[test]
fn test_record_dynamic_opcode_cost() {
let limit = 200;
let mut meter = StorageMeter::new(limit);

// Existing storage entry is updated. No change in storage growth.
let gas_cost = GasCost::SStore {
original: H256::from_low_u64_be(1),
current: Default::default(),
new: H256::from_low_u64_be(2),
target_is_cold: false,
};
let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(1));

meter
.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
.unwrap();
assert_eq!(meter.usage(), 0);

// New storage entry is created. Storage growth is recorded.
let gas_cost = GasCost::SStore {
original: H256::default(),
current: Default::default(),
new: H256::from_low_u64_be(1),
target_is_cold: false,
};
meter
.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
.unwrap();
assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE);

// Try to record the same storage growth again. No change in storage growth.
let gas_cost = GasCost::SStore {
original: H256::default(),
current: Default::default(),
new: H256::from_low_u64_be(1),
target_is_cold: false,
};
meter
.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
.unwrap();
assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE);

// New storage entry is created. Storage growth is recorded. The limit is reached.
let gas_cost = GasCost::SStore {
original: H256::default(),
current: Default::default(),
new: H256::from_low_u64_be(2),
target_is_cold: false,
};
let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(2));

let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target);
assert!(res.is_err());
assert_eq!(res, Err(MeterError::LimitExceeded));
assert_eq!(meter.usage(), 116);
}
}
1 change: 1 addition & 0 deletions frame/evm/src/runner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub mod meter;
pub mod stack;

use crate::{Config, Weight};
Expand Down
Loading
Loading