From 1486191732dc29e68790c8b5d20738b192129388 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Sun, 21 Jan 2024 12:27:03 -0300 Subject: [PATCH 1/6] cleanups Signed-off-by: Ignacio Hagopian --- src/engine_api/execution_payload.zig | 5 +++-- src/exec-spec-tests/execspectests.zig | 12 +++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index 3cca27c..a64d039 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -68,9 +68,10 @@ pub fn newPayloadV2Handler(params: *ExecutionPayload, allocator: std.mem.Allocat // vm.run_block(params.to_block(), params.transactions); // But so far, just print the content of the payload - std.log.info("newPayloadV2Handler: {any}", .{params}); + // std.log.info("newPayloadV2Handler: {any}", .{params}); var block = params.toBlock(); - std.debug.print("block number={}\n", .{block.header.block_number}); + _ = block; + // std.debug.print("block number={}\n", .{block.header.block_number}); params.deinit(allocator); } diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index c1a7e61..6d0e340 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -215,19 +215,21 @@ pub const AccountStateHex = struct { const AccountStorageHex = std.json.ArrayHashMap(HexString); -var test_allocator = std.testing.allocator; test "execution-spec-tests" { - var ft = try Fixture.fromBytes(test_allocator, @embedFile("fixtures/exec-spec-fixture.json")); + var allocator = std.testing.allocator; + + var ft = try Fixture.fromBytes(allocator, @embedFile("fixtures/exec-spec-fixture.json")); defer ft.deinit(); var it = ft.tests.value.map.iterator(); var count: usize = 0; while (it.next()) |entry| { - try std.testing.expect(try entry.value_ptr.run(test_allocator)); + if (count == 1) { + try std.testing.expect(try entry.value_ptr.run(allocator)); + } count += 1; - // TODO: Only run the first test for now. Then we can enable all and continue with the integration. - if (count == 1) { + if (count == 2) { break; } } From 5c546e1be93181c9b07f6d72552a9f76c1f9d529 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Sun, 21 Jan 2024 14:39:39 -0300 Subject: [PATCH 2/6] blockchain/vm: fix typo and add log Signed-off-by: Ignacio Hagopian --- src/blockchain/vm.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index cc1cca8..449f8bc 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -321,8 +321,8 @@ const EVMOneHost = struct { vm.env.state.setBalance(sender, sender_balance - value) catch |err| switch (err) { error.OutOfMemory => @panic("OOO"), }; - const receipient_balance = vm.env.state.getAccount(fromEVMCAddress(msg.*.recipient)).balance; - vm.env.state.setBalance(sender, receipient_balance + value) catch |err| switch (err) { + const recipient_balance = vm.env.state.getAccount(fromEVMCAddress(msg.*.recipient)).balance; + vm.env.state.setBalance(sender, recipient_balance + value) catch |err| switch (err) { error.OutOfMemory => @panic("OOO"), }; } @@ -345,6 +345,7 @@ const EVMOneHost = struct { else // otherwise, we free the backup and indireclty commit to the changes that happened. prev_statedb.deinit(); + evmclog.debug("call() depth={d} ended", .{msg.*.depth}); return result; } }; From f9d24bdc3651cd166d1a04fddf05e060f87787f2 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Sun, 21 Jan 2024 20:18:35 -0300 Subject: [PATCH 3/6] blockchain: add statedb tx scoped variables handling Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 2 ++ src/state/statedb.zig | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 47b431f..95565ab 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -157,6 +157,8 @@ pub const Blockchain = struct { var gas_available = block.header.gas_limit; for (block.transactions) |tx| { const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, txn_signer); + + try state.startTx(); const env: Environment = .{ .origin = txn_info.sender_address, .block_hashes = chain.last_256_blocks_hashes, diff --git a/src/state/statedb.zig b/src/state/statedb.zig index a441ffe..3149680 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -17,6 +17,9 @@ pub const StateDB = struct { allocator: Allocator, db: AccountDB, + + // Tx scoped variables. + original_db: ?AccountDB = null, accessed_accounts: AddressSet, accessed_storage_keys: AddressKeySet, @@ -36,6 +39,17 @@ pub const StateDB = struct { pub fn deinit(self: *StateDB) void { self.db.deinit(); + self.accessed_accounts.deinit(); + self.accessed_storage_keys.deinit(); + } + + pub fn startTx(self: *StateDB) !void { + if (self.original_db) |*original_db| { + original_db.deinit(); + original_db.* = try self.db.clone(); + } + self.accessed_accounts.clearRetainingCapacity(); + self.accessed_storage_keys.clearRetainingCapacity(); } pub fn getAccountOpt(self: *StateDB, addr: Address) ?AccountData { From c79f9b2b76b3511c6c2d8dd821c821a2bd4a25c9 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Sun, 21 Jan 2024 22:42:16 -0300 Subject: [PATCH 4/6] blockchain/vm: implement EIP-220 Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 2 +- src/blockchain/vm.zig | 67 ++++++++++++++++++++++++++++++++++- src/state/statedb.zig | 17 ++++++--- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index 95565ab..eaa506e 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -158,7 +158,6 @@ pub const Blockchain = struct { for (block.transactions) |tx| { const txn_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, txn_signer); - try state.startTx(); const env: Environment = .{ .origin = txn_info.sender_address, .block_hashes = chain.last_256_blocks_hashes, @@ -173,6 +172,7 @@ pub const Blockchain = struct { .chain_id = chain.chain_id, }; + try state.startTx(); const exec_tx_result = try processTransaction(allocator, env, tx); gas_available -= exec_tx_result.gas_used; diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index 449f8bc..ddd5f94 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -152,7 +152,72 @@ const EVMOneHost = struct { evmclog.debug("setStorage addr=0x{} key={} value={}", .{ fmtSliceHexLower(&address), fmtSliceHexLower(&key.*.bytes), fmtSliceHexLower(&value.*.bytes) }); const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); + const k = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); + const storage_status: evmc.enum_evmc_storage_status = blk: { + const original_value = vm.env.state.getOriginalStorage(address, k); + const current_value = vm.env.state.getStorage(address, k); + const new_value = value.*.bytes; + const zero = std.mem.zeroes([32]u8); + + // See: https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa + + // EIP-220: 2. + if (std.mem.eql(u8, ¤t_value, &new_value)) { + break :blk evmc.EVMC_STORAGE_ASSIGNED; + } + // EIP-220: 3. + + // EIP-220: 3.1 + if (std.mem.eql(u8, &original_value, ¤t_value)) { + // EIP-220: 3.1.1 + if (std.mem.eql(u8, &original_value, &zero)) { + // 0->0->Z + break :blk evmc.EVMC_STORAGE_ADDED; + } + if (std.mem.eql(u8, &new_value, &zero)) { + // X->X->0 + break :blk evmc.EVMC_STORAGE_DELETED; + } + // X->X->Z + break :blk evmc.EVMC_STORAGE_MODIFIED; + } + + // EIP-220: 3.2 + // X != Y + + // EIP-220: 3.2.1 + if (!std.mem.eql(u8, &original_value, &zero)) { + // EIP-220: 3.2.1.1 + if (std.mem.eql(u8, ¤t_value, &zero)) { + // X->0->Z + break :blk evmc.EVMC_STORAGE_DELETED_ADDED; + } + // EIP-220: 3.2.1.2 + if (std.mem.eql(u8, &new_value, &zero)) { + // X->Y->0 + break :blk evmc.EVMC_STORAGE_MODIFIED_DELETED; + } + } + + // EIP-220: 3.2.2 + if (std.mem.eql(u8, &original_value, &new_value)) { + if (std.mem.eql(u8, ¤t_value, &zero)) { + // X->0->X + break :blk evmc.EVMC_STORAGE_DELETED_RESTORED; + } + // EIP-220: 3.2.2.1 + if (std.mem.eql(u8, &original_value, &zero)) { + // 0->Y->0 + break :blk evmc.EVMC_STORAGE_ADDED_DELETED; + } + // X->Y->X + break :blk evmc.EVMC_STORAGE_MODIFIED_RESTORED; + } + + break :blk evmc.EVMC_STORAGE_ASSIGNED; + }; + vm.env.state.setStorage(address, k, value.*.bytes) catch |err| switch (err) { // From EVMC docs: "The VM MUST make sure that the account exists. This requirement is only a formality // because VM implementations only modify storage of the account of the current execution context". @@ -160,7 +225,7 @@ const EVMOneHost = struct { error.OutOfMemory => @panic("OOO"), }; - return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix https://evmc.ethereum.org/group__EVMC.html#gae012fd6b8e5c23806b507c2d3e9fb1aa + return storage_status; } fn get_balance(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) evmc.evmc_uint256be { diff --git a/src/state/statedb.zig b/src/state/statedb.zig index 3149680..690ea05 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -41,13 +41,16 @@ pub const StateDB = struct { self.db.deinit(); self.accessed_accounts.deinit(); self.accessed_storage_keys.deinit(); + if (self.original_db) |*original_db| { + original_db.deinit(); + } } pub fn startTx(self: *StateDB) !void { if (self.original_db) |*original_db| { original_db.deinit(); - original_db.* = try self.db.clone(); } + self.original_db = try self.db.clone(); self.accessed_accounts.clearRetainingCapacity(); self.accessed_storage_keys.clearRetainingCapacity(); } @@ -74,6 +77,11 @@ pub const StateDB = struct { return account.storage.get(key) orelse std.mem.zeroes(Bytes32); } + pub fn getOriginalStorage(self: *StateDB, addr: Address, key: u256) Bytes32 { + const account = self.original_db.?.get(addr) orelse return std.mem.zeroes(Bytes32); + return account.storage.get(key) orelse std.mem.zeroes(Bytes32); + } + pub fn getAllStorage(self: *StateDB, addr: Address) ?std.AutoHashMap(u256, Bytes32) { const account = self.db.get(addr) orelse return null; return account.storage; @@ -128,9 +136,10 @@ pub const StateDB = struct { // A much smarter way is doing some "diff" style snapshotting or similar. return StateDB{ .allocator = self.allocator, - .db = try self.db.cloneWithAllocator(self.allocator), - .accessed_accounts = try self.accessed_accounts.cloneWithAllocator(self.allocator), - .accessed_storage_keys = try self.accessed_storage_keys.cloneWithAllocator(self.allocator), + .db = try self.db.clone(), + .original_db = try self.original_db.?.clone(), + .accessed_accounts = try self.accessed_accounts.clone(), + .accessed_storage_keys = try self.accessed_storage_keys.clone(), }; } }; From 59c8407ac3af6864b9d6d0a3c86a05359d02c9bf Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 23 Jan 2024 11:24:39 -0300 Subject: [PATCH 5/6] implement EIP-158 Signed-off-by: Ignacio Hagopian --- src/blockchain/blockchain.zig | 7 ++++++- src/blockchain/vm.zig | 20 +++++++++++++++----- src/exec-spec-tests/execspectests.zig | 4 +--- src/state/statedb.zig | 27 ++++++++++++++++++++++++--- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/blockchain/blockchain.zig b/src/blockchain/blockchain.zig index eaa506e..f4c7520 100644 --- a/src/blockchain/blockchain.zig +++ b/src/blockchain/blockchain.zig @@ -290,7 +290,12 @@ pub const Blockchain = struct { // TODO: self destruct processing // for address in output.accounts_to_delete: - // destroy_account(env.state, address) + // destroy_account(env.state, address) + + for (env.state.touched_addresses.items) |address| { + if (env.state.isEmpty(address)) + env.state.destroyAccount(address); + } return .{ .gas_used = total_gas_used }; } diff --git a/src/blockchain/vm.zig b/src/blockchain/vm.zig index ddd5f94..19de071 100644 --- a/src/blockchain/vm.zig +++ b/src/blockchain/vm.zig @@ -366,6 +366,8 @@ const EVMOneHost = struct { error.OutOfMemory => @panic("OOO"), }; + const recipient_addr = fromEVMCAddress(msg.*.recipient); + // Send value. const value = std.mem.readInt(u256, &msg.*.value.bytes, std.builtin.Endian.Big); if (value > 0) { @@ -386,7 +388,7 @@ const EVMOneHost = struct { vm.env.state.setBalance(sender, sender_balance - value) catch |err| switch (err) { error.OutOfMemory => @panic("OOO"), }; - const recipient_balance = vm.env.state.getAccount(fromEVMCAddress(msg.*.recipient)).balance; + const recipient_balance = vm.env.state.getAccount(recipient_addr).balance; vm.env.state.setBalance(sender, recipient_balance + value) catch |err| switch (err) { error.OutOfMemory => @panic("OOO"), }; @@ -404,12 +406,20 @@ const EVMOneHost = struct { code.len, ); - // If the *CALL failed, we restore the previous statedb. - if (result.status_code != evmc.EVMC_SUCCESS) - vm.env.state.* = prev_statedb - else // otherwise, we free the backup and indireclty commit to the changes that happened. + if (result.status_code == evmc.EVMC_SUCCESS) { + // Free the backup and indireclty commit to the changes that happened. prev_statedb.deinit(); + // EIP-158. + if (vm.env.state.isEmpty(recipient_addr)) + vm.env.state.addTouchedAddress(recipient_addr) catch |err| switch (err) { + error.OutOfMemory => @panic("OOO"), + }; + } else { + // If the *CALL failed, we restore the previous statedb. + vm.env.state.* = prev_statedb; + } + evmclog.debug("call() depth={d} ended", .{msg.*.depth}); return result; } diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 6d0e340..76e0052 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -224,9 +224,7 @@ test "execution-spec-tests" { var it = ft.tests.value.map.iterator(); var count: usize = 0; while (it.next()) |entry| { - if (count == 1) { - try std.testing.expect(try entry.value_ptr.run(allocator)); - } + try std.testing.expect(try entry.value_ptr.run(allocator)); count += 1; if (count == 2) { diff --git a/src/state/statedb.zig b/src/state/statedb.zig index 690ea05..eb3d102 100644 --- a/src/state/statedb.zig +++ b/src/state/statedb.zig @@ -10,16 +10,22 @@ const AddressKeySet = common.AddressKeySet; const AccountData = state.AccountData; const AccountState = state.AccountState; const Bytes32 = types.Bytes32; +const ArrayList = std.ArrayList; const log = std.log.scoped(.statedb); pub const StateDB = struct { const AccountDB = std.AutoHashMap(Address, AccountState); allocator: Allocator, + // original_db contains the state of the world when the transaction starts. + // It assists in charging the right amount of gas for SSTORE. + original_db: ?AccountDB = null, + // db contains the state of the world while the current transaction is executing. + // (i.e: current call scope) db: AccountDB, - // Tx scoped variables. - original_db: ?AccountDB = null, + // Tx-scoped lists. + touched_addresses: ArrayList(Address), accessed_accounts: AddressSet, accessed_storage_keys: AddressKeySet, @@ -34,6 +40,7 @@ pub const StateDB = struct { .db = db, .accessed_accounts = AddressSet.init(allocator), .accessed_storage_keys = AddressKeySet.init(allocator), + .touched_addresses = ArrayList(Address).init(allocator), }; } @@ -55,7 +62,16 @@ pub const StateDB = struct { self.accessed_storage_keys.clearRetainingCapacity(); } - pub fn getAccountOpt(self: *StateDB, addr: Address) ?AccountData { + pub fn isEmpty(self: StateDB, addr: Address) bool { + const account = self.getAccountOpt(addr) orelse return false; + return account.nonce == 0 and account.code.len == 0 and account.balance == 0; + } + + pub fn addTouchedAddress(self: *StateDB, addr: Address) !void { + try self.touched_addresses.append(addr); + } + + pub fn getAccountOpt(self: StateDB, addr: Address) ?AccountData { const account_data = self.db.get(addr) orelse return null; return .{ .nonce = account_data.nonce, @@ -89,6 +105,10 @@ pub const StateDB = struct { pub fn setStorage(self: *StateDB, addr: Address, key: u256, value: Bytes32) !void { var account = self.db.getPtr(addr) orelse return error.AccountDoesNotExist; + if (std.mem.eql(u8, &value, &std.mem.zeroes(Bytes32))) { + _ = account.storage.remove(key); + return; + } try account.storage.put(key, value); } @@ -140,6 +160,7 @@ pub const StateDB = struct { .original_db = try self.original_db.?.clone(), .accessed_accounts = try self.accessed_accounts.clone(), .accessed_storage_keys = try self.accessed_storage_keys.clone(), + .touched_addresses = try self.touched_addresses.clone(), }; } }; From 7e44b235c3cbdfd5e52102dfd4b2b4de64befe81 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Tue, 23 Jan 2024 11:24:40 -0300 Subject: [PATCH 6/6] test: run all tests from spec Signed-off-by: Ignacio Hagopian --- src/exec-spec-tests/execspectests.zig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/exec-spec-tests/execspectests.zig b/src/exec-spec-tests/execspectests.zig index 76e0052..5a308b2 100644 --- a/src/exec-spec-tests/execspectests.zig +++ b/src/exec-spec-tests/execspectests.zig @@ -226,9 +226,5 @@ test "execution-spec-tests" { while (it.next()) |entry| { try std.testing.expect(try entry.value_ptr.run(allocator)); count += 1; - - if (count == 2) { - break; - } } }