Skip to content

Commit 951ac04

Browse files
committed
main stf skeleton
1 parent 366c548 commit 951ac04

15 files changed

+309
-119
lines changed

src/config/beacon_config.zig

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const ForkInfo = params.ForkInfo;
1515
const TOTAL_FORKS = params.TOTAL_FORKS;
1616
const getForkSeqByForkName = params.getForkSeqByForkName;
1717

18-
const DomainByTypeHashMap = std.AutoHashMap([]const u8, []const u8);
18+
const DomainByTypeHashMap = std.StringHashMap([]const u8);
1919
const DomainByTypeByFork = std.ArrayList(DomainByTypeHashMap);
2020

2121
pub const ChainConfig = @import("./chain/chain_config.zig").ChainConfig;
@@ -173,7 +173,7 @@ pub const BeaconConfig = struct {
173173
return if (fork.isForkPostElectra()) self.chain.MAX_REQUEST_BLOB_SIDECARS_ELECTRA else self.chain.MAX_REQUEST_BLOB_SIDECARS;
174174
}
175175

176-
pub fn getDomain(self: *BeaconConfig, state_slot: Slot, domain_type: DomainType, message_slot: ?Slot) ![32]u8 {
176+
pub fn getDomain(self: *const BeaconConfig, state_slot: Slot, domain_type: DomainType, message_slot: ?Slot) ![32]u8 {
177177
const slot = if (message_slot) |s| s else state_slot;
178178
const epoch = @divFloor(slot, preset.SLOTS_PER_EPOCH);
179179
const state_fork_info = self.getForkInfo(state_slot);
@@ -183,27 +183,29 @@ pub const BeaconConfig = struct {
183183
}
184184

185185
// TODO: may not need this method
186-
pub fn getDomainByForkName(self: *BeaconConfig, fork_name: []const u8, domain_type: DomainType) ![32]u8 {
186+
pub fn getDomainByForkName(self: *const BeaconConfig, fork_name: []const u8, domain_type: DomainType) ![32]u8 {
187187
const fork_seq = getForkSeqByForkName(fork_name);
188188
return try self.getDomainByForkSeq(fork_seq, domain_type);
189189
}
190190

191-
pub fn getDomainByForkSeq(self: *BeaconConfig, fork_seq: ForkSeq, domain_type: DomainType) ![32]u8 {
192-
if (fork_seq >= TOTAL_FORKS) return error.ForkSeqOutOfRange;
191+
pub fn getDomainByForkSeq(self: *const BeaconConfig, fork_seq: ForkSeq, domain_type: DomainType) ![32]u8 {
192+
if (@intFromEnum(fork_seq) >= TOTAL_FORKS) return error.ForkSeqOutOfRange;
193193

194-
const domain_by_type = self.domain_cache.items[@intFromEnum(fork_seq)];
195-
const domain = domain_by_type.get(domain_type) orelse {
194+
var domain_by_type = self.domain_cache.items[@intFromEnum(fork_seq)];
195+
var domain: [32]u8 = undefined;
196+
197+
if (domain_by_type.get(&domain_type)) |d| @memcpy(&domain, d) else {
196198
const out = try self.allocator.create([32]u8);
197199
const fork_info = self.forks_ascending_epoch_order[@intFromEnum(fork_seq)];
198-
computeDomain(domain_type, fork_info.version, self.genesis_validator_root, out);
199-
try domain_by_type.put(domain_type, out);
200-
return out;
201-
};
200+
try computeDomain(domain_type, fork_info.version, self.genesis_validator_root, out);
201+
try domain_by_type.put(&domain_type, out);
202+
@memcpy(&domain, out);
203+
}
202204

203-
return domain.*;
205+
return domain;
204206
}
205207

206-
pub fn getDomainForVoluntaryExit(self: *BeaconConfig, state_slot: Slot, message_slot: ?Slot) ![32]u8 {
208+
pub fn getDomainForVoluntaryExit(self: *const BeaconConfig, state_slot: Slot, message_slot: ?Slot) ![32]u8 {
207209
const domain = if (state_slot < self.chain.DENEB_FORK_EPOCH * preset.SLOTS_PER_EPOCH) {
208210
return self.getDomain(state_slot, DOMAIN_VOLUNTARY_EXIT, message_slot);
209211
} else {
@@ -217,17 +219,17 @@ pub const BeaconConfig = struct {
217219
// may not need it for state-transition
218220
};
219221

220-
fn computeDomain(domain_type: DomainType, fork_version: Version, genesis_validators_root: Root, out: *[32]u8) void {
221-
computeForkDataRoot(fork_version, genesis_validators_root, out);
222+
fn computeDomain(domain_type: DomainType, fork_version: Version, genesis_validators_root: Root, out: *[32]u8) !void {
223+
try computeForkDataRoot(fork_version, genesis_validators_root, out);
222224
std.mem.copyForwards(u8, out[0..], domain_type[0..]);
223225
}
224226

225-
fn computeForkDataRoot(current_version: Version, genesis_validators_root: Root, out: *[32]u8) void {
227+
fn computeForkDataRoot(current_version: Version, genesis_validators_root: Root, out: *[32]u8) !void {
226228
const fork_data: ForkData = .{
227229
.current_version = current_version,
228230
.genesis_validators_root = genesis_validators_root,
229231
};
230-
ssz.phase0.ForkData.hashTreeRoot(&fork_data, out);
232+
try ssz.phase0.ForkData.hashTreeRoot(&fork_data, out);
231233
}
232234

233235
// TODO: unit tests

src/state_transition/block/process_block.zig

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const ForkSeq = @import("params").ForkSeq;
55
const ssz = @import("consensus_types");
66
const preset = ssz.preset;
77
const BeaconBlock = @import("../types/beacon_block.zig").BeaconBlock;
8+
const SignedBlock = @import("../state_transition.zig").SignedBlock;
89
const BlockExternalData = @import("external_data.zig").BlockExternalData;
910
const processBlobKzgCommitments = @import("./process_blob_kzg_commitments.zig").processBlobKzgCommitments;
1011
const processBlockHeader = @import("./process_block_header.zig").processBlockHeader;
@@ -22,46 +23,52 @@ const isExecutionEnabled = @import("../utils/execution.zig").isExecutionEnabled;
2223
// TODO
2324
pub fn processBlock(
2425
allocator: Allocator,
25-
cached_state: *CachedBeaconStateAllForks,
26+
cached_state: *const CachedBeaconStateAllForks,
2627
// TODO: support BlindedBeaconBlock
27-
block: *const BeaconBlock,
28+
block: *const SignedBlock,
2829
external_data: BlockExternalData,
2930
opts: ?ProcessBlockOpts,
3031
// TODO: metrics
3132
) !void {
3233
const state = cached_state.state;
33-
const verify_signature = if (opts) |o| {
34-
o.verify_signature orelse true;
35-
} else true;
3634

3735
try processBlockHeader(allocator, cached_state, block);
3836

3937
// The call to the process_execution_payload must happen before the call to the process_randao as the former depends
4038
// on the randao_mix computed with the reveal of the previous block.
41-
if (state.isPostBellatrix() and isExecutionEnabled(cached_state, block)) {
39+
if (state.isPostBellatrix() and isExecutionEnabled(cached_state.state, block)) {
4240
// TODO Deneb: Allow to disable withdrawals for interop testing
4341
// https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals
4442
if (state.isPostCapella()) {
4543
try processWithdrawals(
4644
allocator,
47-
state,
45+
cached_state,
4846
&block.getBeaconBlockBody().getExecutionPayload(),
4947
);
5048
}
5149

52-
try processExecutionPayload(
53-
allocator,
54-
state,
55-
block.getBeaconBlockBody(),
56-
external_data,
57-
);
50+
switch (block) {
51+
.signed_beacon_block => |b| try processExecutionPayload(
52+
allocator,
53+
cached_state,
54+
b.getBeaconBlockBody(),
55+
external_data,
56+
),
57+
.signed_blinded_beacon_block => |b| try processExecutionPayloadHeader(
58+
allocator,
59+
cached_state,
60+
b,
61+
external_data,
62+
),
63+
}
64+
try processExecutionPayload();
5865
}
5966

60-
try processRandao(state, block, verify_signature);
67+
try processRandao(state, block, opts.verify_signature);
6168
try processEth1Data(state, block.getBeaconBlockBody().getEth1Data());
6269
try processOperations(cached_state, block.getBeaconBlockBody(), external_data);
6370
if (state.isPostAltair()) {
64-
try processSyncAggregate(cached_state, block, verify_signature);
71+
try processSyncAggregate(cached_state, block, opts.verify_signature);
6572
}
6673

6774
if (state.isPostDeneb()) {

src/state_transition/block/process_block_header.zig

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ const BeaconBlock = @import("../types/beacon_block.zig").BeaconBlock;
66
const BeaconConfig = @import("config").BeaconConfig;
77
const BeaconBlockHeader = @import("../type.zig").BeaconBlockHeader;
88
const Root = @import("../type.zig").Root;
9+
const SignedBlock = @import("../state_transition.zig").SignedBlock;
910
const ZERO_HASH = @import("../constants.zig").ZERO_HASH;
1011

1112
// TODO: BlindedBeaconBlock
12-
pub fn processBlockHeader(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, block: *const BeaconBlock) !void {
13+
pub fn processBlockHeader(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, block: *const SignedBlock) !void {
1314
const state = cached_state.state;
1415
const epoch_cache = cached_state.getEpochCache();
1516
const slot = state.getSlot();
@@ -25,38 +26,40 @@ pub fn processBlockHeader(allocator: Allocator, cached_state: *CachedBeaconState
2526
}
2627

2728
// verify that proposer index is the correct index
28-
const proposer_index = epoch_cache.getBeaconProposer(slot);
29+
const proposer_index = try epoch_cache.getBeaconProposer(slot);
2930
if (block.getProposerIndex() != proposer_index) {
3031
return error.BlockProposerIndexMismatch;
3132
}
3233

3334
// verify that the parent matches
34-
if (!std.mem.eql(u8, &block.getParentRoot(), &ssz.phase0.BeaconBlockHeader.hashTreeRoot(state.getLatestBlockHeader()))) {
35+
var header_parent_root: [32]u8 = undefined;
36+
try ssz.phase0.BeaconBlockHeader.hashTreeRoot(state.getLatestBlockHeader(), &header_parent_root);
37+
if (!std.mem.eql(u8, &block.getParentRoot(), &header_parent_root)) {
3538
return error.BlockParentRootMismatch;
3639
}
37-
38-
var block_header: BeaconBlockHeader = undefined;
39-
try blockToHeader(allocator, block, &block_header);
40-
41-
// cache current block as the new latest block
42-
state.setLatestBlockHeader(&.{
40+
var body_root: [32]u8 = undefined;
41+
try block.hashTreeRoot(allocator, &body_root);
42+
const block_header: BeaconBlockHeader = .{
4343
.slot = slot,
4444
.proposer_index = proposer_index,
4545
.parent_root = block.getParentRoot(),
4646
.state_root = ZERO_HASH,
47-
.bodyRoot = block_header.body_root,
48-
});
47+
.body_root = body_root,
48+
};
49+
50+
// cache current block as the new latest block
51+
state.setLatestBlockHeader(block_header);
4952

5053
// verify proposer is not slashed. Only once per block, may use the slower read from tree
5154
if (state.getValidator(proposer_index).slashed) {
5255
return error.BlockProposerSlashed;
5356
}
5457
}
5558

56-
pub fn blockToHeader(allocator: Allocator, block: *const BeaconBlock, out: *BeaconBlockHeader) !void {
59+
pub fn blockToHeader(allocator: Allocator, block: *const SignedBlock, out: *BeaconBlockHeader) !void {
5760
out.slot = block.getSlot();
5861
out.proposer_index = block.getProposerIndex();
5962
out.parent_root = block.getParentRoot();
6063
out.state_root = block.getStateRoot();
61-
try block.getBeaconBlockBody().hashTreeRoot(allocator, &out.bodyRoot);
64+
try block.hashTreeRoot(allocator, &out.body_root);
6265
}

src/state_transition/block/process_eth1_data.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub fn processEth1Data(cached_state: *const CachedBeaconStateAllForks, eth1_data
1212
state.getEth1DataVotes().append(eth1_data.*);
1313
}
1414

15-
pub fn becomesNewEth1Data(cached_state: *const CachedBeaconStateAllForks, new_eth1_data: *const Eth1Data) boolean {
15+
pub fn becomesNewEth1Data(cached_state: *const CachedBeaconStateAllForks, new_eth1_data: *const Eth1Data) bool {
1616
const state = cached_state.state;
1717
const SLOTS_PER_ETH1_VOTING_PERIOD = preset.EPOCHS_PER_ETH1_VOTING_PERIOD * preset.SLOTS_PER_EPOCH;
1818

src/state_transition/block/process_execution_payload.zig

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,83 @@ const params = @import("params");
77
const ForkSeq = @import("params").ForkSeq;
88
const BeaconBlockBody = @import("../types/beacon_block.zig").BeaconBlockBody;
99
const ExecutionPayloadStatus = @import("./external_data.zig").ExecutionPayloadStatus;
10+
const SignedBlindedBeaconBlock = @import("types/beacon_block.zig").SignedBlindedBeaconBlock;
1011
const BeaconConfig = @import("config").BeaconConfig;
1112
const isMergeTransitionComplete = @import("../utils/execution.zig").isMergeTransitionComplete;
1213
const computeEpochAtSlot = @import("../utils/epoch.zig").computeEpochAtSlot;
1314
const getRandaoMix = @import("../utils/seed.zig").getRandaoMix;
1415

1516
// TODO: support BlindedBeaconBlockBody
16-
pub fn processExecutionPayload(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, body: BeaconBlockBody, external_data: ExecutionPayloadStatus) !void {
17+
pub fn processExecutionPayload(
18+
allocator: Allocator,
19+
cached_state: *const CachedBeaconStateAllForks,
20+
body: BeaconBlockBody,
21+
external_data: ExecutionPayloadStatus,
22+
) !void {
23+
const state = cached_state.state;
24+
const epoch_cache = cached_state.getEpochCache();
25+
const config = epoch_cache.config;
26+
const payload = body.getExecutionPayload();
27+
// Verify consistency of the parent hash, block number, base fee per gas and gas limit
28+
// with respect to the previous execution payload header
29+
if (isMergeTransitionComplete(state)) {
30+
const execution_payload_header = state.getLatestExecutionPayloadHeader();
31+
if (!std.mem.eql(u8, &payload.getParentHash(), &execution_payload_header.getBlockHash())) {
32+
return error.InvalidExecutionPayloadParentHash;
33+
}
34+
}
35+
36+
// Verify random
37+
const expected_random = getRandaoMix(state.*, epoch_cache.epoch);
38+
if (!std.mem.eql(u8, &payload.getPrevRandao(), &expected_random)) {
39+
return error.InvalidExecutionPayloadRandom;
40+
}
41+
42+
// Verify timestamp
43+
//
44+
// Note: inlined function in if statement
45+
// def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64:
46+
// slots_since_genesis = slot - GENESIS_SLOT
47+
// return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT)
48+
if (payload.getTimestamp() != state.getGenesisTime() + state.getSlot() * config.chain.SECONDS_PER_SLOT) {
49+
return error.InvalidExecutionPayloadTimestamp;
50+
}
51+
52+
if (state.isPostDeneb()) {
53+
const max_blobs_per_block = config.getMaxBlobsPerBlock(computeEpochAtSlot(state.getSlot()));
54+
const blob_kzg_commitments_len = body.getBlobKzgCommitments().items.len;
55+
if (blob_kzg_commitments_len > max_blobs_per_block) {
56+
return error.BlobKzgCommitmentsExceedsLimit;
57+
}
58+
}
59+
60+
// Verify the execution payload is valid
61+
//
62+
// if executionEngine is null, executionEngine.onPayload MUST be called after running processBlock to get the
63+
// correct randao mix. Since executionEngine will be an async call in most cases it is called afterwards to keep
64+
// the state transition sync
65+
//
66+
// Equivalent to `assert executionEngine.notifyNewPayload(payload)
67+
if (external_data == ExecutionPayloadStatus.pre_merge) {
68+
return error.ExecutionPayloadStatusPreMerge;
69+
} else if (external_data == ExecutionPayloadStatus.invalid) {
70+
return error.InvalidExecutionPayload;
71+
}
72+
73+
const payload_header = try payload.toPayloadHeader(allocator);
74+
state.setLatestExecutionPayloadHeader(payload_header);
75+
}
76+
77+
pub fn processExecutionPayloadHeader(
78+
allocator: Allocator,
79+
cached_state: *const CachedBeaconStateAllForks,
80+
block: SignedBlindedBeaconBlock,
81+
) !void {
82+
var signed_header = block.getBeaconBlock().getBeaconBlockBody().signed_execution_payload_header;
83+
try verifyExecutionPayloadHeader(state, signed_header);
84+
85+
const header = signed_header.message;
86+
1787
const state = cached_state.state;
1888
const epoch_cache = cached_state.getEpochCache();
1989
const config = epoch_cache.config;

0 commit comments

Comments
 (0)