Skip to content

Commit 1ca6a44

Browse files
authored
capi: execute single transaction (#2665)
* Provisional execution code * Update remote state * Test run * Make code compile * Update mdbx * Update single_tx * disable domain accessor index * remove T prefix from non-template types * rename InvertedIndexRangeByKeyQuery to entity/entity_name for unification with DomainGetLatestQuery * move murmur_hash3 to snapshots/common * salt file * DomainGetLatestQuery * Update to latest code changes * Add DomainState and tests * Update copyright * Remove temp log comments * make fmt * Fix test execution * Add unit tests and optimize domain state queries * Fix silkworm capi test * Consistent naming for txn index * Simplify domain queries
1 parent 43d4b52 commit 1ca6a44

File tree

16 files changed

+922
-61
lines changed

16 files changed

+922
-61
lines changed

silkworm/capi/instance.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ struct SilkwormInstance {
5353
std::unique_ptr<silkworm::rpc::Daemon> rpcdaemon;
5454
std::unique_ptr<silkworm::stagedsync::ExecutionEngine> execution_engine;
5555

56+
std::optional<silkworm::ChainConfig> chain_config;
57+
5658
// sentry
5759
std::unique_ptr<std::thread> sentry_thread;
5860
boost::asio::cancellation_signal sentry_stop_signal;

silkworm/capi/silkworm.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,33 @@
4242
#include <silkworm/db/buffer.hpp>
4343
#include <silkworm/db/datastore/snapshots/index_builder.hpp>
4444
#include <silkworm/db/datastore/snapshots/segment/segment_reader.hpp>
45+
#include <silkworm/db/kv/grpc/client/remote_client.hpp>
4546
#include <silkworm/db/stages.hpp>
4647
#include <silkworm/db/state/schema_config.hpp>
48+
#include <silkworm/execution/domain_state.hpp>
49+
#include <silkworm/execution/remote_state.hpp>
50+
#include <silkworm/execution/state_factory.hpp>
4751
#include <silkworm/infra/common/bounded_buffer.hpp>
4852
#include <silkworm/infra/common/directories.hpp>
4953
#include <silkworm/infra/common/stopwatch.hpp>
5054
#include <silkworm/infra/concurrency/context_pool_settings.hpp>
5155
#include <silkworm/infra/concurrency/signal_handler.hpp>
56+
#include <silkworm/infra/concurrency/spawn.hpp>
5257
#include <silkworm/infra/concurrency/thread_pool.hpp>
58+
#include <silkworm/infra/grpc/client/client_context_pool.hpp>
5359
#include <silkworm/node/execution/block/block_executor.hpp>
5460
#include <silkworm/node/stagedsync/execution_engine.hpp>
5561
#include <silkworm/rpc/daemon.hpp>
62+
#include <silkworm/rpc/ethbackend/remote_backend.hpp>
63+
#include <silkworm/rpc/ethdb/kv/backend_providers.hpp>
5664

5765
#include "common.hpp"
5866
#include "instance.hpp"
5967

6068
using namespace std::chrono_literals;
6169
using namespace silkworm;
70+
// using namespace silkworm::db;
71+
// using namespace silkworm::rpc;
6272

6373
static MemoryMappedRegion make_region(const SilkwormMemoryMappedFile& mmf) {
6474
return {mmf.memory_address, mmf.memory_length};
@@ -741,6 +751,84 @@ int silkworm_execute_blocks_perpetual(SilkwormHandle handle, MDBX_env* mdbx_env,
741751
}
742752
}
743753

754+
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 {
755+
log::Info{"silkworm_execute_txn", {"block_num", std::to_string(block_num), "txn_index", std::to_string(txn_index)}};
756+
if (!handle) {
757+
return SILKWORM_INVALID_HANDLE;
758+
}
759+
760+
if (!mdbx_tx) {
761+
return SILKWORM_INVALID_MDBX_TXN;
762+
}
763+
764+
if (gas_used) {
765+
*gas_used = 0;
766+
}
767+
768+
if (blob_gas_used) {
769+
*blob_gas_used = 0;
770+
}
771+
772+
silkworm::Hash block_header_hash{};
773+
memcpy(block_header_hash.bytes, block_hash.bytes, sizeof(block_hash.bytes));
774+
BlockNum block_number{block_num};
775+
TxnId txn_id_{txn_num};
776+
777+
auto unmanaged_tx = datastore::kvdb::RWTxnUnmanaged{mdbx_tx};
778+
auto unmanaged_env = silkworm::datastore::kvdb::EnvUnmanaged{::mdbx_txn_env(mdbx_tx)};
779+
auto chain_db = db::DataStore::make_chaindata_database(std::move(unmanaged_env));
780+
auto db_ref = chain_db.ref();
781+
auto state = silkworm::execution::DomainState{txn_id_, unmanaged_tx, db_ref, *handle->blocks_repository, *handle->state_repository};
782+
if (!handle->chain_config) {
783+
handle->chain_config = db::read_chain_config(unmanaged_tx);
784+
}
785+
786+
// TODO: cache block, also consider preloading
787+
silkworm::Block block{};
788+
auto block_read_ok = state.read_body(block_number, block_header_hash, block);
789+
if (!block_read_ok) {
790+
SILK_ERROR << "Block not found"
791+
<< " block_number: " << block_number << " block_hash: " << block_header_hash;
792+
return SILKWORM_INVALID_BLOCK;
793+
}
794+
auto header = state.read_header(block_number, block_header_hash);
795+
if (!header) {
796+
SILK_ERROR << "Header not found"
797+
<< " block_number: " << block_number << " block_hash: " << block_header_hash;
798+
return SILKWORM_INVALID_BLOCK;
799+
}
800+
block.header = header.value();
801+
802+
if (txn_index >= block.transactions.size()) {
803+
SILK_ERROR << "Transaction not found"
804+
<< " txn_index: " << txn_index;
805+
return SILKWORM_INVALID_BLOCK;
806+
}
807+
808+
auto& transaction = block.transactions[txn_index];
809+
810+
auto protocol_rule_set_{protocol::rule_set_factory(*handle->chain_config)};
811+
ExecutionProcessor processor{block, *protocol_rule_set_, state, *handle->chain_config, false};
812+
// TODO: add analysis cache, check block exec for more
813+
814+
silkworm::Receipt receipt{};
815+
const ValidationResult err{protocol::validate_transaction(transaction, processor.intra_block_state(), processor.available_gas())};
816+
if (err != ValidationResult::kOk) {
817+
return SILKWORM_INVALID_BLOCK;
818+
}
819+
processor.execute_transaction(transaction, receipt);
820+
state.insert_receipts(block_number, std::vector<silkworm::Receipt>{receipt});
821+
822+
if (gas_used) {
823+
*gas_used = receipt.cumulative_gas_used;
824+
}
825+
if (blob_gas_used) {
826+
*blob_gas_used = transaction.total_blob_gas();
827+
}
828+
829+
return SILKWORM_OK;
830+
}
831+
744832
SILKWORM_EXPORT int silkworm_fini(SilkwormHandle handle) SILKWORM_NOEXCEPT {
745833
if (!handle) {
746834
return SILKWORM_INVALID_HANDLE;

silkworm/capi/silkworm.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,20 @@ SILKWORM_EXPORT int silkworm_execute_blocks_perpetual(SilkwormHandle handle, MDB
358358
bool write_change_sets, bool write_receipts, bool write_call_traces,
359359
uint64_t* last_executed_block, int* mdbx_error_code) SILKWORM_NOEXCEPT;
360360

361+
/**
362+
* \brief Execute a transaction in a block.
363+
* \param[in] handle A valid Silkworm instance handle, got with silkworm_init.
364+
* \param[in] mdbx_tx A valid external read-write MDBX transaction.
365+
* \param[in] block_num The number of the block containing the transaction.
366+
* \param[in] block_hash The hash of the block.
367+
* \param[in] txn_index The transaction number in the block.
368+
* \param[in] txn_num The canonical transaction ID.
369+
* \param[out] gas_used The gas used by the transaction.
370+
* \param[out] blob_gas_used The blob gas used by the transaction.
371+
* \return SILKWORM_OK (=0) on success, a non-zero error value on failure.
372+
*/
373+
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;
374+
361375
/**
362376
* \brief Finalize the Silkworm C API library.
363377
* \param[in] handle A valid Silkworm instance handle got with silkworm_init.

silkworm/capi/silkworm_test.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ struct SilkwormLibrary {
177177
return result;
178178
}
179179

180+
int execute_txn(MDBX_txn* txn, uint64_t block_num, silkworm::Hash head_hash, uint64_t txn_index, uint64_t txn_id) const {
181+
SilkwormBytes32 head_hash_bytes{};
182+
std::memcpy(head_hash_bytes.bytes, head_hash.bytes, 32);
183+
184+
return silkworm_execute_txn(handle_, txn, block_num, head_hash_bytes, txn_index, txn_id, nullptr, nullptr);
185+
}
186+
180187
int add_snapshot(SilkwormChainSnapshot* snapshot) const {
181188
return silkworm_add_snapshot(handle_, snapshot);
182189
}
@@ -1153,4 +1160,43 @@ TEST_CASE_METHOD(CApiTest, "CAPI silkworm_fork_validator", "[silkworm][capi]") {
11531160
}
11541161
}
11551162

1163+
TEST_CASE_METHOD(CApiTest, "CAPI silkworm_tx: single", "[silkworm][capi]") {
1164+
// Use Silkworm as a library with silkworm_init/silkworm_fini automated by RAII
1165+
SilkwormLibrary silkworm_lib{env_path()};
1166+
silkworm_lib.start_fork_validator(env, &kValidForkValidatorSettings);
1167+
1168+
// Prepare and insert block 10 (just 1 tx w/ value transfer)
1169+
evmc::address from{0x658bdf435d810c91414ec09147daa6db62406379_address}; // funded in genesis
1170+
evmc::address to{0x8b299e2b7d7f43c0ce3068263545309ff4ffb521_address}; // untouched address
1171+
intx::uint256 value{1 * kEther};
1172+
1173+
Block block{};
1174+
block.header.number = 10;
1175+
block.header.gas_limit = 5'000'000;
1176+
block.header.gas_used = 21'000;
1177+
1178+
static constexpr auto kEncoder = [](Bytes& dest, const Receipt& r) { rlp::encode(dest, r); };
1179+
std::vector<Receipt> receipts{
1180+
{TransactionType::kLegacy, true, block.header.gas_used, {}, {}},
1181+
};
1182+
block.header.receipts_root = trie::root_hash(receipts, kEncoder);
1183+
block.transactions.resize(1);
1184+
block.transactions[0].to = to;
1185+
block.transactions[0].gas_limit = block.header.gas_limit;
1186+
block.transactions[0].type = TransactionType::kLegacy;
1187+
block.transactions[0].max_priority_fee_per_gas = 0;
1188+
block.transactions[0].max_fee_per_gas = 20 * kGiga;
1189+
block.transactions[0].value = value;
1190+
block.transactions[0].r = 1; // dummy
1191+
block.transactions[0].s = 1; // dummy
1192+
block.transactions[0].set_sender(from);
1193+
1194+
insert_block(env, block);
1195+
1196+
RWTxnManaged external_txn{env};
1197+
auto result = silkworm_lib.execute_txn(*external_txn, 10, block.header.hash(), 0, 9);
1198+
CHECK(result == SILKWORM_INVALID_BLOCK);
1199+
CHECK_NOTHROW(external_txn.abort());
1200+
}
1201+
11561202
} // namespace silkworm

silkworm/db/datastore/kvdb/domain_get_latest_query.hpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,24 @@ struct DomainGetLatestQuery {
4545
key_encoder.value.timestamp.value = Step{std::numeric_limits<decltype(Step::value)>::max()};
4646
Slice key_slice = key_encoder.encode();
4747

48-
auto result = tx.ro_cursor(entity.values_table)->lower_bound(key_slice, false);
49-
if (!result) return std::nullopt;
48+
auto db_cursor = tx.ro_cursor(entity.values_table);
49+
auto result = entity.has_large_values ? db_cursor->lower_bound(key_slice, false) : db_cursor->find(key_slice, false);
50+
51+
if (!result) {
52+
return std::nullopt;
53+
}
5054

5155
DomainKeyDecoder<RawDecoder<ByteView>> key_decoder{entity.has_large_values};
5256
key_decoder.decode(result.key);
53-
if (key_decoder.value.key.value != from_slice(key_slice)) return std::nullopt;
57+
if (key_decoder.value.key.value != from_slice(key_slice)) {
58+
return std::nullopt;
59+
}
5460

5561
DomainValueDecoder<RawDecoder<ByteView>> empty_value_decoder{entity.has_large_values};
5662
empty_value_decoder.decode(result.value);
57-
if (empty_value_decoder.value.value.value.empty()) return std::nullopt;
63+
if (empty_value_decoder.value.value.value.empty()) {
64+
return std::nullopt;
65+
}
5866

5967
DomainValueDecoder<TValueDecoder> value_decoder{entity.has_large_values};
6068
value_decoder.decode(result.value);

silkworm/db/state/accounts_domain.hpp

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,39 @@ namespace silkworm::db::state {
3030

3131
using AccountsDomainKVSegmentReader = snapshots::segment::KVSegmentReader<AddressSnapshotsDecoder, AccountSnapshotsCodec>;
3232

33-
using AccountsDomainGetLatestQueryBase = datastore::DomainGetLatestQuery<
34-
AddressKVDBEncoder, AddressSnapshotsEncoder,
35-
AccountKVDBCodec, AccountSnapshotsCodec>;
36-
37-
struct AccountsDomainGetLatestQuery : public AccountsDomainGetLatestQueryBase {
33+
struct AccountsDomainGetLatestQuery : public datastore::DomainGetLatestQuery<
34+
AddressKVDBEncoder, AddressSnapshotsEncoder,
35+
AccountKVDBCodec, AccountSnapshotsCodec> {
3836
AccountsDomainGetLatestQuery(
3937
const datastore::kvdb::DatabaseRef& database,
4038
datastore::kvdb::ROTxn& tx,
4139
const snapshots::SnapshotRepositoryROAccess& repository)
42-
: AccountsDomainGetLatestQueryBase{
40+
: datastore::DomainGetLatestQuery<
41+
AddressKVDBEncoder, AddressSnapshotsEncoder,
42+
AccountKVDBCodec, AccountSnapshotsCodec>(
4343
db::state::kDomainNameAccounts,
44-
database,
44+
database.domain(db::state::kDomainNameAccounts),
4545
tx,
46-
repository,
47-
} {}
46+
repository) {}
4847
};
4948

50-
using AccountsDomainPutQuery = datastore::kvdb::DomainPutQuery<AddressKVDBEncoder, AccountKVDBCodec>;
51-
using AccountsDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery<AddressKVDBEncoder, AccountKVDBCodec>;
49+
struct AccountsDomainPutQuery : public datastore::kvdb::DomainPutQuery<AddressKVDBEncoder, AccountKVDBCodec> {
50+
AccountsDomainPutQuery(
51+
const datastore::kvdb::DatabaseRef& database,
52+
datastore::kvdb::RWTxn& rw_tx)
53+
: datastore::kvdb::DomainPutQuery<AddressKVDBEncoder, AccountKVDBCodec>{
54+
rw_tx,
55+
database.domain(db::state::kDomainNameAccounts)} {}
56+
};
57+
58+
struct AccountsDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery<AddressKVDBEncoder, AccountKVDBCodec> {
59+
AccountsDomainDeleteQuery(
60+
const datastore::kvdb::DatabaseRef& database,
61+
datastore::kvdb::RWTxn& rw_tx)
62+
: datastore::kvdb::DomainDeleteQuery<AddressKVDBEncoder, AccountKVDBCodec>{
63+
rw_tx,
64+
database.domain(db::state::kDomainNameAccounts)} {}
65+
};
5266

5367
using AccountsHistoryGetQuery = datastore::HistoryGetQuery<
5468
AddressKVDBEncoder, AddressSnapshotsEncoder,

silkworm/db/state/code_domain.hpp

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,39 @@ namespace silkworm::db::state {
3030

3131
using CodeDomainKVSegmentReader = snapshots::segment::KVSegmentReader<AddressSnapshotsDecoder, snapshots::RawDecoder<Bytes>>;
3232

33-
using CodeDomainGetLatestQueryBase = datastore::DomainGetLatestQuery<
34-
AddressKVDBEncoder, AddressSnapshotsEncoder,
35-
datastore::kvdb::RawDecoder<Bytes>, snapshots::RawDecoder<Bytes>>;
36-
37-
struct CodeDomainGetLatestQuery : public CodeDomainGetLatestQueryBase {
33+
struct CodeDomainGetLatestQuery : public datastore::DomainGetLatestQuery<
34+
AddressKVDBEncoder, AddressSnapshotsEncoder,
35+
datastore::kvdb::RawDecoder<ByteView>, snapshots::RawDecoder<ByteView>> {
3836
CodeDomainGetLatestQuery(
3937
const datastore::kvdb::DatabaseRef& database,
4038
datastore::kvdb::ROTxn& tx,
4139
const snapshots::SnapshotRepositoryROAccess& repository)
42-
: CodeDomainGetLatestQueryBase{
40+
: datastore::DomainGetLatestQuery<
41+
AddressKVDBEncoder, AddressSnapshotsEncoder,
42+
datastore::kvdb::RawDecoder<ByteView>, snapshots::RawDecoder<ByteView>>(
4343
db::state::kDomainNameCode,
44-
database,
44+
database.domain(db::state::kDomainNameCode),
4545
tx,
46-
repository,
47-
} {}
46+
repository) {}
4847
};
4948

50-
using CodeDomainPutQuery = datastore::kvdb::DomainPutQuery<AddressKVDBEncoder, datastore::kvdb::RawEncoder<ByteView>>;
51-
using CodeDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery<AddressKVDBEncoder, datastore::kvdb::RawEncoder<ByteView>>;
49+
struct CodeDomainPutQuery : public datastore::kvdb::DomainPutQuery<AddressKVDBEncoder, datastore::kvdb::RawEncoder<ByteView>> {
50+
CodeDomainPutQuery(
51+
const datastore::kvdb::DatabaseRef& database,
52+
datastore::kvdb::RWTxn& rw_tx)
53+
: datastore::kvdb::DomainPutQuery<AddressKVDBEncoder, datastore::kvdb::RawEncoder<ByteView>>{
54+
rw_tx,
55+
database.domain(db::state::kDomainNameCode)} {}
56+
};
57+
58+
struct CodeDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery<AddressKVDBEncoder, datastore::kvdb::RawEncoder<ByteView>> {
59+
CodeDomainDeleteQuery(
60+
const datastore::kvdb::DatabaseRef& database,
61+
datastore::kvdb::RWTxn& rw_tx)
62+
: datastore::kvdb::DomainDeleteQuery<AddressKVDBEncoder, datastore::kvdb::RawEncoder<ByteView>>{
63+
rw_tx,
64+
database.domain(db::state::kDomainNameCode)} {}
65+
};
5266

5367
using CodeHistoryGetQuery = datastore::HistoryGetQuery<
5468
AddressKVDBEncoder, AddressSnapshotsEncoder,

silkworm/db/state/commitment_domain.hpp

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,39 @@ namespace silkworm::db::state {
2929

3030
using CommitmentDomainKVSegmentReader = snapshots::segment::KVSegmentReader<snapshots::RawDecoder<Bytes>, snapshots::RawDecoder<Bytes>>;
3131

32-
using CommitmentDomainGetLatestQueryBase = datastore::DomainGetLatestQuery<
33-
datastore::kvdb::RawEncoder<ByteView>, snapshots::RawEncoder<ByteView>,
34-
datastore::kvdb::RawDecoder<Bytes>, snapshots::RawDecoder<Bytes>>;
35-
36-
struct CommitmentDomainGetLatestQuery : public CommitmentDomainGetLatestQueryBase {
32+
struct CommitmentDomainGetLatestQuery : public datastore::DomainGetLatestQuery<
33+
datastore::kvdb::RawEncoder<ByteView>, snapshots::RawEncoder<ByteView>,
34+
datastore::kvdb::RawDecoder<Bytes>, snapshots::RawDecoder<Bytes>> {
3735
CommitmentDomainGetLatestQuery(
3836
const datastore::kvdb::DatabaseRef& database,
3937
datastore::kvdb::ROTxn& tx,
4038
const snapshots::SnapshotRepositoryROAccess& repository)
41-
: CommitmentDomainGetLatestQueryBase{
39+
: datastore::DomainGetLatestQuery<
40+
datastore::kvdb::RawEncoder<ByteView>, snapshots::RawEncoder<ByteView>,
41+
datastore::kvdb::RawDecoder<Bytes>, snapshots::RawDecoder<Bytes>>(
4242
db::state::kDomainNameCommitment,
43-
database,
43+
database.domain(db::state::kDomainNameCommitment),
4444
tx,
45-
repository,
46-
} {}
45+
repository) {}
4746
};
4847

49-
using CommitmentDomainPutQuery = datastore::kvdb::DomainPutQuery<datastore::kvdb::RawEncoder<ByteView>, datastore::kvdb::RawEncoder<ByteView>>;
50-
using CommitmentDomainDeleteQuery = datastore::kvdb::DomainDeleteQuery<datastore::kvdb::RawEncoder<ByteView>, datastore::kvdb::RawEncoder<ByteView>>;
48+
struct CommitmentDomainPutQuery : public datastore::kvdb::DomainPutQuery<datastore::kvdb::RawEncoder<ByteView>, datastore::kvdb::RawEncoder<ByteView>> {
49+
CommitmentDomainPutQuery(
50+
const datastore::kvdb::DatabaseRef& database,
51+
datastore::kvdb::RWTxn& rw_tx)
52+
: datastore::kvdb::DomainPutQuery<datastore::kvdb::RawEncoder<ByteView>, datastore::kvdb::RawEncoder<ByteView>>{
53+
rw_tx,
54+
database.domain(db::state::kDomainNameCommitment)} {}
55+
};
56+
57+
struct CommitmentDomainDeleteQuery : datastore::kvdb::DomainDeleteQuery<datastore::kvdb::RawEncoder<ByteView>, datastore::kvdb::RawEncoder<ByteView>> {
58+
CommitmentDomainDeleteQuery(
59+
const datastore::kvdb::DatabaseRef& database,
60+
datastore::kvdb::RWTxn& rw_tx)
61+
: datastore::kvdb::DomainDeleteQuery<datastore::kvdb::RawEncoder<ByteView>, datastore::kvdb::RawEncoder<ByteView>>{
62+
rw_tx,
63+
database.domain(db::state::kDomainNameCommitment)} {}
64+
};
5165

5266
using CommitmentHistoryGetQuery = datastore::HistoryGetQuery<
5367
datastore::kvdb::RawEncoder<ByteView>, snapshots::RawEncoder<ByteView>,

0 commit comments

Comments
 (0)