Skip to content

Commit

Permalink
Add support for chainspec files (#25)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gballet authored Jun 23, 2024
1 parent abc04ef commit 4f67d04
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 11 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path to chainspec>.json
```

## License

MIT
2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions src/chainspecs/mainnet.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
20 changes: 20 additions & 0 deletions src/chainspecs/sepolia.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
85 changes: 85 additions & 0 deletions src/config/config.zig
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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");
3 changes: 2 additions & 1 deletion src/engine_api/engine_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
12 changes: 7 additions & 5 deletions src/engine_api/execution_payload.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 25 additions & 3 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,35 +21,56 @@ 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",
};
};

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,
Expand Down

0 comments on commit 4f67d04

Please sign in to comment.