Skip to content

Commit

Permalink
Add Storage Limit per Tx (mbip-5)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmadkaouk committed Dec 1, 2023
1 parent 1bc27b1 commit c0aa620
Show file tree
Hide file tree
Showing 21 changed files with 587 additions and 31 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ derive_more = "0.99"
environmental = { version = "1.1.4", default-features = false }
ethereum = { version = "0.14.0", default-features = false }
ethereum-types = { version = "0.14.1", default-features = false }
evm = { git = "https://github.com/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false }
evm = { git = "https://github.com/moonbeam-foundation/evm", rev = "a33ac87ad7462b7e7029d12c385492b2a8311d1c", default-features = false }
futures = "0.3.28"
hash-db = { version = "0.16.0", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
Expand Down
5 changes: 4 additions & 1 deletion frame/ethereum/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,15 @@ impl FindAuthor<H160> for FindAuthorTruncated {

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 = 800 * 1024;
parameter_types! {
pub const TransactionByteFee: u64 = 1;
pub const ChainId: u64 = 42;
pub const EVMModuleId: PalletId = PalletId(*b"py/evmpa");
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 const WeightPerGas: Weight = Weight::from_parts(20_000, 0);
}

Expand Down Expand Up @@ -175,6 +177,7 @@ impl pallet_evm::Config for Test {
type OnCreate = ();
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type SuicideQuickClearLimit = SuicideQuickClearLimit;
type Timestamp = Timestamp;
type WeightInfo = ();
Expand Down
7 changes: 5 additions & 2 deletions frame/evm/precompile/dispatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ where
return Err(err);
}

handle
.record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?;
handle.record_external_cost(
Some(info.weight.ref_time()),
Some(info.weight.proof_size()),
None,
)?;

match call.dispatch(Some(origin).into()) {
Ok(post_info) => {
Expand Down
2 changes: 2 additions & 0 deletions frame/evm/precompile/dispatch/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ impl pallet_evm::Config for Test {
type FindAuthor = FindAuthorTruncated;
type SuicideQuickClearLimit = SuicideQuickClearLimit;
type GasLimitPovSizeRatio = ();
type GasLimitStorageGrowthRatio = ();
type Timestamp = Timestamp;
type WeightInfo = ();
}
Expand Down Expand Up @@ -186,6 +187,7 @@ impl PrecompileHandle for MockHandle {
&mut self,
_ref_time: Option<u64>,
_proof_size: Option<u64>,
_storage_growth: Option<u64>,
) -> Result<(), ExitError> {
Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,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.
type Timestamp: Time;

Expand Down
4 changes: 4 additions & 0 deletions frame/evm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,13 @@ impl FindAuthor<H160> for FindAuthorTruncated {
}
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 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 MockPrecompiles: MockPrecompileSet = MockPrecompileSet;
pub SuicideQuickClearLimit: u32 = 0;
Expand Down Expand Up @@ -154,6 +157,7 @@ impl crate::Config for Test {
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type SuicideQuickClearLimit = SuicideQuickClearLimit;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type Timestamp = Timestamp;
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
210 changes: 210 additions & 0 deletions frame/evm/src/runner/meter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This file is part of Frontier.
//
// Copyright (c) 2021-2022 Parity Technologies (UK) Ltd.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

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

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

0 comments on commit c0aa620

Please sign in to comment.