Skip to content

Commit

Permalink
core: amend incarnation logic (#1746)
Browse files Browse the repository at this point in the history
  • Loading branch information
canepat committed Jan 16, 2024
1 parent e713f02 commit d6d3d8a
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 15 deletions.
96 changes: 88 additions & 8 deletions silkworm/core/execution/evm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,18 @@ TEST_CASE("Value transfer", "[core][execution]") {
CHECK(state.touched().count(to) == 1);
}

TEST_CASE("Destruct and create", "[core][execution]") {
TEST_CASE("Destruct and recreate", "[core][execution]") {
evmc::address to{0x8b299e2b7d7f43c0ce3068263545309ff4ffb521_address};

InMemoryState db;

{
IntraBlockState state{db};

// First create the contract in a "transaction" and push it to the DB
// First, create the contract and set one storage location to non-zero in a block
state.clear_journal_and_substate();
REQUIRE(state.get_original_storage(to, {}) == evmc::bytes32{});
REQUIRE(state.get_current_storage(to, {}) == evmc::bytes32{});
state.create_contract(to);
state.set_storage(to, {}, evmc::bytes32{1});
REQUIRE(state.get_current_storage(to, {}) == evmc::bytes32{1});
Expand All @@ -88,27 +90,105 @@ TEST_CASE("Destruct and create", "[core][execution]") {
{
IntraBlockState state{db};

// Then destruct it in another "transaction" and "block"
// Then, in another block, destruct it
state.clear_journal_and_substate();
CHECK(state.record_suicide(to));
REQUIRE(state.get_original_storage(to, {}) == evmc::bytes32{1});
REQUIRE(state.get_current_storage(to, {}) == evmc::bytes32{1});
REQUIRE(state.record_suicide(to));
state.destruct_suicides();
REQUIRE(state.get_current_storage(to, {}) == evmc::bytes32{});
state.finalize_transaction(EVMC_SHANGHAI);

// Add some balance to it
state.clear_journal_and_substate();
state.add_to_balance(to, 1);
state.finalize_transaction(EVMC_SHANGHAI);

// Recreate it
// And recreate it: the storage location previously set to non-zero must be zeroed
state.clear_journal_and_substate();
CHECK(state.get_original_storage(to, {}) == evmc::bytes32{});
CHECK(state.get_current_storage(to, {}) == evmc::bytes32{});
state.create_contract(to);
CHECK(state.get_current_storage(to, {}) == evmc::bytes32{});
state.finalize_transaction(EVMC_SHANGHAI);
state.write_to_db(2);
CHECK(db.state_root_hash() == 0x8e723de3b34ef0632b5421f0f8ad8dfa6c981e99009141b5b7130c790f0d38c6_bytes32);
}
}

TEST_CASE("Create contract, destruct and then recreate", "[core][execution]") {
evmc::address to{0x8b299e2b7d7f43c0ce3068263545309ff4ffb521_address};

InMemoryState db;

{
IntraBlockState state{db};

// First, create an empty contract in one block
REQUIRE((state.get_nonce(to) == 0 && state.get_code_hash(to) == kEmptyHash));
state.create_contract(to);
// The following check does not pass, so skipping for now (fix in PR #1553 breaks state root trie)
// CHECK(state.get_current_storage(to, {}) == evmc::bytes32{});
state.set_code(to, *from_hex("30600155"));
state.finalize_transaction(EVMC_SHANGHAI);

state.write_to_db(1);

const auto account{db.read_account(to)};
CHECK((account && account->incarnation == 1));
}

{
IntraBlockState state{db};

// Then, in another block, destruct it
state.clear_journal_and_substate();
REQUIRE(state.record_suicide(to));
state.destruct_suicides();
state.finalize_transaction(EVMC_SHANGHAI);

state.write_to_db(2);
CHECK(db.state_root_hash() == 0x73ea1e235dec8e2f576eadd4173322b5ff7a442a1d09ff8da4941d18e03ff071_bytes32);

CHECK(!db.read_account(to));
}

{
IntraBlockState state{db};

// Finally, recreate the contract in another block
state.create_contract(to);
state.set_code(to, *from_hex("30600255"));
state.finalize_transaction(EVMC_SHANGHAI);

state.write_to_db(3);

const auto account{db.read_account(to)};
CHECK((account && account->incarnation == 2));
}
}

TEST_CASE("Create empty contract and recreate non-empty in same block", "[core][execution]") {
evmc::address to{0x8b299e2b7d7f43c0ce3068263545309ff4ffb521_address};

InMemoryState db;
IntraBlockState state{db};

// First, create an empty contract in one transaction
REQUIRE((state.get_nonce(to) == 0 && state.get_code_hash(to) == kEmptyHash));
state.create_contract(to);
state.finalize_transaction(EVMC_SHANGHAI);

// Then, recreate it adding some code in another transaction
state.clear_journal_and_substate();
REQUIRE((state.get_nonce(to) == 0 && state.get_code_hash(to) == kEmptyHash));
state.create_contract(to);
state.set_code(to, *from_hex("30600055"));
state.finalize_transaction(EVMC_SHANGHAI);

state.write_to_db(1);

const auto account{db.read_account(to)};
CHECK((account && account->incarnation == 2));
}

TEST_CASE("Smart contract with storage", "[core][execution]") {
Block block{};
block.header.number = 1;
Expand Down
10 changes: 9 additions & 1 deletion silkworm/core/state/intra_block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ void IntraBlockState::create_contract(const evmc::address& address) noexcept {
created.initial = prev->initial;
if (prev->current) {
created.current->balance = prev->current->balance;
prev_incarnation = prev->current->incarnation;
if (prev->initial) {
prev_incarnation = std::max(prev->current->incarnation, prev->initial->incarnation);
} else {
prev_incarnation = prev->current->incarnation;
}
} else if (prev->initial) {
prev_incarnation = prev->initial->incarnation;
}
Expand All @@ -97,8 +101,12 @@ void IntraBlockState::create_contract(const evmc::address& address) noexcept {
if (!prev_incarnation || prev_incarnation == 0) {
prev_incarnation = db_.previous_incarnation(address);
}
if (prev && prev_incarnation < prev->current->previous_incarnation) {
prev_incarnation = prev->current->previous_incarnation;
}

created.current->incarnation = *prev_incarnation + 1;
created.current->previous_incarnation = *prev_incarnation;

objects_[address] = created;

Expand Down
1 change: 1 addition & 0 deletions silkworm/core/types/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct Account {
intx::uint256 balance;
evmc::bytes32 code_hash{kEmptyHash};
uint64_t incarnation{0};
uint64_t previous_incarnation{0};

//! \remarks Erigon's (*Account)EncodeForStorage
[[nodiscard]] Bytes encode_for_storage(bool omit_code_hash = false) const;
Expand Down
4 changes: 3 additions & 1 deletion silkworm/node/db/buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ void Buffer::update_account(const evmc::address& address, std::optional<Account>
accounts_[address] = current;
}

if (account_deleted && initial->incarnation) {
const bool initial_smart_now_deleted{account_deleted && initial->incarnation};
const bool initial_smart_now_eoa{!account_deleted && current->incarnation == 0 && initial && initial->incarnation};
if (initial_smart_now_deleted || initial_smart_now_eoa) {
if (incarnations_.insert_or_assign(address, initial->incarnation).second) {
batch_state_size_ += kAddressLength + kIncarnationLength;
}
Expand Down
40 changes: 35 additions & 5 deletions silkworm/node/db/buffer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
limitations under the License.
*/

#include <string>

#include <catch2/catch.hpp>

#include <silkworm/core/common/endian.hpp>
Expand Down Expand Up @@ -122,7 +124,7 @@ TEST_CASE("Account update") {
auto changeset_address{bytes_to_address(data_value_view)};
REQUIRE(changeset_address == address);
data_value_view.remove_prefix(kAddressLength);
REQUIRE(data_value_view.length() == 0);
REQUIRE(data_value_view.empty());
}

SECTION("Changed EOA account") {
Expand Down Expand Up @@ -153,30 +155,58 @@ TEST_CASE("Account update") {
auto changeset_address{bytes_to_address(data_value_view)};
REQUIRE(changeset_address == address);
data_value_view.remove_prefix(kAddressLength);
REQUIRE(data_value_view.length() != 0);
REQUIRE(!data_value_view.empty());

auto previous_account{Account::from_encoded_storage(data_value_view)};
CHECK(previous_account == initial_account);
}

SECTION("Delete Contract account") {
SECTION("Delete contract account") {
const auto address{0xbe00000000000000000000000000000000000000_address};
Account account;
account.incarnation = kDefaultIncarnation;
account.code_hash = to_bytes32(keccak256(address.bytes).bytes); // Just a fake hash

Buffer buffer{txn, 0};
buffer.begin_block(1);
buffer.update_account(address, /*initial=*/account, std::nullopt);
buffer.update_account(address, /*initial=*/account, /*current=*/std::nullopt);
REQUIRE(buffer.account_changes().empty() == false);
REQUIRE_NOTHROW(buffer.write_to_db());

auto incarnations{db::open_cursor(txn, table::kIncarnationMap)};
REQUIRE_NOTHROW(incarnations.to_first());
auto data{incarnations.current()};
REQUIRE(memcmp(data.key.data(), address.bytes, kAddressLength) == 0);
REQUIRE(std::memcmp(data.key.data(), address.bytes, kAddressLength) == 0);
REQUIRE(endian::load_big_u64(db::from_slice(data.value).data()) == account.incarnation);
}

SECTION("Delete contract account and recreate as EOA") {
const auto address{0xbe00000000000000000000000000000000000000_address};
Account account;
account.incarnation = kDefaultIncarnation;
account.code_hash = to_bytes32(keccak256(address.bytes).bytes); // Just a fake hash

// Block 1: create contract account
Buffer buffer{txn, 0};
buffer.begin_block(1);
buffer.update_account(address, /*initial=*/std::nullopt, /*current=*/account);
REQUIRE(!buffer.account_changes().empty());
REQUIRE_NOTHROW(buffer.write_to_db());

// Block 2 : destroy contract and recreate account as EOA
buffer.begin_block(2);
Account eoa;
eoa.balance = kEther;
buffer.update_account(address, /*initial=*/account, /*current=*/eoa);
REQUIRE(!buffer.account_changes().empty());
REQUIRE_NOTHROW(buffer.write_to_db());

auto incarnations{db::open_cursor(txn, table::kIncarnationMap)};
REQUIRE_NOTHROW(incarnations.to_first());
auto data{incarnations.current()};
CHECK(std::memcmp(data.key.data(), address.bytes, kAddressLength) == 0);
CHECK(endian::load_big_u64(db::from_slice(data.value).data()) == account.incarnation);
}
}

} // namespace silkworm::db

0 comments on commit d6d3d8a

Please sign in to comment.