-
Notifications
You must be signed in to change notification settings - Fork 3
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
implement deserialization of newPayloadV2 request #12
Conversation
const zap = b.dependency("zap", .{ | ||
.target = target, | ||
.optimize = optimize, | ||
}); | ||
const mod_zap = zap.module("zap"); |
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.
zap
is a full web framework. I looked at other, simpler libs, but they either didn't work or weren't offering module support.
src/engine_api/engine_api.zig
Outdated
fn prefixedhex2hash(dst: []u8, src: []const u8) !void { | ||
if (src.len < 2 or src.len % 2 != 0) { | ||
return error.InvalidInput; | ||
} | ||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
if (src[skip0x..].len != 2 * dst.len) { | ||
return error.InvalidOutputLength; | ||
} | ||
_ = try fmt.hexToBytes(dst[0..], src[skip0x..]); | ||
} | ||
|
||
fn prefixedhex2byteslice(allocator: Allocator, src: []const u8) ![]u8 { | ||
// TODO catch the 0x0 corner case | ||
if (src.len < 2 or src.len % 2 != 0) { | ||
return error.InvalidInput; | ||
} | ||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
var dst: []u8 = try allocator.alloc(u8, src[skip0x..].len / 2); | ||
|
||
_ = try fmt.hexToBytes(dst[0..], src[skip0x..]); | ||
|
||
return dst; | ||
} | ||
|
||
fn prefixedhex2u64(src: []const u8) !u64 { | ||
// execution engine integers can be odd-length :facepalm: | ||
if (src.len < 3) { | ||
return error.InvalidInput; | ||
} | ||
|
||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
|
||
return std.fmt.parseInt(u64, src[skip0x..], 16); | ||
} |
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 want to try to turn this into a single hex2bin
function but that will be the role of a subsequent PR
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.
Worth also consider if these kind of helper funcs are worth putting in a separate package, since sounds can be useful for other things.
.block_number = @intCast(self.blockNumber), | ||
.gas_limit = @intCast(self.gasLimit), | ||
.gas_used = self.gasUsed, | ||
.timestamp = @intCast(self.timestamp), |
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 some reason, your Block
type uses i64
for these fields. I assume that it's imposed by evmone
, I worked around it for now.
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.
src/engine_api/execution_payload.zig
Outdated
pub fn newPayloadV2Handler(params: *const ExecutionPayload) !void { | ||
// TODO reconstruct the proof from the (currently undefined) execution witness | ||
// and verify it. Then execute the block and return the result. | ||
// vm.run_block(params.to_block(), params.transactions); | ||
|
||
// But so far, just print the content of the payload | ||
std.log.info("newPayloadV2Handler: {any}", .{params}); | ||
|
||
_ = params.to_block(); | ||
} |
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.
currently this simply checks that I can deserialize the block. I will expand that when I am done updating zig-verkle.
const engine_api = @import("engine_api/engine_api.zig"); | ||
const json = std.json; | ||
|
||
var allocator: std.mem.Allocator = undefined; |
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 made the allocator public as I needed it in my handler.
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.
Woah, OK, that's a bit ugly but I can't see any other workaround considering the forced handler signature.
At some point we can put these handler stuff in a separate file, and make the global allocator scoped there.
Still is a bit ugly that it's implicit that somebody has to initialize that allocator before using things.
var listener = zap.SimpleHttpListener.init(.{ | ||
.port = 8551, | ||
.on_request = engineAPIHandler, | ||
.log = true, | ||
}); | ||
try listener.listen(); | ||
std.log.info("Listening on 8551", .{}); | ||
zap.start(.{ | ||
.threads = 1, | ||
.workers = 1, | ||
}); |
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 added that at the end of the main
function, but I could also move it to a different binary if you prefer.
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 think should be fine; we can spend some time in the future refactoring stuff after we introduce a bunch of features so things don't get out of control.
|
||
var allocator: std.mem.Allocator = undefined; | ||
|
||
fn engineAPIHandler(r: zap.SimpleRequest) void { |
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 return type here is void
and not !void
because you have to report errors as HTTP error codes. Not what I'm used to if you compare it to e.g. rails in dev mode, but why not.
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.
Go is the same:
func handler(w http.ResponseWriter, r *http.Request){}
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.
LGTM. I left some comments/questions for your consideration.
build.zig.zon
Outdated
.dependencies = .{ .@"zig-rlp" = .{ | ||
.url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-0.tar.gz", | ||
.hash = "122000e0811d6cb4758f6122b1de2d384efa32b9b2714caec2236f6b34b0529d699c", | ||
}, .zap = .{ | ||
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.14-pre.tar.gz", | ||
.hash = "122067d12bc7abb93f7ce623f61b94cadfdb180cef12da6559d092e6b374202acda3", | ||
} }, |
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.
Formatting nit:
.{
.@"zig-rlp" = .{
.url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-0.tar.gz",
.hash = "122000e0811d6cb4758f6122b1de2d384efa32b9b2714caec2236f6b34b0529d699c",
},
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.14-pre.tar.gz",
.hash = "122067d12bc7abb93f7ce623f61b94cadfdb180cef12da6559d092e6b374202acda3",
},
},
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.
yeah my zig formatter is broken in visual studio for some reason, thankfully helix comes to the rescue but I have to manually save the file each time.
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.
also the module name was plain wrong
src/engine_api/engine_api.zig
Outdated
if (src[skip0x..].len != 2 * dst.len) { | ||
return error.InvalidOutputLength; | ||
} | ||
_ = try fmt.hexToBytes(dst[0..], src[skip0x..]); |
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.
Nit: the dst[0..]
is not needed, dst
alone should be fine.
src/engine_api/engine_api.zig
Outdated
fn prefixedhex2hash(dst: []u8, src: []const u8) !void { | ||
if (src.len < 2 or src.len % 2 != 0) { | ||
return error.InvalidInput; | ||
} | ||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
if (src[skip0x..].len != 2 * dst.len) { | ||
return error.InvalidOutputLength; | ||
} | ||
_ = try fmt.hexToBytes(dst[0..], src[skip0x..]); | ||
} | ||
|
||
fn prefixedhex2byteslice(allocator: Allocator, src: []const u8) ![]u8 { | ||
// TODO catch the 0x0 corner case | ||
if (src.len < 2 or src.len % 2 != 0) { | ||
return error.InvalidInput; | ||
} | ||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
var dst: []u8 = try allocator.alloc(u8, src[skip0x..].len / 2); | ||
|
||
_ = try fmt.hexToBytes(dst[0..], src[skip0x..]); | ||
|
||
return dst; | ||
} | ||
|
||
fn prefixedhex2u64(src: []const u8) !u64 { | ||
// execution engine integers can be odd-length :facepalm: | ||
if (src.len < 3) { | ||
return error.InvalidInput; | ||
} | ||
|
||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
|
||
return std.fmt.parseInt(u64, src[skip0x..], 16); | ||
} |
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.
Worth also consider if these kind of helper funcs are worth putting in a separate package, since sounds can be useful for other things.
src/engine_api/engine_api.zig
Outdated
return error.InvalidInput; | ||
} | ||
var skip0x: usize = if (src[1] == 'X' or src[1] == 'x') 2 else 0; | ||
var dst: []u8 = try allocator.alloc(u8, src[skip0x..].len / 2); |
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 know that you're planning to refactor this, but this could do this allocation and call prefixedhex2hash
.
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.
added a TODO to remind me to do that, indeed I would rather do it when I refactor.
.block_number = @intCast(self.blockNumber), | ||
.gas_limit = @intCast(self.gasLimit), | ||
.gas_used = self.gasUsed, | ||
.timestamp = @intCast(self.timestamp), |
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.
var listener = zap.SimpleHttpListener.init(.{ | ||
.port = 8551, | ||
.on_request = engineAPIHandler, | ||
.log = true, | ||
}); | ||
try listener.listen(); | ||
std.log.info("Listening on 8551", .{}); | ||
zap.start(.{ | ||
.threads = 1, | ||
.workers = 1, | ||
}); |
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 think should be fine; we can spend some time in the future refactoring stuff after we introduce a bunch of features so things don't get out of control.
const engine_api = @import("engine_api/engine_api.zig"); | ||
const json = std.json; | ||
|
||
var allocator: std.mem.Allocator = undefined; |
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.
Woah, OK, that's a bit ugly but I can't see any other workaround considering the forced handler signature.
At some point we can put these handler stuff in a separate file, and make the global allocator scoped there.
Still is a bit ugly that it's implicit that somebody has to initialize that allocator before using things.
|
||
var allocator: std.mem.Allocator = undefined; | ||
|
||
fn engineAPIHandler(r: zap.SimpleRequest) void { |
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.
Go is the same:
func handler(w http.ResponseWriter, r *http.Request){}
return; | ||
} | ||
const payload = json.parseFromSlice(engine_api.EngineAPIRequest, allocator, r.body.?, .{ .ignore_unknown_fields = true }) catch |err| { | ||
std.log.err("error parsing json: {} (body={s})", .{ err, r.body.? }); |
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 think at some point we have to work on some log package.
@gballet, there's a failing test. I suspect might be related to added fields in the |
right, it seems that zig-rlp doesn't support optional fields the way they should work. |
// TODO reconstruct the proof from the (currently undefined) execution witness | ||
// and verify it. Then execute the block and return the result. | ||
// vm.run_block(params.to_block(), params.transactions); | ||
|
||
// But so far, just print the content of the payload | ||
std.log.info("newPayloadV2Handler: {any}", .{params}); | ||
|
||
_ = params.to_block(); | ||
var block = params.to_block(); |
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.
L63 and L64 look quite weird to me. How do you know that params.to_block()
allocator is the same that we received in this method?
These kind of patterns should probably make block
have the right allocator internally and make L64 be block.deinit()
.
This potential mismatch between the received allocation, and whatever params.to_block()
allocator was used, is quite confusing and hard to know if it's doing the right thing.
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.
There are several issues with this: first of all, the data that is freed has been allocated long before, with params
- but a reference to the allocator could be copied from params
in to_block
. The other problem, however, is that the block can be serialized to RLP, which would make no sense if it holds an allocator.
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.
well actually in ssz I offer the possibility to write a custom encoder, maybe I could do the same here, and so skip the allocator if it exists.
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.
see https://github.com/gballet/phant/pull/1 for the current state
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 think this confusion is mostly making it clear in the to_block()
method comments who is the owner of params
after that call.
It looks like block
is now the owner of param
after to_block()
or something like that? But I'm not sure what it would mean if you do two to_block()
calls -- wouldn't that mean that you risk doing a double-free?
To be sure of course you can do a copy in block
of everything it needs so both things aren't tied together and solve the problem at the cost of a copy.
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.
To be clear, I'm not sure what is the right solution.
But here's when ownership gets interesting and it's good to find good patterns to avoid remembering "who owns what" of if constructing more than one to_block()
is safe or not.
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.
See https://github.com/gballet/phant/pull/1#discussion_r1417657311 for some extra comment
Replaced by #2, closing. |
That was the wrong PR to close 🤦♂️ |
51ca3ac
to
d29ab6a
Compare
d29ab6a
to
761efd0
Compare
This PR adds an RPC server (based on a totally overkill library, but that's the only one I could find that supports modules and works). It will decode the execution data payload and turn it into a block.
I have left the execution witness out for the moment.