Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
58d5129
fix: implement computeIndexedAttestation() functions
twoeths Oct 23, 2025
1bd597a
fix: clone aggregation_bits for phase0 PendingAttestation
twoeths Oct 23, 2025
0d59855
fix: computeDomain()
twoeths Oct 23, 2025
039886b
fix: handle processAttestationPhase0 failing partway
twoeths Oct 23, 2025
242b0c9
fix: equals api in isSlashableAttestationData()
twoeths Oct 23, 2025
847d265
fix: map isValidDepositSignature() exactly to the spec
twoeths Oct 23, 2025
29848c5
fix: consume isValidDepositSignature() without try
twoeths Oct 23, 2025
d29c857
fix: getProposerSlashingSignatureSets signing root
twoeths Oct 23, 2025
2be0eaa
fix: processVoluntaryExit()
twoeths Oct 23, 2025
a493a2c
feat: implement ChainConfig for spec tests
twoeths Oct 24, 2025
991c0c8
fix: free pubkeys in processAttestationsAltair()
twoeths Oct 24, 2025
11dc025
fix: processAttestationsAltair for loop
twoeths Oct 24, 2025
41dea08
fix: bellatrix processExecutionPayload()
twoeths Oct 24, 2025
99f7725
fix: Operations test runner processExecutionPayload()
twoeths Oct 24, 2025
b36bd5c
fix: init correct root in processBlsToExecutionChange()
twoeths Oct 24, 2025
e8733fb
fix: pass payload_withdrawals_root to processWithdrawals()
twoeths Oct 24, 2025
ef300a6
fix: update next_withdrawal_index in processWithdrawals()
twoeths Oct 24, 2025
1ceb59b
fix: processWithdrawals() and getExpectedWithdrawals() misuse variables
twoeths Oct 25, 2025
a19884c
fix: use validator ptr in switchToCompoundingValidator()
twoeths Oct 25, 2025
7312938
fix: cleanup resource when no post state
twoeths Oct 25, 2025
443faa8
fix: write Operations test case without .valid
twoeths Oct 27, 2025
dc05a5e
fix: electra processWithdrawalRequest() validator ptr
twoeths Oct 27, 2025
3cd28bf
fix: Sanity test memory leak and remove .valid
twoeths Oct 27, 2025
a67e15f
fix: only upgrade state if fork transition
twoeths Oct 27, 2025
a65a7b0
fix: EffectiveBalanceIncrements usage
twoeths Oct 27, 2025
76551f3
fix: invalid state root due to processBlockHeader()
twoeths Oct 28, 2025
aa304e7
fix: sanity BlocksTestCase deinit prestate inside blocks loop
twoeths Oct 28, 2025
eb1b956
fix: load slots.yaml
twoeths Oct 28, 2025
e096549
fix: use validator ptr where suitable
twoeths Oct 28, 2025
3f6c9a5
fix: BeaconState.rotateEpochParticipations()
twoeths Oct 28, 2025
842c886
fix: processEffectiveBalanceUpdates integer underflow
twoeths Oct 28, 2025
9bf59f3
fix: EpochCache afterProcessEpoch epoch_after_upcoming
twoeths Oct 28, 2025
4815ffe
fix: test:state_transition
twoeths Oct 28, 2025
81679a6
fix: parent root in electra block int test
twoeths Oct 28, 2025
0c194f1
fix: processPendingDeposit pending_deposit sliceFrom()
twoeths Oct 29, 2025
34a151d
fix: is_compounding_validator_arr double free
twoeths Oct 29, 2025
a38cb71
fix: memory leak if stateTransition() failing partway
twoeths Oct 29, 2025
0ad403b
fix: isValidIndexedAttestationIndices() check sort index
twoeths Oct 29, 2025
8eb7545
fix: computeSyncParticipantReward()
twoeths Oct 30, 2025
f7e193c
fix: proposer_weight_factor f64 const
twoeths Oct 30, 2025
7c03a03
fix: processSyncAggregate()
twoeths Oct 30, 2025
d67614b
fix: processAttestationsAltair increase proposer reward
twoeths Oct 30, 2025
8fcc8eb
fix: isExecutionEnabled()
twoeths Oct 30, 2025
8e6ddfa
fix: deinit withdrawals_result
twoeths Oct 30, 2025
bb93b56
feat: run spec tests on CI
twoeths Oct 30, 2025
f0260ff
fix: create test_case folder
twoeths Oct 30, 2025
57a9fa7
chore: new spec test job
twoeths Oct 30, 2025
0560409
fix: remove conditional statement
twoeths Oct 30, 2025
91be677
fix: missing minimal config definition
twoeths Oct 30, 2025
ab4cc95
fix: various fixes for minimal
twoeths Oct 31, 2025
05c4fcd
fix: ignore error if test/spec_test_case already exist
twoeths Oct 31, 2025
5583474
fix: remove PENDING_PARTIAL_WITHDRAWALS_LIMIT from constant, use preset
twoeths Oct 31, 2025
b8c4dde
fix: processEth1DataReset int test
twoeths Oct 31, 2025
5a80728
fix: processAttestation() minimal spec tests
twoeths Oct 31, 2025
df56c40
fix: active indices to compute proposers
twoeths Oct 31, 2025
cd09f44
fix: run minimal spec tests on CI
twoeths Oct 31, 2025
e7f50b4
chore: loadPreState() and loadPostState()
twoeths Oct 31, 2025
2d24e30
chore: lower case spec test file names
twoeths Oct 31, 2025
8a0876f
feat: init merkle proof spec test
Nov 5, 2025
84a9408
feat: init rewards spec test and fix bugs in rewards
Nov 12, 2025
f63d733
Merge branch 'main' into rewards_spec_test
Nov 13, 2025
fd5a108
fix: fix merge issue
Nov 13, 2025
be0c9ec
Merge branch 'main' into rewards_spec_test
Nov 14, 2025
8a9bc4a
fix: add pmt dep into spec test
Nov 14, 2025
3dd89f0
fix: sync zbuild.zon
Nov 14, 2025
f4d8c11
fix: simplify the proof spec test
Nov 20, 2025
e796967
refactor: refactor the test structure
Nov 20, 2025
34e9f51
refactor: refactor rewards spec test
Nov 20, 2025
b9c01c1
chore: format
Nov 20, 2025
14cdd7b
Merge branch 'main' into rewards_spec_test
Nov 20, 2025
5307c56
feat: add fork spec test
Nov 20, 2025
f52863a
chore: use computePreviousEpoch helper
Nov 20, 2025
5536ea9
chore: sync zbuild
Nov 20, 2025
9ff736e
fix: fix memory leak and writer wrong arg
Nov 21, 2025
04829f3
fix: fix merkle_proof spec test path issue and gindex bug
Nov 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,8 @@ pub fn build(b: *std.Build) void {
module_spec_tests.addImport("state_transition", module_state_transition);
module_spec_tests.addImport("ssz", module_ssz);
module_spec_tests.addImport("blst", dep_blst.module("blst"));
module_spec_tests.addImport("persistent_merkle_tree", module_persistent_merkle_tree);
module_spec_tests.addImport("hex", module_hex);

module_ssz_generic_spec_tests.addImport("hex", module_hex);
module_ssz_generic_spec_tests.addImport("snappy", dep_snappy.module("snappy"));
Expand Down
55 changes: 55 additions & 0 deletions src/persistent_merkle_tree/gindex.zig
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ pub const Gindex = enum(GindexUint) {
}
}.lessThan);
}

