diff --git a/CMakeLists.txt b/CMakeLists.txt index 212e3eec..fd258785 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ endif() # Silkworm itself add_subdirectory(silkworm) +add_subdirectory(eosevm) if(NOT SILKWORM_HAS_PARENT) add_subdirectory(cmd) diff --git a/eosevm/CMakeLists.txt b/eosevm/CMakeLists.txt new file mode 100644 index 00000000..912b68d0 --- /dev/null +++ b/eosevm/CMakeLists.txt @@ -0,0 +1,37 @@ + +find_package(Microsoft.GSL REQUIRED) +find_package(nlohmann_json REQUIRED) +find_package(tl-expected REQUIRED) + +if(MSVC) + add_compile_options(/EHsc) +else() + add_compile_options(-fno-exceptions) +endif() + +file( + GLOB_RECURSE + EOS_EVM_SRC + CONFIGURE_DEPENDS + "*.cpp" + "*.hpp" + "*.c" + "*.h" +) +list(FILTER EOS_EVM_SRC EXCLUDE REGEX "_test\\.cpp$") +list(FILTER EOS_EVM_SRC EXCLUDE REGEX "_benchmark\\.cpp$") + +add_library(eos_evm ${EOS_EVM_SRC}) +target_include_directories(eos_evm PUBLIC ${SILKWORM_MAIN_DIR}) + +set(EOS_EVM_PUBLIC_LIBS + intx::intx + evmc + tl::expected + nlohmann_json::nlohmann_json +) + +target_link_libraries( + eos_evm + PUBLIC ${EOS_EVM_PUBLIC_LIBS} +) diff --git a/eosevm/consensus_parameters.cpp b/eosevm/consensus_parameters.cpp new file mode 100644 index 00000000..86c30db1 --- /dev/null +++ b/eosevm/consensus_parameters.cpp @@ -0,0 +1,78 @@ +#include "consensus_parameters.hpp" + +#if not defined(ANTELOPE) +#include +#include +#endif + +namespace eosevm { +bool operator==(const eosevm::GasFeeParameters& a, const eosevm::GasFeeParameters& b) { + return a.gas_codedeposit == b.gas_codedeposit && a.gas_newaccount == b.gas_newaccount && + a.gas_sset == b.gas_sset && a.gas_txcreate == b.gas_txcreate && a.gas_txnewaccount == b.gas_txnewaccount; +} + +bool operator==(const eosevm::ConsensusParameters& a, const eosevm::ConsensusParameters& b) { + return a.min_gas_price == b.min_gas_price && a.gas_fee_parameters == b.gas_fee_parameters; } + + +#if not defined(ANTELOPE) +[[nodiscard]] silkworm::Bytes GasFeeParameters::encode() const noexcept { + silkworm::Bytes ret(40, '\0'); + silkworm::endian::store_big_u64(&ret[0], gas_txnewaccount); + silkworm::endian::store_big_u64(&ret[8], gas_newaccount); + silkworm::endian::store_big_u64(&ret[16], gas_txcreate); + silkworm::endian::store_big_u64(&ret[24], gas_codedeposit); + silkworm::endian::store_big_u64(&ret[32], gas_sset); + + return ret; +} + +std::optional GasFeeParameters::decode(silkworm::ByteView encoded) noexcept { + SILKWORM_ASSERT(encoded.length() >= 40); + GasFeeParameters feeParams; + feeParams.gas_txnewaccount = silkworm::endian::load_big_u64(&encoded[0]); + feeParams.gas_newaccount = silkworm::endian::load_big_u64(&encoded[8]); + feeParams.gas_txcreate = silkworm::endian::load_big_u64(&encoded[16]); + feeParams.gas_codedeposit = silkworm::endian::load_big_u64(&encoded[24]); + feeParams.gas_sset = silkworm::endian::load_big_u64(&encoded[32]); + + return feeParams; +} +#endif + +#if not defined(ANTELOPE) +[[nodiscard]] silkworm::Bytes ConsensusParameters::encode() const noexcept { + SILKWORM_ASSERT(min_gas_price.has_value()); + SILKWORM_ASSERT(gas_fee_parameters.has_value()); + constexpr size_t size_before_fee_param = 2 * sizeof(uint64_t); + auto value = gas_fee_parameters->encode(); + silkworm::Bytes ret(value.length() + size_before_fee_param, '\0'); + // Always store as latest supported version: currently 0. + silkworm::endian::store_big_u64(&ret[0], 0); + silkworm::endian::store_big_u64(&ret[sizeof(uint64_t)], *min_gas_price); + std::memcpy(&ret[size_before_fee_param], &value[0], value.length()); + return ret; +}; + +std::optional ConsensusParameters::decode(silkworm::ByteView encoded) noexcept { + SILKWORM_ASSERT(encoded.length() > sizeof(uint64_t)); + ConsensusParameters config{}; + const auto version = silkworm::endian::load_big_u64(&encoded[0]); + + // Parse according to version. For now, only 0. + switch (version) { + case 0: { + constexpr size_t size_before_fee_param = 2 * sizeof(uint64_t); + SILKWORM_ASSERT(encoded.length() > size_before_fee_param); + config.min_gas_price = silkworm::endian::load_big_u64(&encoded[sizeof(uint64_t)]); + config.gas_fee_parameters = GasFeeParameters::decode(silkworm::ByteView{&encoded[size_before_fee_param], encoded.length() - size_before_fee_param}); + break; + } + default: SILKWORM_ASSERT(version <= 0); + } + + return config; +} +#endif + +} // namespace eosevm \ No newline at end of file diff --git a/eosevm/consensus_parameters.hpp b/eosevm/consensus_parameters.hpp new file mode 100644 index 00000000..5342bbb9 --- /dev/null +++ b/eosevm/consensus_parameters.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +#include + + +#if not defined(ANTELOPE) +#include +#endif + + +namespace eosevm { + +// Note: GasFeeParameters struct is NOT versioned, version will be handled by ConsensusParameters. +// If we want to change this struct, create GasFeeParametersV2 and let ConsensusParameters use it. +struct GasFeeParameters { + // gas_txnewaccount = account_bytes * gas_per_byte + uint64_t gas_txnewaccount; + // gas_newaccount = account_bytes * gas_per_byte + uint64_t gas_newaccount; + // gas_txcreate = gas_create = contract_fixed_bytes * gas_per_byte + uint64_t gas_txcreate; + // gas_codedeposit = gas_per_byte + uint64_t gas_codedeposit; + // gas_sset = 100 + storage_slot_bytes * gas_per_byte + uint64_t gas_sset; + + #if not defined(ANTELOPE) + // Encode for storage in db. + [[nodiscard]] silkworm::Bytes encode() const noexcept; + + // Decode from storage in db. + static std::optional decode(silkworm::ByteView encoded) noexcept; + #endif + + friend bool operator==(const GasFeeParameters&, const GasFeeParameters&); +}; + +struct ConsensusParameters { + std::optional min_gas_price; + std::optional gas_fee_parameters; + + #if not defined(ANTELOPE) + // Encode for storage in db. + [[nodiscard]] silkworm::Bytes encode() const noexcept; + + // Decode from storage in db. + static std::optional decode(silkworm::ByteView encoded) noexcept; + #endif + + friend bool operator==(const ConsensusParameters&, const ConsensusParameters&); +}; + +} // namespace eosevm diff --git a/silkworm/core/types/block.cpp b/silkworm/core/types/block.cpp index e28d1e65..fa686d70 100644 --- a/silkworm/core/types/block.cpp +++ b/silkworm/core/types/block.cpp @@ -33,7 +33,7 @@ bool operator==(const BlockHeader& a, const BlockHeader& b) { } bool operator==(const BlockBody& a, const BlockBody& b) { - return a.transactions == b.transactions && a.ommers == b.ommers; + return a.transactions == b.transactions && a.ommers == b.ommers && a.consensus_parameter_index == b.consensus_parameter_index; } BlockNum height(const BlockId& b) { return b.number; } @@ -241,6 +241,9 @@ namespace rlp { Header rlp_head{.list = true}; rlp_head.payload_length += length(b.transactions); rlp_head.payload_length += length(b.ommers); + if (b.consensus_parameter_index) { + rlp_head.payload_length += length(*b.consensus_parameter_index); + } if (b.withdrawals) { rlp_head.payload_length += length(*b.withdrawals); } @@ -256,6 +259,9 @@ namespace rlp { encode_header(to, rlp_header_body(block_body)); encode(to, block_body.transactions); encode(to, block_body.ommers); + if (block_body.consensus_parameter_index) { + encode(to, *block_body.consensus_parameter_index); + } if (block_body.withdrawals) { encode(to, *block_body.withdrawals); } @@ -278,6 +284,15 @@ namespace rlp { return res; } + to.consensus_parameter_index = std::nullopt; + if (from.length() > leftover) { + uint64_t consensus_parameter_index; + if (DecodingResult res{decode(from, consensus_parameter_index, Leftover::kAllow)}; !res) { + return res; + } + to.consensus_parameter_index = consensus_parameter_index; + } + to.withdrawals = std::nullopt; if (from.length() > leftover) { std::vector withdrawals; @@ -310,6 +325,15 @@ namespace rlp { return res; } + to.consensus_parameter_index = std::nullopt; + if (from.length() > leftover) { + uint64_t consensus_parameter_index; + if (DecodingResult res{decode(from, consensus_parameter_index, Leftover::kAllow)}; !res) { + return res; + } + to.consensus_parameter_index = consensus_parameter_index; + } + to.withdrawals = std::nullopt; if (from.length() > leftover) { std::vector withdrawals; @@ -341,6 +365,9 @@ namespace rlp { encode(to, block.header); encode(to, block.transactions); encode(to, block.ommers); + if (block.consensus_parameter_index) { + encode(to, *block.consensus_parameter_index); + } if (block.withdrawals) { encode(to, *block.withdrawals); } diff --git a/silkworm/core/types/block.hpp b/silkworm/core/types/block.hpp index 74e97a96..3472d66a 100644 --- a/silkworm/core/types/block.hpp +++ b/silkworm/core/types/block.hpp @@ -32,6 +32,8 @@ #include #include +#include + namespace silkworm { using TotalDifficulty = intx::uint256; @@ -94,6 +96,9 @@ struct BlockBody { std::vector ommers; std::optional> withdrawals{std::nullopt}; + // EOS-EVM + std::optional consensus_parameter_index{std::nullopt}; + friend bool operator==(const BlockBody&, const BlockBody&); }; @@ -101,6 +106,7 @@ struct Block : public BlockBody { BlockHeader header; bool irreversible{false}; + std::optional consensus_parameters_cache; void recover_senders(); }; diff --git a/silkworm/core/types/block_test.cpp b/silkworm/core/types/block_test.cpp index 674ed942..bc86ddb3 100644 --- a/silkworm/core/types/block_test.cpp +++ b/silkworm/core/types/block_test.cpp @@ -104,6 +104,8 @@ TEST_CASE("BlockBody RLP 2") { body.ommers[0].prev_randao = 0xf0a53dfdd6c2f2a661e718ef29092de60d81d45f84044bec7bf4b36630b2bc08_bytes32; body.ommers[0].nonce[7] = 35; + body.consensus_parameter_index = 1234; + Bytes rlp{}; rlp::encode(rlp, body); diff --git a/silkworm/node/CMakeLists.txt b/silkworm/node/CMakeLists.txt index adb2cac0..f36efc26 100644 --- a/silkworm/node/CMakeLists.txt +++ b/silkworm/node/CMakeLists.txt @@ -87,6 +87,7 @@ endif() target_include_directories(silkworm_node PUBLIC "${SILKWORM_MAIN_DIR}") set(SILKWORM_NODE_PUBLIC_LIBS + eos_evm silkworm_core silkworm_infra silkworm_sentry diff --git a/silkworm/node/db/access_layer.cpp b/silkworm/node/db/access_layer.cpp index 1d616a5a..937acb63 100644 --- a/silkworm/node/db/access_layer.cpp +++ b/silkworm/node/db/access_layer.cpp @@ -443,6 +443,7 @@ bool read_body(ROTxn& txn, const Bytes& key, bool read_senders, BlockBody& out) if (!out.transactions.empty() && read_senders) { parse_senders(txn, key, out.transactions); } + out.consensus_parameter_index = body.consensus_parameter_index; return true; } @@ -498,6 +499,7 @@ void write_body(RWTxn& txn, const BlockBody& body, const uint8_t (&hash)[kHashLe body_for_storage.txn_count = body.transactions.size(); body_for_storage.base_txn_id = increment_map_sequence(txn, table::kBlockTransactions.name, body_for_storage.txn_count); + body_for_storage.consensus_parameter_index = body.consensus_parameter_index; Bytes value{body_for_storage.encode()}; auto key{db::block_key(number, hash)}; @@ -1217,4 +1219,22 @@ void write_runtime_states_u64(RWTxn& txn, uint64_t num, RuntimeState runtime_sta write_runtime_states_bytes(txn, value, runtime_state); } +std::optional read_consensus_parameters(ROTxn& txn, BlockNum index) { + auto cursor = txn.ro_cursor(table::kConsensusParameters); + auto key{db::block_key(index)}; + auto data{cursor->find(to_slice(key), /*throw_notfound=*/false)}; + if (!data) { + return std::nullopt; + } + const auto encoded = from_slice(data.value); + return eosevm::ConsensusParameters::decode(encoded); +} + +void update_consensus_parameters(RWTxn& txn, BlockNum index, const eosevm::ConsensusParameters& config) { + auto cursor = txn.rw_cursor(table::kConsensusParameters); + auto key{db::block_key(index)}; + + cursor->upsert(to_slice(key), mdbx::slice(config.encode())); +} + } // namespace silkworm::db diff --git a/silkworm/node/db/access_layer.hpp b/silkworm/node/db/access_layer.hpp index 8d0131ee..5ad62635 100644 --- a/silkworm/node/db/access_layer.hpp +++ b/silkworm/node/db/access_layer.hpp @@ -32,6 +32,8 @@ #include #include +#include + namespace silkworm::db { //! \brief Pulls database schema version @@ -249,6 +251,12 @@ std::optional read_runtime_states_u64(ROTxn& txn, RuntimeState runtime //! \brief Write uint64_t as runtime states by index void write_runtime_states_u64(RWTxn& txn, uint64_t num, RuntimeState runtime_state); +//! \brief Read ConsensusParameters indexed by blocknum that it is added. +std::optional read_consensus_parameters(ROTxn& txn, BlockNum index); + +//! \brief Write ConsensusParameters indexed by blocknum that it is added. Can overwrite during forks. +void update_consensus_parameters(RWTxn& txn, BlockNum index, const eosevm::ConsensusParameters& config); + class DataModel { public: static void set_snapshot_repository(snapshot::SnapshotRepository* repository); diff --git a/silkworm/node/db/access_layer_test.cpp b/silkworm/node/db/access_layer_test.cpp index 0fa09055..c67c76c7 100644 --- a/silkworm/node/db/access_layer_test.cpp +++ b/silkworm/node/db/access_layer_test.cpp @@ -81,6 +81,7 @@ static BlockBody sample_block_body() { body.ommers[0].prev_randao = 0xf0a53dfdd6c2f2a661e718ef29092de60d81d45f84044bec7bf4b36630b2bc08_bytes32; body.ommers[0].nonce[7] = 35; + body.consensus_parameter_index = 1234; return body; } @@ -527,6 +528,8 @@ TEST_CASE("Headers and bodies") { auto [b, h] = split_block_key(key); REQUIRE(b == header.number); REQUIRE(h == header.hash()); + + CHECK(block.consensus_parameter_index == 1234); } SECTION("process_blocks_at_height") { @@ -911,4 +914,48 @@ TEST_CASE("RuntimeStates_bytes") { CHECK(read_runtime_states_bytes(txn, RuntimeState(1)) == value1); } +TEST_CASE("ConsensusParameters") { + test::Context context; + auto& txn{context.rw_txn()}; + + constexpr eosevm::ConsensusParameters value1{ + .min_gas_price = 1, + .gas_fee_parameters = eosevm::GasFeeParameters{ + .gas_txnewaccount = 1, + .gas_newaccount = 1, + .gas_txcreate = 1, + .gas_codedeposit = 1, + .gas_sset = 1 + } + }; + + constexpr eosevm::ConsensusParameters value2{ + .min_gas_price = 2, + .gas_fee_parameters = eosevm::GasFeeParameters{ + .gas_txnewaccount = 2, + .gas_newaccount = 2, + .gas_txcreate = 2, + .gas_codedeposit = 2, + .gas_sset = 2, + }, + }; + + CHECK(read_consensus_parameters(txn, 0) == std::nullopt); + + update_consensus_parameters(txn, 0, value1 ); + CHECK(read_consensus_parameters(txn, 0) == value1); + + update_consensus_parameters(txn, 0, value2 ); + CHECK(read_consensus_parameters(txn, 0) == value2); + + CHECK(read_consensus_parameters(txn, 1) == std::nullopt); + + update_consensus_parameters(txn, 1, value2 ); + CHECK(read_consensus_parameters(txn, 1) == value2); + + update_consensus_parameters(txn, 1, value1 ); + CHECK(read_consensus_parameters(txn, 1) == value1); +} + + } // namespace silkworm::db diff --git a/silkworm/node/db/tables.hpp b/silkworm/node/db/tables.hpp index b39afbd2..c8688cc2 100644 --- a/silkworm/node/db/tables.hpp +++ b/silkworm/node/db/tables.hpp @@ -377,6 +377,9 @@ inline constexpr db::MapConfig kCumulativeGasIndex{kCumulativeGasIndexName}; inline constexpr const char* kRuntimeStatesName{"RuntimeStates"}; inline constexpr db::MapConfig kRuntimeStates{kRuntimeStatesName}; +inline constexpr const char* kConsensusParametersName{"ConsensusParameters"}; +inline constexpr db::MapConfig kConsensusParameters{kConsensusParametersName}; + inline constexpr db::MapConfig kChainDataTables[]{ kAccountChangeSet, kAccountHistory, @@ -422,6 +425,7 @@ inline constexpr db::MapConfig kChainDataTables[]{ kTrieOfStorage, kTxLookup, kRuntimeStates, + kConsensusParameters, }; //! \brief Ensures all defined tables are present in db with consistent flags. Should a table not exist it gets created diff --git a/silkworm/node/db/util.cpp b/silkworm/node/db/util.cpp index 2758728a..fa0cbc73 100644 --- a/silkworm/node/db/util.cpp +++ b/silkworm/node/db/util.cpp @@ -154,6 +154,9 @@ namespace detail { header.payload_length += rlp::length(base_txn_id); header.payload_length += rlp::length(txn_count); header.payload_length += rlp::length(ommers); + if (consensus_parameter_index) { + header.payload_length += rlp::length(*consensus_parameter_index); + } if (withdrawals) { header.payload_length += rlp::length(*withdrawals); } @@ -163,6 +166,9 @@ namespace detail { rlp::encode(to, base_txn_id); rlp::encode(to, txn_count); rlp::encode(to, ommers); + if (consensus_parameter_index) { + rlp::encode(to, *consensus_parameter_index); + } if (withdrawals) { rlp::encode(to, *withdrawals); } @@ -187,6 +193,15 @@ namespace detail { return res; } + to.consensus_parameter_index = std::nullopt; + if (from.length() > leftover) { + uint64_t consensus_parameter_index; + if (DecodingResult res{rlp::decode(from, consensus_parameter_index, rlp::Leftover::kAllow)}; !res) { + return res; + } + to.consensus_parameter_index = consensus_parameter_index; + } + to.withdrawals = std::nullopt; if (from.length() > leftover) { std::vector withdrawals; diff --git a/silkworm/node/db/util.hpp b/silkworm/node/db/util.hpp index ff63210d..89f17462 100644 --- a/silkworm/node/db/util.hpp +++ b/silkworm/node/db/util.hpp @@ -131,6 +131,8 @@ namespace detail { std::vector ommers; std::optional> withdrawals{std::nullopt}; // EIP-4895 + std::optional consensus_parameter_index{std::nullopt}; + [[nodiscard]] Bytes encode() const; friend bool operator==(const BlockBodyForStorage&, const BlockBodyForStorage&) = default; diff --git a/silkworm/node/db/util_test.cpp b/silkworm/node/db/util_test.cpp index 0d28ec8f..82dce395 100644 --- a/silkworm/node/db/util_test.cpp +++ b/silkworm/node/db/util_test.cpp @@ -39,13 +39,20 @@ TEST_CASE("BlockBodyForStorage encoding") { .base_fee_per_gas = 0x244428, }; - // No withdrawals + // No consensus_parameter_index and withdraws BlockBodyForStorage body{.base_txn_id = 15, .txn_count = 3, .ommers = {header}}; Bytes encoded{body.encode()}; ByteView view{encoded}; BlockBodyForStorage decoded{decode_stored_block_body(view)}; CHECK(decoded == body); + // No withdrawals + body.consensus_parameter_index = 1234; + encoded = body.encode(); + view = encoded; + decoded = decode_stored_block_body(view); + CHECK(decoded == body); + // With withdrawals body.ommers.clear(); // no uncles after The Merge body.withdrawals = {{