-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EVM integration progress #4
Conversation
92c765b
to
9108033
Compare
This is sufficient since evmone.h already includes evmc.h. Also, this is necessary since doing @cImport("evmc.h") would duplicate types creating weird compiler errors claiming that two types are different when that isn't the case.
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
…ll implementation Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
Signed-off-by: Ignacio Hagopian <[email protected]>
9108033
to
d8eed51
Compare
unit_tests.addIncludePath(LazyPath{ .path = "evmone" }); | ||
unit_tests.addIncludePath(LazyPath{ .path = "evmone/include/evmone" }); | ||
unit_tests.addIncludePath(LazyPath{ .path = "evmone/evmc/include" }); | ||
if (target.getCpuArch() == .x86_64) { | ||
// On x86_64, some functions are missing from the static library, | ||
// so we define dummy functions to make sure that it compiles. | ||
unit_tests.addCSourceFile(.{ | ||
.file = .{ .path = "src/glue.c" }, | ||
.flags = &cflags, | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gballet, I added an analogous configuration for unit_tests
executable.
At some point we can "unify" this since it will be always something that should be configured both for exe
(main) and unit_test
(tests).
Not a big deal for now.
for (self.blocks) |encoded_block| { | ||
var out = try allocator.alloc(u8, encoded_block.rlp.len / 2); | ||
defer allocator.free(out); | ||
const rlp_bytes = try std.fmt.hexToBytes(out, encoded_block.rlp[2..]); | ||
|
||
var txns = try allocator.alloc(Transaction, block.transactions.len); | ||
const block = try Block.init(rlp_bytes); | ||
|
||
var txns = try allocator.alloc(Transaction, encoded_block.transactions.len); | ||
defer allocator.free(txns); | ||
for (block.transactions, 0..) |tx_hex, i| { | ||
for (encoded_block.transactions, 0..) |tx_hex, i| { | ||
txns[i] = try tx_hex.to_vm_transaction(allocator); | ||
} | ||
|
||
evm.run_txns(txns); | ||
try evm.run_block(block, txns); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Context: we're in the exec-spect-test runner)
Now we RLP-decode the block RLP, and run the block with our VM.
Note in L85 now the API is basically: "Please, run this block".
Still missing returning the result, but we'll get there.
// 3. Verify that the post state matches what the fixture `postState` claims is true. | ||
var it = self.postState.map.iterator(); | ||
while (it.next()) |entry| { | ||
var account_state = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); | ||
_ = account_state; | ||
var exp_account_state: AccountState = try entry.value_ptr.*.to_vm_accountstate(allocator, entry.key_ptr.*); | ||
std.debug.print("checking account state: {s}\n", .{std.fmt.fmtSliceHexLower(&exp_account_state.addr)}); | ||
const got_account_state = try db.get(exp_account_state.addr); | ||
if (!std.mem.eql(u8, &got_account_state.addr, &exp_account_state.addr)) { | ||
return error.post_state_addr_mismatch; | ||
} | ||
if (got_account_state.nonce != exp_account_state.nonce) { | ||
log.err("expected nonce {d} but got {d}", .{ exp_account_state.nonce, got_account_state.nonce }); | ||
return error.post_state_nonce_mismatch; | ||
} | ||
if (got_account_state.balance != exp_account_state.balance) { | ||
log.err("expected balance {d} but got {d}", .{ exp_account_state.balance, got_account_state.balance }); | ||
return error.post_state_balance_mismatch; | ||
} | ||
if (got_account_state.storage.count() != exp_account_state.storage.count()) { | ||
return error.post_state_storage_size_mismatch; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I added code to verify that executing the block had the expected side-effects as described by the test.
The test has a postState
fields, so you can validate that executing the block changed the account state.
For example, see here.
} | ||
// TODO(jsign): verify gas used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhm, see now that I forgot to add this which should work in this PR already... I've verified this manually.
I'll eventually do it here.
try std.testing.expect(try entry.value_ptr.*.run(test_allocator)); | ||
count += 1; | ||
|
||
// TODO: Only run the first test for now. Then we can enable all and continue with the integration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now only one the first test in the json file; we can keep bumping this and making the EVM impl more complete by passing more and more tests.
if (remaining_gas.* < charge) { | ||
return error.OutOfGas; | ||
} | ||
remaining_gas.* -= charge; | ||
} | ||
|
||
// ### EVMC Host Interface ### | ||
|
||
fn get_tx_context(ctx: ?*evmc.struct_evmc_host_context) callconv(.C) evmc.struct_evmc_tx_context { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already implemented this EVMC API... EVMOne calls this to get some info while executing opcodes.
For example, this first exec-spec-test calls the COINBASE
opcode, and EVMOne call us on this method to get what is the address. I'd guess in many other cases it will do the same.
fn set_storage( | ||
ctx: ?*evmc.struct_evmc_host_context, | ||
addr: [*c]const evmc.evmc_address, | ||
key: [*c]const evmc.evmc_bytes32, | ||
value: [*c]const evmc.evmc_bytes32, | ||
) callconv(.C) evmc.enum_evmc_storage_status { | ||
const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?))); | ||
|
||
const skey = std.mem.readIntSlice(u256, &key.*.bytes, std.builtin.Endian.Big); | ||
const svalue = std.mem.readIntSlice(u256, &value.*.bytes, std.builtin.Endian.Big); | ||
|
||
vm.statedb.set_storage(addr.*.bytes, skey, svalue) catch unreachable; // TODO(jsign): manage catch. | ||
|
||
return evmc.EVMC_STORAGE_ADDED; // TODO(jsign): fix | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had also to do some minimal implementation of this one, since this first test saves some result to storage.
} | ||
|
||
fn call(ctx: ?*evmc.struct_evmc_host_context, msg: [*c]const evmc.struct_evmc_message) callconv(.C) evmc.struct_evmc_result { | ||
fn call( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This first test makes to internal contract calls, which is interesting... so I had to do some reasonable implementation of this.
// Create the new context to be used to do the call. | ||
vm.exec_context = ExecutionContext{ .address = util.from_evmc_address(msg.*.recipient) }; | ||
// Check if the target address is a contract, and do the appropiate call. | ||
const recipient_account = vm.statedb.get(util.from_evmc_address(msg.*.code_address)) catch unreachable; // TODO(jsign): fix this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for catch unreachable
is just a quick way to ignore errors that can only happen due to the allocator not being able to allocate memory.
Why is that needed for get
is just that if the account doesn't exists, then it's created. Creating it requires a storage : AutoHashMap(u256, u256)
to be created which requires an allocator.
I'm not convinced about that... probably I'd like to make that storage optional and avoid this failure case. (i.e: get
can't fail).
But.. that's a story for another day.
// Restore previous context after call() returned. | ||
vm.context.?.execution = prev_exec_context; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Showing a bit how "restoring the execution context" works for calls(...)
.
Kind of "stack based" context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interesting changes
This PR made progress in implementing a workable EVM.
We reached the point that the first exec-spec-test passes.
There're still many caveats and TODOs, since having a full implementation means a careful impl of:
Getting one working is good news.
The
exec-spec-tests
test is only running the first test in that file. We can bump this number to allow further tests to run, and keep improving the implementation so more tests pass. But that will work for the future.Sample of runs:
(
main.zig
doesn't have any "interesting" stuff, but it works).Running tests is more interesting:
There are many things in this PR, but I'll create some comments if anyone is curious.