diff --git a/contract/include/evm_runtime/evm_contract.hpp b/contract/include/evm_runtime/evm_contract.hpp index 6f00c967..c8234468 100644 --- a/contract/include/evm_runtime/evm_contract.hpp +++ b/contract/include/evm_runtime/evm_contract.hpp @@ -72,6 +72,8 @@ CONTRACT evm_contract : public contract { check( _config.exists(), "contract not initialized" ); check( _config.get().version == 0u, "unsupported configuration singleton" ); } + + void push_trx(eosio::name ram_payer, silkworm::Block& block, const bytes& rlptx); }; diff --git a/contract/include/evm_runtime/processor.hpp b/contract/include/evm_runtime/processor.hpp deleted file mode 100644 index e1a2ef15..00000000 --- a/contract/include/evm_runtime/processor.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2020-2021 The Silkworm Authors - - 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. -*/ - -#ifndef SILKWORM_EXECUTION_PROCESSOR_HPP_ -#define SILKWORM_EXECUTION_PROCESSOR_HPP_ - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace evm_runtime { - -using namespace silkworm; - -class ExecutionProcessor { - public: - ExecutionProcessor(const ExecutionProcessor&) = delete; - ExecutionProcessor& operator=(const ExecutionProcessor&) = delete; - - ExecutionProcessor(const Block& block, consensus::IEngine& engine, State& state, const ChainConfig& config); - - // Preconditions: - // 1) consensus' pre_validate_transaction(txn) must return kOk - // 2) txn.from must be recovered, otherwise kMissingSender will be returned - ValidationResult validate_transaction(const Transaction& txn) const noexcept; - ValidationResult pre_validate_transaction(const Transaction& txn, uint64_t block_number, const ChainConfig& config, - const std::optional& base_fee_per_gas) const; - - // Execute a transaction, but do not write to the DB yet. - // Precondition: transaction must be valid. - void execute_transaction(const Transaction& txn, Receipt& receipt) noexcept; - - uint64_t cumulative_gas_used() const noexcept { return cumulative_gas_used_; } - - EVM& evm() noexcept { return evm_; } - const EVM& evm() const noexcept { return evm_; } - - private: - uint64_t available_gas() const noexcept; - uint64_t refund_gas(const Transaction& txn, uint64_t gas_left, uint64_t refund_gas) noexcept; - - uint64_t cumulative_gas_used_{0}; - IntraBlockState state_; - consensus::IEngine& consensus_engine_; - EVM evm_; -}; - -} // namespace evm_runtime - -#endif // SILKWORM_EXECUTION_PROCESSOR_HPP_ diff --git a/contract/src/CMakeLists.txt b/contract/src/CMakeLists.txt index 9b29e21a..84896371 100644 --- a/contract/src/CMakeLists.txt +++ b/contract/src/CMakeLists.txt @@ -10,7 +10,6 @@ list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/state.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/actions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/processor.cpp ) if (WITH_TEST_ACTIONS) add_compile_definitions(WITH_TEST_ACTIONS) @@ -47,6 +46,7 @@ list(APPEND SOURCES list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/core/silkworm/common/util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/core/silkworm/common/endian.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/core/silkworm/consensus/engine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/core/silkworm/execution/evm.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/third_party/silkpre/lib/silkpre/precompile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../silkworm/core/silkworm/execution/analysis_cache.cpp diff --git a/contract/src/actions.cpp b/contract/src/actions.cpp index 295bd24e..a72e97b5 100644 --- a/contract/src/actions.cpp +++ b/contract/src/actions.cpp @@ -1,13 +1,17 @@ +#define NDEBUG 1 // make sure assert is no-op in processor.cpp + #include #include #include #include -#include #include #include #include #include +// included here so NDEBUG is defined to disable assert macro +#include + #ifdef WITH_TEST_ACTIONS #include #endif @@ -18,6 +22,13 @@ #define LOGTIME(MSG) #endif +namespace silkworm { + // provide no-op bloom + Bloom logs_bloom(const std::vector& logs) { + return {}; + } +} + static constexpr eosio::name token_account("eosio.token"_n); static constexpr eosio::symbol token_symbol("EOS", 4u); @@ -38,21 +49,31 @@ void evm_contract::init(const uint64_t chainid) { }, get_self()); } -void evm_contract::pushtx( eosio::name ram_payer, const bytes& rlptx ) { - assert_inited(); +void check_result( ValidationResult r, const Transaction& txn, const char* desc ) { + if( r == ValidationResult::kOk ) + return; - LOGTIME("EVM START"); - eosio::require_auth(ram_payer); + if( r == ValidationResult::kMissingSender ) { + eosio::print("txn.from.has_value is empty\n"); + } else if ( r == ValidationResult::kSenderNoEOA ) { + eosio::print("get_code_hash is empty\n"); + } else if ( r == ValidationResult::kWrongNonce ) { + eosio::print("invalid nonce:", txn.nonce, "\n"); + } else if ( r == ValidationResult::kInsufficientFunds ) { + eosio::print("get_balance of from insufficient\n"); + } else if ( r == ValidationResult::kBlockGasLimitExceeded ) { + eosio::print("available_gas\n"); + } + + eosio::print( "ERR: ", uint64_t(r), "\n" ); + eosio::check( false, desc ); +} + +void evm_contract::push_trx( eosio::name ram_payer, Block& block, const bytes& rlptx ) { std::optional> found_chain_config = lookup_known_chain(_config.get().chainid); check( found_chain_config.has_value(), "failed to find expected chain config" ); - Block block; - block.header.difficulty = 1; - block.header.gas_limit = 0x7ffffffffff; - block.header.timestamp = eosio::current_time_point().sec_since_epoch(); - block.header.number = 1 + (block.header.timestamp - _config.get().genesis_time.sec_since_epoch()); // same logic with block_mapping in TrustEVM - Transaction tx; ByteView bv{(const uint8_t*)rlptx.data(), rlptx.size()}; eosio::check(rlp::decode(bv,tx) == DecodingResult::kOk && bv.empty(), "unable to decode transaction"); @@ -65,14 +86,38 @@ void evm_contract::pushtx( eosio::name ram_payer, const bytes& rlptx ) { evm_runtime::engine engine; evm_runtime::state state{get_self(), ram_payer}; - evm_runtime::ExecutionProcessor ep{block, engine, state, *found_chain_config->second}; + silkworm::ExecutionProcessor ep{block, engine, state, *found_chain_config->second}; + + ValidationResult r = consensus::pre_validate_transaction(tx, ep.evm().block().header.number, ep.evm().config(), + ep.evm().block().header.base_fee_per_gas); + check_result( r, tx, "pre_validate_transaction error" ); + r = ep.validate_transaction(tx); + check_result( r, tx, "validate_transaction error" ); Receipt receipt; ep.execute_transaction(tx, receipt); - + + engine.finalize(ep.state(), ep.evm().block(), ep.evm().revision()); + ep.state().write_to_db(ep.evm().block().header.number); + LOGTIME("EVM EXECUTE"); } +void evm_contract::pushtx( eosio::name ram_payer, const bytes& rlptx ) { + assert_inited(); + + LOGTIME("EVM START"); + eosio::require_auth(ram_payer); + + Block block; + block.header.difficulty = 1; + block.header.gas_limit = 0x7ffffffffff; + block.header.timestamp = eosio::current_time_point().sec_since_epoch(); + block.header.number = 1 + (block.header.timestamp - _config.get().genesis_time.sec_since_epoch()); // same logic with block_mapping in TrustEVM + + push_trx( ram_payer, block, rlptx ); +} + void evm_contract::open(eosio::name owner, eosio::name ram_payer) { assert_inited(); require_auth(ram_payer); @@ -144,26 +189,10 @@ ACTION evm_contract::testtx( const bytes& rlptx, const evm_runtime::test::block_ eosio::require_auth(get_self()); - std::optional> found_chain_config = lookup_known_chain(_config.get().chainid); - check( found_chain_config.has_value(), "failed to find expected chain config" ); - Block block; block.header = bi.get_block_header(); - Transaction tx; - ByteView bv{(const uint8_t *)rlptx.data(), rlptx.size()}; - eosio::check(rlp::decode(bv,tx) == DecodingResult::kOk && bv.empty(), "unable to decode transaction"); - - tx.from.reset(); - tx.recover_sender(); - eosio::check(tx.from.has_value(), "unable to recover sender"); - - evm_runtime::test::engine engine; - evm_runtime::state state{get_self(), get_self()}; - evm_runtime::ExecutionProcessor ep{block, engine, state, *found_chain_config->second}; - - Receipt receipt; - ep.execute_transaction(tx, receipt); + push_trx( get_self(), block, rlptx ); } ACTION evm_contract::dumpstorage(const bytes& addy) { diff --git a/contract/src/processor.cpp b/contract/src/processor.cpp deleted file mode 100644 index 8f1cf6f4..00000000 --- a/contract/src/processor.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - Copyright 2020-2022 The Silkworm Authors - - 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. -*/ - -#include -#include -#include - -#include - -#include -#include -#include - -namespace evm_runtime { - -ExecutionProcessor::ExecutionProcessor(const Block& block, consensus::IEngine& consensus_engine, State& state, - const ChainConfig& config) - : state_{state}, consensus_engine_{consensus_engine}, evm_{block, state_, config} { - evm_.beneficiary = consensus_engine.get_beneficiary(block.header); -} - -ValidationResult ExecutionProcessor::pre_validate_transaction(const Transaction& txn, uint64_t block_number, const ChainConfig& config, - const std::optional& base_fee_per_gas) const { - const evmc_revision rev{config.revision(block_number)}; - - if (txn.chain_id.has_value()) { - if (rev < EVMC_SPURIOUS_DRAGON || txn.chain_id.value() != config.chain_id) { - return ValidationResult::kWrongChainId; - } - } - - if (txn.type == Transaction::Type::kEip2930) { - if (rev < EVMC_BERLIN) { - return ValidationResult::kUnsupportedTransactionType; - } - } else if (txn.type == Transaction::Type::kEip1559) { - if (rev < EVMC_LONDON) { - return ValidationResult::kUnsupportedTransactionType; - } - } else if (txn.type != Transaction::Type::kLegacy) { - return ValidationResult::kUnsupportedTransactionType; - } - - if (base_fee_per_gas.has_value() && txn.max_fee_per_gas < base_fee_per_gas.value()) { - return ValidationResult::kMaxFeeLessThanBase; - } - - // https://github.com/ethereum/EIPs/pull/3594 - if (txn.max_priority_fee_per_gas > txn.max_fee_per_gas) { - return ValidationResult::kMaxPriorityFeeGreaterThanMax; - } - - /* Should the sender already be present it means the validation of signature already occurred */ - if (!txn.from.has_value()) { - if (!silkpre::is_valid_signature(txn.r, txn.s, rev >= EVMC_HOMESTEAD)) { - return ValidationResult::kInvalidSignature; - } - } - - const intx::uint128 g0{intrinsic_gas(txn, rev >= EVMC_HOMESTEAD, rev >= EVMC_ISTANBUL)}; - if (txn.gas_limit < g0) { - return ValidationResult::kIntrinsicGas; - } - - // EIP-2681: Limit account nonce to 2^64-1 - if (txn.nonce >= UINT64_MAX) { - return ValidationResult::kNonceTooHigh; - } - - return ValidationResult::kOk; -} - - -ValidationResult ExecutionProcessor::validate_transaction(const Transaction& txn) const noexcept { - auto r = pre_validate_transaction(txn, evm_.block().header.number, evm_.config(), evm_.block().header.base_fee_per_gas); - if(r != ValidationResult::kOk) { - eosio::print("ERR:", uint64_t(r)); - eosio::check(false, "pre_validate_transaction error"); - } - - if (!txn.from.has_value()) { - eosio::print("txn.from.has_value"); - return ValidationResult::kMissingSender; - } - - if (state_.get_code_hash(*txn.from) != kEmptyHash) { - eosio::print("state_.get_code_hash"); - return ValidationResult::kSenderNoEOA; // EIP-3607 - } - - const uint64_t nonce{state_.get_nonce(*txn.from)}; - if (nonce != txn.nonce) { - eosio::print("nonce:", nonce); - return ValidationResult::kWrongNonce; - } - - // https://github.com/ethereum/EIPs/pull/3594 - const intx::uint512 max_gas_cost{intx::umul(intx::uint256{txn.gas_limit}, txn.max_fee_per_gas)}; - // See YP, Eq (57) in Section 6.2 "Execution" - const intx::uint512 v0{max_gas_cost + txn.value}; - if (state_.get_balance(*txn.from) < v0) { - eosio::print("state_.get_balance"); - return ValidationResult::kInsufficientFunds; - } - - if (available_gas() < txn.gas_limit) { - // Corresponds to the final condition of Eq (58) in Yellow Paper Section 6.2 "Execution". - // The sum of the transaction’s gas limit and the gas utilized in this block prior - // must be no greater than the block’s gas limit. - eosio::print("available_gas"); - return ValidationResult::kBlockGasLimitExceeded; - } - - return ValidationResult::kOk; -} - -void ExecutionProcessor::execute_transaction(const Transaction& txn, Receipt& receipt) noexcept { - auto res = validate_transaction(txn); - if(res != ValidationResult::kOk) { - std::string msg = "validate_transaction error: "; - msg += std::to_string((int)res); - eosio::check(false, msg.c_str()); - } - //eosio::check(validate_transaction(txn) == ValidationResult::kOk, "validate_transaction error"); - - // Optimization: since receipt.logs might have some capacity, let's reuse it. - std::swap(receipt.logs, state_.logs()); - - state_.clear_journal_and_substate(); - - eosio::check(txn.from.has_value(), "no from"); - state_.access_account(*txn.from); - - const intx::uint256 base_fee_per_gas{evm_.block().header.base_fee_per_gas.value_or(0)}; - const intx::uint256 effective_gas_price{txn.effective_gas_price(base_fee_per_gas)}; - state_.subtract_from_balance(*txn.from, txn.gas_limit * effective_gas_price); - - if (txn.to.has_value()) { - state_.access_account(*txn.to); - // EVM itself increments the nonce for contract creation - state_.set_nonce(*txn.from, txn.nonce + 1); - } - - for (const AccessListEntry& ae : txn.access_list) { - state_.access_account(ae.account); - for (const evmc::bytes32& key : ae.storage_keys) { - state_.access_storage(ae.account, key); - } - } - - const evmc_revision rev{evm_.revision()}; - - const intx::uint128 g0{intrinsic_gas(txn, rev >= EVMC_HOMESTEAD, rev >= EVMC_ISTANBUL)}; - eosio::check(g0 <= UINT64_MAX, "g0 <= UINT64_MAX"); // true due to the precondition (transaction must be valid) - - const CallResult vm_res{evm_.execute(txn, txn.gas_limit - static_cast(g0))}; - - const uint64_t gas_used{txn.gas_limit - refund_gas(txn, vm_res.gas_left, vm_res.gas_refund)}; - - // award the fee recipient - const intx::uint256 priority_fee_per_gas{txn.priority_fee_per_gas(base_fee_per_gas)}; - state_.add_to_balance(evm_.beneficiary, priority_fee_per_gas * gas_used); - - state_.destruct_suicides(); - if (rev >= EVMC_SPURIOUS_DRAGON) { - state_.destruct_touched_dead(); - } - - state_.finalize_transaction(); - - cumulative_gas_used_ += gas_used; - - receipt.type = txn.type; - receipt.success = vm_res.status == EVMC_SUCCESS; - receipt.cumulative_gas_used = cumulative_gas_used_; - std::swap(receipt.logs, state_.logs()); - - consensus_engine_.finalize(state_, evm_.block(), evm_.revision()); - state_.write_to_db(evm_.block().header.number); -} - -uint64_t ExecutionProcessor::available_gas() const noexcept { - return evm_.block().header.gas_limit - cumulative_gas_used_; -} - -uint64_t ExecutionProcessor::refund_gas(const Transaction& txn, uint64_t gas_left, uint64_t gas_refund) noexcept { - const evmc_revision rev{evm_.revision()}; - - const uint64_t max_refund_quotient{rev >= EVMC_LONDON ? param::kMaxRefundQuotientLondon - : param::kMaxRefundQuotientFrontier}; - const uint64_t max_refund{(txn.gas_limit - gas_left) / max_refund_quotient}; - uint64_t refund = std::min(gas_refund, max_refund); - gas_left += refund; - - const intx::uint256 base_fee_per_gas{evm_.block().header.base_fee_per_gas.value_or(0)}; - const intx::uint256 effective_gas_price{txn.effective_gas_price(base_fee_per_gas)}; - state_.add_to_balance(*txn.from, gas_left * effective_gas_price); - - return gas_left; -} - -} // namespace evm_runtime diff --git a/silkworm b/silkworm index 2ff7dc97..bc5ddd52 160000 --- a/silkworm +++ b/silkworm @@ -1 +1 @@ -Subproject commit 2ff7dc973a6b8943b1b1ca80efaa98841043a4b5 +Subproject commit bc5ddd525e192175456e01fbd1523e0cc3776fed diff --git a/tests/leap/nodeos_trust_evm_server.py b/tests/leap/nodeos_trust_evm_server.py index 62e42fd2..1ed9aab3 100755 --- a/tests/leap/nodeos_trust_evm_server.py +++ b/tests/leap/nodeos_trust_evm_server.py @@ -132,8 +132,10 @@ wasmFile="evm_runtime.wasm" abiFile="evm_runtime.abi" Utils.Print("Publish evm_runtime contract") - trans = prodNode.publishContract(evmAcc, contractDir, wasmFile, abiFile, waitForTransBlock=True) - transId=prodNode.getTransId(trans) + prodNode.publishContract(evmAcc, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans = prodNode.pushMessage(evmAcc.name, "init", '{"chainid":15555}', '-p evmevmevmevm') + prodNode.waitForTransBlockIfNeeded(trans[1], True) + transId=prodNode.getTransId(trans[1]) blockNum = prodNode.getBlockNumByTransId(transId) block = prodNode.getBlock(blockNum) Utils.Print("Block Id: ", block["id"]) @@ -193,7 +195,7 @@ } for k in addys: - trans = prodNode.pushMessage(evmAcc.name, "setbal", '{"addy":"' + k[2:].lower() + '", "bal":"0000000000000000000000000000000010000000000000000000000000000000"}', '-p evmevmevmevm') + trans = prodNode.pushMessage(evmAcc.name, "setbal", '{"addy":"' + k[2:].lower() + '", "bal":"0000000000000000000000000000000000100000000000000000000000000000"}', '-p evmevmevmevm') genesis_info["alloc"][k.lower()] = {"balance":"0x10000000000000000000000000000000"} prodNode.waitForTransBlockIfNeeded(trans[1], True) diff --git a/tests/leap/nodeos_trust_evm_test.py b/tests/leap/nodeos_trust_evm_test.py index a9ffe219..82a5ebe9 100755 --- a/tests/leap/nodeos_trust_evm_test.py +++ b/tests/leap/nodeos_trust_evm_test.py @@ -76,7 +76,7 @@ total_nodes=pnodes + 2 def generate_evm_transactions(nonce): - while True: + for i in range(1, 5): # execute a few Utils.Print("Execute ETH contract") nonce += 1 toAdd = "2787b98fc4e731d0456b3941f0b3fe2e01430000" @@ -93,7 +93,6 @@ def generate_evm_transactions(nonce): actData = {"ram_payer":"evmevmevmevm", "rlptx":rlptx.hex()} retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p evmevmevmevm') assert retValue[0], "pushtx to ETH contract failed." - Utils.Print("\tReturn value:", retValue[1]["processed"]["action_traces"][0]["return_value_data"]) Utils.Print("\tBlock#", retValue[1]["processed"]["block_num"]) row0=prodNode.getTableRow(evmAcc.name, 3, "storage", 0) Utils.Print("\tTable row:", row0) @@ -154,15 +153,17 @@ def generate_evm_transactions(nonce): wasmFile="evm_runtime.wasm" abiFile="evm_runtime.abi" Utils.Print("Publish evm_runtime contract") - trans = prodNode.publishContract(evmAcc, contractDir, wasmFile, abiFile, waitForTransBlock=True) - transId=prodNode.getTransId(trans) + prodNode.publishContract(evmAcc, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans = prodNode.pushMessage(evmAcc.name, "init", '{"chainid":15555}', '-p evmevmevmevm') + prodNode.waitForTransBlockIfNeeded(trans[1], True) + transId=prodNode.getTransId(trans[1]) blockNum = prodNode.getBlockNumByTransId(transId) block = prodNode.getBlock(blockNum) Utils.Print("Block Id: ", block["id"]) Utils.Print("Block timestamp: ", block["timestamp"]) Utils.Print("Set balance") - trans = prodNode.pushMessage(evmAcc.name, "setbal", '{"addy":"2787b98fc4e731d0456b3941f0b3fe2e01439961", "bal":"0000000000000000000000000000000100000000000000000000000000000000"}', '-p evmevmevmevm') + trans = prodNode.pushMessage(evmAcc.name, "setbal", '{"addy":"2787b98fc4e731d0456b3941f0b3fe2e01439961", "bal":"0000000000000000000000000000000000100000000000000000000000000000"}', '-p evmevmevmevm') prodNode.waitForTransBlockIfNeeded(trans[1], True) @@ -186,10 +187,52 @@ def generate_evm_transactions(nonce): trans = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p evmevmevmevm') prodNode.waitForTransBlockIfNeeded(trans[1], True) + # + # Test some failure cases + # + + # incorrect nonce Utils.Print("Send balance again, should fail with wrong nonce") retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p evmevmevmevm', silentErrors=True, force=True) assert not retValue[0], f"push trx should have failed: {retValue}" + # correct nonce + nonce += 1 + unsignedTrx = transactions.Transaction( + nonce, + 150000000000, #150 GWei + 100000, #100k Gas + toAdd, + amount, + b'' + ) + rlptx = rlp.encode(unsignedTrx.sign(evmSendKey, evmChainId), transactions.Transaction) + actData = {"ram_payer":"evmevmevmevm", "rlptx":rlptx.hex()} + Utils.Print("Send balance again, with correct nonce") + retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p evmevmevmevm', silentErrors=True, force=True) + assert retValue[0], f"push trx should have succeeded: {retValue}" + + # incorrect chainid + nonce += 1 + unsignedTrx = transactions.Transaction( + nonce, + 150000000000, #150 GWei + 100000, #100k Gas + toAdd, + amount, + b'' + ) + evmChainId = 8888 + rlptx = rlp.encode(unsignedTrx.sign(evmSendKey, evmChainId), transactions.Transaction) + actData = {"ram_payer":"evmevmevmevm", "rlptx":rlptx.hex()} + Utils.Print("Send balance again, with invalid chainid") + retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p evmevmevmevm', silentErrors=True, force=True) + assert not retValue[0], f"push trx should have failed: {retValue}" + + # correct values for continuing + nonce -= 1 + evmChainId = 15555 + Utils.Print("Simple Solidity contract") # // SPDX-License-Identifier: GPL-3.0 # pragma solidity >=0.7.0 <0.9.0;