diff --git a/silkworm/capi/instance.hpp b/silkworm/capi/instance.hpp index 59b0c5fb02..11bf0e302a 100644 --- a/silkworm/capi/instance.hpp +++ b/silkworm/capi/instance.hpp @@ -53,6 +53,8 @@ struct SilkwormInstance { std::unique_ptr rpcdaemon; std::unique_ptr execution_engine; + std::optional chain_config; + // sentry std::unique_ptr sentry_thread; boost::asio::cancellation_signal sentry_stop_signal; diff --git a/silkworm/capi/silkworm.cpp b/silkworm/capi/silkworm.cpp index a99290927c..ab86806aa9 100644 --- a/silkworm/capi/silkworm.cpp +++ b/silkworm/capi/silkworm.cpp @@ -42,23 +42,33 @@ #include #include #include +#include #include #include +#include +#include +#include #include #include #include #include #include +#include #include +#include #include #include #include +#include +#include #include "common.hpp" #include "instance.hpp" using namespace std::chrono_literals; using namespace silkworm; +// using namespace silkworm::db; +// using namespace silkworm::rpc; static MemoryMappedRegion make_region(const SilkwormMemoryMappedFile& mmf) { return {mmf.memory_address, mmf.memory_length}; @@ -741,6 +751,84 @@ int silkworm_execute_blocks_perpetual(SilkwormHandle handle, MDBX_env* mdbx_env, } } +SILKWORM_EXPORT int silkworm_execute_txn(SilkwormHandle handle, MDBX_txn* mdbx_tx, uint64_t block_num, struct SilkwormBytes32 block_hash, uint64_t txn_index, uint64_t txn_num, uint64_t* gas_used, uint64_t* blob_gas_used) SILKWORM_NOEXCEPT { + log::Info{"silkworm_execute_txn", {"block_num", std::to_string(block_num), "txn_index", std::to_string(txn_index)}}; + if (!handle) { + return SILKWORM_INVALID_HANDLE; + } + + if (!mdbx_tx) { + return SILKWORM_INVALID_MDBX_TXN; + } + + if (gas_used) { + *gas_used = 0; + } + + if (blob_gas_used) { + *blob_gas_used = 0; + } + + silkworm::Hash block_header_hash{}; + memcpy(block_header_hash.bytes, block_hash.bytes, sizeof(block_hash.bytes)); + BlockNum block_number{block_num}; + TxnId txn_id_{txn_num}; + + auto unmanaged_tx = datastore::kvdb::RWTxnUnmanaged{mdbx_tx}; + auto unmanaged_env = silkworm::datastore::kvdb::EnvUnmanaged{::mdbx_txn_env(mdbx_tx)}; + auto chain_db = db::DataStore::make_chaindata_database(std::move(unmanaged_env)); + auto db_ref = chain_db.ref(); + auto state = silkworm::execution::DomainState{txn_id_, unmanaged_tx, db_ref, *handle->blocks_repository, *handle->state_repository}; + if (!handle->chain_config) { + handle->chain_config = db::read_chain_config(unmanaged_tx); + } + + // TODO: cache block, also consider preloading + silkworm::Block block{}; + auto block_read_ok = state.read_body(block_number, block_header_hash, block); + if (!block_read_ok) { + SILK_ERROR << "Block not found" + << " block_number: " << block_number << " block_hash: " << block_header_hash; + return SILKWORM_INVALID_BLOCK; + } + auto header = state.read_header(block_number, block_header_hash); + if (!header) { + SILK_ERROR << "Header not found" + << " block_number: " << block_number << " block_hash: " << block_header_hash; + return SILKWORM_INVALID_BLOCK; + } + block.header = header.value(); + + if (txn_index >= block.transactions.size()) { + SILK_ERROR << "Transaction not found" + << " txn_index: " << txn_index; + return SILKWORM_INVALID_BLOCK; + } + + auto& transaction = block.transactions[txn_index]; + + auto protocol_rule_set_{protocol::rule_set_factory(*handle->chain_config)}; + ExecutionProcessor processor{block, *protocol_rule_set_, state, *handle->chain_config, false}; + // TODO: add analysis cache, check block exec for more + + silkworm::Receipt receipt{}; + const ValidationResult err{protocol::validate_transaction(transaction, processor.intra_block_state(), processor.available_gas())}; + if (err != ValidationResult::kOk) { + return SILKWORM_INVALID_BLOCK; + } + processor.execute_transaction(transaction, receipt); + state.insert_receipts(block_number, std::vector{receipt}); + + if (gas_used) { + *gas_used = receipt.cumulative_gas_used; + } + if (blob_gas_used) { + *blob_gas_used = transaction.total_blob_gas(); + } + + return SILKWORM_OK; +} + SILKWORM_EXPORT int silkworm_fini(SilkwormHandle handle) SILKWORM_NOEXCEPT { if (!handle) { return SILKWORM_INVALID_HANDLE; diff --git a/silkworm/capi/silkworm.h b/silkworm/capi/silkworm.h index 679e267bfb..25b65beb0e 100644 --- a/silkworm/capi/silkworm.h +++ b/silkworm/capi/silkworm.h @@ -358,6 +358,20 @@ SILKWORM_EXPORT int silkworm_execute_blocks_perpetual(SilkwormHandle handle, MDB bool write_change_sets, bool write_receipts, bool write_call_traces, uint64_t* last_executed_block, int* mdbx_error_code) SILKWORM_NOEXCEPT; +/** + * \brief Execute a transaction in a block. + * \param[in] handle A valid Silkworm instance handle, got with silkworm_init. + * \param[in] mdbx_tx A valid external read-write MDBX transaction. + * \param[in] block_num The number of the block containing the transaction. + * \param[in] block_hash The hash of the block. + * \param[in] txn_index The transaction number in the block. + * \param[in] txn_num The canonical transaction ID. + * \param[out] gas_used The gas used by the transaction. + * \param[out] blob_gas_used The blob gas used by the transaction. + * \return SILKWORM_OK (=0) on success, a non-zero error value on failure. + */ +SILKWORM_EXPORT int silkworm_execute_txn(SilkwormHandle handle, MDBX_txn* mdbx_tx, uint64_t block_num, struct SilkwormBytes32 block_hash, uint64_t txn_index, uint64_t txn_num, uint64_t* gas_used, uint64_t* blob_gas_used) SILKWORM_NOEXCEPT; + /** * \brief Finalize the Silkworm C API library. * \param[in] handle A valid Silkworm instance handle got with silkworm_init. diff --git a/silkworm/capi/silkworm_test.cpp b/silkworm/capi/silkworm_test.cpp index 2783786326..50e4589ab0 100644 --- a/silkworm/capi/silkworm_test.cpp +++ b/silkworm/capi/silkworm_test.cpp @@ -177,6 +177,13 @@ struct SilkwormLibrary { return result; } + int execute_txn(MDBX_txn* txn, uint64_t block_num, silkworm::Hash head_hash, uint64_t txn_index, uint64_t txn_id) const { + SilkwormBytes32 head_hash_bytes{}; + std::memcpy(head_hash_bytes.bytes, head_hash.bytes, 32); + + return silkworm_execute_txn(handle_, txn, block_num, head_hash_bytes, txn_index, txn_id, nullptr, nullptr); + } + int add_snapshot(SilkwormChainSnapshot* snapshot) const { return silkworm_add_snapshot(handle_, snapshot); } @@ -1153,4 +1160,43 @@ TEST_CASE_METHOD(CApiTest, "CAPI silkworm_fork_validator", "[silkworm][capi]") { } } +TEST_CASE_METHOD(CApiTest, "CAPI silkworm_tx: single", "[silkworm][capi]") { + // Use Silkworm as a library with silkworm_init/silkworm_fini automated by RAII + SilkwormLibrary silkworm_lib{env_path()}; + silkworm_lib.start_fork_validator(env, &kValidForkValidatorSettings); + + // Prepare and insert block 10 (just 1 tx w/ value transfer) + evmc::address from{0x658bdf435d810c91414ec09147daa6db62406379_address}; // funded in genesis + evmc::address to{0x8b299e2b7d7f43c0ce3068263545309ff4ffb521_address}; // untouched address + intx::uint256 value{1 * kEther}; + + Block block{}; + block.header.number = 10; + block.header.gas_limit = 5'000'000; + block.header.gas_used = 21'000; + + static constexpr auto kEncoder = [](Bytes& dest, const Receipt& r) { rlp::encode(dest, r); }; + std::vector receipts{ + {TransactionType::kLegacy, true, block.header.gas_used, {}, {}}, + }; + block.header.receipts_root = trie::root_hash(receipts, kEncoder); + block.transactions.resize(1); + block.transactions[0].to = to; + block.transactions[0].gas_limit = block.header.gas_limit; + block.transactions[0].type = TransactionType::kLegacy; + block.transactions[0].max_priority_fee_per_gas = 0; + block.transactions[0].max_fee_per_gas = 20 * kGiga; + block.transactions[0].value = value; + block.transactions[0].r = 1; // dummy + block.transactions[0].s = 1; // dummy + block.transactions[0].set_sender(from); + + insert_block(env, block); + + RWTxnManaged external_txn{env}; + auto result = silkworm_lib.execute_txn(*external_txn, 10, block.header.hash(), 0, 9); + CHECK(result == SILKWORM_INVALID_BLOCK); + CHECK_NOTHROW(external_txn.abort()); +} + } // namespace silkworm diff --git a/silkworm/db/datastore/kvdb/domain_get_latest_query.hpp b/silkworm/db/datastore/kvdb/domain_get_latest_query.hpp index a691c37fe0..275d0d204e 100644 --- a/silkworm/db/datastore/kvdb/domain_get_latest_query.hpp +++ b/silkworm/db/datastore/kvdb/domain_get_latest_query.hpp @@ -45,16 +45,24 @@ struct DomainGetLatestQuery { key_encoder.value.timestamp.value = Step{std::numeric_limits::max()}; Slice key_slice = key_encoder.encode(); - auto result = tx.ro_cursor(entity.values_table)->lower_bound(key_slice, false); - if (!result) return std::nullopt; + auto db_cursor = tx.ro_cursor(entity.values_table); + auto result = entity.has_large_values ? db_cursor->lower_bound(key_slice, false) : db_cursor->find(key_slice, false); + + if (!result) { + return std::nullopt; + } DomainKeyDecoder> key_decoder{entity.has_large_values}; key_decoder.decode(result.key); - if (key_decoder.value.key.value != from_slice(key_slice)) return std::nullopt; + if (key_decoder.value.key.value != from_slice(key_slice)) { + return std::nullopt; + } DomainValueDecoder> empty_value_decoder{entity.has_large_values}; empty_value_decoder.decode(result.value); - if (empty_value_decoder.value.value.value.empty()) return std::nullopt; + if (empty_value_decoder.value.value.value.empty()) { + return std::nullopt; + } DomainValueDecoder value_decoder{entity.has_large_values}; value_decoder.decode(result.value); diff --git a/silkworm/db/state/accounts_domain.hpp b/silkworm/db/state/accounts_domain.hpp index e4d75e54d0..8e7bc5053e 100644 --- a/silkworm/db/state/accounts_domain.hpp +++ b/silkworm/db/state/accounts_domain.hpp @@ -30,25 +30,39 @@ namespace silkworm::db::state { using AccountsDomainKVSegmentReader = snapshots::segment::KVSegmentReader; -using AccountsDomainGetLatestQueryBase = datastore::DomainGetLatestQuery< - AddressKVDBEncoder, AddressSnapshotsEncoder, - AccountKVDBCodec, AccountSnapshotsCodec>; - -struct AccountsDomainGetLatestQuery : public AccountsDomainGetLatestQueryBase { +struct AccountsDomainGetLatestQuery : public datastore::DomainGetLatestQuery< + AddressKVDBEncoder, AddressSnapshotsEncoder, + AccountKVDBCodec, AccountSnapshotsCodec> { AccountsDomainGetLatestQuery( const datastore::kvdb::DatabaseRef& database, datastore::kvdb::ROTxn& tx, const snapshots::SnapshotRepositoryROAccess& repository) - : AccountsDomainGetLatestQueryBase{ + : datastore::DomainGetLatestQuery< + AddressKVDBEncoder, AddressSnapshotsEncoder, + AccountKVDBCodec, AccountSnapshotsCodec>( db::state::kDomainNameAccounts, - database, + database.domain(db::state::kDomainNameAccounts), tx, - repository, - } {} + repository) {} }; -using AccountsDomainPutQuery = datastore::kvdb::DomainPutQuery; -using AccountsDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery; +struct AccountsDomainPutQuery : public datastore::kvdb::DomainPutQuery { + AccountsDomainPutQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainPutQuery{ + rw_tx, + database.domain(db::state::kDomainNameAccounts)} {} +}; + +struct AccountsDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery { + AccountsDomainDeleteQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainDeleteQuery{ + rw_tx, + database.domain(db::state::kDomainNameAccounts)} {} +}; using AccountsHistoryGetQuery = datastore::HistoryGetQuery< AddressKVDBEncoder, AddressSnapshotsEncoder, diff --git a/silkworm/db/state/code_domain.hpp b/silkworm/db/state/code_domain.hpp index 888693f3a9..7925615411 100644 --- a/silkworm/db/state/code_domain.hpp +++ b/silkworm/db/state/code_domain.hpp @@ -30,25 +30,39 @@ namespace silkworm::db::state { using CodeDomainKVSegmentReader = snapshots::segment::KVSegmentReader>; -using CodeDomainGetLatestQueryBase = datastore::DomainGetLatestQuery< - AddressKVDBEncoder, AddressSnapshotsEncoder, - datastore::kvdb::RawDecoder, snapshots::RawDecoder>; - -struct CodeDomainGetLatestQuery : public CodeDomainGetLatestQueryBase { +struct CodeDomainGetLatestQuery : public datastore::DomainGetLatestQuery< + AddressKVDBEncoder, AddressSnapshotsEncoder, + datastore::kvdb::RawDecoder, snapshots::RawDecoder> { CodeDomainGetLatestQuery( const datastore::kvdb::DatabaseRef& database, datastore::kvdb::ROTxn& tx, const snapshots::SnapshotRepositoryROAccess& repository) - : CodeDomainGetLatestQueryBase{ + : datastore::DomainGetLatestQuery< + AddressKVDBEncoder, AddressSnapshotsEncoder, + datastore::kvdb::RawDecoder, snapshots::RawDecoder>( db::state::kDomainNameCode, - database, + database.domain(db::state::kDomainNameCode), tx, - repository, - } {} + repository) {} }; -using CodeDomainPutQuery = datastore::kvdb::DomainPutQuery>; -using CodeDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery>; +struct CodeDomainPutQuery : public datastore::kvdb::DomainPutQuery> { + CodeDomainPutQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainPutQuery>{ + rw_tx, + database.domain(db::state::kDomainNameCode)} {} +}; + +struct CodeDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery> { + CodeDomainDeleteQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainDeleteQuery>{ + rw_tx, + database.domain(db::state::kDomainNameCode)} {} +}; using CodeHistoryGetQuery = datastore::HistoryGetQuery< AddressKVDBEncoder, AddressSnapshotsEncoder, diff --git a/silkworm/db/state/commitment_domain.hpp b/silkworm/db/state/commitment_domain.hpp index 78164271f4..05833157b3 100644 --- a/silkworm/db/state/commitment_domain.hpp +++ b/silkworm/db/state/commitment_domain.hpp @@ -29,25 +29,39 @@ namespace silkworm::db::state { using CommitmentDomainKVSegmentReader = snapshots::segment::KVSegmentReader, snapshots::RawDecoder>; -using CommitmentDomainGetLatestQueryBase = datastore::DomainGetLatestQuery< - datastore::kvdb::RawEncoder, snapshots::RawEncoder, - datastore::kvdb::RawDecoder, snapshots::RawDecoder>; - -struct CommitmentDomainGetLatestQuery : public CommitmentDomainGetLatestQueryBase { +struct CommitmentDomainGetLatestQuery : public datastore::DomainGetLatestQuery< + datastore::kvdb::RawEncoder, snapshots::RawEncoder, + datastore::kvdb::RawDecoder, snapshots::RawDecoder> { CommitmentDomainGetLatestQuery( const datastore::kvdb::DatabaseRef& database, datastore::kvdb::ROTxn& tx, const snapshots::SnapshotRepositoryROAccess& repository) - : CommitmentDomainGetLatestQueryBase{ + : datastore::DomainGetLatestQuery< + datastore::kvdb::RawEncoder, snapshots::RawEncoder, + datastore::kvdb::RawDecoder, snapshots::RawDecoder>( db::state::kDomainNameCommitment, - database, + database.domain(db::state::kDomainNameCommitment), tx, - repository, - } {} + repository) {} }; -using CommitmentDomainPutQuery = datastore::kvdb::DomainPutQuery, datastore::kvdb::RawEncoder>; -using CommitmentDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery, datastore::kvdb::RawEncoder>; +struct CommitmentDomainPutQuery : public datastore::kvdb::DomainPutQuery, datastore::kvdb::RawEncoder> { + CommitmentDomainPutQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainPutQuery, datastore::kvdb::RawEncoder>{ + rw_tx, + database.domain(db::state::kDomainNameCommitment)} {} +}; + +struct CommitmentDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery, datastore::kvdb::RawEncoder> { + CommitmentDomainDeleteQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainDeleteQuery, datastore::kvdb::RawEncoder>{ + rw_tx, + database.domain(db::state::kDomainNameCommitment)} {} +}; using CommitmentHistoryGetQuery = datastore::HistoryGetQuery< datastore::kvdb::RawEncoder, snapshots::RawEncoder, diff --git a/silkworm/db/state/receipts_domain.hpp b/silkworm/db/state/receipts_domain.hpp index 231eddc036..d508a40228 100644 --- a/silkworm/db/state/receipts_domain.hpp +++ b/silkworm/db/state/receipts_domain.hpp @@ -62,25 +62,39 @@ static_assert(snapshots::DecoderConcept); using ReceiptsDomainKVSegmentReader = snapshots::segment::KVSegmentReader; -using ReceiptsDomainGetLatestQueryBase = datastore::DomainGetLatestQuery< - datastore::kvdb::RawEncoder, snapshots::RawEncoder, - datastore::kvdb::RawDecoder, snapshots::RawDecoder>; - -struct ReceiptsDomainGetLatestQuery : public ReceiptsDomainGetLatestQueryBase { +struct ReceiptsDomainGetLatestQuery : public datastore::DomainGetLatestQuery< + datastore::kvdb::RawEncoder, snapshots::RawEncoder, + datastore::kvdb::RawDecoder, snapshots::RawDecoder> { ReceiptsDomainGetLatestQuery( const datastore::kvdb::DatabaseRef& database, datastore::kvdb::ROTxn& tx, const snapshots::SnapshotRepositoryROAccess& repository) - : ReceiptsDomainGetLatestQueryBase{ + : datastore::DomainGetLatestQuery< + datastore::kvdb::RawEncoder, snapshots::RawEncoder, + datastore::kvdb::RawDecoder, snapshots::RawDecoder>( db::state::kDomainNameReceipts, - database, + database.domain(db::state::kDomainNameReceipts), tx, - repository, - } {} + repository) {} }; -using ReceiptsDomainPutQuery = datastore::kvdb::DomainPutQuery, datastore::kvdb::RawEncoder>; -using ReceiptsDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery, datastore::kvdb::RawEncoder>; +struct ReceiptsDomainPutQuery : public datastore::kvdb::DomainPutQuery, datastore::kvdb::RawEncoder> { + ReceiptsDomainPutQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainPutQuery, datastore::kvdb::RawEncoder>{ + rw_tx, + database.domain(db::state::kDomainNameReceipts)} {} +}; + +struct ReceiptsDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery, datastore::kvdb::RawEncoder> { + ReceiptsDomainDeleteQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainDeleteQuery, datastore::kvdb::RawEncoder>{ + rw_tx, + database.domain(db::state::kDomainNameReceipts)} {} +}; using ReceiptsHistoryGetQuery = datastore::HistoryGetQuery< datastore::kvdb::RawEncoder, snapshots::RawEncoder, diff --git a/silkworm/db/state/storage_domain.hpp b/silkworm/db/state/storage_domain.hpp index 7b4c9785ad..de49a7cc2b 100644 --- a/silkworm/db/state/storage_domain.hpp +++ b/silkworm/db/state/storage_domain.hpp @@ -29,25 +29,39 @@ namespace silkworm::db::state { using StorageDomainKVSegmentReader = snapshots::segment::KVSegmentReader; -using StorageDomainGetLatestQueryBase = datastore::DomainGetLatestQuery< - StorageAddressAndLocationKVDBEncoder, StorageAddressAndLocationSnapshotsCodec, - Bytes32KVDBCodec, Bytes32SnapshotsCodec>; - -struct StorageDomainGetLatestQuery : public StorageDomainGetLatestQueryBase { +struct StorageDomainGetLatestQuery : public datastore::DomainGetLatestQuery< + StorageAddressAndLocationKVDBEncoder, StorageAddressAndLocationSnapshotsCodec, + Bytes32KVDBCodec, Bytes32SnapshotsCodec> { StorageDomainGetLatestQuery( const datastore::kvdb::DatabaseRef& database, datastore::kvdb::ROTxn& tx, const snapshots::SnapshotRepositoryROAccess& repository) - : StorageDomainGetLatestQueryBase{ + : datastore::DomainGetLatestQuery< + StorageAddressAndLocationKVDBEncoder, StorageAddressAndLocationSnapshotsCodec, + Bytes32KVDBCodec, Bytes32SnapshotsCodec>( db::state::kDomainNameStorage, - database, + database.domain(db::state::kDomainNameStorage), tx, - repository, - } {} + repository) {} }; -using StorageDomainPutQuery = datastore::kvdb::DomainPutQuery; -using StorageDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery; +struct StorageDomainPutQuery : public datastore::kvdb::DomainPutQuery { + StorageDomainPutQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainPutQuery{ + rw_tx, + database.domain(db::state::kDomainNameStorage)} {} +}; + +struct StorageDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery { + StorageDomainDeleteQuery( + const datastore::kvdb::DatabaseRef& database, + datastore::kvdb::RWTxn& rw_tx) + : datastore::kvdb::DomainDeleteQuery{ + rw_tx, + database.domain(db::state::kDomainNameStorage)} {} +}; using StorageHistoryGetQuery = datastore::HistoryGetQuery< StorageAddressAndLocationKVDBEncoder, StorageAddressAndLocationSnapshotsCodec, diff --git a/silkworm/db/test_util/mock_txn.hpp b/silkworm/db/test_util/mock_txn.hpp index aa8347a6fb..7fe6fa910f 100644 --- a/silkworm/db/test_util/mock_txn.hpp +++ b/silkworm/db/test_util/mock_txn.hpp @@ -36,4 +36,24 @@ class MockROTxn : public datastore::kvdb::ROTxn { ::mdbx::txn txn_; }; +class MockRwTxn : public datastore::kvdb::RWTxn { + public: + explicit MockRwTxn() : datastore::kvdb::RWTxn(txn_) {} + + MOCK_METHOD((bool), is_open, (), (const, override)); + MOCK_METHOD((mdbx::env), db, (), (const, override)); + MOCK_METHOD((std::unique_ptr), ro_cursor, (const datastore::kvdb::MapConfig&), (override)); + MOCK_METHOD((std::unique_ptr), ro_cursor_dup_sort, (const datastore::kvdb::MapConfig&), (override)); + MOCK_METHOD((std::unique_ptr), rw_cursor, (const datastore::kvdb::MapConfig&), ()); + MOCK_METHOD((std::unique_ptr), rw_cursor_dup_sort, (const datastore::kvdb::MapConfig&), ()); + MOCK_METHOD((void), commit, (), ()); + MOCK_METHOD((void), abort, (), (override)); + MOCK_METHOD((void), commit_and_renew, (), ()); + MOCK_METHOD((void), commit_and_stop, (), ()); + + private: + ::mdbx::txn txn_; + // silkworm::datastore::kvdb::RWCursor aa; +}; + } // namespace silkworm::db::test_util diff --git a/silkworm/execution/CMakeLists.txt b/silkworm/execution/CMakeLists.txt index ff783102dc..b857616a2b 100644 --- a/silkworm/execution/CMakeLists.txt +++ b/silkworm/execution/CMakeLists.txt @@ -19,6 +19,7 @@ include("${SILKWORM_MAIN_DIR}/cmake/common/targets.cmake") find_package(asio-grpc REQUIRED) find_package(gRPC REQUIRED) find_package(GTest REQUIRED) +find_package(nlohmann_json REQUIRED) # cmake-format: off set(LIBS_PRIVATE @@ -30,6 +31,7 @@ set(LIBS_PRIVATE # cmake-format: off set(LIBS_PUBLIC asio-grpc::asio-grpc + nlohmann_json::nlohmann_json silkworm_core silkworm_infra silkworm_db @@ -42,4 +44,4 @@ silkworm_library( PRIVATE ${LIBS_PRIVATE} ) -target_link_libraries(silkworm_execution_test PRIVATE GTest::gmock silkworm_infra_test_util) +target_link_libraries(silkworm_execution_test PRIVATE GTest::gmock silkworm_infra_test_util silkworm_db_test_util) diff --git a/silkworm/execution/domain_state.cpp b/silkworm/execution/domain_state.cpp new file mode 100644 index 0000000000..0fe76a8226 --- /dev/null +++ b/silkworm/execution/domain_state.cpp @@ -0,0 +1,167 @@ +/* + Copyright 2024 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 "domain_state.hpp" + +#include +#include +#include +#include +#include + +namespace silkworm::execution { + +using namespace db::state; +using namespace datastore; + +// TODO: +// - implement: state_root_hash, canonize_block, decanonize_block (optional) +// - implement insert_call_traces (mandatory) +// - add begin_txn method (replacing begin_block?) +// - insert_receipts should write to domain receipts table db::state::kDomainNameReceipts +// - add buffer saving previous steps for values in accounts, code and storage domains - for updates +// - extend transaction to include txn_id, or +// - add base_txn_id to block and get_txn_by_id method + +std::optional DomainState::read_account(const evmc::address& address) const noexcept { + AccountsDomainGetLatestQuery query{database_, tx_, state_repository_}; + auto result = query.exec(address); + if (result) { + return std::move(result->value); + } + return std::nullopt; +} + +ByteView DomainState::read_code(const evmc::address& address, const evmc::bytes32& /*code_hash*/) const noexcept { + CodeDomainGetLatestQuery query{database_, tx_, state_repository_}; + auto result = query.exec(address); + if (result) { + return std::move(result->value); + } + return ByteView{}; +} + +evmc::bytes32 DomainState::read_storage( + const evmc::address& address, + uint64_t /*incarnation*/, + const evmc::bytes32& location) const noexcept { + StorageDomainGetLatestQuery query{database_, tx_, state_repository_}; + auto result = query.exec({address, location}); + if (result) { + return std::move(result->value); + } + return {}; +} + +uint64_t DomainState::previous_incarnation(const evmc::address& address) const noexcept { + auto prev_incarnation = db::read_previous_incarnation(tx_, address); + return prev_incarnation.value_or(0); +} + +std::optional DomainState::read_header(BlockNum block_num, const evmc::bytes32& block_hash) const noexcept { + return data_model_.read_header(block_num, block_hash); +} + +bool DomainState::read_body(BlockNum block_num, const evmc::bytes32& block_hash, BlockBody& out) const noexcept { + return data_model_.read_body(block_hash, block_num, out); +} + +std::optional DomainState::total_difficulty(BlockNum block_num, const evmc::bytes32& block_hash) const noexcept { + return data_model_.read_total_difficulty(block_num, block_hash); +} + +evmc::bytes32 DomainState::state_root_hash() const { + // TODO: implement + return evmc::bytes32{}; +} + +BlockNum DomainState::current_canonical_block() const { + auto head = db::read_canonical_head(tx_); + return std::get<0>(head); +} + +std::optional DomainState::canonical_hash(BlockNum block_num) const { + return data_model_.read_canonical_header_hash(block_num); +} + +void DomainState::insert_receipts(BlockNum block_num, const std::vector& receipts) { + // TODO: write to domain receipts table db::state::kDomainNameReceipts + db::write_receipts(tx_, receipts, block_num); +} + +void DomainState::update_account( + const evmc::address& address, + std::optional original, + std::optional current) { + if (!original) { + AccountsDomainGetLatestQuery query_prev{database_, tx_, state_repository_}; + auto result_prev = query_prev.exec(address); + if (result_prev) { + original = std::move(result_prev->value); + } + } + + if (current) { + AccountsDomainPutQuery query{database_, tx_}; + query.exec(address, *current, txn_id_, original, Step::from_txn_id(txn_id_)); + } else { + AccountsDomainDeleteQuery query{database_, tx_}; + query.exec(address, txn_id_, original, Step::from_txn_id(txn_id_)); + } +} + +void DomainState::update_account_code( + const evmc::address& address, + uint64_t /*incarnation*/, + const evmc::bytes32& /*code_hash*/, + ByteView code) { + CodeDomainGetLatestQuery query_prev{database_, tx_, state_repository_}; + auto result_prev = query_prev.exec(address); + + std::optional original_code = std::nullopt; + if (result_prev) { + original_code = std::move(result_prev->value); + } + + CodeDomainPutQuery query{database_, tx_}; + query.exec(address, code, txn_id_, original_code, Step::from_txn_id(txn_id_)); +} + +void DomainState::update_storage( + const evmc::address& address, + uint64_t /*incarnation*/, + const evmc::bytes32& location, + const evmc::bytes32& initial, + const evmc::bytes32& current) { + evmc::bytes32 original_value{}; + + if (initial == evmc::bytes32{}) { + StorageDomainGetLatestQuery query_prev{database_, tx_, state_repository_}; + auto result_prev = query_prev.exec({address, location}); + if (result_prev) { + original_value = std::move(result_prev->value); + } + } else { + original_value = initial; + } + + Step prev_step{0}; + + StorageDomainPutQuery query{database_, tx_}; + query.exec({address, location}, current, txn_id_, original_value, Step::from_txn_id(txn_id_)); +} + +} // namespace silkworm::execution diff --git a/silkworm/execution/domain_state.hpp b/silkworm/execution/domain_state.hpp new file mode 100644 index 0000000000..fd0eff98e4 --- /dev/null +++ b/silkworm/execution/domain_state.hpp @@ -0,0 +1,122 @@ +/* + Copyright 2024 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. +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace silkworm::execution { + +class DomainState : public State { + public: + explicit DomainState( + TxnId txn_id, + datastore::kvdb::RWTxn& tx, + datastore::kvdb::DatabaseRef& database, + snapshots::SnapshotRepository& blocks_repository, + snapshots::SnapshotRepository& state_repository) + : txn_id_{txn_id}, + tx_{tx}, + database_{database}, + state_repository_{state_repository}, + data_model_{db::DataModel{tx_, blocks_repository}} {} + + explicit DomainState( + TxnId txn_id, + datastore::kvdb::RWTxn& tx, + datastore::kvdb::DatabaseRef& database, + snapshots::SnapshotRepository& state_repository, + db::DataModel& data_model) + + : txn_id_{txn_id}, + tx_{tx}, + database_{database}, + state_repository_{state_repository}, + data_model_{data_model} {} + + std::optional read_account(const evmc::address& address) const noexcept override; + + ByteView read_code(const evmc::address& address, const evmc::bytes32& code_hash) const noexcept override; + + evmc::bytes32 read_storage(const evmc::address& address, uint64_t incarnation, const evmc::bytes32& location) const noexcept override; + + uint64_t previous_incarnation(const evmc::address& address) const noexcept override; + + std::optional read_header(BlockNum block_num, const evmc::bytes32& block_hash) const noexcept override; + + bool read_body(BlockNum block_num, const evmc::bytes32& block_hash, BlockBody& out) const noexcept override; + + std::optional total_difficulty(BlockNum block_num, const evmc::bytes32& block_hash) const noexcept override; + + evmc::bytes32 state_root_hash() const override; + + BlockNum current_canonical_block() const override; + + std::optional canonical_hash(BlockNum block_num) const override; + + void insert_block(const Block& /*block*/, const evmc::bytes32& /*hash*/) override {} + + void canonize_block(BlockNum /*block_num*/, const evmc::bytes32& /*block_hash*/) override {} + + void decanonize_block(BlockNum /*block_num*/) override {} + + void insert_receipts(BlockNum block_num, const std::vector& receipts) override; + + void insert_call_traces(BlockNum /*block_num*/, const CallTraces& /*traces*/) override {} + + void begin_block(BlockNum /*block_num*/, size_t /*updated_accounts_count*/) override {} + + void update_account( + const evmc::address& address, + std::optional initial, + std::optional current) override; + + void update_account_code( + const evmc::address& address, + uint64_t incarnation, + const evmc::bytes32& code_hash, + ByteView code) override; + + void update_storage( + const evmc::address& address, + uint64_t incarnation, + const evmc::bytes32& location, + const evmc::bytes32& initial, + const evmc::bytes32& current) override; + + void unwind_state_changes(BlockNum /*block_num*/) override {} + + private: + TxnId txn_id_; + datastore::kvdb::RWTxn& tx_; + datastore::kvdb::DatabaseRef& database_; + snapshots::SnapshotRepository& state_repository_; + db::DataModel data_model_; +}; + +} // namespace silkworm::execution diff --git a/silkworm/execution/domain_state_test.cpp b/silkworm/execution/domain_state_test.cpp new file mode 100644 index 0000000000..bdfcabbb95 --- /dev/null +++ b/silkworm/execution/domain_state_test.cpp @@ -0,0 +1,322 @@ +/* + Copyright 2024 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 "domain_state.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace silkworm::execution { + +using testing::_; +using testing::Invoke; +using testing::InvokeWithoutArgs; +using testing::Unused; + +TEST_CASE("DomainState data access", "[execution][domain][state]") { + TemporaryDirectory tmp_dir; + silkworm::db::test_util::TestDataStore ds_context{tmp_dir}; + log::init(log::Settings{.log_verbosity = log::Level::kDebug}); + + auto rw_tx = ds_context.chaindata_rw().start_rw_tx(); + + auto db_ref = ds_context->chaindata().ref(); + auto sut = DomainState{1, rw_tx, db_ref, ds_context->blocks_repository(), ds_context->state_repository()}; + auto header0_hash = sut.canonical_hash(0); + auto header0 = sut.read_header(0, header0_hash.value()); + + const silkworm::Hash header1_hash{0x7cb4dd3daba1f739d0c1ec7d998b4a2f6fd83019116455afa54ca4f49dfa0ad4_bytes32}; + + SECTION("reads existing block") { + auto header1 = sut.read_header(1, header1_hash); + CHECK(header1.has_value()); + CHECK(header1->number == 1); + CHECK(header1->hash() == header1_hash); + CHECK(header1->parent_hash == header0_hash); + } + + SECTION("reads non-existing block") { + auto header2 = sut.read_header(2, header1_hash); + CHECK_FALSE(header2.has_value()); + } + + SECTION("reads existing block body") { + BlockBody body1{}; + auto body_read_ok = sut.read_body(1, header1_hash, body1); + CHECK(body_read_ok); + CHECK(body1.transactions.size() == 1); + } + + SECTION("reads non-existing block body") { + BlockBody body2{}; + auto body_read_ok = sut.read_body(2, header1_hash, body2); + CHECK_FALSE(body_read_ok); + } + + SECTION("reads existing total difficulty") { + auto td1 = sut.total_difficulty(1, header1_hash); + CHECK(td1.has_value()); + CHECK(*td1 == 1); + } + + SECTION("reads head canonical block number") { + auto head_block_num = sut.current_canonical_block(); + CHECK(head_block_num == 9); + } + + //! Current genesis preloading does not store data to domain tables + + // SECTION("read_account preloaded block") { + // auto account_65 = sut.read_account(0x658bdf435d810c91414ec09147daa6db62406379_address); + // CHECK(account_65.has_value()); + // CHECK(account_65->balance == intx::uint256{72, 8834426692912283648}); + // } + + // SECTION("read_code preloaded block") { + // auto code_aa = sut.read_code( + // 0xaa00000000000000000000000000000000000000_address, + // 0x39a32aa611e90196e88985d1de5179d967b0cab8d198f6186d5f4d3f073d4fbe_bytes32); + // CHECK(code_aa.size() > 0); + // CHECK(code_aa == from_hex("0x6042")); + // } + + // SECTION("read_storage preloaded block") { + // auto storage_bb_01 = sut.read_storage( + // 0xbb00000000000000000000000000000000000000_address, + // 0, + // 0x0100000000000000000000000000000000000000000000000000000000000000_bytes32); + // CHECK(storage_bb_01 == 0x0100000000000000000000000000000000000000000000000000000000000000_bytes32); + // } + + SECTION("update_account") { + Account account_66{ + .nonce = 8, + .balance = 260, + .incarnation = kDefaultIncarnation, + }; + sut.update_account( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + {}, + account_66); + + auto account_66_read = sut.read_account(0x668bdf435d810c91414ec09147daa6db62406379_address); + CHECK(account_66_read.has_value()); + CHECK(account_66_read->incarnation == account_66.incarnation); + CHECK(account_66_read->nonce == account_66.nonce); + CHECK(account_66_read->balance == account_66.balance); + } + + SECTION("update_account with different steps") { + Account account_66{ + .nonce = 8, + .balance = 260, + .incarnation = kDefaultIncarnation, + }; + sut.update_account( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + {}, + account_66); + + auto account_66_read = sut.read_account(0x668bdf435d810c91414ec09147daa6db62406379_address); + CHECK(account_66_read.has_value()); + CHECK(account_66_read->incarnation == account_66.incarnation); + CHECK(account_66_read->nonce == account_66.nonce); + CHECK(account_66_read->balance == account_66.balance); + + Account account_66_v2{ + .nonce = 9, + .balance = 261552435, + .incarnation = kDefaultIncarnation, + }; + sut.update_account( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + account_66, + account_66_v2); + + account_66_read = sut.read_account(0x668bdf435d810c91414ec09147daa6db62406379_address); + CHECK(account_66_read.has_value()); + CHECK(account_66_read->incarnation == account_66_v2.incarnation); + CHECK(account_66_read->nonce == account_66_v2.nonce); + CHECK(account_66_read->balance == account_66_v2.balance); + + auto next_step_txn_id = silkworm::datastore::kStepSizeForTemporalSnapshots + 1; + auto sut2 = DomainState{next_step_txn_id, rw_tx, db_ref, ds_context->blocks_repository(), ds_context->state_repository()}; + Account account_66_v3{ + .nonce = 10, + .balance = 262, + .incarnation = kDefaultIncarnation, + }; + + sut2.update_account( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + account_66_v2, + account_66_v3); + + account_66_read = sut2.read_account(0x668bdf435d810c91414ec09147daa6db62406379_address); + CHECK(account_66_read.has_value()); + CHECK(account_66_read->incarnation == account_66_v3.incarnation); + CHECK(account_66_read->nonce == account_66_v3.nonce); + CHECK(account_66_read->balance == account_66_v3.balance); + } + + SECTION("update_account_code") { + auto code_66 = *from_hex("0x6042"); + auto code_hash_66 = std::bit_cast(keccak256(code_66)); + sut.update_account_code( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + code_hash_66, + code_66); + + auto code_66_read = sut.read_code(0x668bdf435d810c91414ec09147daa6db62406379_address, code_hash_66); + CHECK(code_66_read.size() > 0); + CHECK(code_66_read == code_66); + } + + SECTION("update_account_code with different steps") { + auto code_66 = *from_hex("0x6042"); + auto code_hash_66 = std::bit_cast(keccak256(code_66)); + sut.update_account_code( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + code_hash_66, + code_66); + + auto code_66_read = sut.read_code(0x668bdf435d810c91414ec09147daa6db62406379_address, code_hash_66); + CHECK(code_66_read.size() > 0); + CHECK(code_66_read == code_66); + + code_66 = *from_hex("0x6043"); + code_hash_66 = std::bit_cast(keccak256(code_66)); + sut.update_account_code( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + code_hash_66, + code_66); + + code_66_read = sut.read_code(0x668bdf435d810c91414ec09147daa6db62406379_address, code_hash_66); + CHECK(code_66_read.size() > 0); + CHECK(code_66_read == code_66); + + auto next_step_txn_id = silkworm::datastore::kStepSizeForTemporalSnapshots + 1; + auto sut2 = DomainState{next_step_txn_id, rw_tx, db_ref, ds_context->blocks_repository(), ds_context->state_repository()}; + code_66 = *from_hex("0x6044"); + code_hash_66 = std::bit_cast(keccak256(code_66)); + sut2.update_account_code( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + code_hash_66, + code_66); + + code_66_read = sut2.read_code(0x668bdf435d810c91414ec09147daa6db62406379_address, code_hash_66); + CHECK(code_66_read.size() > 0); + CHECK(code_66_read == code_66); + } + + SECTION("update_storage") { + sut.update_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32, + evmc::bytes32{}, + 0x0123_bytes32); + + auto storage_66_01 = sut.read_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32); + CHECK(storage_66_01 == 0x0123_bytes32); + } + + SECTION("update_storage with different steps") { + sut.update_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32, + evmc::bytes32{}, + 0x0123_bytes32); + + auto storage_66_01 = sut.read_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32); + CHECK(storage_66_01 == 0x0123_bytes32); + + sut.update_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32, + 0x0123_bytes32, + 0x0124_bytes32); + + storage_66_01 = sut.read_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32); + CHECK(storage_66_01 == 0x0124_bytes32); + + auto next_step_txn_id = silkworm::datastore::kStepSizeForTemporalSnapshots + 1; + auto sut2 = DomainState{next_step_txn_id, rw_tx, db_ref, ds_context->blocks_repository(), ds_context->state_repository()}; + sut2.update_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32, + 0x0124_bytes32, + 0x0456_bytes32); + storage_66_01 = sut2.read_storage( + 0x668bdf435d810c91414ec09147daa6db62406379_address, + kDefaultIncarnation, + 0x0100_bytes32); + CHECK(storage_66_01 == 0x0456_bytes32); + } +} + +TEST_CASE("DomainState empty overriden methods do nothing", "[execution][domain][state]") { + TemporaryDirectory tmp_dir; + silkworm::db::test_util::TestDataStore ds_context{tmp_dir}; + + auto rw_tx = ds_context.chaindata_rw().start_rw_tx(); + + auto db_ref = ds_context->chaindata().ref(); + auto sut = DomainState{1, rw_tx, db_ref, ds_context->blocks_repository(), ds_context->state_repository()}; + + CHECK_NOTHROW(sut.insert_block(Block{}, evmc::bytes32{})); + CHECK_NOTHROW(sut.canonize_block(0, evmc::bytes32{})); + CHECK_NOTHROW(sut.decanonize_block(0)); + CHECK_NOTHROW(sut.insert_call_traces(0, CallTraces{})); + CHECK_NOTHROW(sut.begin_block(0, 0)); + CHECK_NOTHROW(sut.unwind_state_changes(0)); + + auto state_root_hash = sut.state_root_hash(); + CHECK(state_root_hash == evmc::bytes32{}); +} +} // namespace silkworm::execution diff --git a/third_party/erigon-mdbx-go/mdbx-go b/third_party/erigon-mdbx-go/mdbx-go index 839cb7b1a9..aab789d157 160000 --- a/third_party/erigon-mdbx-go/mdbx-go +++ b/third_party/erigon-mdbx-go/mdbx-go @@ -1 +1 @@ -Subproject commit 839cb7b1a9a5e6f8cff751ec1b25814d24a7ab38 +Subproject commit aab789d15716460c36950ddb3b9f3b7a7c4c8089