Skip to content

Commit f3e96f8

Browse files
committed
node: update state batch size for initial=current=empty account
node: fix state batch size computation for account code and storage node: sort storage locations to insert ordered data into db node: fix unmanaged r/w txn destruction add unit tests
1 parent ab574ca commit f3e96f8

File tree

7 files changed

+489
-27
lines changed

7 files changed

+489
-27
lines changed

silkworm/api/silkworm_api.cpp

+6-4
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,12 @@ static log::Args log_args_for_version() {
8080
}
8181

8282
//! Generate log arguments for execution flush at specified block
83-
static log::Args log_args_for_exec_flush(const db::Buffer& state_buffer, uint64_t current_block) {
83+
static log::Args log_args_for_exec_flush(const db::Buffer& state_buffer, uint64_t max_batch_size, uint64_t current_block) {
8484
return {
85-
"state",
85+
"batch",
8686
std::to_string(state_buffer.current_batch_state_size()),
87+
"max_batch",
88+
std::to_string(max_batch_size),
8789
"block",
8890
std::to_string(current_block)};
8991
}
@@ -469,7 +471,7 @@ int silkworm_execute_blocks(SilkwormHandle* handle, MDBX_txn* mdbx_txn, uint64_t
469471
// Flush state buffer if we've reached the target batch size
470472
if (state_buffer.current_batch_state_size() >= max_batch_size) {
471473
log::Info{"[4/12 Execution] Flushing state", // NOLINT(*-unused-raii)
472-
log_args_for_exec_flush(state_buffer, block.header.number)};
474+
log_args_for_exec_flush(state_buffer, max_batch_size, block.header.number)};
473475
state_buffer.write_state_to_db();
474476
gas_batch_size = 0;
475477
StopWatch sw{/*auto_start=*/true};
@@ -496,7 +498,7 @@ int silkworm_execute_blocks(SilkwormHandle* handle, MDBX_txn* mdbx_txn, uint64_t
496498
}
497499

498500
log::Info{"[4/12 Execution] Flushing state", // NOLINT(*-unused-raii)
499-
log_args_for_exec_flush(state_buffer, max_block)};
501+
log_args_for_exec_flush(state_buffer, max_batch_size, max_block)};
500502
state_buffer.write_state_to_db();
501503
StopWatch sw{/*auto_start=*/true};
502504
txn.commit_and_renew();

silkworm/core/state/intra_block_state.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -332,12 +332,12 @@ void IntraBlockState::write_to_db(uint64_t block_number) {
332332

333333
for (const auto& [address, obj] : objects_) {
334334
db_.update_account(address, obj.initial, obj.current);
335-
if (!obj.current.has_value()) {
335+
if (!obj.current) {
336336
continue;
337337
}
338338
const auto& code_hash{obj.current->code_hash};
339339
if (code_hash != kEmptyHash &&
340-
(!obj.initial.has_value() || obj.initial->incarnation != obj.current->incarnation)) {
340+
(!obj.initial || obj.initial->incarnation != obj.current->incarnation)) {
341341
if (auto it{new_code_.find(code_hash)}; it != new_code_.end()) {
342342
ByteView code_view{it->second.data(), it->second.size()};
343343
db_.update_account_code(address, obj.current->incarnation, code_hash, code_view);

silkworm/node/db/buffer.cpp

+32-9
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ void Buffer::begin_block(uint64_t block_number) {
3838

3939
void Buffer::update_account(const evmc::address& address, std::optional<Account> initial,
4040
std::optional<Account> current) {
41+
// Skip update if both initial and final state are non-existent (i.e. contract creation+destruction within the same block)
42+
if (!initial && !current) {
43+
// Only to perfectly match Erigon state batch size (Erigon does count any account w/ old=new=empty value).
44+
batch_state_size_ += kAddressLength;
45+
return;
46+
}
47+
4148
const bool equal{current == initial};
4249
const bool account_deleted{!current.has_value()};
4350

@@ -83,10 +90,12 @@ void Buffer::update_account(const evmc::address& address, std::optional<Account>
8390

8491
void Buffer::update_account_code(const evmc::address& address, uint64_t incarnation, const evmc::bytes32& code_hash,
8592
ByteView code) {
86-
// Don't overwrite already existing code so that views of it
87-
// that were previously returned by read_code() are still valid.
88-
if (hash_to_code_.try_emplace(code_hash, code).second) {
93+
// Don't overwrite existing code so that views of it that were previously returned by read_code are still valid
94+
const auto [inserted_or_existing_it, inserted] = hash_to_code_.try_emplace(code_hash, code);
95+
if (inserted) {
8996
batch_state_size_ += kHashLength + code.length();
97+
} else {
98+
batch_state_size_ += code.length() - inserted_or_existing_it->second.length();
9099
}
91100

92101
if (storage_prefix_to_code_hash_.insert_or_assign(storage_prefix(address, incarnation), code_hash).second) {
@@ -109,8 +118,14 @@ void Buffer::update_storage(const evmc::address& address, uint64_t incarnation,
109118
}
110119
}
111120

112-
if (storage_[address][incarnation].insert_or_assign(location, current).second) {
113-
batch_state_size_ += kPlainStoragePrefixLength + kHashLength + kHashLength;
121+
// Iterator in insert_or_assign return value "is pointing at the element that was inserted or updated"
122+
// so we cannot use it to determine the old value size: we need to use initial instead
123+
const auto [_, inserted] = storage_[address][incarnation].insert_or_assign(location, current);
124+
ByteView current_val{zeroless_view(current.bytes)};
125+
if (inserted) {
126+
batch_state_size_ += kPlainStoragePrefixLength + kHashLength + current_val.length();
127+
} else {
128+
batch_state_size_ += current_val.length() - zeroless_view(initial.bytes).length();
114129
}
115130
}
116131

@@ -335,9 +350,17 @@ void Buffer::write_state_to_db() {
335350
if (auto it{storage_.find(address)}; it != storage_.end()) {
336351
for (const auto& [incarnation, contract_storage] : it->second) {
337352
Bytes prefix{storage_prefix(address, incarnation)};
338-
for (const auto& [location, value] : contract_storage) {
339-
upsert_storage_value(*state_table, prefix, location.bytes, value.bytes);
340-
written_size += prefix.length() + kLocationLength + kHashLength;
353+
// Extract sorted set of storage locations to insert ordered data into the DB
354+
absl::btree_set<evmc::bytes32> storage_locations;
355+
for (auto& storage_entry : contract_storage) {
356+
storage_locations.insert(storage_entry.first);
357+
}
358+
for (const auto& location : storage_locations) {
359+
if (auto storage_it{contract_storage.find(location)}; storage_it != contract_storage.end()) {
360+
const auto& value{storage_it->second};
361+
upsert_storage_value(*state_table, prefix, location.bytes, value.bytes);
362+
written_size += prefix.length() + kLocationLength + zeroless_view(value.bytes).size();
363+
}
341364
}
342365
}
343366
storage_.erase(it);
@@ -439,7 +462,7 @@ void Buffer::insert_block(const Block& block, const evmc::bytes32& hash) {
439462
uint64_t block_number{block.header.number};
440463
Bytes key{block_key(block_number, hash.bytes)};
441464
headers_[key] = block.header;
442-
bodies_[key] = block;
465+
bodies_[key] = block; // NOLINT(cppcoreguidelines-slicing)
443466

444467
if (block_number == 0) {
445468
difficulty_[key] = 0;

silkworm/node/db/buffer_test.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,26 @@ TEST_CASE("Account update") {
177177
REQUIRE(memcmp(data.key.data(), address.bytes, kAddressLength) == 0);
178178
REQUIRE(endian::load_big_u64(db::from_slice(data.value).data()) == account.incarnation);
179179
}
180+
181+
SECTION("Change EOA account w/ new value equal to old one") {
182+
const auto address{0xbe00000000000000000000000000000000000000_address};
183+
Account initial_account;
184+
initial_account.nonce = 2;
185+
initial_account.balance = kEther;
186+
187+
Account current_account;
188+
current_account.nonce = 2;
189+
current_account.balance = kEther;
190+
191+
Buffer buffer{txn, 0};
192+
buffer.begin_block(1);
193+
buffer.update_account(address, /*initial=*/initial_account, current_account);
194+
REQUIRE(buffer.account_changes().empty());
195+
REQUIRE_NOTHROW(buffer.write_to_db());
196+
197+
auto account_changeset{db::open_cursor(txn, table::kAccountChangeSet)};
198+
REQUIRE(txn->get_map_stat(account_changeset.map()).ms_entries == 0);
199+
}
180200
}
181201

182202
} // namespace silkworm::db

silkworm/node/db/mdbx.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ void RWTxnManaged::commit_and_stop() {
268268
}
269269

270270
RWTxnUnmanaged::~RWTxnUnmanaged() {
271-
abort();
271+
if (handle_) {
272+
abort();
273+
}
272274
}
273275

274276
void RWTxnUnmanaged::abort() {
@@ -309,6 +311,7 @@ void RWTxnUnmanaged::commit() {
309311
if (err.code() != MDBX_SUCCESS) {
310312
err.throw_exception();
311313
}
314+
SILKWORM_ASSERT(!handle_);
312315
SILK_TRACE << "Commit latency" << detail::log_args_for_commit_latency(commit_latency);
313316
}
314317

silkworm/node/db/mdbx.hpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ struct EnvConfig {
372372
size_t page_size{os::page_size()}; // Mdbx page size
373373
size_t max_size{3_Tebi}; // Mdbx max map size
374374
size_t growth_size{2_Gibi}; // Increment size for each extension
375-
uint32_t max_tables{128}; // Default max number of named tables
375+
uint32_t max_tables{256}; // Default max number of named tables
376376
uint32_t max_readers{100}; // Default max number of readers
377377
};
378378

@@ -394,12 +394,12 @@ ::mdbx::map_handle open_map(::mdbx::txn& tx, const MapConfig& config);
394394
//! \return A handle to the opened cursor
395395
::mdbx::cursor_managed open_cursor(::mdbx::txn& tx, const MapConfig& config);
396396

397-
//! \brief Computes the max size of value data to fit in a leaf data page
398-
//! \param [in] page_size : the actually configured MDBX's page size
397+
//! \brief Computes the max size of single-value data to fit into a leaf data page
398+
//! \param [in] page_size : the actually configured MDBX page size
399399
//! \param [in] key_size : the known key size to fit in bundle computed value size
400400
size_t max_value_size_for_leaf_page(size_t page_size, size_t key_size);
401401

402-
//! \brief Computes the max size of value data to fit in a leaf data page
402+
//! \brief Computes the max size of single-value data to fit into a leaf data page
403403
//! \param [in] txn : the transaction used to derive pagesize from
404404
//! \param [in] key_size : the known key size to fit in bundle computed value size
405405
size_t max_value_size_for_leaf_page(const ::mdbx::txn& txn, size_t key_size);

0 commit comments

Comments
 (0)