Skip to content

Commit

Permalink
implement deserialization of newPayloadV2 request
Browse files Browse the repository at this point in the history
  • Loading branch information
gballet committed Oct 30, 2023
1 parent ecea4b6 commit a69a1eb
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 9 deletions.
12 changes: 10 additions & 2 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ pub fn build(b: *std.Build) void {
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

const mod_rlp = b.dependency("zig-rlp", .{}).module("zig-rlp");

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const mod_rlp = b.dependency("zig-rlp", .{}).module("zig-rlp");

const zap = b.dependency("zap", .{
.target = target,
.optimize = optimize,
});
const mod_zap = zap.module("zap");

const ethash = b.addStaticLibrary(.{
.name = "ethash",
.optimize = optimize,
Expand Down Expand Up @@ -95,6 +101,8 @@ pub fn build(b: *std.Build) void {
exe.linkLibrary(ethash);
exe.linkLibrary(evmone);
exe.linkLibC();
exe.addModule("zap", mod_zap);
exe.linkLibrary(zap.artifact("facil.io"));
exe.addModule("zig-rlp", mod_rlp);

// This declares intent for the executable to be installed into the
Expand Down
13 changes: 7 additions & 6 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.{
.name = "zig-rlp",
.version = "0.0.1-beta-0",
.dependencies = .{
.@"zig-rlp" = .{
.url = "https://github.com/gballet/zig-rlp/archive/refs/tags/v0.0.1-beta-0.tar.gz",
.hash = "122000e0811d6cb4758f6122b1de2d384efa32b9b2714caec2236f6b34b0529d699c",
},
},
.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",
} },
}
111 changes: 111 additions & 0 deletions src/engine_api/engine_api.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const std = @import("std");
const fmt = std.fmt;
const Allocator = std.mem.Allocator;
pub const execution_payload = @import("./execution_payload.zig");
const ExecutionPayload = execution_payload.ExecutionPayload;

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);
}

// This is an intermediate structure used to deserialize the hex strings
// from the JSON request. I have seen some zig libraries that can do this
// out of the box, but it seems that approach hasn't been merged into the
// std yet.
const ExecutionPayloadJSON = struct {
parentHash: []const u8,
feeRecipient: []const u8,
stateRoot: []const u8,
receiptsRoot: []const u8,
logsBloom: []const u8,
prevRandao: []const u8,
blockNumber: []const u8,
gasLimit: []const u8,
gasUsed: []const u8,
timestamp: []const u8,
extraData: []const u8,
baseFeePerGas: []const u8,
blockHash: []const u8,
transactions: [][]const u8,

pub fn to_execution_payload(self: *const ExecutionPayloadJSON, allocator: Allocator) !ExecutionPayload {
var parentHash: [32]u8 = undefined;
var feeRecipient: [20]u8 = undefined;
var stateRoot: [32]u8 = undefined;
var receiptsRoot: [32]u8 = undefined;
var logsBloom: [256]u8 = undefined;
var prevRandao: [32]u8 = undefined;
var blockHash: [32]u8 = undefined;

var transactions: [][]const u8 = &[_][]const u8{};
if (self.transactions.len > 0) {
transactions = try allocator.alloc([]const u8, self.transactions.len);
}

_ = try prefixedhex2hash(parentHash[0..], self.parentHash);
_ = try prefixedhex2hash(feeRecipient[0..], self.feeRecipient);
_ = try prefixedhex2hash(stateRoot[0..], self.stateRoot);
_ = try prefixedhex2hash(receiptsRoot[0..], self.receiptsRoot);
_ = try prefixedhex2hash(logsBloom[0..], self.logsBloom);
_ = try prefixedhex2hash(prevRandao[0..], self.prevRandao);
_ = try prefixedhex2hash(blockHash[0..], self.blockHash);

return ExecutionPayload{
.parentHash = parentHash,
.feeRecipient = feeRecipient,
.stateRoot = stateRoot,
.receiptsRoot = receiptsRoot,
.prevRandao = prevRandao,
.extraData = try prefixedhex2byteslice(allocator, self.extraData),
.blockHash = blockHash,
.logsBloom = logsBloom,
.blockNumber = try prefixedhex2u64(self.blockNumber),
.gasLimit = try prefixedhex2u64(self.gasLimit),
.gasUsed = try prefixedhex2u64(self.gasUsed),
.timestamp = try prefixedhex2u64(self.timestamp),
.baseFeePerGas = try prefixedhex2u64(self.baseFeePerGas),
.transactions = transactions,
.withdrawals = null,
.blobGasUsed = null,
.excessBlobGas = null,
};
}
};

pub const EngineAPIRequest = struct {
jsonrpc: []const u8,
id: u64,
method: []const u8,
params: []const ExecutionPayloadJSON,
};
64 changes: 64 additions & 0 deletions src/engine_api/execution_payload.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const std = @import("std");
const types = @import("../types/types.zig");

