Skip to content

Commit

Permalink
Merge pull request #38 from gballet/fork-manager-and-2935
Browse files Browse the repository at this point in the history
create fork manager + implement 2935 with it
  • Loading branch information
jsign authored Sep 2, 2024
2 parents cb246bb + a6ee8ba commit 97301be
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 27 deletions.
18 changes: 8 additions & 10 deletions src/blockchain/blockchain.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ const Log = types.Log;
const TxSigner = signer.TxSigner;
const VM = vm.VM;
const Keccak256 = std.crypto.hash.sha3.Keccak256;
pub const Fork = @import("fork.zig");

pub const Blockchain = struct {
allocator: Allocator,
chain_id: config.ChainId,
state: *StateDB,
prev_block: BlockHeader,
last_256_blocks_hashes: [256]Hash32, // ordered in asc order
tx_signer: TxSigner,
fork: *Fork,

// init initializes a blockchain.
// The caller **does not** transfer ownership of prev_block.
Expand All @@ -45,14 +46,14 @@ pub const Blockchain = struct {
chain_id: config.ChainId,
state: *StateDB,
prev_block: BlockHeader,
last_256_blocks_hashes: [256]Hash32,
fork: *Fork,
) !Blockchain {
return .{
.allocator = allocator,
.chain_id = chain_id,
.state = state,
.prev_block = try prev_block.clone(allocator),
.last_256_blocks_hashes = last_256_blocks_hashes,
.fork = fork,
.tx_signer = try signer.TxSigner.init(@intFromEnum(chain_id)),
};
}
Expand All @@ -66,6 +67,9 @@ pub const Blockchain = struct {
defer arena.deinit();
const allocator = arena.allocator();

// Add the current block to the last 256 block hashes.
try self.fork.update_parent_block_hash(block.header.block_number - 1, block.header.parent_hash);

// Execute block.
var result = try applyBody(allocator, self, self.state, block, self.tx_signer);

Expand All @@ -85,12 +89,6 @@ pub const Blockchain = struct {
if (!std.mem.eql(u8, &result.withdrawals_root, &block.header.withdrawals_root))
return error.InvalidWithdrawalsRoot;

// Add the current block to the last 256 block hashes.
// TODO: this can be done more efficiently with some ring buffer to avoid copying the slice
// to make room for the new block hash.
std.mem.copyForwards(Hash32, &self.last_256_blocks_hashes, self.last_256_blocks_hashes[1..255]);
self.last_256_blocks_hashes[255] = try common.encodeToRLPAndHash(BlockHeader, allocator, block.header, null);

// Note that we free and clone with the Blockchain allocator, and not the arena allocator.
// This is required since Blockchain field lifetimes are longer than the block execution processing.
self.prev_block.deinit(self.allocator);
Expand Down Expand Up @@ -164,8 +162,8 @@ pub const Blockchain = struct {
const tx_info = try checkTransaction(allocator, tx, block.header.base_fee_per_gas, gas_available, tx_signer);

const env: Environment = .{
.fork = chain.fork,
.origin = tx_info.sender_address,
.block_hashes = chain.last_256_blocks_hashes,
.coinbase = block.header.fee_recipient,
.number = block.header.block_number,
.gas_limit = block.header.gas_limit,
Expand Down
28 changes: 28 additions & 0 deletions src/blockchain/fork.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const lib = @import("../lib.zig");
const Hash32 = lib.types.Hash32;
const Fork = @This();
pub const frontier = @import("./forks/frontier.zig");
pub const prague = @import("./forks/prague.zig");

vtable: *const VTable,

pub const VTable = struct {
update_parent_block_hash: *const fn (self: *Fork, num: u64, hash: Hash32) anyerror!void,
get_parent_block_hash: *const fn (self: *Fork, index: u64) anyerror!Hash32,
deinit: *const fn (self: *Fork) void,
};

// Used to update the parent hash at the end of a block execution
pub fn update_parent_block_hash(self: *Fork, num: u64, hash: Hash32) !void {
return self.vtable.update_parent_block_hash(self, num, hash);
}

// Used to get the block hash of a parent when implementing the
// BLOCKHASH instruction.
pub fn get_parent_block_hash(self: *Fork, index: u64) !Hash32 {
return self.vtable.get_parent_block_hash(self, index);
}

pub fn deinit(self: *Fork) void {
self.vtable.deinit(self);
}
58 changes: 58 additions & 0 deletions src/blockchain/forks/frontier.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const std = @import("std");
const Fork = @import("../fork.zig");
const lib = @import("../../lib.zig");
const Hash32 = lib.types.Hash32;

const base_fork_vtable = Fork.VTable{
.update_parent_block_hash = update_parent_block_hash,
.get_parent_block_hash = get_parent_block_hash,
.deinit = deinit,
};

const FrontierFork = struct {
const Self = @This();

fork: Fork = .{
.vtable = &base_fork_vtable,
},

allocator: std.mem.Allocator,
next_block_hash_index: u64 = 0, // index of the next block hash to be written
block_hashes: [256]Hash32 = [_]Hash32{[_]u8{0} ** 32} ** 256,

fn init(self: *Self) void {
self.next_block_hash_index = 0;
self.fork.vtable = &base_fork_vtable;
}
};

fn update_parent_block_hash(self: *Fork, block_num: u64, hash: Hash32) anyerror!void {
var base_fork: *FrontierFork = @fieldParentPtr("fork", self);
if (block_num != base_fork.next_block_hash_index) {
return error.NonSequentialParentUpdate;
}

base_fork.block_hashes[base_fork.next_block_hash_index % base_fork.block_hashes.len] = hash;
base_fork.next_block_hash_index += 1;
}

fn get_parent_block_hash(self: *Fork, block_num: u64) !Hash32 {
const base_fork: *FrontierFork = @fieldParentPtr("fork", self);
if (block_num > base_fork.next_block_hash_index or block_num + base_fork.block_hashes.len < base_fork.next_block_hash_index) {
return std.mem.zeroes(Hash32);
}

return base_fork.block_hashes[block_num % base_fork.block_hashes.len];
}

pub fn newFrontierFork(allocator: std.mem.Allocator) !*Fork {
var base_fork = try allocator.create(FrontierFork);
base_fork.init();
base_fork.allocator = allocator;
return &base_fork.fork;
}

fn deinit(self: *Fork) void {
var base_fork: *FrontierFork = @fieldParentPtr("fork", self);
base_fork.allocator.destroy(base_fork);
}
57 changes: 57 additions & 0 deletions src/blockchain/forks/prague.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const std = @import("std");
const Fork = @import("../fork.zig");
const lib = @import("../../lib.zig");
const Hash32 = lib.types.Hash32;
const StateDB = lib.state.StateDB;
const Address = lib.types.Address;

const system_addr: Address = [_]u8{0xff} ** 19 ++ [_]u8{0xfe};
const history_size: u64 = 8192;

const vtable = Fork.VTable{
.update_parent_block_hash = update_parent_block_hash,
.get_parent_block_hash = get_parent_block_hash,
.deinit = deinit,
};

const PragueFork = struct {
fork: Fork = Fork{
.vtable = &vtable,
},

state_db: *StateDB,
allocator: std.mem.Allocator,
};

fn update_parent_block_hash(self: *Fork, num: u64, hash: Hash32) anyerror!void {
const prague_fork: *PragueFork = @fieldParentPtr("fork", self);
const slot: u256 = @intCast(num % history_size);
try prague_fork.state_db.setStorage(system_addr, slot, hash);
}

fn get_parent_block_hash(self: *Fork, index: u64) !Hash32 {
const prague_fork: *PragueFork = @fieldParentPtr("fork", self);
const slot: u256 = @intCast(index % history_size);
return prague_fork.state_db.getStorage(system_addr, slot);
}

// This method takes a parent fork and activate all the
// Prague-specific methods, superseding the previous fork.
pub fn enablePrague(state_db: *StateDB, _: ?*Fork, allocator: std.mem.Allocator) !*Fork {
var prague_fork = try allocator.create(PragueFork);
prague_fork.allocator = allocator;
prague_fork.state_db = state_db;
prague_fork.fork = Fork{ .vtable = &vtable };
return &prague_fork.fork;
}

fn deinit(self: *Fork) void {
const prague_fork: *PragueFork = @fieldParentPtr("fork", self);
prague_fork.allocator.destroy(prague_fork);
}

// a helper function to deploy the 2935 contract on a testnet.
pub fn deployContract(self: *Fork) !void {
const prague_fork: *PragueFork = @fieldParentPtr("fork", self);
try prague_fork.state_db.db.put(system_addr, try lib.state.AccountState.init(prague_fork.allocator, system_addr, 1, 0, &[0]u8{}));
}
3 changes: 2 additions & 1 deletion src/blockchain/types.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const types = @import("../types/types.zig");
const config = @import("../config/config.zig");
const common = @import("../common/common.zig");
const Fork = @import("fork.zig");
const StateDB = @import("../state/state.zig").StateDB;
const Address = types.Address;
const Hash32 = types.Hash32;
Expand All @@ -10,7 +11,7 @@ const AddresssKey = common.AddressKey;
const AddressKeySet = common.AddressKeySet;

pub const Environment = struct {
block_hashes: [256]Hash32,
fork: *Fork,
origin: Address,
coinbase: Address,
number: u64,
Expand Down
6 changes: 1 addition & 5 deletions src/blockchain/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,7 @@ const EVMOneHost = struct {

const vm: *VM = @as(*VM, @alignCast(@ptrCast(ctx.?)));
const idx = vm.env.number - @as(u64, @intCast(block_number));
if (idx < 0 or idx >= vm.env.block_hashes.len) {
return std.mem.zeroes(evmc.evmc_bytes32);
}

return .{ .bytes = vm.env.block_hashes[idx] };
return .{ .bytes = vm.env.fork.get_parent_block_hash(idx) catch @panic("unhandled error getting parent hash") };
}

fn account_exists(ctx: ?*evmc.struct_evmc_host_context, addr: [*c]const evmc.evmc_address) callconv(.C) bool {
Expand Down
20 changes: 12 additions & 8 deletions src/engine_api/engine_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const Allocator = std.mem.Allocator;
const Withdrawal = types.Withdrawal;
const Tx = types.Tx;
const ExecutionPayload = execution_payload.ExecutionPayload;
const lib = @import("../lib.zig");
const Fork = lib.blockchain.Fork;

pub const execution_payload = @import("execution_payload.zig");

Expand Down Expand Up @@ -85,21 +87,19 @@ pub const EngineAPIRequest = struct {
test "deserialize sample engine_newPayloadV2" {
const json = std.json;
const expect = std.testing.expect;
const lib = @import("./../lib.zig");
const StateDB = lib.state.StateDB;
const Blockchain = lib.blockchain.Blockchain;
const AccountState = lib.state.AccountState;
const BlockHeader = lib.blockchain.BlockHeader;
const Hash32 = lib.types.Hash32;
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const allocator = arena.allocator();

const fileContent = @embedFile("./test_req.json");

const payload = try json.parseFromSlice(EngineAPIRequest, arena.allocator(), fileContent, .{ .ignore_unknown_fields = true });
const payload = try json.parseFromSlice(EngineAPIRequest, allocator, fileContent, .{ .ignore_unknown_fields = true });
defer payload.deinit();

var statedb = try StateDB.init(arena.allocator(), &[0]AccountState{});
var statedb = try StateDB.init(allocator, &[0]AccountState{});
defer statedb.deinit();
const parent_header = BlockHeader{
.parent_hash = [_]u8{0} ** 32,
Expand All @@ -120,11 +120,15 @@ test "deserialize sample engine_newPayloadV2" {
.base_fee_per_gas = 7,
.withdrawals_root = [_]u8{0} ** 32,
};
var blockchain = try Blockchain.init(arena.allocator(), .Testing, &statedb, parent_header, [_]Hash32{[_]u8{0} ** 32} ** 256);
// TODO pick the fork based on chain config + block number + timestamp
const prague_fork = try Fork.prague.enablePrague(&statedb, null, allocator);
defer prague_fork.deinit();
try Fork.prague.deployContract(prague_fork);
var blockchain = try Blockchain.init(allocator, .Testing, &statedb, parent_header, prague_fork);

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(arena.allocator());
defer ep.deinit(arena.allocator());
var ep = try execution_payload_json.to_execution_payload(allocator);
defer ep.deinit(allocator);
try execution_payload.newPayloadV2Handler(&blockchain, &ep);
}
3 changes: 2 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const json = std.json;
const simargs = @import("simargs");
const version = @import("version.zig").version;
const Blockchain = lib.blockchain.Blockchain;
const Fork = lib.blockchain.Fork;

fn engineAPIHandler(blockchain: *Blockchain, req: *httpz.Request, res: *httpz.Response) !void {
if (try req.json(engine_api.EngineAPIRequest)) |payload| {
Expand Down Expand Up @@ -98,7 +99,7 @@ pub fn main() !void {
.base_fee_per_gas = 0,
.withdrawals_root = [_]u8{0} ** 32,
};
var blockchain = try Blockchain.init(allocator, config.chainId, &statedb, parent_header, [_]Hash32{[_]u8{0} ** 32} ** 256);
var blockchain = try Blockchain.init(allocator, config.chainId, &statedb, parent_header, try Fork.frontier.newFrontierFork(allocator));

var engine_api_server = try httpz.ServerApp(*Blockchain).init(allocator, .{
.port = port,
Expand Down
3 changes: 2 additions & 1 deletion src/tests/custom_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const Hash32 = types.Hash32;
const Address = types.Address;
const StateDB = state.StateDB;
const ChainID = config.ChainId;
const Fork = blockchain.Fork;

test "create contract" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
Expand All @@ -25,7 +26,7 @@ test "create contract" {

// Configure an EVM execution enviroment for a block from this coinbase.
const env: Environment = .{
.block_hashes = [_]Hash32{std.mem.zeroes(Hash32)} ** 256,
.fork = try Fork.frontier.newFrontierFork(allocator),
.origin = coinbase,
.coinbase = coinbase,
.number = 100,
Expand Down
3 changes: 2 additions & 1 deletion src/tests/spec_tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const VM = vm.VM;
const StateDB = state.StateDB;
const AccountState = state.AccountState;
const log = std.log.scoped(.execspectests);
const Fork = blockchain.Fork;

const HexString = []const u8;

Expand Down Expand Up @@ -78,7 +79,7 @@ pub const FixtureTest = struct {
var out = try allocator.alloc(u8, self.genesisRLP.len / 2);
var rlp_bytes = try std.fmt.hexToBytes(out, self.genesisRLP[2..]);
const parent_block = try Block.decode(allocator, rlp_bytes);
var chain = try blockchain.Blockchain.init(allocator, config.ChainId.Mainnet, &statedb, parent_block.header, std.mem.zeroes([256]Hash32));
var chain = try blockchain.Blockchain.init(allocator, config.ChainId.Mainnet, &statedb, parent_block.header, try Fork.frontier.newFrontierFork(allocator));

// Execute blocks.
for (self.blocks) |encoded_block| {
Expand Down

0 comments on commit 97301be

Please sign in to comment.