From 4f67d04a9c59db1698d1c9200ef10f1cea059c9b Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:44:25 +0200 Subject: [PATCH] Add support for chainspec files (#25) * add support for chainspecs * rework the loading of config * populate the fork list display * handle missing fields in chainspec * remove incorrect comment * remove unused allcator parameter for newpayloadv2handler * rename Config to ChainConfig --- README.md | 23 +++++++- build.zig | 2 + src/chainspecs/mainnet.json | 22 +++++++ src/chainspecs/sepolia.json | 20 +++++++ src/config/config.zig | 85 ++++++++++++++++++++++++++++ src/engine_api/engine_api.zig | 3 +- src/engine_api/execution_payload.zig | 12 ++-- src/lib.zig | 1 + src/main.zig | 28 ++++++++- 9 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 src/chainspecs/mainnet.json create mode 100644 src/chainspecs/sepolia.json diff --git a/README.md b/README.md index ddeba07..5ba54af 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,36 @@ After pulling this repo for the first time, do: 1. `git submodule init` 2. `git submodule update -v` -## Run +## Running tests You can run _something_ here with: -- `zig build run`: which runs `main.zig`, which is a simple playground for phant-EVMOne intregration. - `zig build test`: it attempts to run a particular [exec-spec-tests](https://github.com/ethereum/execution-spec-tests) fixture `exec-spec/tests/fixtures/exec-spec/fixture.json`, which is an official spec test fixture. This does a bunch of decoding into EVM types, creates a `statedb` with pre-state (and post-state for posterior check), and tries to execute the block transactions. Now, everything is quite messy until we have a passing test for this official exec-spec-test fixture. Probably after that, we can refactor a bit the code to create proper modules and define some clear path forward in each module. +## Running the client + +To run the (wip) client, type + +``` +zig build run +``` + +By default, the network is mainnet. You can run the sepolia chain configuration by using the `network_id` option: + +``` +zig build run -- --network_id Sepolia +``` + +Any other network requires its own _chainspec_ file. You can run a custom chainspec by using the `chainspec` option: + +``` +zig build run -- --chainspec .json +``` + ## License MIT diff --git a/build.zig b/build.zig index 75a7665..638b1cb 100644 --- a/build.zig +++ b/build.zig @@ -111,6 +111,7 @@ pub fn build(b: *std.Build) void { exe.root_module.addImport("zig-eth-secp256k1", mod_secp256k1); exe.root_module.addImport("httpz", mod_httpz); exe.root_module.addImport("simargs", zigcli.module("simargs")); + exe.root_module.addImport("pretty-table", zigcli.module("pretty-table")); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -163,6 +164,7 @@ pub fn build(b: *std.Build) void { unit_tests.root_module.addImport("zig-rlp", dep_rlp.module("zig-rlp")); unit_tests.linkLibrary(depSecp256k1.artifact("secp256k1")); unit_tests.root_module.addImport("zig-eth-secp256k1", mod_secp256k1); + unit_tests.root_module.addImport("pretty-table", zigcli.module("pretty-table")); const run_unit_tests = b.addRunArtifact(unit_tests); run_unit_tests.has_side_effects = true; diff --git a/src/chainspecs/mainnet.json b/src/chainspecs/mainnet.json new file mode 100644 index 0000000..2de0e0b --- /dev/null +++ b/src/chainspecs/mainnet.json @@ -0,0 +1,22 @@ +{ + "ChainName": "mainnet", + "chainId": 1, + "consensus": "ethash", + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "eip150Block": 2463000, + "eip155Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "terminalTotalDifficulty": 58750000000000000000000, + "terminalTotalDifficultyPassed": true, + "shanghaiTime": 1681338455, + "ethash": {} +} diff --git a/src/chainspecs/sepolia.json b/src/chainspecs/sepolia.json new file mode 100644 index 0000000..9c9d424 --- /dev/null +++ b/src/chainspecs/sepolia.json @@ -0,0 +1,20 @@ +{ + "ChainName": "sepolia", + "chainId": 11155111, + "consensus": "ethash", + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "terminalTotalDifficulty": 17000000000000000, + "terminalTotalDifficultyPassed": true, + "mergeNetsplitBlock": 1735371, + "shanghaiTime": 1677557088, + "ethash": {} +} diff --git a/src/config/config.zig b/src/config/config.zig index 3cb10fe..69bfb81 100644 --- a/src/config/config.zig +++ b/src/config/config.zig @@ -1,3 +1,10 @@ +const std = @import("std"); +const json = std.json; +const Allocator = std.mem.Allocator; +const ptable = @import("pretty-table"); +const Table = ptable.Table; +const String = ptable.String; + pub const ChainId = enum(u64) { SpecTest = 0, Mainnet = 1, @@ -6,3 +13,81 @@ pub const ChainId = enum(u64) { Kaustinen = 69420, Sepolia = 11155111, }; + +pub const ChainConfig = struct { + ChainName: []const u8, + chainId: u64 = @intFromEnum(ChainId.Mainnet), + homesteadBlock: ?u64 = null, + daoForkBlock: ?u64 = null, + eip150Block: ?u64 = null, + eip155Block: ?u64 = null, + byzantiumBlock: ?u64 = null, + constantinopleBlock: ?u64 = null, + petersburgBlock: ?u64 = null, + istanbulBlock: ?u64 = null, + muirGlacierBlock: ?u64 = null, + berlinBlock: ?u64 = null, + londonBlock: ?u64 = null, + arrowGlacierBlock: ?u64 = null, + grayGlacierBlock: ?u64 = null, + terminalTotalDifficulty: ?u256 = null, + terminalTotalDifficultyPassed: ?bool = null, + shanghaiTime: ?u64 = null, + cancunTime: ?u64 = null, + pragueTime: ?u64 = null, + osakaTime: ?u64 = null, + + const Self = @This(); + + pub fn fromChainId(id: ChainId, allocator: Allocator) !Self { + return switch (id) { + .Mainnet => fromChainSpec(mainnetChainSpec, allocator), + .Sepolia => fromChainSpec(sepoliaChainSpec, allocator), + .Goerli => error.DeprecatedNetwork, + else => error.UnsupportedNetwork, + }; + } + + pub fn fromChainSpec(chainspec: []const u8, allocator: Allocator) !Self { + var config: ChainConfig = undefined; + const options = json.ParseOptions{ + .ignore_unknown_fields = true, + .allocate = .alloc_if_needed, + }; + + config = (try json.parseFromSlice(ChainConfig, allocator, chainspec, options)).value; + return config; + } + + pub fn default(allocator: Allocator) !Self { + return fromChainSpec(mainnetChainSpec, allocator); + } + + pub fn dump(self: *Self, allocator: Allocator) !void { + const table = Table(3){ + .header = [_]String{ "Fork", "Block number", "Timestamp" }, + .rows = &[_][3]String{ + .{ "Homestead", if (self.homesteadBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.homesteadBlock}) else "inactive", "na" }, + .{ "DAO", if (self.homesteadBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.daoForkBlock}) else "inactive", "na" }, + .{ "Byzantium", if (self.byzantiumBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.byzantiumBlock}) else "inactive", "na" }, + .{ "Constantinople", if (self.constantinopleBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.constantinopleBlock}) else "inactive", "na" }, + .{ "Petersburg", if (self.petersburgBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.petersburgBlock}) else "inactive", "na" }, + .{ "Istanbul", if (self.istanbulBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.istanbulBlock}) else "inactive", "na" }, + .{ "Muir Glacier", if (self.muirGlacierBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.muirGlacierBlock}) else "inactive", "na" }, + .{ "Berlin", if (self.berlinBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.berlinBlock}) else "inactive", "na" }, + .{ "London", if (self.londonBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.londonBlock}) else "inactive", "na" }, + .{ "Arrow Glacier", if (self.arrowGlacierBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.arrowGlacierBlock}) else "inactive", "na" }, + .{ "Gray Glacier", if (self.grayGlacierBlock != null) try std.fmt.allocPrint(allocator, "{any}", .{self.grayGlacierBlock}) else "inactive", "na" }, + .{ "Shanghai", "na", if (self.shanghaiTime != null) try std.fmt.allocPrint(allocator, "{any}", .{self.shanghaiTime}) else "inactive" }, + .{ "Cancun", "na", if (self.cancunTime != null) try std.fmt.allocPrint(allocator, "{any}", .{self.cancunTime}) else "inactive" }, + .{ "Prague", "na", if (self.pragueTime != null) try std.fmt.allocPrint(allocator, "{any}", .{self.pragueTime}) else "inactive" }, + .{ "Osaka", "na", if (self.osakaTime != null) try std.fmt.allocPrint(allocator, "{any}", .{self.osakaTime}) else "inactive" }, + }, + .mode = .box, + }; + std.log.info("{}\n", .{table}); + } +}; + +const mainnetChainSpec = @embedFile("../chainspecs/mainnet.json"); +const sepoliaChainSpec = @embedFile("../chainspecs/sepolia.json"); diff --git a/src/engine_api/engine_api.zig b/src/engine_api/engine_api.zig index 0cd184f..73eaa8d 100644 --- a/src/engine_api/engine_api.zig +++ b/src/engine_api/engine_api.zig @@ -94,5 +94,6 @@ test "deserialize sample engine_newPayloadV2" { 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); + defer ep.deinit(std.testing.allocator); + try execution_payload.newPayloadV2Handler(&ep); } diff --git a/src/engine_api/execution_payload.zig b/src/engine_api/execution_payload.zig index cd5947a..8e0bfb3 100644 --- a/src/engine_api/execution_payload.zig +++ b/src/engine_api/execution_payload.zig @@ -1,5 +1,8 @@ const std = @import("std"); const types = @import("../types/types.zig"); +const lib = @import("../lib.zig"); +const blockchain = lib.blockchain; +const state = lib.state; const Allocator = std.mem.Allocator; const BlockHeader = types.BlockHeader; const Withdrawal = types.Withdrawal; @@ -62,16 +65,15 @@ pub const ExecutionPayload = struct { } }; -pub fn newPayloadV2Handler(params: *ExecutionPayload, allocator: std.mem.Allocator) !void { +pub fn newPayloadV2Handler(params: *ExecutionPayload) !void { + const block = params.toBlock(); + _ = block; // 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); + // bc.run_block(block, params.transactions); // But so far, just print the content of the payload // std.log.info("newPayloadV2Handler: {any}", .{params}); - const block = params.toBlock(); - _ = block; // std.debug.print("block number={}\n", .{block.header.block_number}); - params.deinit(allocator); } diff --git a/src/lib.zig b/src/lib.zig index 1936cdd..fab1236 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -7,6 +7,7 @@ pub const crypto = @import("crypto/crypto.zig"); pub const signer = @import("signer/signer.zig"); pub const engine_api = @import("engine_api/engine_api.zig"); pub const mpt = @import("mpt/mpt.zig"); +pub const config = @import("config/config.zig"); test "tests" { std.testing.log_level = .debug; diff --git a/src/main.zig b/src/main.zig index 445b371..a27e880 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,8 +1,9 @@ const std = @import("std"); +const lib = @import("lib.zig"); +const ChainConfig = lib.config.ChainConfig; const types = @import("types/types.zig"); const crypto = @import("crypto/crypto.zig"); const ecdsa = crypto.ecdsa; -const config = @import("config/config.zig"); const AccountState = @import("state/state.zig").AccountState; const Address = types.Address; const VM = @import("blockchain/vm.zig").VM; @@ -20,22 +21,29 @@ fn engineAPIHandler(req: *httpz.Request, res: *httpz.Response) !void { if (std.mem.eql(u8, payload.method, "engine_newPayloadV2")) { const execution_payload_json = payload.params[0]; var execution_payload = try execution_payload_json.to_execution_payload(res.arena); - try engine_api.execution_payload.newPayloadV2Handler(&execution_payload, res.arena); + defer execution_payload.deinit(res.arena); + try engine_api.execution_payload.newPayloadV2Handler(&execution_payload); } else { res.status = 500; } } } +var config: ChainConfig = undefined; + const PhantArgs = struct { engine_api_port: ?u16, + network_id: lib.config.ChainId = .Mainnet, + chainspec: ?[]const u8, pub const __shorts__ = .{ .engine_api_port = .p, }; pub const __messages__ = .{ - .engine_api_port = "Specify the port to listen to for Engine API messages", + .engine_api_port = "Speficy the port to listen to for Engine API messages", + .network_id = "Specify the chain id of the network", + .chainspec = "Specify a custom chainspec JSON file", }; }; @@ -43,12 +51,26 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); + // TODO print usage upon failure (requires upstream changes) + // TODO generate version from build and add it here const opts = try simargs.parse(gpa.allocator(), PhantArgs, "", null); defer opts.deinit(); const port: u16 = if (opts.args.engine_api_port == null) 8551 else opts.args.engine_api_port.?; + // Get the chain config from 2 possible sources, by priority + // 1. Specified chainspec file + // 2. embedded config based on a chain id specified with `--network_id`. If no network + // is specified then the default (mainnet) is chosen. + if (opts.args.chainspec == null) { + config = try ChainConfig.fromChainId(opts.args.network_id, gpa.allocator()); + } else { + var file = try std.fs.cwd().openFile(opts.args.chainspec.?, .{}); + config = try ChainConfig.fromChainSpec(try file.readToEndAlloc(gpa.allocator(), try file.getEndPos()), gpa.allocator()); + } + std.log.info("Welcome to phant! 🐘", .{}); + try config.dump(allocator); var engine_api_server = try httpz.Server().init(allocator, .{ .port = port,