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

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
45 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
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
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.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ clap = { version = "4.3", features = ["derive", "deprecated"] }
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/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false }
evm = { git = "https://github.com/moonbeam-foundation/evm.git", branch = "ahmad-update-external-operation", default-features = false }
futures = "0.3.28"
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
hex-literal = "0.4.1"
Expand Down
6 changes: 5 additions & 1 deletion frame/ethereum/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,16 @@ 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 (80 Kb).
const MAX_STORAGE_GROWTH: u64 = 80 * 1024;
const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH);
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 = GAS_LIMIT_STORAGE_GROWTH_RATIO;
pub const WeightPerGas: Weight = Weight::from_parts(20_000, 0);
}

Expand Down Expand Up @@ -178,6 +181,7 @@ impl pallet_evm::Config for Test {
type OnCreate = ();
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
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 @@ -81,8 +81,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 @@ -164,6 +164,7 @@ impl pallet_evm::Config for Test {
type OnCreate = ();
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = ();
type GasLimitStorageGrowthRatio = ();
type Timestamp = Timestamp;
type WeightInfo = ();
}
Expand Down Expand Up @@ -194,6 +195,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 @@ -167,6 +167,9 @@ pub mod pallet {
/// Gas limit Pov size ratio.
type GasLimitPovSizeRatio: Get<u64>;

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

/// Get the timestamp for the current block.
type Timestamp: Time;

Expand Down
5 changes: 5 additions & 0 deletions frame/evm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,14 @@ 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 (40 Kb).
const MAX_STORAGE_GROWTH: u64 = 40 * 1024;
const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH);
ahmadkaouk marked this conversation as resolved.
Show resolved Hide resolved

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 = GAS_LIMIT_STORAGE_GROWTH_RATIO;
pub WeightPerGas: Weight = Weight::from_parts(20_000, 0);
pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet;
}
Expand All @@ -160,6 +164,7 @@ impl crate::Config for Test {
type OnCreate = ();
type FindAuthor = FindAuthorTruncated;
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
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
194 changes: 194 additions & 0 deletions frame/evm/src/runner/meter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use evm::{
gasometer::{GasCost, StorageTarget},
Opcode,
};
use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE;
use sp_core::{H160, H256};
use sp_std::collections::btree_map::BTreeMap;

/// 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: BTreeMap<(H160, H256), ()>,
ahmadkaouk marked this conversation as resolved.
Show resolved Hide resolved
}

impl StorageMeter {
/// Creates a new storage meter with the given limit.
pub fn new(limit: u64) -> Self {
Self {
usage: 0,
limit,
recorded_new_entries: BTreeMap::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> {
self.usage = self.usage.checked_add(amount).ok_or_else(|| {
self.usage = self.limit;
MeterError::LimitExceeded
})?;

if self.usage > self.limit {
return Err(MeterError::LimitExceeded);
}
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> {
match gas_cost {
GasCost::SStore { original, new, .. } => {
// 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_key(&(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 + 1);
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(), 232);
}
}
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