/// Concatenate multiple Generalized Indices.
/// Given generalized indices i1 for A -> B, i2 for B -> C, ..., i_n for Y -> Z,
/// returns the generalized index for A -> Z.
///
pub fn concat(gindices: []const Gindex) Gindex {
if (gindices.len == 0) {
return Gindex.fromUint(1); // Root gindex
}

var result = gindices[0];
for (gindices[1..]) |gindex| {
const path_len = gindex.pathLen();
const gindex_path = @intFromEnum(gindex) & ((@as(GindexUint, 1) << @intCast(path_len)) - 1);
result = @enumFromInt((@intFromEnum(result) << @intCast(path_len)) | gindex_path);
}
return result;
}
};

test {
Expand All @@ -115,3 +133,40 @@ test {
try std.testing.expectEqualSlices(u1, &[_]u1{ 0, 1, 0 }, b.toPathBits(&bits));
try std.testing.expectEqual(@as(Gindex.Path, @enumFromInt(2)), b.toPath());
}

test "concat gindices" {
// [2, 3] -> 5
const case1: []const Gindex = &.{ Gindex.fromUint(2), Gindex.fromUint(3) };
try std.testing.expectEqual(@as(GindexUint, 5), @intFromEnum(Gindex.concat(case1)));

// [31, 3] -> 63
const case2: []const Gindex = &.{ Gindex.fromUint(31), Gindex.fromUint(3) };
try std.testing.expectEqual(@as(GindexUint, 63), @intFromEnum(Gindex.concat(case2)));

// [31, 6] -> 126
const case3: []const Gindex = &.{ Gindex.fromUint(31), Gindex.fromUint(6) };
try std.testing.expectEqual(@as(GindexUint, 126), @intFromEnum(Gindex.concat(case3)));

const empty: []const Gindex = &.{};
try std.testing.expectEqual(@as(GindexUint, 1), @intFromEnum(Gindex.concat(empty)));

const single: []const Gindex = &.{Gindex.fromUint(42)};
try std.testing.expectEqual(@as(GindexUint, 42), @intFromEnum(Gindex.concat(single)));

// [1, 5] -> 5
const with_root: []const Gindex = &.{ Gindex.fromUint(1), Gindex.fromUint(5) };
try std.testing.expectEqual(@as(GindexUint, 5), @intFromEnum(Gindex.concat(with_root)));

// [5, 1] -> 5
const root_suffix: []const Gindex = &.{ Gindex.fromUint(5), Gindex.fromUint(1) };
try std.testing.expectEqual(@as(GindexUint, 5), @intFromEnum(Gindex.concat(root_suffix)));

// [2, 2, 2] -> 8 (going left 3 times from root)
const three_lefts: []const Gindex = &.{ Gindex.fromUint(2), Gindex.fromUint(2), Gindex.fromUint(2) };
try std.testing.expectEqual(@as(GindexUint, 8), @intFromEnum(Gindex.concat(three_lefts)));

// [3, 3, 3] -> 15 (going right 3 times from root)
// concat(3, 3) = 7, concat(7, 3) = 15
const three_rights: []const Gindex = &.{ Gindex.fromUint(3), Gindex.fromUint(3), Gindex.fromUint(3) };
try std.testing.expectEqual(@as(GindexUint, 15), @intFromEnum(Gindex.concat(three_rights)));
}
2 changes: 2 additions & 0 deletions src/ssz/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub const VariableVectorType = types.VariableVectorType;
pub const FixedContainerType = types.FixedContainerType;
pub const VariableContainerType = types.VariableContainerType;

pub const getPathGindex = types.getPathGindex;

const hasher = @import("hasher.zig");
pub const Hasher = hasher.Hasher;
pub const HasherData = hasher.HasherData;
Expand Down
1 change: 1 addition & 0 deletions src/ssz/type/byte_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ pub fn ByteListType(comptime _limit: comptime_int) type {
}

const nodes = try allocator.alloc(Node.Id, chunk_count);
defer allocator.free(nodes);
Comment thread
twoeths marked this conversation as resolved.
for (0..chunk_count) |i| {
var leaf_buf = [_]u8{0} ** 32;
const start_idx = i * 32;
Expand Down
78 changes: 71 additions & 7 deletions src/ssz/type/path.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const std = @import("std");
const isFixedType = @import("type_kind.zig").isFixedType;
const isBasicType = @import("type_kind.zig").isBasicType;
const Gindex = @import("persistent_merkle_tree").Gindex;
const BYTES_PER_CHUNK = @import("root.zig").BYTES_PER_CHUNK;

const PathItemType = union(enum) {
child: struct {
Expand Down Expand Up @@ -131,20 +134,81 @@ pub fn PathType(comptime ST: type, comptime path_str: []const u8) type {
}
}

const types = @import("root.zig");
/// Get the gindex for a field/element relative to the parent type.
fn getFieldGindex(comptime item: PathItem) Gindex {
const ST = item.ST;
switch (item.item_type) {
.child => |child| {
switch (ST.kind) {
.container => {
return Gindex.fromDepth(ST.chunk_depth, child.index);
},
.vector, .list => {
// Lists have an extra depth level for the length mixin
const depth = ST.chunk_depth + @as(u8, if (ST.kind == .list) 1 else 0);
const chunk_index = if (comptime isBasicType(ST.Element))
child.index / (BYTES_PER_CHUNK / ST.Element.fixed_size)
else
child.index;
return Gindex.fromDepth(depth, chunk_index);
},
else => @compileError("Cannot get field gindex for basic types"),
}
},
.length => {
// Length node is at gindex 3 (right child of root in list structure)
return Gindex.fromDepth(1, 1);
},
}
}

test {
// std.testing.refAllDecls(@This());
/// Get the gindex for a path relative to the root of the type.
pub fn getPathGindex(comptime ST: type, comptime path_str: []const u8) Gindex {
const items = getPathItems(ST, path_str);
var gindices: [items.len + 1]Gindex = undefined;
gindices[0] = Gindex.fromUint(1); // root

inline for (items, 0..) |item, i| {
gindices[i + 1] = getFieldGindex(item);
}

return Gindex.concat(&gindices);
}

const types = @import("root.zig");

test "PathType" {
const Root = types.ByteVectorType(32);
const Checkpoint = types.FixedContainerType(struct {
slot: types.UintType(64),
root: Root,
});

_ = PathType(Checkpoint, "slot");
// _ = getPath(Checkpoint, "root");
// _ = getPath(Checkpoint, "root.31");
// _ = getPath(Root, "0");
// _ = getOffset(Checkpoint, "root.20");
}

test "getPathGindex" {
const Root = types.ByteVectorType(32);
const Checkpoint = types.FixedContainerType(struct {
epoch: types.UintType(64),
root: Root,
});

try std.testing.expectEqual(@as(Gindex.Uint, 2), @intFromEnum(getPathGindex(Checkpoint, "epoch")));
try std.testing.expectEqual(@as(Gindex.Uint, 3), @intFromEnum(getPathGindex(Checkpoint, "root")));

const BeaconState = types.FixedContainerType(struct {
slot: types.UintType(64),
finalized_checkpoint: Checkpoint,
});

try std.testing.expectEqual(@as(Gindex.Uint, 7), @intFromEnum(getPathGindex(BeaconState, "finalized_checkpoint.root")));

const Balances = types.FixedListType(types.UintType(64), 4);
const SimpleState = types.VariableContainerType(struct {
slot: types.UintType(64),
balances: Balances,
});

try std.testing.expectEqual(@as(Gindex.Uint, 6), @intFromEnum(getPathGindex(SimpleState, "balances.0")));
}
3 changes: 3 additions & 0 deletions src/ssz/type/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub const VariableVectorType = @import("vector.zig").VariableVectorType;
pub const FixedContainerType = @import("container.zig").FixedContainerType;
pub const VariableContainerType = @import("container.zig").VariableContainerType;

pub const getPathGindex = @import("path.zig").getPathGindex;

pub const BYTES_PER_CHUNK: usize = 32;

test {
Expand All @@ -40,6 +42,7 @@ test {
_ = @import("byte_vector.zig");
_ = @import("list.zig");
_ = @import("container.zig");
_ = @import("path.zig");
}

const std = @import("std");
3 changes: 2 additions & 1 deletion src/state_transition/cache/epoch_cache.zig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllF
const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks;
const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache;
const computeEpochAtSlot = @import("../utils/epoch.zig").computeEpochAtSlot;
const computePreviousEpoch = @import("../utils/epoch.zig").computePreviousEpoch;
const computeActivationExitEpoch = @import("../utils/epoch.zig").computeActivationExitEpoch;
const getEffectiveBalanceIncrementsWithLen = @import("./effective_balance_increments.zig").getEffectiveBalanceIncrementsWithLen;
const getTotalSlashingsByIncrement = @import("../epoch/process_slashings.zig").getTotalSlashingsByIncrement;
Expand Down Expand Up @@ -624,7 +625,7 @@ pub const EpochCache = struct {
}

pub fn getShufflingAtEpochOrNull(self: *const EpochCache, epoch: Epoch) ?*const EpochShuffling {
const previous_epoch = if (self.epoch == GENESIS_EPOCH) GENESIS_EPOCH else self.epoch - 1;
const previous_epoch = computePreviousEpoch(self.epoch);
const shuffling = if (epoch == previous_epoch)
self.getPreviousShuffling()
else if (epoch == self.epoch) self.getCurrentShuffling() else if (epoch == self.epoch + 1)
Expand Down
7 changes: 7 additions & 0 deletions src/state_transition/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub const processInactivityUpdates = @import("./epoch/process_inactivity_updates
pub const processRegistryUpdates = @import("./epoch/process_registry_updates.zig").processRegistryUpdates;
pub const processSlashings = @import("./epoch/process_slashings.zig").processSlashings;
pub const processRewardsAndPenalties = @import("./epoch/process_rewards_and_penalties.zig").processRewardsAndPenalties;
pub const getRewardsAndPenalties = @import("./epoch/process_rewards_and_penalties.zig").getRewardsAndPenalties;
pub const processEth1DataReset = @import("./epoch/process_eth1_data_reset.zig").processEth1DataReset;
pub const processPendingDeposits = @import("./epoch/process_pending_deposits.zig").processPendingDeposits;
pub const processPendingConsolidations = @import("./epoch/process_pending_consolidations.zig").processPendingConsolidations;
Expand All @@ -38,6 +39,12 @@ pub const processParticipationFlagUpdates = @import("./epoch/process_participati
pub const processSyncCommitteeUpdates = @import("./epoch/process_sync_committee_updates.zig").processSyncCommitteeUpdates;
pub const getNextSyncCommitteeIndices = @import("./utils/sync_committee.zig").getNextSyncCommitteeIndices;

pub const upgradeStateToAltair = @import("./slot/upgrade_state_to_altair.zig").upgradeStateToAltair;
pub const upgradeStateToBellatrix = @import("./slot/upgrade_state_to_bellatrix.zig").upgradeStateToBellatrix;
pub const upgradeStateToCapella = @import("./slot/upgrade_state_to_capella.zig").upgradeStateToCapella;
pub const upgradeStateToDeneb = @import("./slot/upgrade_state_to_deneb.zig").upgradeStateToDeneb;
pub const upgradeStateToElectra = @import("./slot/upgrade_state_to_electra.zig").upgradeStateToElectra;

// Block
pub const processBlockHeader = @import("./block/process_block_header.zig").processBlockHeader;
pub const processWithdrawals = @import("./block/process_withdrawals.zig").processWithdrawals;
Expand Down
3 changes: 2 additions & 1 deletion src/state_transition/slot/upgrade_state_to_altair.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllF
const getNextSyncCommittee = @import("../utils/sync_committee.zig").getNextSyncCommittee;
const SyncCommitteeInfo = @import("../utils/sync_committee.zig").SyncCommitteeInfo;
const sumTargetUnslashedBalanceIncrements = @import("../utils/target_unslashed_balance.zig").sumTargetUnslashedBalanceIncrements;
const computePreviousEpoch = @import("../utils/epoch.zig").computePreviousEpoch;
const types = @import("consensus_types");
const ValidatorIndex = types.primitive.ValidatorIndex.Type;
const RootCache = @import("../utils/root_cache.zig").RootCache;
Expand Down Expand Up @@ -52,7 +53,7 @@ pub fn upgradeStateToAltair(allocator: Allocator, cached_state: *CachedBeaconSta
try cached_state.epoch_cache_ref.get().setSyncCommitteesIndexed(sync_committee_info.indices.items);
try translateParticipation(allocator, cached_state, phase0_state.previous_epoch_attestations);

const previous_epoch = epoch_cache.epoch - 1;
const previous_epoch = computePreviousEpoch(epoch_cache.epoch);
epoch_cache.previous_target_unslashed_balance_increments = sumTargetUnslashedBalanceIncrements(state.previousEpochParticipations().items, previous_epoch, state.validators().items);
}

Expand Down
7 changes: 5 additions & 2 deletions src/state_transition/utils/epoch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,12 @@ pub fn getCurrentEpoch(state: BeaconStateAllForks) Epoch {
return computeEpochAtSlot(state.slot());
}

pub fn computePreviousEpoch(epoch: Epoch) Epoch {
return if (epoch == GENESIS_EPOCH) GENESIS_EPOCH else epoch - 1;
}

pub fn getPreviousEpoch(state: BeaconStateAllForks) Epoch {
const current_epoch = getCurrentEpoch(state);
return if (current_epoch == GENESIS_EPOCH) GENESIS_EPOCH else current_epoch - 1;
return computePreviousEpoch(getCurrentEpoch(state));
}

pub fn computeSyncPeriodAtSlot(slot: Slot) SyncPeriod {
Expand Down
8 changes: 5 additions & 3 deletions src/state_transition/utils/finality.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ const std = @import("std");
const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks;
const preset = @import("preset").preset;
const MIN_EPOCHS_TO_INACTIVITY_PENALTY = preset.MIN_EPOCHS_TO_INACTIVITY_PENALTY;
const computePreviousEpoch = @import("./epoch.zig").computePreviousEpoch;

pub fn getFinalityDelay(cached_state: *const CachedBeaconStateAllForks) u64 {
std.debug.assert(cached_state.getEpochCache().epoch > 0);
std.debug.assert(cached_state.getEpochCache().epoch >= cached_state.state.finalizedCheckpoint().epoch + 1);
const previous_epoch = computePreviousEpoch(cached_state.getEpochCache().epoch);
std.debug.assert(previous_epoch >= cached_state.state.finalizedCheckpoint().epoch);

// previous_epoch = epoch - 1
return cached_state.getEpochCache().epoch - 1 - cached_state.state.finalizedCheckpoint().epoch;
return previous_epoch - cached_state.state.finalizedCheckpoint().epoch;
}

/// If the chain has not been finalized for >4 epochs, the chain enters an "inactivity leak" mode,
Expand Down
3 changes: 3 additions & 0 deletions test/spec/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
const testing = @import("std").testing;

comptime {
testing.refAllDecls(@import("./test_case/merkle_proof_tests.zig"));
testing.refAllDecls(@import("./test_case/operations_tests.zig"));
testing.refAllDecls(@import("./test_case/rewards_tests.zig"));
testing.refAllDecls(@import("./test_case/sanity_tests.zig"));
testing.refAllDecls(@import("./test_case/epoch_processing_tests.zig"));
testing.refAllDecls(@import("./test_case/fork_tests.zig"));
testing.refAllDecls(@import("./test_case/transition_tests.zig"));
}
Loading