diff --git a/Makefile b/Makefile index 32c301b26..a3e22efb7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: build lint test clean run-image build-image clean-vectors \ setup-hive test-pattern-default run-hive run-hive-debug clean-hive-logs loc-detailed \ - loc-compare-detailed + loc-compare-detailed load-test-fibonacci load-test-io help: ## 📚 Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -193,16 +193,14 @@ start-node-with-flamegraph: rm-test-db ## 🚀🔥 Starts an ethrex client used --dev \ --datadir test_ethrex -load-node: install-cli ## 🚧 Runs a load-test. Run make start-node-with-flamegraph and in a new terminal make load-node - @if [ -z "$$C" ]; then \ - CONTRACT_INTERACTION=""; \ - echo "Running the load-test without contract interaction"; \ - echo "If you want to interact with contracts to load the evm, run the target with a C at the end: make C=1"; \ - else \ - CONTRACT_INTERACTION="-c"; \ - echo "Running the load-test with contract interaction"; \ - fi; \ - ethrex_l2 test load --path test_data/private_keys.txt -i 1000 -v --value 100000 $$CONTRACT_INTERACTION +load-test: install-cli ## 🚧 Runs a load-test. Run make start-node-with-flamegraph and in a new terminal make load-node + ethrex_l2 test load --path test_data/private_keys.txt -i 1000 -v --value 100000 + +load-test-fibonacci: + ethrex_l2 test load --path test_data/private_keys.txt -i 1000 -v --value 100000 --fibonacci + +load-test-io: + ethrex_l2 test load --path test_data/private_keys.txt -i 1000 -v --value 100000 --io rm-test-db: ## 🛑 Removes the DB used by the ethrex client used for testing sudo cargo run --release --bin ethrex -- removedb --datadir test_ethrex diff --git a/cmd/ethrex_l2/src/commands/test.rs b/cmd/ethrex_l2/src/commands/test.rs index 429d037e9..4b757200a 100644 --- a/cmd/ethrex_l2/src/commands/test.rs +++ b/cmd/ethrex_l2/src/commands/test.rs @@ -54,12 +54,13 @@ pub(crate) enum Command { )] verbose: bool, #[clap( - short = 'c', - long = "contract", + long = "fibonacci", default_value = "false", - help = "send value to address with contract" + help = "Run fibonacci load test" )] - contract: bool, + fibonacci: bool, + #[clap(long = "io", default_value = "false", help = "Run I/O-heavy load test")] + i_o_heavy: bool, }, } @@ -116,7 +117,7 @@ async fn transfer_from( }, max_fee_per_gas: Some(3121115334), max_priority_fee_per_gas: Some(3000000000), - gas_limit: Some(TX_GAS_COST * 5), + gas_limit: Some(TX_GAS_COST * 100), ..Default::default() }, 10, @@ -163,7 +164,8 @@ impl Command { value, iterations, verbose, - contract, + fibonacci, + i_o_heavy, } => { let Ok(lines) = read_lines(path) else { return Ok(()); @@ -178,7 +180,7 @@ impl Command { None => Address::random(), }; - let calldata: Bytes = if contract { + let calldata: Bytes = if fibonacci { // This is the bytecode for the contract with the following functions // version() -> always returns 2 // function fibonacci(uint n) public pure returns (uint) -> returns the nth fib number @@ -201,6 +203,24 @@ impl Command { &[Value::Uint(100000000000000_u64.into())], )? .into() + } else if i_o_heavy { + // Contract with a function that touches 100 storage slots on every transaction. + // See `test_data/IOHeavyContract.sol` for the code. + let init_code = hex::decode("6080604052348015600e575f5ffd5b505f5f90505b6064811015603e57805f8260648110602d57602c6043565b5b018190555080806001019150506014565b506070565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b6102728061007d5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063431aabc21461004357806358faa02f1461007357806362f8e72a1461007d575b5f5ffd5b61005d6004803603810190610058919061015c565b61009b565b60405161006a9190610196565b60405180910390f35b61007b6100b3565b005b61008561010a565b6040516100929190610196565b60405180910390f35b5f81606481106100a9575f80fd5b015f915090505481565b5f5f90505b60648110156101075760015f82606481106100d6576100d56101af565b5b01546100e29190610209565b5f82606481106100f5576100f46101af565b5b018190555080806001019150506100b8565b50565b5f5f5f6064811061011e5761011d6101af565b5b0154905090565b5f5ffd5b5f819050919050565b61013b81610129565b8114610145575f5ffd5b50565b5f8135905061015681610132565b92915050565b5f6020828403121561017157610170610125565b5b5f61017e84828501610148565b91505092915050565b61019081610129565b82525050565b5f6020820190506101a95f830184610187565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61021382610129565b915061021e83610129565b9250828201905080821115610236576102356101dc565b5b9291505056fea264697066735822122055f6d7149afdb56c745a203d432710eaa25a8ccdb030503fb970bf1c964ac03264736f6c634300081b0033")?; + let client = EthClient::new(&cfg.network.l2_rpc_url); + + let (_, contract_address) = client + .deploy( + cfg.wallet.address, + cfg.wallet.private_key, + init_code.into(), + Overrides::default(), + ) + .await?; + + to_address = contract_address; + + calldata::encode_calldata("incrementNumbers()", &[])?.into() } else { Bytes::new() }; diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 6eb3e9df7..d88f17b66 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -412,7 +412,7 @@ impl Blockchain { metrics!(METRICS_TX.inc_tx()); // Execute tx - let receipt = match self.apply_transaction(&head_tx, context) { + let receipt = match self.apply_transaction(&head_tx, context, head_tx.tx.sender()) { Ok(receipt) => { txs.shift()?; // Pull transaction from the mempool @@ -455,10 +455,11 @@ impl Blockchain { &self, head: &HeadTransaction, context: &mut PayloadBuildContext, + sender: Address ) -> Result { match **head { Transaction::EIP4844Transaction(_) => self.apply_blob_transaction(head, context), - _ => self.apply_plain_transaction(head, context), + _ => self.apply_plain_transaction(head, context, sender), } } @@ -491,7 +492,7 @@ impl Blockchain { return Err(EvmError::Custom("max data blobs reached".to_string()).into()); }; // Apply transaction - let receipt = self.apply_plain_transaction(head, context)?; + let receipt = self.apply_plain_transaction(head, context, head.tx.sender())?; // Update context with blob data let prev_blob_gas = context.payload.header.blob_gas_used.unwrap_or_default(); context.payload.header.blob_gas_used = @@ -505,6 +506,7 @@ impl Blockchain { &self, head: &HeadTransaction, context: &mut PayloadBuildContext, + sender: Address, ) -> Result { let chain_config = context.chain_config()?; let (report, gas_used) = self.vm.execute_tx( @@ -514,6 +516,7 @@ impl Blockchain { &mut context.block_cache, &chain_config, &mut context.remaining_gas, + sender )?; context.block_value += U256::from(gas_used) * head.tip; Ok(report) diff --git a/crates/l2/docs/README.md b/crates/l2/docs/README.md index e65a48086..3dd960090 100644 --- a/crates/l2/docs/README.md +++ b/crates/l2/docs/README.md @@ -31,7 +31,25 @@ Configuration is done through env vars. A detailed list is available in each par ## Testing -Load tests are available via L2 CLI. The test take a list of private keys and send a bunch of transactions from each of them to some address. +Load tests are available via L2 CLI and Makefile targets. + +### Makefile + +There are currently three different load tests you can run: + +``` +make load-test +make load-test-fibonacci +make load-test-io +``` + +The first one sends regular transfers between accounts, the second runs an EVM-heavy contract that computes fibonacci numbers, the third a heavy IO contract that writes to 100 storage slots per transaction. + +### CLI + +To have more control over the load tests and its parameters, you can use the CLI (the Makefile targets use the CLI underneath). + +The tests take a list of private keys and send a bunch of transactions from each of them to some address (either the address of some account to send eth to or the address of the contract that we're interacting with). The CLI can be installed with the `cli` target: diff --git a/crates/vm/backends/mod.rs b/crates/vm/backends/mod.rs index dff1598a4..15aa61ea3 100644 --- a/crates/vm/backends/mod.rs +++ b/crates/vm/backends/mod.rs @@ -67,6 +67,7 @@ impl EVM { block_cache: &mut CacheDB, chain_config: &ChainConfig, remaining_gas: &mut u64, + sender: Address ) -> Result<(Receipt, u64), EvmError> { match self { EVM::REVM => { @@ -75,6 +76,7 @@ impl EVM { block_header, state, spec_id(chain_config, block_header.timestamp), + sender )?; *remaining_gas = remaining_gas.saturating_sub(execution_result.gas_used()); diff --git a/crates/vm/backends/revm_b.rs b/crates/vm/backends/revm_b.rs index 5f05d9e43..ae6cb74e6 100644 --- a/crates/vm/backends/revm_b.rs +++ b/crates/vm/backends/revm_b.rs @@ -68,7 +68,7 @@ impl REVM { let mut cumulative_gas_used = 0; for tx in block.body.transactions.iter() { - let result = Self::execute_tx(tx, block_header, state, spec_id)?; + let result = Self::execute_tx(tx, block_header, state, spec_id, tx.sender())?; cumulative_gas_used += result.gas_used(); let receipt = Receipt::new( tx.tx_type(), @@ -105,9 +105,10 @@ impl REVM { header: &BlockHeader, state: &mut EvmState, spec_id: SpecId, + sender: Address ) -> Result { let block_env = block_env(header, spec_id); - let tx_env = tx_env(tx); + let tx_env = tx_env(tx, sender); run_evm(tx_env, block_env, state, spec_id) } @@ -374,14 +375,14 @@ pub fn block_env(header: &BlockHeader, spec_id: SpecId) -> BlockEnv { // Used for the L2 pub const DEPOSIT_MAGIC_DATA: &[u8] = b"mint"; -pub fn tx_env(tx: &Transaction) -> TxEnv { +pub fn tx_env(tx: &Transaction, sender: Address) -> TxEnv { let max_fee_per_blob_gas = tx .max_fee_per_blob_gas() .map(|x| RevmU256::from_be_bytes(x.to_big_endian())); TxEnv { caller: match tx { Transaction::PrivilegedL2Transaction(_tx) => RevmAddress::ZERO, - _ => RevmAddress(tx.sender().0.into()), + _ => RevmAddress(sender.0.into()), }, gas_limit: tx.gas_limit(), gas_price: RevmU256::from(tx.gas_price()), diff --git a/crates/vm/execution_db.rs b/crates/vm/execution_db.rs index 3885b52da..70cf99e35 100644 --- a/crates/vm/execution_db.rs +++ b/crates/vm/execution_db.rs @@ -159,7 +159,7 @@ impl ExecutionDB { let block_env = block_env(&block.header, spec_id); for transaction in &block.body.transactions { - let tx_env = tx_env(transaction); + let tx_env = tx_env(transaction, transaction.sender()); // execute tx let evm_builder = Evm::builder() diff --git a/test_data/load_test/IOHeavyContract.sol b/test_data/load_test/IOHeavyContract.sol new file mode 100644 index 000000000..c4081c050 --- /dev/null +++ b/test_data/load_test/IOHeavyContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +// Contract used in the `load-test-io` Makefile target. The test sends transactions calling +// the `incrementNumbers()` function, which writes to 100 storage slots. + +contract Counter { + uint256[100] public number; + + constructor() { + for(uint i = 0; i < 100; i++) { + number[i] = i; + } + } + + function incrementNumbers() public { + for(uint i = 0; i < 100; i++) { + number[i] = number[i] + 1; + } + } + + function getFirstNumber() public view returns(uint256) { + return number[0]; + } +}