pub const ExecutionPayload = struct {
parentHash: types.Hash32,
feeRecipient: types.Address,
stateRoot: types.Hash32,
receiptsRoot: types.Hash32,
logsBloom: [256]u8,
prevRandao: types.Hash32,
blockNumber: u64,
gasLimit: u64,
gasUsed: u64,
timestamp: u64,
extraData: []const u8,
baseFeePerGas: u256,
blockHash: types.Hash32,
transactions: [][]const u8,

withdrawals: ?[]types.Withdrawal,
blobGasUsed: ?u64,
excessBlobGas: ?u64,
// executionWitness : ?types.ExecutionWitness,

pub fn to_block(self: *const ExecutionPayload) types.Block {
return types.Block{
.header = types.BlockHeader{
.parent_hash = self.parentHash,
.uncle_hash = types.empty_uncle_hash,
.fee_recipient = self.feeRecipient,
.state_root = self.stateRoot,
.receipts_root = self.receiptsRoot,
.logs_bloom = self.logsBloom,
.prev_randao = self.prevRandao,
.block_number = @intCast(self.blockNumber),
.gas_limit = @intCast(self.gasLimit),
.gas_used = self.gasUsed,
.timestamp = @intCast(self.timestamp),
.extra_data = self.extraData,
.base_fee_per_gas = self.baseFeePerGas,
.transactions_root = [_]u8{0} ** 32,
.mix_hash = 0,
.nonce = [_]u8{0} ** 8,
.blob_gas_used = null,
.withdrawals_root = null,
.excess_blob_gas = null,
},
// .blockHash = self.blockHash,
// .transactions = self.transactions,
// .withdrawals = self.withdrawals,
};
}
};

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();
}
57 changes: 56 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,50 @@ const VM = @import("vm/vm.zig").VM;
const StateDB = @import("vm/statedb.zig");
const Block = types.Block;
const Transaction = types.Transaction;
const zap = @import("zap");
const engine_api = @import("engine_api/engine_api.zig");
const json = std.json;

var allocator: std.mem.Allocator = undefined;

fn engineAPIHandler(r: zap.SimpleRequest) void {
if (r.body == null) {
r.setStatus(.bad_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.? });
r.setStatus(.bad_request);
return;
};
defer payload.deinit();

if (std.mem.eql(u8, payload.value.method, "engine_newPayloadV2")) {
const execution_payload_json = payload.value.params[0];
const execution_payload = execution_payload_json.to_execution_payload(allocator) catch |err| {
std.log.warn("error parsing execution payload: {}", .{err});
r.setStatus(.bad_request);
return;
};
engine_api.execution_payload.newPayloadV2Handler(&execution_payload) catch |err| {
std.log.err("error handling newPayloadV2: {}", .{err});
r.setStatus(.internal_server_error);
return;
};
r.setStatus(.ok);
} else {
r.setStatus(.internal_server_error);
}
r.setContentType(.JSON) catch |err| {
std.log.err("error setting content type: {}", .{err});
r.setStatus(.internal_server_error);
return;
};
}

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var allocator = gpa.allocator();
allocator = gpa.allocator();

std.log.info("Welcome to phant! 🐘", .{});

Expand All @@ -32,6 +72,9 @@ pub fn main() !void {
.mix_hash = 0,
.nonce = [_]u8{0} ** 8,
.base_fee_per_gas = 10,
.withdrawals_root = null,
.blob_gas_used = null,
.excess_blob_gas = null,
},
};

Expand Down Expand Up @@ -68,6 +111,18 @@ pub fn main() !void {
std.log.err("error executing transaction: {}", .{err});
return;
};

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,
});
}

test "tests" {
Expand Down
5 changes: 5 additions & 0 deletions src/types/block.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub const Block = struct {
}
};

pub const empty_uncle_hash: types.Hash32 = [_]u8{ 29, 204, 77, 232, 222, 199, 93, 122, 171, 133, 181, 103, 182, 204, 212, 26, 211, 18, 69, 27, 148, 138, 116, 19, 240, 161, 66, 253, 64, 212, 147, 71 };

pub const Header = struct {
parent_hash: types.Hash32,
uncle_hash: types.Hash32,
Expand All @@ -36,6 +38,9 @@ pub const Header = struct {
mix_hash: u256,
nonce: [8]u8,
base_fee_per_gas: u256,
withdrawals_root: ?types.Hash32,
blob_gas_used: ?u64,
excess_blob_gas: ?u64,
};

var test_allocator = std.testing.allocator;
Expand Down
3 changes: 3 additions & 0 deletions src/types/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ pub const AccountState = @import("account_state.zig");
pub const Transaction = @import("transaction.zig");
pub const Block = @import("block.zig").Block;
pub const BlockHeader = @import("block.zig").Header;
pub const Withdrawal = @import("withdrawal.zig");

pub const empty_uncle_hash = @import("block.zig").empty_uncle_hash;
9 changes: 9 additions & 0 deletions src/types/withdrawal.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const std = @import("std");
const types = @import("types.zig");

pub const Withdrawal = struct {
index: u64,
validator: u64,
address: types.Address,
amount: u64,
};

0 comments on commit a69a1eb

Please sign in to comment.