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 Dec 8, 2023
1 parent ac737bc commit 761efd0
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 52 deletions.
7 changes: 7 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ pub fn build(b: *std.Build) void {
const mod_rlp = b.dependency("zig-rlp", .{ .target = target, .optimize = optimize }).module("rlp");
const depSecp256k1 = b.dependency("zig-eth-secp256k1", .{ .target = target, .optimize = optimize });
const mod_secp256k1 = depSecp256k1.module("zig-eth-secp256k1");
const zap = b.dependency("zap", .{
.target = target,
.optimize = optimize,
});
const mod_zap = zap.module("zap");

const ethash = b.addStaticLibrary(.{
.name = "ethash",
Expand Down Expand Up @@ -100,6 +105,8 @@ pub fn build(b: *std.Build) void {
exe.addModule("zig-rlp", mod_rlp);
exe.linkLibrary(depSecp256k1.artifact("secp256k1"));
exe.addModule("zig-eth-secp256k1", mod_secp256k1);
exe.addModule("zap", mod_zap);
exe.linkLibrary(zap.artifact("facil.io"));

// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
Expand Down
6 changes: 5 additions & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.{
.name = "zig-rlp",
.name = "phant",
.version = "0.0.1-beta-0",
.dependencies = .{
.@"zig-rlp" = .{
Expand All @@ -10,5 +10,9 @@
.url = "https://github.com/jsign/zig-eth-secp256k1/archive/refs/tags/v0.1.0-beta-3.tar.gz",
.hash = "1220fcf062f8ee89b343e1588ac3cc002f37ee3f72841dd7f9493d9c09acad7915a3",
},
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.1.14-pre.tar.gz",
.hash = "122067d12bc7abb93f7ce623f61b94cadfdb180cef12da6559d092e6b374202acda3",
}
},
}
4 changes: 4 additions & 0 deletions src/common/common.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const hexutils = @import("./hexutils.zig");
pub const prefixedhex2hash = hexutils.prefixedhex2hash;
pub const prefixedhex2byteslice = hexutils.prefixedhex2byteslice;
pub const prefixedhex2u64 = hexutils.prefixedhex2u64;
45 changes: 45 additions & 0 deletions src/common/hexutils.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const std = @import("std");
const fmt = std.fmt;
const Allocator = std.mem.Allocator;

// This function turns an optionally '0x'-prefixed hex string
// to a types.Hash32
pub 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, src[skip0x..]);
}

// This function turns an optionally '0x'-prefixed hex string
// to a byte slice
pub 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;
// TODO when refactoring, ensure the alloc is also made in the equivalent for prefixedhex2hash
var dst: []u8 = try allocator.alloc(u8, src[skip0x..].len / 2);

_ = try fmt.hexToBytes(dst[0..], src[skip0x..]);

return dst;
}

// This function turns an optionally '0x'-prefixed hex string
// to a u64
pub 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);
}
103 changes: 103 additions & 0 deletions src/engine_api/engine_api.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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;
const common = @import("../common/common.zig");

// 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.
// Because the JSON libary won't be able to deserialize a union unless
// the union is explicitly named, all possible object keys are declared in
// this object, and the caller is responsible for sifting through them by
// calling any of the `to_*` method, based on the context.
const AllPossibleExecutionParams = 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 AllPossibleExecutionParams, allocator: Allocator) !ExecutionPayload {
var transactions: [][]const u8 = &[_][]const u8{};
if (self.transactions.len > 0) {
transactions = try allocator.alloc([]const u8, self.transactions.len);
for (self.transactions, 0..) |tx, txidx| {
transactions[txidx] = try common.prefixedhex2byteslice(allocator, tx);
}
}

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

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

return ret;
}
};

pub const EngineAPIRequest = struct {
jsonrpc: []const u8,
id: u64,
method: []const u8,
params: []const AllPossibleExecutionParams,
};

test "deserialize sample engine_newPayloadV2" {
const json = std.json;
const expect = std.testing.expect;

const filePath = "./src/engine_api/test_req.json";

const file = try std.fs.cwd().openFile(filePath, .{});
defer file.close();

const stat = try file.stat();

var buffer = try std.testing.allocator.alloc(u8, stat.size);
defer std.testing.allocator.free(buffer);
_ = try file.readAll(buffer);

const payload = try json.parseFromSlice(EngineAPIRequest, std.testing.allocator, buffer, .{ .ignore_unknown_fields = true });
defer payload.deinit();

try expect(std.mem.eql(u8, payload.value.method, "engine_newPayloadV2"));
const execution_payload_json = payload.value.params[0];
var ep = try execution_payload_json.to_execution_payload(std.testing.allocator);
try execution_payload.newPayloadV2Handler(&ep, std.testing.allocator);
}
75 changes: 75 additions & 0 deletions src/engine_api/execution_payload.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const std = @import("std");
const types = @import("../types/types.zig");
const Allocator = std.mem.Allocator;

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,

allocator: Allocator,

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 deinit(self: *ExecutionPayload, allocator: std.mem.Allocator) void {
if (self.extraData.len > 0) {
allocator.free(self.extraData);
}
}
};

pub fn newPayloadV2Handler(params: *ExecutionPayload, allocator: std.mem.Allocator) !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});

var block = params.to_block();
std.debug.print("block number={}\n", .{block.header.block_number});
params.deinit(allocator);
}
21 changes: 21 additions & 0 deletions src/engine_api/test_req.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"jsonrpc": "2.0",
"id": 60282,
"method": "engine_newPayloadV2",
"params": [
{
"parentHash": "0x522930864dc2f6569f4d4b92f40001fe6752727ff53db5cb5c5ffd51d3cc53e4",
"feeRecipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134",
"stateRoot": "0x27bb13dc19c41288dd29236b27baac49338c466b8c0eb44fb4c0c0083dfb214a",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x8ad449de1358704350f28598677be5c4cd609b42d31653b967f615b47ad1e63e",
"blockNumber": "0x112b8",
"gasLimit": "0x1c9c380",
"gasUsed": "0x0",
"timestamp": "0x65361030",
"extraData": "0xd983010c01846765746889676f312e32302e3130856c696e7578",
"baseFeePerGas": "0x7",
"blockHash": "0x56f5dba155cc4317aaa479f26f135dd543d5977026e83a9bb6b0ae895b4c7a10",
"transactions": []
}]}
2 changes: 1 addition & 1 deletion src/exec-spec-tests/execspectests.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const std = @import("std");
const rlp = @import("zig-rlp");
const rlp = @import("rlp");
const Allocator = std.mem.Allocator;
const types = @import("../types/types.zig");
const Address = types.Address;
Expand Down
58 changes: 57 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,50 @@ const StateDB = @import("vm/statedb.zig");
const Block = types.Block;
const Transaction = types.Transaction;
const TxnSigner = @import("signer/signer.zig").TxnSigner;
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];
var 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, allocator) 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! 🐘", .{});
const txn_signer = try TxnSigner.init();
Expand All @@ -35,6 +75,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 @@ -81,6 +124,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 All @@ -91,4 +146,5 @@ test "tests" {
_ = @import("types/types.zig");
_ = @import("vm/vm.zig");
_ = @import("crypto/ecdsa.zig");
_ = @import("engine_api/engine_api.zig");
}
Loading

0 comments on commit 761efd0

Please sign in to comment.