diff --git a/.gitignore b/.gitignore index 7680583e8..a7dc9f15f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,9 @@ test/spec/test_case/ *.ssz test/spec/ssz/generic_tests.zig test/spec/ssz/static_tests.zig -*.era \ No newline at end of file +*.era + +# perf +flamegraph.html +perf.data +perf.data.old diff --git a/bench/state_transition/process_block.zig b/bench/state_transition/process_block.zig index a2a3f845a..835cccf32 100644 --- a/bench/state_transition/process_block.zig +++ b/bench/state_transition/process_block.zig @@ -5,13 +5,16 @@ const std = @import("std"); const zbench = @import("zbench"); +const Node = @import("persistent_merkle_tree").Node; const state_transition = @import("state_transition"); const types = @import("consensus_types"); const config = @import("config"); +const download_era_options = @import("download_era_options"); +const era = @import("era"); const preset = state_transition.preset; const ForkSeq = config.ForkSeq; -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const CachedBeaconState = state_transition.CachedBeaconState; +const BeaconState = state_transition.BeaconState; const SignedBlock = state_transition.SignedBlock; const SignedBeaconBlock = state_transition.SignedBeaconBlock; const Body = state_transition.Body; @@ -29,11 +32,11 @@ const BenchOpts = struct { }; const ProcessBlockHeaderBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: ProcessBlockHeaderBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -44,11 +47,11 @@ const ProcessBlockHeaderBench = struct { }; const ProcessWithdrawalsBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: ProcessWithdrawalsBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -81,11 +84,11 @@ const ProcessWithdrawalsBench = struct { }; const ProcessExecutionPayloadBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, body: Body, pub fn run(self: ProcessExecutionPayloadBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -97,11 +100,11 @@ const ProcessExecutionPayloadBench = struct { fn ProcessRandaoBench(comptime opts: BenchOpts) type { return struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: @This(), allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -114,11 +117,11 @@ fn ProcessRandaoBench(comptime opts: BenchOpts) type { } const ProcessEth1DataBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: ProcessEth1DataBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -131,11 +134,11 @@ const ProcessEth1DataBench = struct { fn ProcessOperationsBench(comptime opts: BenchOpts) type { return struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: @This(), allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -149,11 +152,11 @@ fn ProcessOperationsBench(comptime opts: BenchOpts) type { fn ProcessSyncAggregateBench(comptime opts: BenchOpts) type { return struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: @This(), allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -167,11 +170,11 @@ fn ProcessSyncAggregateBench(comptime opts: BenchOpts) type { fn ProcessBlockBench(comptime opts: BenchOpts) type { return struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, pub fn run(self: @This(), allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -237,12 +240,12 @@ fn printSegmentStats(stdout: anytype) !void { } const ProcessBlockSegmentedBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, signed_block: SignedBlock, body: Body, pub fn run(self: @This(), allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -258,7 +261,7 @@ const ProcessBlockSegmentedBench = struct { state_transition.processBlockHeader(allocator, cloned, block) catch unreachable; recordSegment(.block_header, elapsedSince(header_start)); - if (state.isPostCapella()) { + if (state.forkSeq().gte(.capella)) { const withdrawals_start = std.time.nanoTimestamp(); var withdrawals_result = WithdrawalsResult{ .withdrawals = Withdrawals.initCapacity(allocator, preset.MAX_WITHDRAWALS_PER_PAYLOAD) catch unreachable, @@ -280,7 +283,7 @@ const ProcessBlockSegmentedBench = struct { recordSegment(.withdrawals, elapsedSince(withdrawals_start)); } - if (state.isPostBellatrix()) { + if (state.forkSeq().gte(.bellatrix)) { const exec_start = std.time.nanoTimestamp(); const external_data = BlockExternalData{ .execution_payload_status = .valid, .data_availability_status = .available }; state_transition.processExecutionPayload(allocator, cloned, self.body, external_data) catch unreachable; @@ -299,7 +302,7 @@ const ProcessBlockSegmentedBench = struct { state_transition.processOperations(allocator, cloned, beacon_body, .{ .verify_signature = true }) catch unreachable; recordSegment(.operations, elapsedSince(ops_start)); - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { const sync_start = std.time.nanoTimestamp(); state_transition.processSyncAggregate(allocator, cloned, beacon_body.syncAggregate(), true) catch unreachable; recordSegment(.sync_aggregate, elapsedSince(sync_start)); @@ -310,17 +313,24 @@ const ProcessBlockSegmentedBench = struct { }; pub fn main() !void { - const allocator = std.heap.page_allocator; + var gpa: std.heap.DebugAllocator(.{}) = .init; + const allocator = gpa.allocator(); const stdout = std.io.getStdOut().writer(); + var pool = try Node.Pool.init(allocator, 10_000_000); + defer pool.deinit(); - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); - const state_path = if (args.len > 1) args[1] else "bench/state_transition/state.ssz"; - const block_path = if (args.len > 2) args[2] else "bench/state_transition/block.ssz"; + // Use download_era_options.era_files[0] for state - const state_file = try std.fs.cwd().openFile(state_path, .{}); - defer state_file.close(); - const state_bytes = try state_file.readToEndAlloc(allocator, 10_000_000_000); + const era_path_0 = try std.fs.path.join( + allocator, + &[_][]const u8{ download_era_options.era_out_dir, download_era_options.era_files[0] }, + ); + defer allocator.free(era_path_0); + + var era_reader_0 = try era.Reader.open(allocator, config.mainnet.config, era_path_0); + defer era_reader_0.close(allocator); + + const state_bytes = try era_reader_0.readSerializedState(allocator, null); defer allocator.free(state_bytes); const chain_config = config.mainnet.chain_config; @@ -328,36 +338,52 @@ pub fn main() !void { const detected_fork = config.mainnet.config.forkSeq(slot); try stdout.print("Benchmarking processBlock with state at fork: {s} (slot {})\n", .{ @tagName(detected_fork), slot }); - const block_file = try std.fs.cwd().openFile(block_path, .{}); - defer block_file.close(); - const block_bytes = try block_file.readToEndAlloc(allocator, 100_000_000); + // Use download_era_options.era_files[1] for state + + const era_path_1 = try std.fs.path.join( + allocator, + &[_][]const u8{ download_era_options.era_out_dir, download_era_options.era_files[1] }, + ); + defer allocator.free(era_path_1); + + var era_reader_1 = try era.Reader.open(allocator, config.mainnet.config, era_path_1); + defer era_reader_1.close(allocator); + + const block_slot = try era.era.computeStartBlockSlotFromEraNumber(era_reader_1.era_number) + 1; + + const block_bytes = try era_reader_1.readSerializedBlock(allocator, block_slot) orelse return error.InvalidEraFile; defer allocator.free(block_bytes); inline for (comptime std.enums.values(ForkSeq)) |fork| { - if (detected_fork == fork) return runBenchmark(fork, allocator, stdout, state_bytes, block_bytes, chain_config); + if (detected_fork == fork) return runBenchmark(fork, allocator, &pool, stdout, state_bytes, block_bytes, chain_config); } return error.NoBenchmarkRan; } -fn runBenchmark(comptime fork: ForkSeq, allocator: std.mem.Allocator, stdout: anytype, state_bytes: []const u8, block_bytes: []const u8, chain_config: config.ChainConfig) !void { - const beacon_state = try loadState(fork, allocator, state_bytes); +fn runBenchmark(comptime fork: ForkSeq, allocator: std.mem.Allocator, pool: *Node.Pool, stdout: anytype, state_bytes: []const u8, block_bytes: []const u8, chain_config: config.ChainConfig) !void { + const beacon_state = try loadState(fork, allocator, pool, state_bytes); const signed_beacon_block = try loadBlock(fork, allocator, block_bytes); const block_slot = signed_beacon_block.beaconBlock().slot(); + try stdout.print("Block: slot: {}\n", .{block_slot}); - const beacon_config = config.BeaconConfig.init(chain_config, beacon_state.genesisValidatorsRoot()); + const beacon_config = config.BeaconConfig.init(chain_config, (try beacon_state.genesisValidatorsRoot()).*); const pubkey_index_map = try PubkeyIndexMap.init(allocator); const index_pubkey_cache = try allocator.create(state_transition.Index2PubkeyCache); index_pubkey_cache.* = state_transition.Index2PubkeyCache.init(allocator); - try state_transition.syncPubkeys(beacon_state.validators().items, pubkey_index_map, index_pubkey_cache); + const validators = try beacon_state.validatorsSlice(allocator); + defer allocator.free(validators); + + try state_transition.syncPubkeys(validators, pubkey_index_map, index_pubkey_cache); - const cached_state = try CachedBeaconStateAllForks.createCachedBeaconState(allocator, beacon_state, .{ + const cached_state = try CachedBeaconState.createCachedBeaconState(allocator, beacon_state, .{ .config = &beacon_config, .index_to_pubkey = index_pubkey_cache, .pubkey_to_index = pubkey_index_map, }, .{ .skip_sync_committee_cache = !comptime fork.gte(.altair), .skip_sync_pubkeys = false }); - try state_transition.state_transition.processSlotsWithTransientCache(allocator, cached_state, block_slot, .{}); - try stdout.print("State: slot={}, validators={}\n", .{ cached_state.state.slot(), beacon_state.validators().items.len }); + try state_transition.state_transition.processSlots(allocator, cached_state, block_slot, .{}); + try cached_state.state.commit(); + try stdout.print("State: slot={}, validators={}\n", .{ try cached_state.state.slot(), try beacon_state.validatorsCount() }); const signed_block = SignedBlock{ .regular = signed_beacon_block }; const body = Body{ .regular = signed_beacon_block.beaconBlock().beaconBlockBody() }; @@ -390,7 +416,7 @@ fn runBenchmark(comptime fork: ForkSeq, allocator: std.mem.Allocator, stdout: an try bench.addParam("process_block", &ProcessBlockBench(.{ .verify_signature = true }){ .cached_state = cached_state, .signed_block = signed_block }, .{}); try bench.addParam("process_block_no_sig", &ProcessBlockBench(.{ .verify_signature = false }){ .cached_state = cached_state, .signed_block = signed_block }, .{}); - // Segmented benchmark (step-by-step timing) + // // Segmented benchmark (step-by-step timing) resetSegmentStats(); try bench.addParam("block(segments)", &ProcessBlockSegmentedBench{ .cached_state = cached_state, .signed_block = signed_block, .body = body }, .{}); diff --git a/bench/state_transition/process_epoch.zig b/bench/state_transition/process_epoch.zig index d921b362a..cd5f5d4b5 100644 --- a/bench/state_transition/process_epoch.zig +++ b/bench/state_transition/process_epoch.zig @@ -1,16 +1,18 @@ //! Benchmark for fork-specific epoch processing. //! //! Uses a mainnet state at slot 13180928. -//! Run with: zig build run:bench_process_epoch -Doptimize=ReleaseFast [-- /path/to/state.ssz] +//! Run with: zig build run:bench_process_epoch -Doptimize=ReleaseFast const std = @import("std"); const zbench = @import("zbench"); +const Node = @import("persistent_merkle_tree").Node; const state_transition = @import("state_transition"); const types = @import("consensus_types"); const config = @import("config"); +const download_era_options = @import("download_era_options"); +const era = @import("era"); const ForkSeq = config.ForkSeq; -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const CachedBeaconState = state_transition.CachedBeaconState; const EpochTransitionCache = state_transition.EpochTransitionCache; const ValidatorIndex = types.primitive.ValidatorIndex.Type; const PubkeyIndexMap = state_transition.PubkeyIndexMap(ValidatorIndex); @@ -18,10 +20,10 @@ const slotFromStateBytes = @import("utils.zig").slotFromStateBytes; const loadState = @import("utils.zig").loadState; const ProcessJustificationAndFinalizationBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessJustificationAndFinalizationBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -36,10 +38,10 @@ const ProcessJustificationAndFinalizationBench = struct { }; const ProcessInactivityUpdatesBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessInactivityUpdatesBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -54,10 +56,10 @@ const ProcessInactivityUpdatesBench = struct { }; const ProcessRewardsAndPenaltiesBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessRewardsAndPenaltiesBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -72,10 +74,10 @@ const ProcessRewardsAndPenaltiesBench = struct { }; const ProcessRegistryUpdatesBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessRegistryUpdatesBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -90,10 +92,10 @@ const ProcessRegistryUpdatesBench = struct { }; const ProcessSlashingsBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessSlashingsBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -108,10 +110,10 @@ const ProcessSlashingsBench = struct { }; const ProcessEth1DataResetBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessEth1DataResetBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -121,15 +123,15 @@ const ProcessEth1DataResetBench = struct { cache.deinit(); allocator.destroy(cache); } - state_transition.processEth1DataReset(allocator, cloned, cache); + state_transition.processEth1DataReset(cloned, cache) catch unreachable; } }; const ProcessPendingDepositsBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessPendingDepositsBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -144,10 +146,10 @@ const ProcessPendingDepositsBench = struct { }; const ProcessPendingConsolidationsBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessPendingConsolidationsBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -157,15 +159,15 @@ const ProcessPendingConsolidationsBench = struct { cache.deinit(); allocator.destroy(cache); } - state_transition.processPendingConsolidations(allocator, cloned, cache) catch unreachable; + state_transition.processPendingConsolidations(cloned, cache) catch unreachable; } }; const ProcessEffectiveBalanceUpdatesBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessEffectiveBalanceUpdatesBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -175,15 +177,15 @@ const ProcessEffectiveBalanceUpdatesBench = struct { cache.deinit(); allocator.destroy(cache); } - _ = state_transition.processEffectiveBalanceUpdates(cloned, cache) catch unreachable; + _ = state_transition.processEffectiveBalanceUpdates(allocator, cloned, cache) catch unreachable; } }; const ProcessSlashingsResetBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessSlashingsResetBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -193,15 +195,15 @@ const ProcessSlashingsResetBench = struct { cache.deinit(); allocator.destroy(cache); } - state_transition.processSlashingsReset(cloned, cache); + state_transition.processSlashingsReset(cloned, cache) catch unreachable; } }; const ProcessRandaoMixesResetBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessRandaoMixesResetBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -211,15 +213,15 @@ const ProcessRandaoMixesResetBench = struct { cache.deinit(); allocator.destroy(cache); } - state_transition.processRandaoMixesReset(cloned, cache); + state_transition.processRandaoMixesReset(cloned, cache) catch unreachable; } }; const ProcessHistoricalSummariesUpdateBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessHistoricalSummariesUpdateBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -229,28 +231,28 @@ const ProcessHistoricalSummariesUpdateBench = struct { cache.deinit(); allocator.destroy(cache); } - state_transition.processHistoricalSummariesUpdate(allocator, cloned, cache) catch unreachable; + state_transition.processHistoricalSummariesUpdate(cloned, cache) catch unreachable; } }; const ProcessParticipationFlagUpdatesBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessParticipationFlagUpdatesBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); } - state_transition.processParticipationFlagUpdates(allocator, cloned) catch unreachable; + state_transition.processParticipationFlagUpdates(cloned) catch unreachable; } }; const ProcessSyncCommitteeUpdatesBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessSyncCommitteeUpdatesBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -260,10 +262,10 @@ const ProcessSyncCommitteeUpdatesBench = struct { }; const ProcessProposerLookaheadBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessProposerLookaheadBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -337,10 +339,10 @@ fn printSegmentStats(stdout: anytype) !void { } const ProcessEpochBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessEpochBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -357,10 +359,10 @@ const ProcessEpochBench = struct { }; const ProcessEpochSegmentedBench = struct { - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pub fn run(self: ProcessEpochSegmentedBench, allocator: std.mem.Allocator) void { - const cloned = self.cached_state.clone(allocator) catch unreachable; + const cloned = self.cached_state.clone(allocator, .{}) catch unreachable; defer { cloned.deinit(); allocator.destroy(cloned); @@ -380,7 +382,7 @@ const ProcessEpochSegmentedBench = struct { state_transition.processJustificationAndFinalization(cloned, cache) catch unreachable; recordSegment(.justification_finalization, elapsedSince(jf_start)); - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { const inactivity_start = std.time.nanoTimestamp(); state_transition.processInactivityUpdates(cloned, cache) catch unreachable; recordSegment(.inactivity_updates, elapsedSince(inactivity_start)); @@ -399,58 +401,58 @@ const ProcessEpochSegmentedBench = struct { recordSegment(.rewards_and_penalties, elapsedSince(rewards_start)); const eth1_start = std.time.nanoTimestamp(); - state_transition.processEth1DataReset(allocator, cloned, cache); + state_transition.processEth1DataReset(cloned, cache) catch unreachable; recordSegment(.eth1_data_reset, elapsedSince(eth1_start)); - if (state.isPostElectra()) { + if (state.forkSeq().gte(.electra)) { const pending_deposits_start = std.time.nanoTimestamp(); state_transition.processPendingDeposits(allocator, cloned, cache) catch unreachable; recordSegment(.pending_deposits, elapsedSince(pending_deposits_start)); const pending_consolidations_start = std.time.nanoTimestamp(); - state_transition.processPendingConsolidations(allocator, cloned, cache) catch unreachable; + state_transition.processPendingConsolidations(cloned, cache) catch unreachable; recordSegment(.pending_consolidations, elapsedSince(pending_consolidations_start)); } const eb_start = std.time.nanoTimestamp(); - _ = state_transition.processEffectiveBalanceUpdates(cloned, cache) catch unreachable; + _ = state_transition.processEffectiveBalanceUpdates(allocator, cloned, cache) catch unreachable; recordSegment(.effective_balance_updates, elapsedSince(eb_start)); const slashings_reset_start = std.time.nanoTimestamp(); - state_transition.processSlashingsReset(cloned, cache); + state_transition.processSlashingsReset(cloned, cache) catch unreachable; recordSegment(.slashings_reset, elapsedSince(slashings_reset_start)); const randao_reset_start = std.time.nanoTimestamp(); - state_transition.processRandaoMixesReset(cloned, cache); + state_transition.processRandaoMixesReset(cloned, cache) catch unreachable; recordSegment(.randao_mixes_reset, elapsedSince(randao_reset_start)); - if (state.isPostCapella()) { + if (state.forkSeq().gte(.capella)) { const historical_summaries_start = std.time.nanoTimestamp(); - state_transition.processHistoricalSummariesUpdate(allocator, cloned, cache) catch unreachable; + state_transition.processHistoricalSummariesUpdate(cloned, cache) catch unreachable; recordSegment(.historical_summaries, elapsedSince(historical_summaries_start)); } else { const historical_roots_start = std.time.nanoTimestamp(); - state_transition.processHistoricalRootsUpdate(allocator, cloned, cache) catch unreachable; + state_transition.processHistoricalRootsUpdate(cloned, cache) catch unreachable; recordSegment(.historical_roots, elapsedSince(historical_roots_start)); } - if (state.isPhase0()) { + if (state.forkSeq() == .phase0) { const participation_record_start = std.time.nanoTimestamp(); - state_transition.processParticipationRecordUpdates(allocator, cloned); + state_transition.processParticipationRecordUpdates(cloned) catch unreachable; recordSegment(.participation_record, elapsedSince(participation_record_start)); } else { const participation_flag_start = std.time.nanoTimestamp(); - state_transition.processParticipationFlagUpdates(allocator, cloned) catch unreachable; + state_transition.processParticipationFlagUpdates(cloned) catch unreachable; recordSegment(.participation_flags, elapsedSince(participation_flag_start)); } - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { const sync_updates_start = std.time.nanoTimestamp(); state_transition.processSyncCommitteeUpdates(allocator, cloned) catch unreachable; recordSegment(.sync_committee_updates, elapsedSince(sync_updates_start)); } - if (state.isFulu()) { + if (state.forkSeq() == .fulu) { const lookahead_start = std.time.nanoTimestamp(); state_transition.processProposerLookahead(allocator, cloned, cache) catch unreachable; recordSegment(.proposer_lookahead, elapsedSince(lookahead_start)); @@ -461,20 +463,23 @@ const ProcessEpochSegmentedBench = struct { }; pub fn main() !void { - const allocator = std.heap.page_allocator; + var gpa: std.heap.DebugAllocator(.{}) = .init; + const allocator = gpa.allocator(); const stdout = std.io.getStdOut().writer(); + var pool = try Node.Pool.init(allocator, 10_000_000); + defer pool.deinit(); - // Parse CLI args for state file path - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); + // Use download_era_options.era_files[0] for state + const era_path = try std.fs.path.join( + allocator, + &[_][]const u8{ download_era_options.era_out_dir, download_era_options.era_files[0] }, + ); + defer allocator.free(era_path); - const state_path = if (args.len > 1) args[1] else "bench/state_transition/state.ssz"; + var era_reader = try era.Reader.open(allocator, config.mainnet.config, era_path); + defer era_reader.close(allocator); - try stdout.print("Loading state from {s}...\n", .{state_path}); - - const state_file = try std.fs.cwd().openFile(state_path, .{}); - defer state_file.close(); - const state_bytes = try state_file.readToEndAlloc(allocator, 10_000_000_000); + const state_bytes = try era_reader.readSerializedState(allocator, null); defer allocator.free(state_bytes); try stdout.print("State file loaded: {} bytes\n", .{state_bytes.len}); @@ -488,7 +493,7 @@ pub fn main() !void { // Dispatch to fork-specific loading inline for (comptime std.enums.values(ForkSeq)) |fork| { if (detected_fork == fork) { - return runBenchmark(fork, allocator, stdout, state_bytes, chain_config); + return runBenchmark(fork, allocator, &pool, stdout, state_bytes, chain_config); } } return error.NoBenchmarkRan; @@ -497,23 +502,27 @@ pub fn main() !void { fn runBenchmark( comptime fork: ForkSeq, allocator: std.mem.Allocator, + pool: *Node.Pool, stdout: anytype, state_bytes: []const u8, chain_config: config.ChainConfig, ) !void { - const beacon_state = try loadState(fork, allocator, state_bytes); + const beacon_state = try loadState(fork, allocator, pool, state_bytes); try stdout.print("State deserialized: slot={}, validators={}\n", .{ - beacon_state.slot(), - beacon_state.validators().items.len, + try beacon_state.slot(), + try beacon_state.validatorsCount(), }); - const beacon_config = config.BeaconConfig.init(chain_config, beacon_state.genesisValidatorsRoot()); + const beacon_config = config.BeaconConfig.init(chain_config, (try beacon_state.genesisValidatorsRoot()).*); const pubkey_index_map = try PubkeyIndexMap.init(allocator); const index_pubkey_cache = try allocator.create(state_transition.Index2PubkeyCache); index_pubkey_cache.* = state_transition.Index2PubkeyCache.init(allocator); - try state_transition.syncPubkeys(beacon_state.validators().items, pubkey_index_map, index_pubkey_cache); + const validators = try beacon_state.validatorsSlice(allocator); + defer allocator.free(validators); + + try state_transition.syncPubkeys(validators, pubkey_index_map, index_pubkey_cache); const immutable_data = state_transition.EpochCacheImmutableData{ .config = &beacon_config, @@ -521,12 +530,12 @@ fn runBenchmark( .pubkey_to_index = pubkey_index_map, }; - const cached_state = try CachedBeaconStateAllForks.createCachedBeaconState(allocator, beacon_state, immutable_data, .{ + const cached_state = try CachedBeaconState.createCachedBeaconState(allocator, beacon_state, immutable_data, .{ .skip_sync_committee_cache = !comptime fork.gte(.altair), .skip_sync_pubkeys = false, }); - try stdout.print("Cached state created at slot {}\n", .{cached_state.state.slot()}); + try stdout.print("Cached state created at slot {}\n", .{try cached_state.state.slot()}); try stdout.print("\nStarting process_epoch benchmarks for {s} fork...\n\n", .{@tagName(fork)}); var bench = zbench.Benchmark.init(allocator, .{ .iterations = 50 }); diff --git a/bench/state_transition/utils.zig b/bench/state_transition/utils.zig index 6b571a54a..9bac65026 100644 --- a/bench/state_transition/utils.zig +++ b/bench/state_transition/utils.zig @@ -1,12 +1,13 @@ //! Shared utilities for state transition benchmarks const std = @import("std"); +const Node = @import("persistent_merkle_tree").Node; const types = @import("consensus_types"); const config = @import("config"); const state_transition = @import("state_transition"); const ForkSeq = config.ForkSeq; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const BeaconState = state_transition.BeaconState; const Slot = types.primitive.Slot.Type; /// Read slot from raw BeaconState SSZ bytes (offset 40) @@ -22,15 +23,17 @@ pub fn slotFromBlockBytes(block_bytes: []const u8) Slot { } /// Load and deserialize BeaconState from SSZ bytes for a specific fork -pub fn loadState(comptime fork: ForkSeq, allocator: std.mem.Allocator, state_bytes: []const u8) !*BeaconStateAllForks { - const BeaconState = @field(types, @tagName(fork)).BeaconState; - const state_data = try allocator.create(BeaconState.Type); - errdefer allocator.destroy(state_data); - state_data.* = BeaconState.default_value; - try BeaconState.deserializeFromBytes(allocator, state_bytes, state_data); +pub fn loadState(comptime fork: ForkSeq, allocator: std.mem.Allocator, pool: *Node.Pool, state_bytes: []const u8) !*BeaconState { + const BeaconStateST = @field(types, @tagName(fork)).BeaconState; + var state_data = try BeaconStateST.TreeView.init( + allocator, + pool, + try BeaconStateST.tree.deserializeFromBytes(pool, state_bytes), + ); + errdefer state_data.deinit(); - const beacon_state = try allocator.create(BeaconStateAllForks); - beacon_state.* = @unionInit(BeaconStateAllForks, @tagName(fork), state_data); + const beacon_state = try allocator.create(BeaconState); + beacon_state.* = @unionInit(BeaconState, @tagName(fork), state_data); return beacon_state; } diff --git a/build.zig b/build.zig index 0846f2aad..adcfd7640 100644 --- a/build.zig +++ b/build.zig @@ -383,6 +383,8 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("bench/state_transition/process_block.zig"), .target = target, .optimize = optimize, + .strip = false, + .omit_frame_pointer = false, }); b.modules.put(b.dupe("bench_process_block"), module_bench_process_block) catch @panic("OOM"); @@ -870,6 +872,7 @@ pub fn build(b: *std.Build) void { module_era.addImport("preset", module_preset); module_era.addImport("state_transition", module_state_transition); module_era.addImport("snappy", dep_snappy.module("snappy")); + module_era.addImport("persistent_merkle_tree", module_persistent_merkle_tree); module_hashing.addImport("build_options", options_module_build_options); module_hashing.addImport("hex", module_hex); @@ -895,6 +898,7 @@ pub fn build(b: *std.Build) void { module_state_transition.addImport("preset", module_preset); module_state_transition.addImport("constants", module_constants); module_state_transition.addImport("hex", module_hex); + module_state_transition.addImport("persistent_merkle_tree", module_persistent_merkle_tree); module_download_era_files.addImport("download_era_options", options_module_download_era_options); @@ -936,11 +940,17 @@ pub fn build(b: *std.Build) void { module_bench_process_block.addImport("consensus_types", module_consensus_types); module_bench_process_block.addImport("config", module_config); module_bench_process_block.addImport("zbench", dep_zbench.module("zbench")); + module_bench_process_block.addImport("persistent_merkle_tree", module_persistent_merkle_tree); + module_bench_process_block.addImport("download_era_options", options_module_download_era_options); + module_bench_process_block.addImport("era", module_era); module_bench_process_epoch.addImport("state_transition", module_state_transition); module_bench_process_epoch.addImport("consensus_types", module_consensus_types); module_bench_process_epoch.addImport("config", module_config); module_bench_process_epoch.addImport("zbench", dep_zbench.module("zbench")); + module_bench_process_epoch.addImport("persistent_merkle_tree", module_persistent_merkle_tree); + module_bench_process_epoch.addImport("download_era_options", options_module_download_era_options); + module_bench_process_epoch.addImport("era", module_era); module_int.addImport("build_options", options_module_build_options); module_int.addImport("state_transition", module_state_transition); @@ -949,6 +959,7 @@ pub fn build(b: *std.Build) void { module_int.addImport("preset", module_preset); module_int.addImport("constants", module_constants); module_int.addImport("blst", dep_blst.module("blst")); + module_int.addImport("persistent_merkle_tree", module_persistent_merkle_tree); module_int_slow.addImport("config", module_config); module_int_slow.addImport("download_era_options", options_module_download_era_options); diff --git a/src/consensus_types/fulu.zig b/src/consensus_types/fulu.zig index 9b9e61004..a7a90832f 100644 --- a/src/consensus_types/fulu.zig +++ b/src/consensus_types/fulu.zig @@ -112,6 +112,8 @@ pub const BlindedBeaconBlockBody = electra.BlindedBeaconBlockBody; pub const BlindedBeaconBlock = electra.BlindedBeaconBlock; pub const SignedBlindedBeaconBlock = electra.SignedBlindedBeaconBlock; +pub const ProposerLookahead = ssz.FixedVectorType(p.ValidatorIndex, (preset.MIN_SEED_LOOKAHEAD + 1) * preset.SLOTS_PER_EPOCH); + // BeaconState with new proposer_lookahead field pub const BeaconState = ssz.VariableContainerType(struct { genesis_time: p.Uint64, @@ -151,7 +153,7 @@ pub const BeaconState = ssz.VariableContainerType(struct { pending_deposits: ssz.FixedListType(PendingDeposit, preset.PENDING_DEPOSITS_LIMIT), pending_partial_withdrawals: ssz.FixedListType(PendingPartialWithdrawal, preset.PENDING_PARTIAL_WITHDRAWALS_LIMIT), pending_consolidations: ssz.FixedListType(PendingConsolidation, preset.PENDING_CONSOLIDATIONS_LIMIT), - proposer_lookahead: ssz.FixedVectorType(p.ValidatorIndex, (preset.MIN_SEED_LOOKAHEAD + 1) * preset.SLOTS_PER_EPOCH), + proposer_lookahead: ProposerLookahead, }); pub const SignedBeaconBlock = ssz.VariableContainerType(struct { diff --git a/src/era/Reader.zig b/src/era/Reader.zig index 00bfdd170..661418fa5 100644 --- a/src/era/Reader.zig +++ b/src/era/Reader.zig @@ -3,6 +3,7 @@ const std = @import("std"); const c = @import("config"); const preset = @import("preset").preset; +const Node = @import("persistent_merkle_tree").Node; const state_transition = @import("state_transition"); const snappy = @import("snappy").frame; const e2s = @import("e2s.zig"); @@ -17,19 +18,37 @@ era_number: u64, short_historical_root: [8]u8, /// An array of state and block indices, one per group group_indices: []era.GroupIndex, +/// Persistent merkle tree pool used by TreeViews (must outlive returned views) +pool: *Node.Pool, const Reader = @This(); pub fn open(allocator: std.mem.Allocator, config: c.BeaconConfig, path: []const u8) !Reader { const file = try std.fs.cwd().openFile(path, .{}); + errdefer file.close(); const era_file_name = try era.EraFileName.parse(path); const group_indices = try era.readAllGroupIndices(allocator, file); + errdefer { + for (group_indices) |group_index| { + allocator.free(group_index.state_index.offsets); + if (group_index.blocks_index) |bi| { + allocator.free(bi.offsets); + } + } + allocator.free(group_indices); + } + + const pool = try allocator.create(Node.Pool); + errdefer allocator.destroy(pool); + pool.* = try Node.Pool.init(allocator, 500_000); + errdefer pool.deinit(); return .{ .config = config, .file = file, .era_number = era_file_name.era_number, .short_historical_root = era_file_name.short_historical_root, .group_indices = group_indices, + .pool = pool, }; } @@ -42,6 +61,11 @@ pub fn close(self: *Reader, allocator: std.mem.Allocator) void { } } allocator.free(self.group_indices); + + self.pool.deinit(); + allocator.destroy(self.pool); + + self.* = undefined; } pub fn readCompressedState(self: Reader, allocator: std.mem.Allocator, era_number: ?u64) ![]const u8 { @@ -67,14 +91,14 @@ pub fn readSerializedState(self: Reader, allocator: std.mem.Allocator, era_numbe return try snappy.uncompress(allocator, compressed) orelse error.InvalidE2SHeader; } -pub fn readState(self: Reader, allocator: std.mem.Allocator, era_number: ?u64) !state_transition.BeaconStateAllForks { +pub fn readState(self: Reader, allocator: std.mem.Allocator, era_number: ?u64) !state_transition.BeaconState { const serialized = try self.readSerializedState(allocator, era_number); defer allocator.free(serialized); const state_slot = era.readSlotFromBeaconStateBytes(serialized); const state_fork = self.config.forkSeq(state_slot); - return try state_transition.BeaconStateAllForks.deserialize(allocator, state_fork, serialized); + return try state_transition.BeaconState.deserialize(allocator, self.pool, state_fork, serialized); } pub fn readCompressedBlock(self: Reader, allocator: std.mem.Allocator, slot: u64) !?[]const u8 { @@ -143,9 +167,9 @@ pub fn validate(self: Reader, allocator: std.mem.Allocator) !void { // validate state // the state is loadable and consistent with the given config var state = try self.readState(allocator, era_number); - defer state.deinit(allocator); + defer state.deinit(); - if (!std.mem.eql(u8, &self.config.genesis_validator_root, &state.genesisValidatorsRoot())) { + if (!std.mem.eql(u8, &self.config.genesis_validator_root, try state.genesisValidatorsRoot())) { return error.GenesisValidatorRootMismatch; } @@ -166,18 +190,21 @@ pub fn validate(self: Reader, allocator: std.mem.Allocator) !void { return error.InvalidBlockIndex; } - const blockRoots = state.blockRoots(); + var blockRoots = try state.blockRoots(); for (start_slot..end_slot) |slot| { const block = try self.readBlock(allocator, slot) orelse { if (slot == start_slot) { // first slot in the era can't be easily validated continue; } - if (std.mem.eql( - u8, - &blockRoots[(slot - 1) % preset.SLOTS_PER_HISTORICAL_ROOT], - &blockRoots[slot % preset.SLOTS_PER_HISTORICAL_ROOT], - )) { + var prev_root: [32]u8 = undefined; + var curr_root: [32]u8 = undefined; + var prev_view = try blockRoots.get(@intCast((slot - 1) % preset.SLOTS_PER_HISTORICAL_ROOT)); + try prev_view.toValue(allocator, &prev_root); + var curr_view = try blockRoots.get(@intCast(slot % preset.SLOTS_PER_HISTORICAL_ROOT)); + try curr_view.toValue(allocator, &curr_root); + + if (std.mem.eql(u8, &prev_root, &curr_root)) { continue; } return error.MissingBlock; @@ -187,11 +214,11 @@ pub fn validate(self: Reader, allocator: std.mem.Allocator) !void { var block_root: [32]u8 = undefined; try block.beaconBlock().hashTreeRoot(allocator, &block_root); - if (!std.mem.eql( - u8, - &blockRoots[slot % preset.SLOTS_PER_HISTORICAL_ROOT], - &block_root, - )) { + var expected_root: [32]u8 = undefined; + var expected_view = try blockRoots.get(@intCast(slot % preset.SLOTS_PER_HISTORICAL_ROOT)); + try expected_view.toValue(allocator, &expected_root); + + if (!std.mem.eql(u8, &expected_root, &block_root)) { return error.BlockRootMismatch; } } diff --git a/src/era/Writer.zig b/src/era/Writer.zig index 48cc13e27..028eca048 100644 --- a/src/era/Writer.zig +++ b/src/era/Writer.zig @@ -184,8 +184,9 @@ pub fn writeSerializedState(self: *Writer, allocator: std.mem.Allocator, slot: u try self.writeCompressedState(allocator, slot, short_historical_root, compressed); } -pub fn writeState(self: *Writer, allocator: std.mem.Allocator, state: state_transition.BeaconStateAllForks) !void { - const slot = state.slot(); +pub fn writeState(self: *Writer, allocator: std.mem.Allocator, state: state_transition.BeaconState) !void { + var s = state; + const slot = try s.slot(); const short_historical_root = try era.getShortHistoricalRoot(state); const serialized = try state.serialize(allocator); defer allocator.free(serialized); diff --git a/src/era/era.zig b/src/era/era.zig index 1335c7800..9563076c2 100644 --- a/src/era/era.zig +++ b/src/era/era.zig @@ -133,15 +133,26 @@ pub fn computeStartBlockSlotFromEraNumber(era_number: u64) !u64 { return (try std.math.sub(u64, era_number, 1)) * preset.SLOTS_PER_HISTORICAL_ROOT; } -pub fn getShortHistoricalRoot(state: state_transition.BeaconStateAllForks) ![8]u8 { +pub fn getShortHistoricalRoot(state: state_transition.BeaconState) ![8]u8 { + const allocator = std.heap.page_allocator; var short_historical_root: [8]u8 = undefined; - const historical_root = if (state.slot() == 0) - state.genesisValidatorsRoot() - else if (state.forkSeq().gte(.capella)) blk: { - var root: [32]u8 = undefined; - try ct.capella.HistoricalSummary.hashTreeRoot(&state.historicalSummaries().getLast(), &root); - break :blk root; - } else state.historicalRoots().getLast(); + var s = state; + var historical_root: [32]u8 = undefined; + if (try s.slot() == 0) { + historical_root = (try s.genesisValidatorsRoot()).*; + } else if (s.forkSeq().gte(.capella)) { + var summaries = try s.historicalSummaries(); + const len = try summaries.length(); + if (len == 0) return error.EmptyHistoricalSummaries; + var last = try summaries.get(len - 1); + historical_root = (try last.hashTreeRoot()).*; + } else { + var roots = try s.historicalRoots(); + const len = try roots.length(); + if (len == 0) return error.EmptyHistoricalRoots; + var last = try roots.get(len - 1); + try last.toValue(allocator, &historical_root); + } _ = try std.fmt.bufPrint(&short_historical_root, "{x}", .{std.fmt.fmtSliceHexLower(historical_root[0..4])}); return short_historical_root; diff --git a/src/ssz/tree_view/array_basic.zig b/src/ssz/tree_view/array_basic.zig index f6e77e5a0..d072bbc72 100644 --- a/src/ssz/tree_view/array_basic.zig +++ b/src/ssz/tree_view/array_basic.zig @@ -61,8 +61,10 @@ pub fn ArrayBasicTreeView(comptime ST: type) type { self.base_view.clearCache(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.base_view.hashTreeRoot(out); + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); } pub fn get(self: *Self, index: usize) !Element { @@ -94,6 +96,11 @@ pub fn ArrayBasicTreeView(comptime ST: type) type { pub fn serializedSize(_: *Self) usize { return ST.fixed_size; } + + pub fn toValue(self: *Self, _: Allocator, out: *ST.Type) !void { + try self.commit(); + try ST.tree.toValue(self.base_view.data.root, self.base_view.pool, out); + } }; } @@ -129,10 +136,9 @@ test "TreeView vector element roundtrip" { var expected_root: [32]u8 = undefined; try VectorType.hashTreeRoot(&expected, &expected_root); - var actual_root: [32]u8 = undefined; - try view.hashTreeRoot(&actual_root); + const actual_root = try view.hashTreeRoot(); - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); var roundtrip: VectorType.Type = undefined; try VectorType.tree.toValue(view.base_view.data.root, &pool, &roundtrip); @@ -203,6 +209,7 @@ test "TreeView vector getAllAlloc repeat reflects updates" { try view.set(3, 99); + try view.commit(); const second = try view.getAll(allocator); defer allocator.free(second); values[3] = 99; @@ -376,9 +383,8 @@ test "ArrayBasicTreeView - serialize (uint64 vector)" { const view_size = view.serializedSize(); try std.testing.expectEqual(tc.expected_serialized.len, view_size); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); - try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root); + const hash_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &tc.expected_root, hash_root); } } diff --git a/src/ssz/tree_view/array_composite.zig b/src/ssz/tree_view/array_composite.zig index 63c067a15..d80919c7e 100644 --- a/src/ssz/tree_view/array_composite.zig +++ b/src/ssz/tree_view/array_composite.zig @@ -61,8 +61,17 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type { self.base_view.clearCache(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.base_view.hashTreeRoot(out); + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); + } + + pub fn getRoot(self: *Self, index: usize) !*const [32]u8 { + if (index >= length) return error.IndexOutOfBounds; + const field_data = try self.base_view.getChildData(Gindex.fromDepth(chunk_depth, index)); + try field_data.commit(self.base_view.allocator, self.base_view.pool); + return field_data.root.getRoot(self.base_view.pool); } pub fn get(self: *Self, index: usize) !Element { @@ -71,10 +80,13 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type { } pub fn getReadonly(self: *Self, index: usize) !Element { - // TODO: Implement read-only access after other PRs land. - _ = self; - _ = index; - return error.NotImplemented; + if (index >= length) return error.IndexOutOfBounds; + return try Chunks.getReadonly(&self.base_view, index); + } + + pub fn getValue(self: *Self, allocator: Allocator, index: usize, out: *ST.Element.Type) !void { + if (index >= length) return error.IndexOutOfBounds; + return try Chunks.getValue(&self.base_view, allocator, index, out); } pub fn set(self: *Self, index: usize, value: Element) !void { @@ -82,6 +94,11 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type { try Chunks.set(&self.base_view, index, value); } + pub fn setValue(self: *Self, index: usize, value: *const ST.Element.Type) !void { + if (index >= length) return error.IndexOutOfBounds; + try Chunks.setValue(&self.base_view, index, value); + } + pub fn getAllReadonly(self: *Self, allocator: Allocator) ![]Element { // TODO: Implement bulk read-only access after other PRs land. _ = self; @@ -89,6 +106,16 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type { return error.NotImplemented; } + /// Get all element values in a single traversal. + /// + /// WARNING: Returns all committed changes. If there are any pending changes, + /// commit them beforehand. + /// + /// Caller owns the returned slice and must free it with the same allocator. + pub fn getAllReadonlyValues(self: *Self, allocator: Allocator) ![]ST.Element.Type { + return try Chunks.getAllValues(&self.base_view, allocator, length); + } + /// Serialize the tree view into a provided buffer. /// Returns the number of bytes written. pub fn serializeIntoBytes(self: *Self, out: []u8) !usize { @@ -105,6 +132,15 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type { return try ST.tree.serializedSize(self.base_view.data.root, self.base_view.pool); } } + + pub fn toValue(self: *Self, allocator: Allocator, out: *ST.Type) !void { + try self.commit(); + if (comptime isFixedType(ST)) { + try ST.tree.toValue(self.base_view.data.root, self.base_view.pool, out); + } else { + try ST.tree.toValue(allocator, self.base_view.data.root, self.base_view.pool, out); + } + } }; } @@ -148,13 +184,12 @@ test "TreeView vector composite element set/get/commit" { try view.commit(); - var actual_root: [32]u8 = undefined; - try view.hashTreeRoot(&actual_root); + const actual_root = try view.hashTreeRoot(); var expected: VectorType.Type = .{ v0, replacement }; var expected_root: [32]u8 = undefined; try VectorType.hashTreeRoot(&expected, &expected_root); - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); var roundtrip: VectorType.Type = undefined; try VectorType.tree.toValue(view.base_view.data.root, &pool, &roundtrip); @@ -215,13 +250,12 @@ test "TreeView vector composite clearCache does not break subsequent commits" { try view.set(0, replacement_view.?); replacement_view = null; - var actual_root: [32]u8 = undefined; - try view.hashTreeRoot(&actual_root); + const actual_root = try view.hashTreeRoot(); var expected: VectorType.Type = .{ replacement, v1 }; var expected_root: [32]u8 = undefined; try VectorType.hashTreeRoot(&expected, &expected_root); - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } test "TreeView vector composite clone isolates updates" { @@ -476,11 +510,10 @@ test "ArrayCompositeTreeView - serialize (Container vector)" { }; try std.testing.expectEqualSlices(u8, &expected, &view_serialized); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); + const actual_root = try view.hashTreeRoot(); // 0xb1a797eb50654748ba239010edccea7b46b55bf740730b700684f48b0c478372 const expected_root = [_]u8{ 0xb1, 0xa7, 0x97, 0xeb, 0x50, 0x65, 0x47, 0x48, 0xba, 0x23, 0x90, 0x10, 0xed, 0xcc, 0xea, 0x7b, 0x46, 0xb5, 0x5b, 0xf7, 0x40, 0x73, 0x0b, 0x70, 0x06, 0x84, 0xf4, 0x8b, 0x0c, 0x47, 0x83, 0x72 }; - try std.testing.expectEqualSlices(u8, &expected_root, &hash_root); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } test "ArrayCompositeTreeView - get and set" { @@ -504,7 +537,6 @@ test "ArrayCompositeTreeView - get and set" { defer view.deinit(); var elem0 = try view.get(0); - defer elem0.deinit(); var bytes0: [Root32.fixed_size]u8 = undefined; const bytes0_written = try elem0.serializeIntoBytes(&bytes0); try std.testing.expectEqual(bytes0.len, bytes0_written); @@ -516,7 +548,6 @@ test "ArrayCompositeTreeView - get and set" { try view.set(1, new_elem); var elem1 = try view.get(1); - defer elem1.deinit(); var bytes1: [Root32.fixed_size]u8 = undefined; const bytes1_written = try elem1.serializeIntoBytes(&bytes1); try std.testing.expectEqual(bytes1.len, bytes1_written); diff --git a/src/ssz/tree_view/base.zig b/src/ssz/tree_view/base.zig index 8657118bb..92f6cba8c 100644 --- a/src/ssz/tree_view/base.zig +++ b/src/ssz/tree_view/base.zig @@ -16,19 +16,22 @@ pub const TreeViewData = struct { children_nodes: std.AutoHashMapUnmanaged(Gindex, Node.Id), /// cached data for faster access of already-visited children - children_data: std.AutoHashMapUnmanaged(Gindex, TreeViewData), + children_data: std.AutoHashMapUnmanaged(Gindex, *TreeViewData), /// whether the corresponding child node/data has changed since the last update of the root changed: std.AutoArrayHashMapUnmanaged(Gindex, void), - pub fn init(pool: *Node.Pool, root: Node.Id) !TreeViewData { + pub fn init(allocator: Allocator, pool: *Node.Pool, root: Node.Id) !*TreeViewData { + const data = try allocator.create(TreeViewData); + errdefer allocator.destroy(data); try pool.ref(root); - return TreeViewData{ + data.* = TreeViewData{ .root = root, .children_nodes = .empty, .children_data = .empty, .changed = .empty, }; + return data; } /// Deinitialize the Data and free all associated resources. @@ -40,6 +43,7 @@ pub const TreeViewData = struct { self.clearChildrenDataCache(allocator, pool); self.children_data.deinit(allocator); self.changed.deinit(allocator); + allocator.destroy(self); } pub fn clearChildrenNodesCache(self: *TreeViewData, pool: *Node.Pool) void { @@ -56,7 +60,7 @@ pub const TreeViewData = struct { pub fn clearChildrenDataCache(self: *TreeViewData, allocator: Allocator, pool: *Node.Pool) void { var value_iter = self.children_data.valueIterator(); while (value_iter.next()) |child_data| { - child_data.deinit(allocator, pool); + child_data.*.deinit(allocator, pool); } self.children_data.clearRetainingCapacity(); } @@ -69,11 +73,17 @@ pub const TreeViewData = struct { const nodes = try allocator.alloc(Node.Id, self.changed.count()); defer allocator.free(nodes); - const gindices = self.changed.keys(); - Gindex.sortAsc(gindices); + const SortCtx = struct { + keys: []Gindex, + pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { + return @intFromEnum(ctx.keys[a_index]) < @intFromEnum(ctx.keys[b_index]); + } + }; + self.changed.sortUnstable(SortCtx{ .keys = self.changed.keys() }); + const gindices_sorted = self.changed.keys(); - for (gindices, 0..) |gindex, i| { - if (self.children_data.getPtr(gindex)) |child_data| { + for (gindices_sorted, 0..) |gindex, i| { + if (self.children_data.get(gindex)) |child_data| { try child_data.commit(allocator, pool); nodes[i] = child_data.root; } else if (self.children_nodes.get(gindex)) |child_node| { @@ -83,12 +93,17 @@ pub const TreeViewData = struct { } } - const new_root = try self.root.setNodesGrouped(pool, gindices, nodes); + const new_root = try self.root.setNodesGrouped(pool, gindices_sorted, nodes); try pool.ref(new_root); pool.unref(self.root); self.root = new_root; self.changed.clearRetainingCapacity(); + + // The tree root has changed and any cached child nodes may now be stale (e.g. for + // ancestor gindices of the modified nodes). Clear cached nodes to avoid returning + // nodes from the previous root in subsequent reads/writes. + self.clearChildrenNodesCache(pool); } }; @@ -103,7 +118,7 @@ pub const TreeViewData = struct { pub const BaseTreeView = struct { allocator: Allocator, pool: *Node.Pool, - data: TreeViewData, + data: *TreeViewData, pub const CloneOpts = struct { /// When true, transfer *safe* cache entries from `self` into the clone. @@ -115,7 +130,7 @@ pub const BaseTreeView = struct { return BaseTreeView{ .allocator = allocator, .pool = pool, - .data = try TreeViewData.init(pool, root), + .data = try TreeViewData.init(allocator, pool, root), }; } @@ -191,9 +206,9 @@ pub const BaseTreeView = struct { self.data.changed.clearRetainingCapacity(); } - pub fn hashTreeRoot(self: *BaseTreeView, out: *[32]u8) !void { + pub fn hashTreeRoot(self: *BaseTreeView) !*const [32]u8 { try self.commit(); - out.* = self.data.root.getRoot(self.pool).*; + return self.data.root.getRoot(self.pool); } pub fn getChildNode(self: *BaseTreeView, gindex: Gindex) !Node.Id { @@ -208,6 +223,7 @@ pub const BaseTreeView = struct { pub fn setChildNode(self: *BaseTreeView, gindex: Gindex, node: Node.Id) !void { try self.data.changed.put(self.allocator, gindex, {}); + const opt_old_node = try self.data.children_nodes.fetchPut( self.allocator, gindex, @@ -222,30 +238,38 @@ pub const BaseTreeView = struct { } } - pub fn getChildData(self: *BaseTreeView, gindex: Gindex) !TreeViewData { + pub fn getChildData(self: *BaseTreeView, gindex: Gindex) !*TreeViewData { + const child_data = try self.getChildDataReadonly(gindex); + try self.data.changed.put(self.allocator, gindex, {}); + return child_data; + } + + pub fn getChildDataReadonly(self: *BaseTreeView, gindex: Gindex) !*TreeViewData { const gop = try self.data.children_data.getOrPut(self.allocator, gindex); if (gop.found_existing) { return gop.value_ptr.*; } - const child_node = try self.getChildNode(gindex); - const child_data = try TreeViewData.init(self.pool, child_node); + errdefer _ = self.data.children_data.remove(gindex); + const child_node = try self.data.root.getNode(self.pool, gindex); + const child_data = try TreeViewData.init(self.allocator, self.pool, child_node); gop.value_ptr.* = child_data; - // TODO only update changed if the subview is mutable - try self.data.changed.put(self.allocator, gindex, {}); return child_data; } - pub fn setChildData(self: *BaseTreeView, gindex: Gindex, child_data: TreeViewData) !void { + pub fn setChildData(self: *BaseTreeView, gindex: Gindex, child_data: *TreeViewData) !void { try self.data.changed.put(self.allocator, gindex, {}); + const opt_old_data = try self.data.children_data.fetchPut( self.allocator, gindex, child_data, ); - if (opt_old_data) |old_data_value| { - var old_data = @constCast(&old_data_value.value); - old_data.deinit(self.allocator, self.pool); + if (opt_old_data) |old_data| { + if (old_data.value == child_data) { + return; + } + old_data.value.deinit(self.allocator, self.pool); } } }; diff --git a/src/ssz/tree_view/bit_list.zig b/src/ssz/tree_view/bit_list.zig index 2a031f79b..1bcc6a442 100644 --- a/src/ssz/tree_view/bit_list.zig +++ b/src/ssz/tree_view/bit_list.zig @@ -54,9 +54,10 @@ pub fn BitListTreeView(comptime ST: type) type { self.base_view.clearCache(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.commit(); - out.* = self.base_view.data.root.getRoot(self.base_view.pool).*; + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); } fn readLength(self: *Self) !usize { diff --git a/src/ssz/tree_view/bit_vector.zig b/src/ssz/tree_view/bit_vector.zig index b0d13041d..cdec46be9 100644 --- a/src/ssz/tree_view/bit_vector.zig +++ b/src/ssz/tree_view/bit_vector.zig @@ -51,8 +51,10 @@ pub fn BitVectorTreeView(comptime ST: type) type { self.base_view.clearCache(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.base_view.hashTreeRoot(out); + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); } pub fn get(self: *Self, index: usize) !Element { @@ -63,17 +65,20 @@ pub fn BitVectorTreeView(comptime ST: type) type { return BitOps.set(&self.base_view, index, value, length); } - /// Caller must free the returned slice. - pub fn toBoolArray(self: *Self, allocator: Allocator) ![]bool { - const values = try allocator.alloc(bool, length); - errdefer allocator.free(values); - try self.toBoolArrayInto(values); + pub fn toBoolArray(self: *Self) ![length]bool { + var values: [length]bool = undefined; + try self.toBoolArrayInto(&values); return values; } pub fn toBoolArrayInto(self: *Self, out: []bool) !void { try BitOps.fillBools(&self.base_view, out, length); } + + pub fn toValue(self: *Self, _: Allocator, out: *ST.Type) !void { + try self.commit(); + try ST.tree.toValue(self.base_view.data.root, self.base_view.pool, out); + } }; } diff --git a/src/ssz/tree_view/chunks.zig b/src/ssz/tree_view/chunks.zig index 022a67dee..63dc38810 100644 --- a/src/ssz/tree_view/chunks.zig +++ b/src/ssz/tree_view/chunks.zig @@ -7,6 +7,8 @@ const Depth = hashing.Depth; const Node = @import("persistent_merkle_tree").Node; const Gindex = @import("persistent_merkle_tree").Gindex; +const isFixedType = @import("../type/type_kind.zig").isFixedType; + const tree_view_root = @import("root.zig"); const BaseTreeView = tree_view_root.BaseTreeView; const TreeViewData = tree_view_root.TreeViewData; @@ -29,18 +31,11 @@ pub fn BasicPackedChunks( pub fn set(base_view: *BaseTreeView, index: usize, value: Element) !void { const gindex = Gindex.fromDepth(chunk_depth, index / items_per_chunk); - try base_view.data.changed.put(base_view.allocator, gindex, {}); const child_node = try base_view.getChildNode(gindex); - const opt_old_node = try base_view.data.children_nodes.fetchPut( - base_view.allocator, + try base_view.setChildNode( gindex, try ST.Element.tree.fromValuePacked(child_node, base_view.pool, index, &value), ); - if (opt_old_node) |old_node| { - if (old_node.value.getState(base_view.pool).getRefCount() == 0) { - base_view.pool.unref(old_node.value); - } - } } pub fn getAll( @@ -61,6 +56,11 @@ pub fn BasicPackedChunks( if (values.len != len) return error.InvalidSize; if (len == 0) return values; + // TODO revisit this restriction later + if (base_view.data.changed.count() != 0) { + return error.MustCommitBeforeBulkRead; + } + const len_full_chunks = len / items_per_chunk; const remainder = len % items_per_chunk; const chunk_count = len_full_chunks + @intFromBool(remainder != 0); @@ -120,6 +120,7 @@ pub fn CompositeChunks( ) type { return struct { pub const Element = ST.Element.TreeView; + pub const Value = ST.Element.Type; pub fn get(base_view: *BaseTreeView, index: usize) !Element { const child_data = try base_view.getChildData(Gindex.fromDepth(chunk_depth, index)); @@ -132,18 +133,98 @@ pub fn CompositeChunks( }; } + pub fn getReadonly(base_view: *BaseTreeView, index: usize) !Element { + const child_data = try base_view.getChildDataReadonly(Gindex.fromDepth(chunk_depth, index)); + return .{ + .base_view = .{ + .allocator = base_view.allocator, + .pool = base_view.pool, + .data = child_data, + }, + }; + } + + pub fn getValue(base_view: *BaseTreeView, allocator: Allocator, index: usize, out: *ST.Element.Type) !void { + var child_view = try getReadonly(base_view, index); + try child_view.toValue(allocator, out); + } + pub fn set(base_view: *BaseTreeView, index: usize, value: Element) !void { const gindex = Gindex.fromDepth(chunk_depth, index); - try base_view.data.changed.put(base_view.allocator, gindex, {}); - const opt_old_data = try base_view.data.children_data.fetchPut( + try base_view.setChildData(gindex, value.base_view.data); + } + + pub fn setValue(base_view: *BaseTreeView, index: usize, value: *const ST.Element.Type) !void { + const root = try ST.Element.tree.fromValue(base_view.pool, value); + errdefer base_view.pool.unref(root); + var child_view = try ST.Element.TreeView.init( base_view.allocator, - gindex, - value.base_view.data, + base_view.pool, + root, ); - if (opt_old_data) |old_data_value| { - var data_ptr: *TreeViewData = @constCast(&old_data_value.value); - data_ptr.deinit(base_view.allocator, base_view.pool); + errdefer child_view.deinit(); + try set(base_view, index, child_view); + } + + /// Get all element values in a single traversal. + /// + /// WARNING: Returns all committed changes. If there are any pending changes, + /// commit them beforehand. + /// + /// Caller owns the returned slice and must free it with the same allocator. + pub fn getAllValues( + base_view: *BaseTreeView, + allocator: Allocator, + len: usize, + ) ![]Value { + const values = try allocator.alloc(Value, len); + errdefer allocator.free(values); + return try getAllValuesInto(base_view, allocator, values); + } + + /// Fills `values` with all element values in a single traversal. + /// `values.len` determines the number of elements read. + pub fn getAllValuesInto( + base_view: *BaseTreeView, + allocator: Allocator, + values: []Value, + ) ![]Value { + const len = values.len; + if (len == 0) return values; + + if (base_view.data.changed.count() != 0) { + return error.MustCommitBeforeBulkRead; } + + const nodes = try allocator.alloc(Node.Id, len); + defer allocator.free(nodes); + + try base_view.data.root.getNodesAtDepth(base_view.pool, chunk_depth, 0, nodes); + + for (nodes, 0..) |node, i| { + if (comptime @hasDecl(ST.Element, "deinit")) { + errdefer { + for (values[0..i]) |*value| { + ST.Element.deinit(allocator, value); + } + } + } + + // Some variable-size value types (e.g. BitList) expect the output value to start in a valid + // initialized state because `toValue()` may call methods like `resize()` which assume internal + // buffers/sentinels are well-formed. Initialize with `default_value` to avoid mutating + // uninitialized memory during bulk reads. + if (comptime @hasDecl(ST.Element, "default_value")) { + values[i] = ST.Element.default_value; + } + if (comptime isFixedType(ST.Element)) { + try ST.Element.tree.toValue(node, base_view.pool, &values[i]); + } else { + try ST.Element.tree.toValue(allocator, node, base_view.pool, &values[i]); + } + } + + return values; } }; } diff --git a/src/ssz/tree_view/container.zig b/src/ssz/tree_view/container.zig index 4f64c5a9a..1c3bc448d 100644 --- a/src/ssz/tree_view/container.zig +++ b/src/ssz/tree_view/container.zig @@ -39,11 +39,16 @@ pub fn ContainerTreeView(comptime ST: type) type { try self.base_view.commit(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.base_view.hashTreeRoot(out); + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); } pub fn Field(comptime field_name: []const u8) type { + comptime { + @setEvalBranchQuota(20000); + } const ChildST = ST.getFieldType(field_name); if (comptime isBasicType(ChildST)) { return ChildST.Type; @@ -52,9 +57,55 @@ pub fn ContainerTreeView(comptime ST: type) type { } } + pub fn FieldValue(comptime field_name: []const u8) type { + const ChildST = ST.getFieldType(field_name); + return ChildST.Type; + } + + pub fn getGindex(comptime field_name: []const u8) Gindex { + const field_index = comptime ST.getFieldIndex(field_name); + return Gindex.fromDepth(ST.chunk_depth, field_index); + } + + pub fn getRootNode(self: *Self, comptime field_name: []const u8) !Node.Id { + const ChildST = ST.getFieldType(field_name); + const field_gindex = Self.getGindex(field_name); + if (comptime isBasicType(ChildST)) { + return try self.base_view.getChildNode(field_gindex); + } else { + const field_data = try self.base_view.getChildDataReadonly(field_gindex); + try field_data.commit(self.base_view.allocator, self.base_view.pool); + return field_data.root; + } + } + + pub fn setRootNode(self: *Self, comptime field_name: []const u8, root: Node.Id) !void { + const ChildST = ST.getFieldType(field_name); + const field_gindex = Self.getGindex(field_name); + if (comptime isBasicType(ChildST)) { + return try self.base_view.setChildNode(field_gindex, root); + } else { + const field_data = try TreeViewData.init( + self.base_view.allocator, + self.base_view.pool, + root, + ); + errdefer field_data.deinit(self.base_view.allocator, self.base_view.pool); + try self.base_view.setChildData(field_gindex, field_data); + } + } + + pub fn getRoot(self: *Self, comptime field_name: []const u8) !*const [32]u8 { + const field_node = try self.getRootNode(field_name); + return field_node.getRoot(self.base_view.pool); + } + /// Get a field by name. If the field is a basic type, returns the value directly. /// Caller borrows a copy of the value so there is no need to deinit it. pub fn get(self: *Self, comptime field_name: []const u8) !Field(field_name) { + comptime { + @setEvalBranchQuota(20000); + } const field_index = comptime ST.getFieldIndex(field_name); const ChildST = ST.getFieldType(field_name); const child_gindex = Gindex.fromDepth(ST.chunk_depth, field_index); @@ -76,11 +127,51 @@ pub fn ContainerTreeView(comptime ST: type) type { } } + pub fn getValue(self: *Self, allocator: Allocator, comptime field_name: []const u8, out: *FieldValue(field_name)) !void { + const ChildST = ST.getFieldType(field_name); + if (comptime isBasicType(ChildST)) { + out.* = try self.getReadonly(field_name); + } else { + var child_view = try self.getReadonly(field_name); + try child_view.toValue(allocator, out); + } + } + + /// Get a field by name. If the field is a basic type, returns the value directly. + /// Caller borrows a copy of the value so there is no need to deinit it. + pub fn getReadonly(self: *Self, comptime field_name: []const u8) !Field(field_name) { + comptime { + @setEvalBranchQuota(20000); + } + const field_index = comptime ST.getFieldIndex(field_name); + const ChildST = ST.getFieldType(field_name); + const child_gindex = Gindex.fromDepth(ST.chunk_depth, field_index); + if (comptime isBasicType(ChildST)) { + var value: ChildST.Type = undefined; + const child_node = try self.base_view.getChildNode(child_gindex); + try ChildST.tree.toValue(child_node, self.base_view.pool, &value); + return value; + } else { + const child_data = try self.base_view.getChildDataReadonly(child_gindex); + + return .{ + .base_view = .{ + .allocator = self.base_view.allocator, + .pool = self.base_view.pool, + .data = child_data, + }, + }; + } + } + /// Set a field by name. If the field is a basic type, pass the value directly. /// If the field is a complex type, pass a TreeView of the corresponding type. /// The caller transfers ownership of the `value` TreeView to this parent view. /// The existing TreeView, if any, will be deinited by this function. pub fn set(self: *Self, comptime field_name: []const u8, value: Field(field_name)) !void { + comptime { + @setEvalBranchQuota(20000); + } const field_index = comptime ST.getFieldIndex(field_name); const ChildST = ST.getFieldType(field_name); const child_gindex = Gindex.fromDepth(ST.chunk_depth, field_index); @@ -113,6 +204,43 @@ pub fn ContainerTreeView(comptime ST: type) type { return try ST.tree.serializedSize(self.base_view.data.root, self.base_view.pool); } } + + pub fn deserialize(allocator: Allocator, pool: *Node.Pool, bytes: []const u8) !Self { + const root = try ST.tree.deserializeFromBytes(pool, bytes); + return try Self.init(allocator, pool, root); + } + + pub fn fromValue(allocator: Allocator, pool: *Node.Pool, value: *const ST.Type) !Self { + const root = try ST.tree.fromValue(pool, value); + errdefer pool.unref(root); + return try Self.init(allocator, pool, root); + } + + pub fn toValue(self: *Self, allocator: Allocator, out: *ST.Type) !void { + try self.commit(); + if (comptime isFixedType(ST)) { + try ST.tree.toValue(self.base_view.data.root, self.base_view.pool, out); + } else { + try ST.tree.toValue(allocator, self.base_view.data.root, self.base_view.pool, out); + } + } + + pub fn setValue(self: *Self, comptime field_name: []const u8, value: *const FieldValue(field_name)) !void { + const ChildST = ST.getFieldType(field_name); + if (comptime isBasicType(ChildST)) { + try self.set(field_name, value.*); + } else { + const root = try ChildST.tree.fromValue(self.base_view.pool, value); + errdefer self.base_view.pool.unref(root); + var child_view = try ChildST.TreeView.init( + self.base_view.allocator, + self.base_view.pool, + root, + ); + errdefer child_view.deinit(); + try self.set(field_name, child_view); + } + } }; } @@ -176,14 +304,8 @@ test "TreeView container field roundtrip" { }; try Checkpoint.hashTreeRoot(&expected_checkpoint, &htr_from_value); - var htr_from_tree: [32]u8 = undefined; - try cp_view.hashTreeRoot(&htr_from_tree); - - try std.testing.expectEqualSlices( - u8, - &htr_from_value, - &htr_from_tree, - ); + const htr_from_tree = try cp_view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &htr_from_value, htr_from_tree); } test "TreeView container nested types set/get/commit" { @@ -238,6 +360,7 @@ test "TreeView container nested types set/get/commit" { try bytes_view.push(@as(u8, 0xAA)); try bytes_view.push(@as(u8, 0xBB)); try bytes_view.set(1, @as(u8, 0xCC)); + try bytes_view.commit(); const all = try bytes_view.getAll(allocator); defer allocator.free(all); @@ -254,6 +377,7 @@ test "TreeView container nested types set/get/commit" { try std.testing.expectEqual(@as(u16, 0), try basic_vec_view.get(0)); try basic_vec_view.set(0, @as(u16, 1)); try basic_vec_view.set(3, @as(u16, 4)); + try basic_vec_view.commit(); const all = try basic_vec_view.getAll(allocator); defer allocator.free(all); @@ -501,9 +625,8 @@ test "ContainerTreeView - serialize (basic fields)" { const view_size = try view.serializedSize(); try std.testing.expectEqual(tc.expected_serialized.len, view_size); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); - try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root); + const hash_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &tc.expected_root, hash_root); } } @@ -581,9 +704,8 @@ test "ContainerTreeView - serialize (with nested list)" { try std.testing.expectEqualSlices(u8, &expected_serialized, view_serialized); try std.testing.expectEqualSlices(u8, value_serialized, view_serialized); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); // 0xdc3619cbbc5ef0e0a3b38e3ca5d31c2b16868eacb6e4bcf8b4510963354315f5 const expected_root = [_]u8{ 0xdc, 0x36, 0x19, 0xcb, 0xbc, 0x5e, 0xf0, 0xe0, 0xa3, 0xb3, 0x8e, 0x3c, 0xa5, 0xd3, 0x1c, 0x2b, 0x16, 0x86, 0x8e, 0xac, 0xb6, 0xe4, 0xbc, 0xf8, 0xb4, 0x51, 0x09, 0x63, 0x35, 0x43, 0x15, 0xf5 }; - try std.testing.expectEqualSlices(u8, &expected_root, &hash_root); + const hash_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, hash_root); } diff --git a/src/ssz/tree_view/list_basic.zig b/src/ssz/tree_view/list_basic.zig index c0fcdd8ae..b6e2d01ac 100644 --- a/src/ssz/tree_view/list_basic.zig +++ b/src/ssz/tree_view/list_basic.zig @@ -61,9 +61,10 @@ pub fn ListBasicTreeView(comptime ST: type) type { self.base_view.clearCache(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.commit(); - out.* = self.base_view.data.root.getRoot(self.base_view.pool).*; + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); } pub fn length(self: *Self) !usize { @@ -72,6 +73,10 @@ pub fn ListBasicTreeView(comptime ST: type) type { return std.mem.readInt(usize, length_chunk[0..@sizeOf(usize)], .little); } + pub fn setLength(self: *Self, new_length: usize) !void { + try self.updateListLength(new_length); + } + pub fn get(self: *Self, index: usize) !Element { const list_length = try self.length(); if (index >= list_length) return error.IndexOutOfBounds; @@ -172,11 +177,70 @@ pub fn ListBasicTreeView(comptime ST: type) type { return try ST.tree.serializeIntoBytes(self.base_view.data.root, self.base_view.pool, out); } + pub fn toValue(self: *Self, allocator: Allocator, out: *ST.Type) !void { + try self.commit(); + try ST.tree.toValue(allocator, self.base_view.data.root, self.base_view.pool, out); + } + /// Get the serialized size of this tree view. pub fn serializedSize(self: *Self) !usize { try self.commit(); return try ST.tree.serializedSize(self.base_view.data.root, self.base_view.pool); } + + /// Get a read-only iterator over the elements of the list. + /// This only iterates over committed elements. + /// It is up to the caller to ensure that the iterator doesn't run past the end of the list. + /// + /// Convenience wrapper around `ReadonlyIterator.init`. + pub fn iteratorReadonly(self: *const Self, start_index: usize) ReadonlyIterator { + return ReadonlyIterator.init(self, start_index); + } + + /// Get a read-only iterator over the elements of the list. + /// This only iterates over committed elements. + /// It is up to the caller to ensure that the iterator doesn't run past the end of the list. + pub const ReadonlyIterator = struct { + tree_view: *const Self, + depth_iterator: Node.DepthIterator, + elem_index: usize, + elem_node: ?Node.Id, + + pub fn init(tree_view: *const Self, start_index: usize) ReadonlyIterator { + return .{ + .tree_view = tree_view, + .depth_iterator = Node.DepthIterator.init( + tree_view.base_view.pool, + tree_view.base_view.data.root, + ST.chunk_depth + 1, + ST.chunkIndex(start_index), + ), + .elem_index = start_index, + .elem_node = null, + }; + } + + pub fn next(self: *ReadonlyIterator) !Element { + const elem_index = self.elem_index; + const n = if (self.elem_node) |node| + if (ST.chunkIndex(self.elem_index) != (self.depth_iterator.index - 1)) + try self.depth_iterator.next() + else + node + else + try self.depth_iterator.next(); + self.elem_node = n; + self.elem_index += 1; + var out = ST.Element.default_value; + try ST.Element.tree.toValuePacked( + n, + self.tree_view.base_view.pool, + elem_index, + &out, + ); + return out; + } + }; }; } @@ -218,10 +282,8 @@ test "TreeView list element roundtrip" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &expected_list, &expected_root); - var actual_root: [32]u8 = undefined; - try view.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); var roundtrip: ListType.Type = .empty; defer roundtrip.deinit(allocator); @@ -264,10 +326,8 @@ test "TreeView list push updates cached length" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &expected, &expected_root); - var actual_root: [32]u8 = undefined; - try view.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } test "TreeView list getAllAlloc handles zero length" { @@ -354,9 +414,8 @@ test "TreeView list push batches before commit" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &expected, &expected_root); - var actual_root: [32]u8 = undefined; - try view.hashTreeRoot(&actual_root); - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } test "TreeView list push across chunk boundary resets prefetch" { @@ -385,6 +444,7 @@ test "TreeView list push across chunk boundary resets prefetch" { try std.testing.expectEqual(@as(usize, 10), try view.length()); try std.testing.expectEqual(@as(u32, 9), try view.get(9)); + try view.commit(); const filled = try view.getAll(allocator); defer allocator.free(filled); var expected: [10]u32 = [_]u32{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; @@ -604,10 +664,8 @@ test "TreeView list sliceTo returns original when truncation unnecessary" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &list, &expected_root); - var actual_root: [32]u8 = undefined; - try sliced.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try sliced.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } // Refer to https://github.com/ChainSafe/ssz/blob/7f5580c2ea69f9307300ddb6010a8bc7ce2fc471/packages/ssz/test/unit/byType/listBasic/tree.test.ts#L219-L247 @@ -667,10 +725,8 @@ test "TreeView basic list sliceTo matches incremental snapshots" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &expected, &expected_root); - var actual_root: [32]u8 = undefined; - try sliced.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try sliced.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } } @@ -711,10 +767,8 @@ test "TreeView list sliceTo truncates tail elements" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &expected, &expected_root); - var actual_root: [32]u8 = undefined; - try sliced.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try sliced.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } // Tests ported from TypeScript ssz packages/ssz/test/unit/byType/listBasic/tree.test.ts @@ -775,9 +829,8 @@ test "ListBasicTreeView - serialize (uint8 list)" { try std.testing.expectEqual(tc.expected_serialized.len, view_size); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); - try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root); + const actual_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &tc.expected_root, actual_root); } } @@ -839,9 +892,8 @@ test "ListBasicTreeView - serialize (uint64 list)" { try std.testing.expectEqual(tc.expected_serialized.len, view_size); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); - try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root); + const actual_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &tc.expected_root, actual_root); } } @@ -877,10 +929,9 @@ test "ListBasicTreeView - push and serialize" { const len = try view.length(); try std.testing.expectEqual(@as(usize, 4), len); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); const expected_root = [_]u8{ 0xba, 0xc5, 0x11, 0xd1, 0xf6, 0x41, 0xd6, 0xb8, 0x82, 0x32, 0x00, 0xbb, 0x4b, 0x3c, 0xce, 0xd3, 0xbd, 0x47, 0x20, 0x70, 0x1f, 0x18, 0x57, 0x1d, 0xff, 0x35, 0xa5, 0xd2, 0xa4, 0x01, 0x90, 0xfa }; - try std.testing.expectEqualSlices(u8, &expected_root, &hash_root); + const actual_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } test "ListBasicTreeView - sliceTo and serialize" { @@ -915,3 +966,30 @@ test "ListBasicTreeView - sliceTo and serialize" { try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 2 }, serialized); try std.testing.expectEqual(@as(usize, 2), try sliced.length()); } + +test "ListBasicTreeView - ReadonlyIterator" { + const allocator = std.testing.allocator; + + const Uint16 = UintType(16); + const ListU16Type = FixedListType(Uint16, 64); + + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var value: ListU16Type.Type = ListU16Type.default_value; + defer value.deinit(allocator); + const test_values = &[_]u16{ 10, 20, 30, 40, 50 }; + for (test_values) |v| { + try value.append(allocator, v); + } + + const tree_node = try ListU16Type.tree.fromValue(&pool, &value); + var view = try ListU16Type.TreeView.init(allocator, &pool, tree_node); + defer view.deinit(); + + var iter = view.iteratorReadonly(0); + for (test_values) |expected| { + const val = try iter.next(); + try std.testing.expectEqual(expected, val); + } +} diff --git a/src/ssz/tree_view/list_composite.zig b/src/ssz/tree_view/list_composite.zig index 16d1f933a..45d184d7a 100644 --- a/src/ssz/tree_view/list_composite.zig +++ b/src/ssz/tree_view/list_composite.zig @@ -60,9 +60,10 @@ pub fn ListCompositeTreeView(comptime ST: type) type { self.base_view.clearCache(); } - pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void { - try self.commit(); - out.* = self.base_view.data.root.getRoot(self.base_view.pool).*; + /// Return the root hash of the tree. + /// The returned array is owned by the internal pool and must not be modified. + pub fn hashTreeRoot(self: *Self) !*const [32]u8 { + return try self.base_view.hashTreeRoot(); } pub fn length(self: *Self) !usize { @@ -71,6 +72,16 @@ pub fn ListCompositeTreeView(comptime ST: type) type { return std.mem.readInt(usize, length_chunk[0..@sizeOf(usize)], .little); } + pub fn setLength(self: *Self, new_length: usize) !void { + try self.updateListLength(new_length); + } + + pub fn getRoot(self: *Self, index: usize) !*const [32]u8 { + const field_data = try self.base_view.getChildData(Gindex.fromDepth(chunk_depth, index)); + try field_data.commit(self.base_view.allocator, self.base_view.pool); + return field_data.root.getRoot(self.base_view.pool); + } + pub fn get(self: *Self, index: usize) !Element { const list_length = try self.length(); if (index >= list_length) return error.IndexOutOfBounds; @@ -78,10 +89,15 @@ pub fn ListCompositeTreeView(comptime ST: type) type { } pub fn getReadonly(self: *Self, index: usize) !Element { - // TODO: Implement read-only access after other PRs land. - _ = self; - _ = index; - return error.NotImplemented; + const list_length = try self.length(); + if (index >= list_length) return error.IndexOutOfBounds; + return try Chunks.getReadonly(&self.base_view, index); + } + + pub fn getValue(self: *Self, allocator: Allocator, index: usize, out: *ST.Element.Type) !void { + const list_length = try self.length(); + if (index >= list_length) return error.IndexOutOfBounds; + return try Chunks.getValue(&self.base_view, allocator, index, out); } pub fn set(self: *Self, index: usize, value: Element) !void { @@ -90,6 +106,12 @@ pub fn ListCompositeTreeView(comptime ST: type) type { try Chunks.set(&self.base_view, index, value); } + pub fn setValue(self: *Self, index: usize, value: *const ST.Element.Type) !void { + const list_length = try self.length(); + if (index >= list_length) return error.IndexOutOfBounds; + try Chunks.setValue(&self.base_view, index, value); + } + pub fn getAllReadonly(self: *Self, allocator: Allocator) ![]Element { // TODO: Implement bulk read-only access after other PRs land. _ = self; @@ -97,6 +119,17 @@ pub fn ListCompositeTreeView(comptime ST: type) type { return error.NotImplemented; } + /// Get all element values in a single traversal. + /// + /// WARNING: Returns all committed changes. If there are any pending changes, + /// commit them beforehand. + /// + /// Caller owns the returned slice and must free it with the same allocator. + pub fn getAllReadonlyValues(self: *Self, allocator: Allocator) ![]ST.Element.Type { + const list_length = try self.length(); + return try Chunks.getAllValues(&self.base_view, allocator, list_length); + } + /// Appends an element to the end of the list. /// /// Ownership of the `value` TreeView is transferred to the list view. @@ -104,12 +137,22 @@ pub fn ListCompositeTreeView(comptime ST: type) type { /// as it is now owned by the list. pub fn push(self: *Self, value: Element) !void { const list_length = try self.length(); - if (list_length >= ST.limit) { - return error.LengthOverLimit; - } + if (list_length >= ST.limit) return error.LengthOverLimit; try self.updateListLength(list_length + 1); - try self.set(list_length, value); + try Chunks.set(&self.base_view, list_length, value); + } + + pub fn pushValue(self: *Self, value: *const ST.Element.Type) !void { + const root = try ST.Element.tree.fromValue(self.base_view.pool, value); + errdefer self.base_view.pool.unref(root); + var child_view = try ST.Element.TreeView.init( + self.base_view.allocator, + self.base_view.pool, + root, + ); + errdefer child_view.deinit(); + try self.push(child_view); } /// Return a new view containing all elements up to and including `index`. @@ -197,6 +240,78 @@ pub fn ListCompositeTreeView(comptime ST: type) type { try self.commit(); return try ST.tree.serializedSize(self.base_view.data.root, self.base_view.pool); } + + pub fn fromValue(allocator: Allocator, pool: *Node.Pool, value: *const ST.Type) !Self { + const root = try ST.tree.fromValue(pool, value); + errdefer pool.unref(root); + return try Self.init(allocator, pool, root); + } + + pub fn toValue(self: *Self, allocator: Allocator, out: *ST.Type) !void { + try self.commit(); + try ST.tree.toValue(allocator, self.base_view.data.root, self.base_view.pool, out); + } + + /// Get a read-only iterator over the elements of the list. + /// This only iterates over committed elements. + /// It is up to the caller to ensure that the iterator doesn't run past the end of the list. + /// + /// Convenience wrapper around `ReadonlyIterator.init`. + pub fn iteratorReadonly(self: *const Self, start_index: usize) ReadonlyIterator { + return ReadonlyIterator.init(self, start_index); + } + + /// Get a read-only iterator over the elements of the list. + /// This only iterates over committed elements. + /// It is up to the caller to ensure that the iterator doesn't run past the end of the list. + pub const ReadonlyIterator = struct { + tree_view: *const Self, + depth_iterator: Node.DepthIterator, + + pub fn init(tree_view: *const Self, start_index: usize) ReadonlyIterator { + return .{ + .tree_view = tree_view, + .depth_iterator = Node.DepthIterator.init( + tree_view.base_view.pool, + tree_view.base_view.data.root, + ST.chunk_depth + 1, + start_index, + ), + }; + } + + /// Return the next element of the iterator as a TreeView. + /// Caller owns the returned TreeView. + pub fn next(self: *ReadonlyIterator) !Element { + const node = try self.depth_iterator.next(); + return try ST.Element.TreeView.init( + self.tree_view.base_view.allocator, + self.tree_view.base_view.pool, + node, + ); + } + + /// Return the root of the next element of the iterator + /// Caller must not modify the returned root. + pub fn nextRoot(self: *ReadonlyIterator) !*const [32]u8 { + const node = try self.depth_iterator.next(); + return node.getRoot(self.tree_view.base_view.pool); + } + + /// Return the next element of the iterator as a value. + /// Caller owns the returned value. + pub fn nextValue(self: *ReadonlyIterator, allocator: Allocator) !ST.Element.Type { + const node = try self.depth_iterator.next(); + var out = ST.Element.default_value; + if (comptime isFixedType(ST.Element)) { + try ST.Element.tree.toValue(node, self.tree_view.base_view.pool, &out); + } else { + try ST.Element.tree.toValue(allocator, node, self.tree_view.base_view.pool, &out); + } + + return out; + } + }; }; } @@ -355,10 +470,8 @@ test "TreeView composite list sliceFrom handles boundary conditions" { var expected_root: [32]u8 = expected_node.getRoot(&pool).*; defer pool.unref(expected_node); - var actual_root: [32]u8 = undefined; - try sliced.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try sliced.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); } } } @@ -619,6 +732,7 @@ test "TreeView list of list commits inner length updates" { try payload.push(@as(u8, 0xAA)); try payload.push(@as(u8, 0xAB)); try payload.set(1, @as(u8, 0xAC)); + try payload.commit(); { const all = try payload.getAll(allocator); defer allocator.free(all); @@ -642,6 +756,7 @@ test "TreeView list of list commits inner length updates" { try numbers.push(@as(u32, 1)); try numbers.push(@as(u32, 2)); try numbers.set(0, @as(u32, 3)); + try numbers.commit(); { const all = try numbers.getAll(allocator); defer allocator.free(all); @@ -666,6 +781,7 @@ test "TreeView list of list commits inner length updates" { try vec.set(0, @as(u32, 9)); try vec.set(1, @as(u32, 10)); + try vec.commit(); { const all = try vec.getAll(allocator); defer allocator.free(all); @@ -796,10 +912,8 @@ test "TreeView composite list sliceTo matches incremental snapshots" { var expected_root: [32]u8 = undefined; try ListType.hashTreeRoot(allocator, &expected, &expected_root); - var actual_root: [32]u8 = undefined; - try sliced.hashTreeRoot(&actual_root); - - try std.testing.expectEqualSlices(u8, &expected_root, &actual_root); + const actual_root = try sliced.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, actual_root); const serialized_len = ListType.serializedSize(&expected); const expected_bytes = try allocator.alloc(u8, serialized_len); @@ -872,9 +986,8 @@ test "ListCompositeTreeView - serialize (ByteVector32 list)" { try std.testing.expectEqualSlices(u8, value_serialized, view_serialized); try std.testing.expectEqual(value_serialized.len, view_size); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); - try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root); + const hash_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &tc.expected_root, hash_root); } } @@ -948,9 +1061,8 @@ test "ListCompositeTreeView - serialize (Container list)" { try std.testing.expectEqualSlices(u8, tc.expected_serialized, view_serialized); try std.testing.expectEqualSlices(u8, value_serialized, view_serialized); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); - try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root); + const hash_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &tc.expected_root, hash_root); } } @@ -993,8 +1105,54 @@ test "ListCompositeTreeView - push and serialize" { try std.testing.expectEqualSlices(u8, &val1, serialized[0..32]); try std.testing.expectEqualSlices(u8, &val2, serialized[32..64]); - var hash_root: [32]u8 = undefined; - try view.hashTreeRoot(&hash_root); const expected_root = [_]u8{ 0x0c, 0xb9, 0x47, 0x37, 0x7e, 0x17, 0x7f, 0x77, 0x47, 0x19, 0xea, 0xd8, 0xd2, 0x10, 0xaf, 0x9c, 0x64, 0x61, 0xf4, 0x1b, 0xaf, 0x5b, 0x40, 0x82, 0xf8, 0x6a, 0x39, 0x11, 0x45, 0x48, 0x31, 0xb8 }; - try std.testing.expectEqualSlices(u8, &expected_root, &hash_root); + const hash_root = try view.hashTreeRoot(); + try std.testing.expectEqualSlices(u8, &expected_root, hash_root); +} + +test "ListCompositeTreeView - ReadonlyIterator" { + const allocator = std.testing.allocator; + + const Root32 = ByteVectorType(32); + const ListRootsType = FixedListType(Root32, 128); + + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var value: ListRootsType.Type = ListRootsType.default_value; + defer value.deinit(allocator); + + const data = [_][32]u8{ + [_]u8{0xaa} ** 32, + [_]u8{0xbb} ** 32, + [_]u8{0xcc} ** 32, + [_]u8{0xdd} ** 32, + [_]u8{0xee} ** 32, + [_]u8{0xff} ** 32, + }; + for (data) |d| { + try value.append(allocator, d); + } + + const tree_node = try ListRootsType.tree.fromValue(&pool, &value); + var view = try ListRootsType.TreeView.init(allocator, &pool, tree_node); + defer view.deinit(); + + var iter = view.iteratorReadonly(0); + + for (data) |expected| { + var elem = try iter.next(); + defer elem.deinit(); + var elem_value: [32]u8 = undefined; + + try Root32.tree.toValue(elem.base_view.data.root, &pool, &elem_value); + try std.testing.expectEqualSlices(u8, &expected, &elem_value); + } + + iter = view.iteratorReadonly(0); + for (data) |expected| { + const elem_value = try iter.nextValue(undefined); + + try std.testing.expectEqualSlices(u8, &expected, &elem_value); + } } diff --git a/src/ssz/type/bit_list.zig b/src/ssz/type/bit_list.zig index e60ad4ba9..083dfa6f7 100644 --- a/src/ssz/type/bit_list.zig +++ b/src/ssz/type/bit_list.zig @@ -11,6 +11,7 @@ const hexLenFromBytes = @import("hex").hexLenFromBytes; const merkleize = @import("hashing").merkleize; const mixInLength = @import("hashing").mixInLength; const maxChunksToDepth = @import("hashing").maxChunksToDepth; +const getZeroHash = @import("hashing").getZeroHash; const Node = @import("persistent_merkle_tree").Node; const BitListTreeView = @import("../tree_view/root.zig").BitListTreeView; @@ -257,6 +258,12 @@ pub fn BitListType(comptime _limit: comptime_int) type { pub const default_value: Type = Type.empty; + pub const default_root: [32]u8 = blk: { + var buf = getZeroHash(chunk_depth).*; + mixInLength(0, &buf); + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { return a.equals(b); } @@ -415,6 +422,26 @@ pub fn BitListType(comptime _limit: comptime_int) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + return try pool.createBranch( + @enumFromInt(chunk_depth), + @enumFromInt(0), + ); + } + + pub fn zeros(pool: *Node.Pool, bit_len: usize) !Node.Id { + if (bit_len > limit) { + return error.tooLarge; + } + const len_mixin = try pool.createLeafFromUint(bit_len); + errdefer pool.unref(len_mixin); + + return try pool.createBranch( + @enumFromInt(chunk_depth), + len_mixin, + ); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { const bit_len = try serialized.length(data); const chunk_count = (bit_len + 255) / 256; @@ -944,3 +971,41 @@ test "BitListType equals" { try std.testing.expect(BL.equals(&a, &b)); try std.testing.expect(!BL.equals(&a, &c)); } + +test "BitListType - default_root" { + const Bits2048 = BitListType(2048); + var expected_root: [32]u8 = undefined; + + try Bits2048.hashTreeRoot(std.testing.allocator, &Bits2048.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &Bits2048.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try Bits2048.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} + +test "BitListType - tree.zeros" { + const allocator = std.testing.allocator; + + const Bits257 = BitListType(257); + + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + for (Bits257.limit / 2..Bits257.limit) |len| { + const tree_node = try Bits257.tree.zeros(&pool, len); + defer pool.unref(tree_node); + + var value = Bits257.default_value; + defer Bits257.deinit(allocator, &value); + // Implicitly set all bits to 0 + try value.resize(allocator, len); + + var expected_root: [32]u8 = undefined; + try Bits257.hashTreeRoot(allocator, &value, &expected_root); + + try std.testing.expectEqualSlices(u8, &expected_root, tree_node.getRoot(&pool)); + } +} diff --git a/src/ssz/type/bit_vector.zig b/src/ssz/type/bit_vector.zig index 8fe0a1ccf..3dacd72ed 100644 --- a/src/ssz/type/bit_vector.zig +++ b/src/ssz/type/bit_vector.zig @@ -8,6 +8,7 @@ const hexToBytes = @import("hex").hexToBytes; const hexLenFromBytes = @import("hex").hexLenFromBytes; const bytesToHex = @import("hex").bytesToHex; const maxChunksToDepth = @import("hashing").maxChunksToDepth; +const getZeroHash = @import("hashing").getZeroHash; const Node = @import("persistent_merkle_tree").Node; const BitVectorTreeView = @import("../tree_view/root.zig").BitVectorTreeView; @@ -174,6 +175,8 @@ pub fn BitVectorType(comptime _length: comptime_int) type { pub const default_value: Type = Type.empty; + pub const default_root: [32]u8 = getZeroHash(chunk_depth).*; + pub fn equals(a: *const Type, b: *const Type) bool { return a.equals(b); } @@ -219,6 +222,10 @@ pub fn BitVectorType(comptime _length: comptime_int) type { }; pub const tree = struct { + pub fn default(_: *Node.Pool) !Node.Id { + return @enumFromInt(chunk_depth); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { try serialized.validate(data); @@ -634,3 +641,23 @@ test "BitVectorType equals" { try std.testing.expect(BV.equals(&a, &b)); try std.testing.expect(!BV.equals(&a, &c)); } + +test "BitVectorType - default_root" { + const Bits128 = BitVectorType(128); + var expected_root: [32]u8 = undefined; + try Bits128.hashTreeRoot(&Bits128.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &Bits128.default_root, &expected_root); + + const Bits513 = BitVectorType(513); + try Bits513.hashTreeRoot(&Bits513.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &Bits513.default_root, &expected_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node_128 = try Bits128.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &Bits128.default_root, node_128.getRoot(&pool)); + + const node_513 = try Bits513.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &Bits513.default_root, node_513.getRoot(&pool)); +} diff --git a/src/ssz/type/bool.zig b/src/ssz/type/bool.zig index 3f1cb9ae3..42b2e0852 100644 --- a/src/ssz/type/bool.zig +++ b/src/ssz/type/bool.zig @@ -12,6 +12,8 @@ pub fn BoolType() type { pub const default_value: Type = false; + pub const default_root: [32]u8 = [_]u8{0} ** 32; + pub fn equals(a: *const Type, b: *const Type) bool { return a.* == b.*; } @@ -233,3 +235,11 @@ test "BoolType - tree.deserializeFromBytes" { try std.testing.expectError(error.invalidBoolean, Bool.tree.deserializeFromBytes(&pool, &[_]u8{0x02})); try std.testing.expectError(error.InvalidSize, Bool.tree.deserializeFromBytes(&pool, &[_]u8{})); } + +test "BoolType - default_root" { + const Bool = BoolType(); + var expected_root: [32]u8 = undefined; + + try Bool.hashTreeRoot(&Bool.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &Bool.default_root); +} diff --git a/src/ssz/type/byte_list.zig b/src/ssz/type/byte_list.zig index 68480b816..dc5afaa14 100644 --- a/src/ssz/type/byte_list.zig +++ b/src/ssz/type/byte_list.zig @@ -10,6 +10,8 @@ const bytesToHex = @import("hex").bytesToHex; const merkleize = @import("hashing").merkleize; const mixInLength = @import("hashing").mixInLength; const maxChunksToDepth = @import("hashing").maxChunksToDepth; +const getZeroHash = @import("hashing").getZeroHash; +const Depth = @import("persistent_merkle_tree").Depth; const Node = @import("persistent_merkle_tree").Node; const ListBasicTreeView = @import("../tree_view/root.zig").ListBasicTreeView; @@ -32,10 +34,16 @@ pub fn ByteListType(comptime _limit: comptime_int) type { pub const min_size: usize = 0; pub const max_size: usize = Element.fixed_size * limit; pub const max_chunk_count: usize = std.math.divCeil(usize, max_size, 32) catch unreachable; - pub const chunk_depth: u8 = maxChunksToDepth(max_chunk_count); + pub const chunk_depth: Depth = maxChunksToDepth(max_chunk_count); pub const default_value: Type = Type.empty; + pub const default_root: [32]u8 = blk: { + var buf = getZeroHash(chunk_depth).*; + mixInLength(0, &buf); + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { return std.mem.eql(u8, a.items, b.items); } @@ -105,6 +113,26 @@ pub fn ByteListType(comptime _limit: comptime_int) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + return try pool.createBranch( + @enumFromInt(chunk_depth), + @enumFromInt(0), + ); + } + + pub fn zeros(pool: *Node.Pool, len: usize) !Node.Id { + if (len > limit) { + return error.tooLarge; + } + const len_mixin = try pool.createLeafFromUint(len); + errdefer pool.unref(len_mixin); + + return try pool.createBranch( + @enumFromInt(chunk_depth), + len_mixin, + ); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { if (data.len > limit) { return error.gtLimit; @@ -578,3 +606,41 @@ test "ByteListType" { try TypeTest.run(allocator, tc); } } + +test "ByteListType - default_root" { + const ByteList256 = ByteListType(256); + var expected_root: [32]u8 = undefined; + + try ByteList256.hashTreeRoot(std.testing.allocator, &ByteList256.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &ByteList256.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try ByteList256.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} + +test "ByteListType - tree.zeros" { + const allocator = std.testing.allocator; + + const ByteList256 = ByteListType(256); + + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + for (0..ByteList256.limit) |len| { + const tree_node = try ByteList256.tree.zeros(&pool, len); + defer pool.unref(tree_node); + + var value = ByteList256.default_value; + defer ByteList256.deinit(allocator, &value); + try value.resize(allocator, len); + @memset(value.items, 0); + + var expected_root: [32]u8 = undefined; + try ByteList256.hashTreeRoot(allocator, &value, &expected_root); + + try std.testing.expectEqualSlices(u8, &expected_root, tree_node.getRoot(&pool)); + } +} diff --git a/src/ssz/type/byte_vector.zig b/src/ssz/type/byte_vector.zig index a2cc0ce0a..21a35a0ae 100644 --- a/src/ssz/type/byte_vector.zig +++ b/src/ssz/type/byte_vector.zig @@ -9,7 +9,8 @@ const hexLenFromBytes = @import("hex").hexLenFromBytes; const bytesToHex = @import("hex").bytesToHex; const merkleize = @import("hashing").merkleize; const maxChunksToDepth = @import("hashing").maxChunksToDepth; -const Depth = @import("hashing").Depth; +const getZeroHash = @import("hashing").getZeroHash; +const Depth = @import("persistent_merkle_tree").Depth; const Node = @import("persistent_merkle_tree").Node; const ArrayBasicTreeView = @import("../tree_view/root.zig").ArrayBasicTreeView; @@ -35,6 +36,8 @@ pub fn ByteVectorType(comptime _length: comptime_int) type { pub const default_value: Type = [_]Element.Type{Element.default_value} ** length; + pub const default_root: [32]u8 = getZeroHash(chunk_depth).*; + pub fn equals(a: *const Type, b: *const Type) bool { return std.mem.eql(u8, a, b); } @@ -77,6 +80,10 @@ pub fn ByteVectorType(comptime _length: comptime_int) type { }; pub const tree = struct { + pub fn default(_: *Node.Pool) !Node.Id { + return @enumFromInt(chunk_depth); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { if (data.len != fixed_size) { return error.InvalidSize; @@ -408,3 +415,30 @@ test "ByteVectorType(96) - tree.deserializeFromBytes" { try ByteVector96.hashTreeRoot(&value_from_tree, &hash_root); try std.testing.expectEqualSlices(u8, &expected_root, &hash_root); } + +test "ByteVectorType - default_root" { + const ByteVector4 = ByteVectorType(4); + var expected_root: [32]u8 = undefined; + try ByteVector4.hashTreeRoot(&ByteVector4.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &ByteVector4.default_root); + + const ByteVector32 = ByteVectorType(32); + try ByteVector32.hashTreeRoot(&ByteVector32.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &ByteVector32.default_root); + + const ByteVector96 = ByteVectorType(96); + try ByteVector96.hashTreeRoot(&ByteVector96.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &ByteVector96.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node_4 = try ByteVector4.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &ByteVector4.default_root, node_4.getRoot(&pool)); + + const node_32 = try ByteVector32.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &ByteVector32.default_root, node_32.getRoot(&pool)); + + const node_96 = try ByteVector96.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &ByteVector96.default_root, node_96.getRoot(&pool)); +} diff --git a/src/ssz/type/container.zig b/src/ssz/type/container.zig index cb7c07bcf..82329da94 100644 --- a/src/ssz/type/container.zig +++ b/src/ssz/type/container.zig @@ -69,6 +69,16 @@ pub fn FixedContainerType(comptime ST: type) type { break :blk out; }; + pub const default_root: [32]u8 = blk: { + var buf: [32]u8 = undefined; + var chunks = [_][32]u8{[_]u8{0} ** 32} ** ((chunk_count + 1) / 2 * 2); + for (fields, 0..) |field, i| { + @memcpy(&chunks[i], &field.type.default_root); + } + merkleize(@ptrCast(&chunks), chunk_depth, &buf) catch unreachable; + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { inline for (fields) |field| { if (!field.type.equals(&@field(a, field.name), &@field(b, field.name))) { @@ -148,6 +158,19 @@ pub fn FixedContainerType(comptime ST: type) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + var nodes: [chunk_count]Node.Id = undefined; + errdefer pool.free(&nodes); + inline for (fields, 0..) |field, i| { + if (comptime isBasicType(field.type)) { + nodes[i] = @enumFromInt(0); + } else { + nodes[i] = try field.type.tree.default(pool); + } + } + return try Node.fillWithContents(pool, &nodes, chunk_depth); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { if (data.len != fixed_size) { return error.InvalidSize; @@ -238,6 +261,9 @@ pub fn FixedContainerType(comptime ST: type) type { } pub fn getFieldIndex(comptime name: []const u8) usize { + comptime { + @setEvalBranchQuota(20000); + } inline for (fields, 0..) |field, i| { if (std.mem.eql(u8, name, field.name)) { return i; @@ -247,7 +273,19 @@ pub fn FixedContainerType(comptime ST: type) type { } } + pub fn hasField(comptime name: []const u8) bool { + inline for (fields) |field| { + if (std.mem.eql(u8, name, field.name)) { + return true; + } + } + return false; + } + pub fn getFieldType(comptime name: []const u8) type { + comptime { + @setEvalBranchQuota(20000); + } inline for (fields) |field| { if (std.mem.eql(u8, name, field.name)) { return field.type; @@ -339,6 +377,16 @@ pub fn VariableContainerType(comptime ST: type) type { break :blk out; }; + pub const default_root: [32]u8 = blk: { + var buf: [32]u8 = undefined; + var chunks = [_][32]u8{[_]u8{0} ** 32} ** ((chunk_count + 1) / 2 * 2); + for (fields, 0..) |field, i| { + @memcpy(&chunks[i], &field.type.default_root); + } + merkleize(@ptrCast(&chunks), chunk_depth, &buf) catch unreachable; + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { inline for (fields) |field| { if (!field.type.equals(&@field(a, field.name), &@field(b, field.name))) { @@ -454,6 +502,15 @@ pub fn VariableContainerType(comptime ST: type) type { } } + pub fn hasField(comptime name: []const u8) bool { + inline for (fields) |field| { + if (std.mem.eql(u8, name, field.name)) { + return true; + } + } + return false; + } + pub fn getFieldType(comptime name: []const u8) type { inline for (fields) |field| { if (std.mem.eql(u8, name, field.name)) { @@ -560,6 +617,19 @@ pub fn VariableContainerType(comptime ST: type) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + var nodes: [chunk_count]Node.Id = undefined; + errdefer pool.free(&nodes); + inline for (fields, 0..) |field, i| { + if (comptime isBasicType(field.type)) { + nodes[i] = @enumFromInt(0); + } else { + nodes[i] = try field.type.tree.default(pool); + } + } + return try Node.fillWithContents(pool, &nodes, chunk_depth); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { if (data.len > max_size or data.len < min_size) { return error.InvalidSize; @@ -1182,3 +1252,38 @@ test "VariableContainerType equals" { try std.testing.expect(Container.equals(&a, &b)); try std.testing.expect(!Container.equals(&a, &c)); } + +test "FixedContainerType - default_root" { + const Container = FixedContainerType(struct { + a: UintType(64), + b: UintType(64), + c: UintType(16), + }); + var expected_root: [32]u8 = undefined; + + try Container.hashTreeRoot(&Container.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &Container.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try Container.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} + +test "VariableContainerType - default_root" { + var expected_root: [32]u8 = undefined; + const Container = VariableContainerType(struct { + a: FixedListType(UintType(64), 128), + b: UintType(64), + }); + + try Container.hashTreeRoot(std.testing.allocator, &Container.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &Container.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try Container.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} diff --git a/src/ssz/type/list.zig b/src/ssz/type/list.zig index ab87d3657..9e99d9503 100644 --- a/src/ssz/type/list.zig +++ b/src/ssz/type/list.zig @@ -8,6 +8,7 @@ const OffsetIterator = @import("offsets.zig").OffsetIterator; const merkleize = @import("hashing").merkleize; const mixInLength = @import("hashing").mixInLength; const maxChunksToDepth = @import("hashing").maxChunksToDepth; +const getZeroHash = @import("hashing").getZeroHash; const Node = @import("persistent_merkle_tree").Node; const tree_view = @import("../tree_view/root.zig"); const ListBasicTreeView = tree_view.ListBasicTreeView; @@ -38,6 +39,12 @@ pub fn FixedListType(comptime ST: type, comptime _limit: comptime_int) type { pub const default_value: Type = Type.empty; + pub const default_root: [32]u8 = blk: { + var buf = getZeroHash(chunk_depth).*; + mixInLength(0, &buf); + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { if (a.items.len != b.items.len) { return false; @@ -54,6 +61,12 @@ pub fn FixedListType(comptime ST: type, comptime _limit: comptime_int) type { value.deinit(allocator); } + pub fn chunkIndex(index: usize) usize { + if (comptime isBasicType(Element)) { + return (index * Element.fixed_size) / 32; + } else return index; + } + pub fn chunkCount(value: *const Type) usize { if (comptime isBasicType(Element)) { return (Element.fixed_size * value.items.len + 31) / 32; @@ -199,6 +212,42 @@ pub fn FixedListType(comptime ST: type, comptime _limit: comptime_int) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + return try pool.createBranch( + @enumFromInt(chunk_depth), + @enumFromInt(0), + ); + } + + pub fn zeros(pool: *Node.Pool, len: usize) !Node.Id { + if (len > limit) { + return error.gtLimit; + } + + const len_mixin = try pool.createLeafFromUint(len); + errdefer pool.unref(len_mixin); + + if (comptime isBasicType(Element)) { + const content_root: Node.Id = @enumFromInt(chunk_depth); + return try pool.createBranch(content_root, len_mixin); + } else { + var it = Node.FillWithContentsIterator.init(pool, chunk_depth); + errdefer it.deinit(); + + const element_zero = try Element.tree.default(pool); + errdefer pool.unref(element_zero); + + for (0..len) |_| { + try it.append(element_zero); + } + + const content_root = try it.finish(); + errdefer pool.unref(content_root); + + return try pool.createBranch(content_root, len_mixin); + } + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { const len = try std.math.divExact(usize, data.len, Element.fixed_size); if (len > limit) { @@ -410,6 +459,12 @@ pub fn VariableListType(comptime ST: type, comptime _limit: comptime_int) type { pub const default_value: Type = Type.empty; + pub const default_root: [32]u8 = blk: { + var buf = getZeroHash(chunk_depth).*; + mixInLength(0, &buf); + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { if (a.items.len != b.items.len) { return false; @@ -571,6 +626,37 @@ pub fn VariableListType(comptime ST: type, comptime _limit: comptime_int) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + return try pool.createBranch( + @enumFromInt(chunk_depth), + @enumFromInt(0), + ); + } + + pub fn zeros(pool: *Node.Pool, len: usize) !Node.Id { + if (len > limit) { + return error.gtLimit; + } + + const len_mixin = try pool.createLeafFromUint(len); + errdefer pool.unref(len_mixin); + + var it = Node.FillWithContentsIterator.init(pool, chunk_depth); + errdefer it.deinit(); + + const element_zero = try Element.tree.default(pool); + errdefer pool.unref(element_zero); + + for (0..len) |_| { + try it.append(element_zero); + } + + const content_root = try it.finish(); + errdefer pool.unref(content_root); + + return try pool.createBranch(content_root, len_mixin); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { var iterator = OffsetIterator(Self).init(data); const first_offset = if (data.len == 0) 0 else try iterator.next(); @@ -1701,3 +1787,81 @@ test "VariableListType of FixedList" { try TypeTest.run(allocator, tc); } } + +test "FixedListType - default_root" { + const ListU32 = FixedListType(UintType(32), 16); + var expected_root: [32]u8 = undefined; + + try ListU32.hashTreeRoot(std.testing.allocator, &ListU32.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &ListU32.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try ListU32.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} + +test "VariableListType - default_root" { + const ListU32 = FixedListType(UintType(32), 16); + const ListListU32 = VariableListType(ListU32, 16); + var expected_root: [32]u8 = undefined; + + try ListListU32.hashTreeRoot(std.testing.allocator, &ListListU32.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &ListListU32.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try ListListU32.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} + +test "FixedListType - tree.zeros" { + const allocator = std.testing.allocator; + + const ListU16 = FixedListType(UintType(16), 8); + + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + for (0..ListU16.limit) |len| { + const tree_node = try ListU16.tree.zeros(&pool, len); + defer pool.unref(tree_node); + + var value = ListU16.default_value; + defer ListU16.deinit(allocator, &value); + try value.resize(allocator, len); + @memset(value.items, 0); + + var expected_root: [32]u8 = undefined; + try ListU16.hashTreeRoot(allocator, &value, &expected_root); + + try std.testing.expectEqualSlices(u8, &expected_root, tree_node.getRoot(&pool)); + } +} + +test "VariableListType - tree.zeros" { + const allocator = std.testing.allocator; + + const ListU32 = FixedListType(UintType(32), 16); + const ListListU32 = VariableListType(ListU32, 16); + + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + for (0..ListListU32.limit) |len| { + const tree_node = try ListListU32.tree.zeros(&pool, len); + defer pool.unref(tree_node); + + var value = ListListU32.default_value; + defer ListListU32.deinit(allocator, &value); + try value.resize(allocator, len); + @memset(value.items, ListListU32.Element.default_value); + + var expected_root: [32]u8 = undefined; + try ListListU32.hashTreeRoot(allocator, &value, &expected_root); + + try std.testing.expectEqualSlices(u8, &expected_root, tree_node.getRoot(&pool)); + } +} diff --git a/src/ssz/type/uint.zig b/src/ssz/type/uint.zig index 833d84833..5e8cbfa96 100644 --- a/src/ssz/type/uint.zig +++ b/src/ssz/type/uint.zig @@ -22,6 +22,8 @@ pub fn UintType(comptime bits: comptime_int) type { pub const default_value: Type = 0; + pub const default_root: [32]u8 = [_]u8{0} ** 32; + pub fn equals(a: *const Type, b: *const Type) bool { return a.* == b.*; } @@ -307,3 +309,11 @@ test "UintType(256) - max" { &[_]u8{0xff} ** 32, ); } + +test "UintType - default_root" { + const Uint16 = UintType(16); + var expected_root: [32]u8 = undefined; + + try Uint16.hashTreeRoot(&Uint16.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &Uint16.default_root); +} diff --git a/src/ssz/type/vector.zig b/src/ssz/type/vector.zig index f0022a329..89fe26c6e 100644 --- a/src/ssz/type/vector.zig +++ b/src/ssz/type/vector.zig @@ -9,6 +9,7 @@ const isFixedType = @import("type_kind.zig").isFixedType; const OffsetIterator = @import("offsets.zig").OffsetIterator; const merkleize = @import("hashing").merkleize; const maxChunksToDepth = @import("hashing").maxChunksToDepth; +const getZeroHash = @import("hashing").getZeroHash; const Node = @import("persistent_merkle_tree").Node; const tree_view = @import("../tree_view/root.zig"); const ArrayBasicTreeView = tree_view.ArrayBasicTreeView; @@ -38,6 +39,8 @@ pub fn FixedVectorType(comptime ST: type, comptime _length: comptime_int) type { pub const default_value: Type = [_]Element.Type{Element.default_value} ** length; + pub const default_root: [32]u8 = getZeroHash(chunk_depth).*; + pub fn equals(a: *const Type, b: *const Type) bool { for (a, b) |a_elem, b_elem| { if (!Element.equals(&a_elem, &b_elem)) { @@ -124,6 +127,23 @@ pub fn FixedVectorType(comptime ST: type, comptime _length: comptime_int) type { }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + if (comptime isBasicType(Element)) { + return @enumFromInt(chunk_depth); + } else { + var nodes: [chunk_count]Node.Id = undefined; + + const element_default = try Element.tree.default(pool); + defer pool.free(&element_default); + + for (0..chunk_count) |i| { + nodes[i] = element_default; + } + + return try Node.fillWithContents(pool, &nodes, chunk_depth); + } + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { if (data.len != fixed_size) { return error.InvalidSize; @@ -276,6 +296,14 @@ pub fn VariableVectorType(comptime ST: type, comptime _length: comptime_int) typ pub const default_value: Type = [_]Element.Type{Element.default_value} ** length; + pub const default_root: [32]u8 = blk: { + var buf: [32]u8 = undefined; + var chunks = [_][32]u8{[_]u8{0} ** 32} ** ((chunk_count + 1) / 2 * 2); + @memset(chunks[0..length], Element.default_root); + merkleize(@ptrCast(&chunks), chunk_depth, &buf) catch unreachable; + break :blk buf; + }; + pub fn equals(a: *const Type, b: *const Type) bool { for (a, b) |a_elem, b_elem| { if (!Element.equals(&a_elem, &b_elem)) { @@ -375,6 +403,19 @@ pub fn VariableVectorType(comptime ST: type, comptime _length: comptime_int) typ }; pub const tree = struct { + pub fn default(pool: *Node.Pool) !Node.Id { + var nodes: [chunk_count]Node.Id = undefined; + + const element_default = try Element.tree.default(pool); + defer pool.unref(element_default); + + for (0..chunk_count) |i| { + nodes[i] = element_default; + } + + return try Node.fillWithContents(pool, &nodes, chunk_depth); + } + pub fn deserializeFromBytes(pool: *Node.Pool, data: []const u8) !Node.Id { if (data.len > max_size or data.len < min_size) { return error.InvalidSize; @@ -954,3 +995,32 @@ test "VectorCompositeType of Container" { try TypeTest.run(allocator, tc); } } + +test "FixedVectorType - default_root" { + const VectorU64 = FixedVectorType(UintType(64), 4); + var expected_root: [32]u8 = undefined; + + try VectorU64.hashTreeRoot(&VectorU64.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &VectorU64.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try VectorU64.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} + +test "VariableVectorType - default_root" { + const ListU64 = FixedListType(UintType(64), 8); + const VectorList = VariableVectorType(ListU64, 2); + var expected_root: [32]u8 = undefined; + + try VectorList.hashTreeRoot(std.testing.allocator, &VectorList.default_value, &expected_root); + try std.testing.expectEqualSlices(u8, &expected_root, &VectorList.default_root); + + var pool = try Node.Pool.init(std.testing.allocator, 1024); + defer pool.deinit(); + + const node = try VectorList.tree.default(&pool); + try std.testing.expectEqualSlices(u8, &expected_root, node.getRoot(&pool)); +} diff --git a/src/state_transition/block/initiate_validator_exit.zig b/src/state_transition/block/initiate_validator_exit.zig index 1196924d2..089ed55c3 100644 --- a/src/state_transition/block/initiate_validator_exit.zig +++ b/src/state_transition/block/initiate_validator_exit.zig @@ -1,8 +1,7 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const types = @import("consensus_types"); -const Validator = types.phase0.Validator.Type; const c = @import("constants"); const FAR_FUTURE_EPOCH = c.FAR_FUTURE_EPOCH; const computeExitEpochAndUpdateChurn = @import("../utils/epoch.zig").computeExitEpochAndUpdateChurn; @@ -26,17 +25,17 @@ const computeExitEpochAndUpdateChurn = @import("../utils/epoch.zig").computeExit /// ``` /// Forcing consumers to pass the SubTree of `validator` directly mitigates this issue. /// -pub fn initiateValidatorExit(cached_state: *const CachedBeaconStateAllForks, validator: *Validator) !void { +pub fn initiateValidatorExit(cached_state: *const CachedBeaconState, validator: *types.phase0.Validator.TreeView) !void { const config = cached_state.config.chain; const epoch_cache = cached_state.getEpochCache(); const state = cached_state.state; // return if validator already initiated exit - if (validator.exit_epoch != FAR_FUTURE_EPOCH) { + if ((try validator.get("exit_epoch")) != FAR_FUTURE_EPOCH) { return; } - if (state.isPreElectra()) { + if (state.forkSeq().lt(.electra)) { // Limits the number of validators that can exit on each epoch. // Expects all state.validators to follow this rule, i.e. no validator.exitEpoch is greater than exitQueueEpoch. // If there the churnLimit is reached at this current exitQueueEpoch, advance epoch and reset churn. @@ -50,12 +49,18 @@ pub fn initiateValidatorExit(cached_state: *const CachedBeaconStateAllForks, val } // set validator exit epoch - validator.exit_epoch = epoch_cache.exit_queue_epoch; + try validator.set("exit_epoch", epoch_cache.exit_queue_epoch); } else { // set validator exit epoch // Note we don't use epochCtx.exitQueueChurn and exitQueueEpoch anymore - validator.exit_epoch = computeExitEpochAndUpdateChurn(cached_state, validator.effective_balance); + try validator.set( + "exit_epoch", + try computeExitEpochAndUpdateChurn(cached_state, try validator.get("effective_balance")), + ); } - validator.withdrawable_epoch = try std.math.add(u64, validator.exit_epoch, config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY); + try validator.set( + "withdrawable_epoch", + try std.math.add(u64, try validator.get("exit_epoch"), config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY), + ); } diff --git a/src/state_transition/block/is_valid_indexed_attestation.zig b/src/state_transition/block/is_valid_indexed_attestation.zig index 698837570..ce9a34716 100644 --- a/src/state_transition/block/is_valid_indexed_attestation.zig +++ b/src/state_transition/block/is_valid_indexed_attestation.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ValidatorIndex = types.primitive.ValidatorIndex.Type; const ForkSeq = @import("config").ForkSeq; const types = @import("consensus_types"); @@ -8,8 +8,13 @@ const verifySingleSignatureSet = @import("../utils/signature_sets.zig").verifySi const verifyAggregatedSignatureSet = @import("../utils/signature_sets.zig").verifyAggregatedSignatureSet; const getIndexedAttestationSignatureSet = @import("../signature_sets/indexed_attestation.zig").getIndexedAttestationSignatureSet; -pub fn isValidIndexedAttestation(comptime IA: type, cached_state: *const CachedBeaconStateAllForks, indexed_attestation: *const IA, verify_signature: bool) !bool { - if (!isValidIndexedAttestationIndices(cached_state, indexed_attestation.attesting_indices.items)) { +pub fn isValidIndexedAttestation( + comptime IA: type, + cached_state: *const CachedBeaconState, + indexed_attestation: *const IA, + verify_signature: bool, +) !bool { + if (!(try isValidIndexedAttestationIndices(cached_state, indexed_attestation.attesting_indices.items))) { return false; } @@ -22,7 +27,7 @@ pub fn isValidIndexedAttestation(comptime IA: type, cached_state: *const CachedB } } -pub fn isValidIndexedAttestationIndices(cached_state: *const CachedBeaconStateAllForks, indices: []const ValidatorIndex) bool { +pub fn isValidIndexedAttestationIndices(cached_state: *const CachedBeaconState, indices: []const ValidatorIndex) !bool { // verify max number of indices const fork_seq = cached_state.state.forkSeq(); const max_indices: usize = if (fork_seq.gte(.electra)) @@ -46,7 +51,7 @@ pub fn isValidIndexedAttestationIndices(cached_state: *const CachedBeaconStateAl } // check if indices are out of bounds, by checking the highest index (since it is sorted) - const validator_count = cached_state.state.validators().items.len; + const validator_count = try cached_state.state.validatorsCount(); if (indices.len > 0) { const last_index = indices[indices.len - 1]; if (last_index >= validator_count) { diff --git a/src/state_transition/block/process_attestation_altair.zig b/src/state_transition/block/process_attestation_altair.zig index 978c18db7..f37e32fd4 100644 --- a/src/state_transition/block/process_attestation_altair.zig +++ b/src/state_transition/block/process_attestation_altair.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const types = @import("consensus_types"); const Epoch = types.primitive.Epoch.Type; const preset = @import("preset").preset; @@ -28,11 +28,11 @@ const SLOTS_PER_EPOCH_SQRT = std.math.sqrt(preset.SLOTS_PER_EPOCH); /// AT = AttestationType /// for phase0 it's `types.phase0.Attestation.Type` /// for electra it's `types.electra.Attestation.Type` -pub fn processAttestationsAltair(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, comptime AT: type, attestations: []AT, verify_signature: bool) !void { - const state = cached_state.state; +pub fn processAttestationsAltair(allocator: Allocator, cached_state: *CachedBeaconState, comptime AT: type, attestations: []AT, verify_signature: bool) !void { + var state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const effective_balance_increments = epoch_cache.effective_balance_increment.get().items; - const state_slot = state.slot(); + const state_slot = try state.slot(); const current_epoch = epoch_cache.epoch; const root_cache = try RootCache.init(allocator, cached_state); @@ -45,7 +45,7 @@ pub fn processAttestationsAltair(allocator: Allocator, cached_state: *const Cach var proposer_reward: u64 = 0; for (attestations) |*attestation| { - const data = attestation.data; + const data = &attestation.data; try validateAttestation(AT, cached_state, attestation); // Retrieve the validator indices from the attestation participation bitfield @@ -64,22 +64,22 @@ pub fn processAttestationsAltair(allocator: Allocator, cached_state: *const Cach } const in_current_epoch = data.target.epoch == current_epoch; - var epoch_participation = if (in_current_epoch) state.currentEpochParticipations().items else state.previousEpochParticipations().items; + var epoch_participation = if (in_current_epoch) try state.currentEpochParticipation() else try state.previousEpochParticipation(); const flags_attestation = try getAttestationParticipationStatus(state, data, state_slot - data.slot, current_epoch, root_cache); // For each participant, update their participation // In epoch processing, this participation info is used to calculate balance updates var total_balance_increments_with_weight: u64 = 0; - const validators = state.validators().items; + var validators = try state.validators(); for (attesting_indices.items) |validator_index| { - const flags = epoch_participation[validator_index]; + const flags = try epoch_participation.get(validator_index); // For normal block, > 90% of attestations belong to current epoch // At epoch boundary, 100% of attestations belong to previous epoch // so we want to update the participation flag tree in batch // no setBitwiseOR implemented in zig ssz, so we do it manually here - epoch_participation[validator_index] = flags_attestation | flags; + try epoch_participation.set(validator_index, flags_attestation | flags); // Returns flags that are NOT set before (~ bitwise NOT) AND are set after const flags_new_set = ~flags & flags_attestation; @@ -99,8 +99,9 @@ pub fn processAttestationsAltair(allocator: Allocator, cached_state: *const Cach // TODO: describe issue. Compute progressive target balances // When processing each attestation, increase the cummulative target balance. Only applies post-altair if ((flags_new_set & TIMELY_TARGET) == TIMELY_TARGET) { - const validator = validators[validator_index]; - if (!validator.slashed) { + var validator = try validators.get(validator_index); + const slashed = try validator.get("slashed"); + if (!slashed) { if (in_current_epoch) { epoch_cache.current_target_unslashed_balance_increments += effective_balance_increments[validator_index]; } else { @@ -114,22 +115,22 @@ pub fn processAttestationsAltair(allocator: Allocator, cached_state: *const Cach const proposer_reward_numerator = total_increments * epoch_cache.base_reward_per_increment; proposer_reward += @divFloor(proposer_reward_numerator, PROPOSER_REWARD_DOMINATOR); } - increaseBalance(state, try cached_state.getBeaconProposer(state_slot), proposer_reward); + try increaseBalance(state, try cached_state.getBeaconProposer(state_slot), proposer_reward); } -pub fn getAttestationParticipationStatus(state: *const BeaconStateAllForks, data: types.phase0.AttestationData.Type, inclusion_delay: u64, current_epoch: Epoch, root_cache: *RootCache) !u8 { - const justified_checkpoint: Checkpoint = if (data.target.epoch == current_epoch) - root_cache.current_justified_checkpoint +pub fn getAttestationParticipationStatus(state: *BeaconState, data: *const types.phase0.AttestationData.Type, inclusion_delay: u64, current_epoch: Epoch, root_cache: *RootCache) !u8 { + const justified_checkpoint = if (data.target.epoch == current_epoch) + &root_cache.current_justified_checkpoint else - root_cache.previous_justified_checkpoint; - const is_matching_source = checkpointValueEquals(data.source, justified_checkpoint); + &root_cache.previous_justified_checkpoint; + const is_matching_source = checkpointValueEquals(&data.source, justified_checkpoint); if (!is_matching_source) return error.InvalidAttestationSource; - const is_matching_target = std.mem.eql(u8, &data.target.root, &try root_cache.getBlockRoot(data.target.epoch)); + const is_matching_target = std.mem.eql(u8, &data.target.root, try root_cache.getBlockRoot(data.target.epoch)); // a timely head is only be set if the target is _also_ matching const is_matching_head = - is_matching_target and std.mem.eql(u8, &data.beacon_block_root, &try root_cache.getBlockRootAtSlot(data.slot)); + is_matching_target and std.mem.eql(u8, &data.beacon_block_root, try root_cache.getBlockRootAtSlot(data.slot)); var flags: u8 = 0; if (is_matching_source and inclusion_delay <= SLOTS_PER_EPOCH_SQRT) flags |= TIMELY_SOURCE; @@ -138,6 +139,6 @@ pub fn getAttestationParticipationStatus(state: *const BeaconStateAllForks, data return flags; } -pub fn checkpointValueEquals(cp1: Checkpoint, cp2: Checkpoint) bool { +pub fn checkpointValueEquals(cp1: *const Checkpoint, cp2: *const Checkpoint) bool { return cp1.epoch == cp2.epoch and std.mem.eql(u8, &cp1.root, &cp2.root); } diff --git a/src/state_transition/block/process_attestation_phase0.zig b/src/state_transition/block/process_attestation_phase0.zig index 46471711a..9c37c65de 100644 --- a/src/state_transition/block/process_attestation_phase0.zig +++ b/src/state_transition/block/process_attestation_phase0.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const ssz = @import("ssz"); const types = @import("consensus_types"); const preset = @import("preset").preset; @@ -9,51 +9,39 @@ const ForkSeq = @import("config").ForkSeq; const computeEpochAtSlot = @import("../utils/epoch.zig").computeEpochAtSlot; const isValidIndexedAttestation = @import("./is_valid_indexed_attestation.zig").isValidIndexedAttestation; const Slot = types.primitive.Slot.Type; -const Checkpoint = types.phase0.Checkpoint.Type; const Phase0Attestation = types.phase0.Attestation.Type; const ElectraAttestation = types.electra.Attestation.Type; const PendingAttestation = types.phase0.PendingAttestation.Type; -pub fn processAttestationPhase0(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, attestation: *const Phase0Attestation, verify_signature: bool) !void { +pub fn processAttestationPhase0(allocator: Allocator, cached_state: *CachedBeaconState, attestation: *const Phase0Attestation, verify_signature: bool) !void { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); - const slot = state.slot(); + const slot = try state.slot(); const data = attestation.data; try validateAttestation(types.phase0.Attestation.Type, cached_state, attestation); - // should store a clone of aggregation_bits on Phase0 BeaconState to avoid double free error - var cloned_aggregation_bits: ssz.BitListType(preset.MAX_VALIDATORS_PER_COMMITTEE).Type = undefined; - try ssz.BitListType(preset.MAX_VALIDATORS_PER_COMMITTEE).clone(allocator, &attestation.aggregation_bits, &cloned_aggregation_bits); - var appended: bool = false; - errdefer { - if (!appended) { - cloned_aggregation_bits.deinit(allocator); - } - } - const pending_attestation = PendingAttestation{ .data = data, - .aggregation_bits = cloned_aggregation_bits, + .aggregation_bits = attestation.aggregation_bits, .inclusion_delay = slot - data.slot, .proposer_index = try epoch_cache.getBeaconProposer(slot), }; + var justified_checkpoint: types.phase0.Checkpoint.Type = undefined; + var epoch_pending_attestations: types.phase0.EpochAttestations.TreeView = undefined; if (data.target.epoch == epoch_cache.epoch) { - if (!types.phase0.Checkpoint.equals(&data.source, state.currentJustifiedCheckpoint())) { - return error.InvalidAttestationSourceNotEqualToCurrentJustifiedCheckpoint; - } - if (state.currentEpochPendingAttestations().append(allocator, pending_attestation)) |_| { - appended = true; - } else |err| return err; + try state.currentJustifiedCheckpoint(&justified_checkpoint); + epoch_pending_attestations = try state.currentEpochPendingAttestations(); } else { - if (!types.phase0.Checkpoint.equals(&data.source, state.previousJustifiedCheckpoint())) { - return error.InvalidAttestationSourceNotEqualToPreviousJustifiedCheckpoint; - } - if (state.previousEpochPendingAttestations().append(allocator, pending_attestation)) |_| { - appended = true; - } else |err| return err; + try state.previousJustifiedCheckpoint(&justified_checkpoint); + epoch_pending_attestations = try state.previousEpochPendingAttestations(); + } + + if (!types.phase0.Checkpoint.equals(&data.source, &justified_checkpoint)) { + return error.InvalidAttestationSourceNotEqualToJustifiedCheckpoint; } + try epoch_pending_attestations.pushValue(&pending_attestation); var indexed_attestation: types.phase0.IndexedAttestation.Type = undefined; try epoch_cache.computeIndexedAttestationPhase0(attestation, &indexed_attestation); @@ -65,10 +53,10 @@ pub fn processAttestationPhase0(allocator: Allocator, cached_state: *CachedBeaco } /// AT could be either Phase0Attestation or ElectraAttestation -pub fn validateAttestation(comptime AT: type, cached_state: *const CachedBeaconStateAllForks, attestation: *const AT) !void { +pub fn validateAttestation(comptime AT: type, cached_state: *const CachedBeaconState, attestation: *const AT) !void { const epoch_cache = cached_state.getEpochCache(); - const state = cached_state.state; - const state_slot = state.slot(); + var state = cached_state.state; + const state_slot = try state.slot(); const data = attestation.data; const computed_epoch = computeEpochAtSlot(data.slot); const committee_count = try epoch_cache.getCommitteeCountPerSlot(computed_epoch); @@ -148,9 +136,9 @@ pub fn validateAttestation(comptime AT: type, cached_state: *const CachedBeaconS } } -pub fn isTimelyTarget(state: *const BeaconStateAllForks, inclusion_distance: Slot) bool { +pub fn isTimelyTarget(state: *BeaconState, inclusion_distance: Slot) bool { // post deneb attestation is valid till end of next epoch for target - if (state.isPostDeneb()) { + if (state.forkSeq().gte(.deneb)) { return true; } diff --git a/src/state_transition/block/process_attestations.zig b/src/state_transition/block/process_attestations.zig index 7dc89c1cd..9c2008f44 100644 --- a/src/state_transition/block/process_attestations.zig +++ b/src/state_transition/block/process_attestations.zig @@ -1,8 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const TestCachedBeaconStateAllForks = @import("../test_utils/root.zig").TestCachedBeaconStateAllForks; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const TestCachedBeaconState = @import("../test_utils/root.zig").TestCachedBeaconState; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const EpochCacheImmutableData = @import("../cache/epoch_cache.zig").EpochCacheImmutableData; const types = @import("consensus_types"); const Epoch = types.primitive.Epoch.Type; @@ -12,11 +12,11 @@ const Attestations = @import("../types/attestation.zig").Attestations; const processAttestationPhase0 = @import("./process_attestation_phase0.zig").processAttestationPhase0; const processAttestationsAltair = @import("./process_attestation_altair.zig").processAttestationsAltair; -pub fn processAttestations(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, attestations: Attestations, verify_signatures: bool) !void { +pub fn processAttestations(allocator: Allocator, cached_state: *CachedBeaconState, attestations: Attestations, verify_signatures: bool) !void { const state = cached_state.state; switch (attestations) { .phase0 => |attestations_phase0| { - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { // altair to deneb try processAttestationsAltair(allocator, cached_state, types.phase0.Attestation.Type, attestations_phase0.items, verify_signatures); } else { @@ -35,8 +35,12 @@ pub fn processAttestations(allocator: Allocator, cached_state: *CachedBeaconStat test "process attestations - sanity" { const allocator = std.testing.allocator; + const Node = @import("persistent_merkle_tree").Node; + { - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 16); + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + var test_state = try TestCachedBeaconState.init(allocator, &pool, 16); defer test_state.deinit(); var phase0: std.ArrayListUnmanaged(types.phase0.Attestation.Type) = .empty; const attestation = types.phase0.Attestation.default_value; @@ -46,7 +50,9 @@ test "process attestations - sanity" { phase0.deinit(allocator); } { - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 16); + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + var test_state = try TestCachedBeaconState.init(allocator, &pool, 16); defer test_state.deinit(); var electra: std.ArrayListUnmanaged(types.electra.Attestation.Type) = .empty; const attestation = types.electra.Attestation.default_value; diff --git a/src/state_transition/block/process_attester_slashing.zig b/src/state_transition/block/process_attester_slashing.zig index 908167a39..329f0ff17 100644 --- a/src/state_transition/block/process_attester_slashing.zig +++ b/src/state_transition/block/process_attester_slashing.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const types = @import("consensus_types"); const AttesterSlashing = types.phase0.AttesterSlashing.Type; @@ -12,8 +12,8 @@ const slashValidator = @import("./slash_validator.zig").slashValidator; /// AS is the AttesterSlashing type /// - for phase0 it is `types.phase0.AttesterSlashing.Type` /// - for electra it is `types.electra.AttesterSlashing.Type` -pub fn processAttesterSlashing(comptime AS: type, cached_state: *const CachedBeaconStateAllForks, attester_slashing: *const AS, verify_signature: bool) !void { - const state = cached_state.state; +pub fn processAttesterSlashing(comptime AS: type, cached_state: *CachedBeaconState, attester_slashing: *const AS, verify_signature: bool) !void { + var state = cached_state.state; const epoch = cached_state.getEpochCache().epoch; try assertValidAttesterSlashing(AS, cached_state, attester_slashing, verify_signature); @@ -21,9 +21,12 @@ pub fn processAttesterSlashing(comptime AS: type, cached_state: *const CachedBea defer intersecting_indices.deinit(); var slashed_any: bool = false; + var validators = try state.validators(); // Spec requires to sort indices beforehand but we validated sorted asc AttesterSlashing in the above functions for (intersecting_indices.items) |validator_index| { - const validator = state.validators().items[validator_index]; + var validator: types.phase0.Validator.Type = undefined; + try validators.getValue(undefined, validator_index, &validator); + if (isSlashableValidator(&validator, epoch)) { try slashValidator(cached_state, validator_index, null); slashed_any = true; @@ -38,7 +41,7 @@ pub fn processAttesterSlashing(comptime AS: type, cached_state: *const CachedBea /// AS is the AttesterSlashing type /// - for phase0 it is `types.phase0.AttesterSlashing.Type` /// - for electra it is `types.electra.AttesterSlashing.Type` -pub fn assertValidAttesterSlashing(comptime AS: type, cached_state: *const CachedBeaconStateAllForks, attester_slashing: *const AS, verify_signatures: bool) !void { +pub fn assertValidAttesterSlashing(comptime AS: type, cached_state: *const CachedBeaconState, attester_slashing: *const AS, verify_signatures: bool) !void { const attestations = &.{ attester_slashing.attestation_1, attester_slashing.attestation_2 }; if (!isSlashableAttestationData(&attestations[0].data, &attestations[1].data)) { return error.InvalidAttesterSlashingNotSlashable; diff --git a/src/state_transition/block/process_blob_kzg_commitments.zig b/src/state_transition/block/process_blob_kzg_commitments.zig index 6d5710d34..63220f412 100644 --- a/src/state_transition/block/process_blob_kzg_commitments.zig +++ b/src/state_transition/block/process_blob_kzg_commitments.zig @@ -1,5 +1,5 @@ const BlockExternalData = @import("../state_transition.zig").BlockExternalData; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; pub fn processBlobKzgCommitments(external_data: BlockExternalData) !void { switch (external_data.execution_payload_status) { diff --git a/src/state_transition/block/process_block.zig b/src/state_transition/block/process_block.zig index 50d31fb15..7a745a1d6 100644 --- a/src/state_transition/block/process_block.zig +++ b/src/state_transition/block/process_block.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const types = @import("consensus_types"); const Root = types.primitive.Root.Type; @@ -31,7 +31,7 @@ pub const ProcessBlockOpts = struct { /// Process a block and update the state following Ethereum Consensus specifications. pub fn processBlock( allocator: Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, block: Block, external_data: BlockExternalData, opts: ProcessBlockOpts, @@ -44,10 +44,10 @@ pub fn processBlock( // The call to the process_execution_payload must happen before the call to the process_randao as the former depends // on the randao_mix computed with the reveal of the previous block. - if (state.isPostBellatrix() and isExecutionEnabled(cached_state.state, block)) { + if (state.forkSeq().gte(.bellatrix) and isExecutionEnabled(state, block)) { // TODO Deneb: Allow to disable withdrawals for interop testing // https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals - if (state.isPostCapella()) { + if (state.forkSeq().gte(.capella)) { // TODO: given max withdrawals of MAX_WITHDRAWALS_PER_PAYLOAD, can use fixed size array instead of heap alloc var withdrawals_result = WithdrawalsResult{ .withdrawals = try Withdrawals.initCapacity( allocator, @@ -83,11 +83,11 @@ pub fn processBlock( try processRandao(cached_state, body, block.proposerIndex(), opts.verify_signature); try processEth1Data(allocator, cached_state, body.eth1Data()); try processOperations(allocator, cached_state, body, opts); - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { try processSyncAggregate(allocator, cached_state, body.syncAggregate(), opts.verify_signature); } - if (state.isPostDeneb()) { + if (state.forkSeq().gte(.deneb)) { try processBlobKzgCommitments(external_data); // Only throw PreData so beacon can also sync/process blocks optimistically // and let forkChoice handle it diff --git a/src/state_transition/block/process_block_header.zig b/src/state_transition/block/process_block_header.zig index 112cfd4da..46fe49c33 100644 --- a/src/state_transition/block/process_block_header.zig +++ b/src/state_transition/block/process_block_header.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const types = @import("consensus_types"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const BeaconBlock = @import("../types/beacon_block.zig").BeaconBlock; const BeaconConfig = @import("config").BeaconConfig; const BeaconBlockHeader = types.phase0.BeaconBlockHeader.Type; @@ -10,9 +10,9 @@ const SignedBlock = @import("../types/block.zig").SignedBlock; const ZERO_HASH = @import("constants").ZERO_HASH; const Block = @import("../types/block.zig").Block; -pub fn processBlockHeader(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, block: Block) !void { +pub fn processBlockHeader(allocator: Allocator, cached_state: *CachedBeaconState, block: Block) !void { const state = cached_state.state; - const slot = state.slot(); + const slot = try state.slot(); // verify that the slots match if (block.slot() != slot) { @@ -20,7 +20,9 @@ pub fn processBlockHeader(allocator: Allocator, cached_state: *const CachedBeaco } // Verify that the block is newer than latest block header - if (!(block.slot() > state.latestBlockHeader().slot)) { + var latest_header_view = try state.latestBlockHeader(); + const latest_header_slot = try latest_header_view.get("slot"); + if (!(block.slot() > latest_header_slot)) { return error.BlockNotNewerThanLatestHeader; } @@ -31,15 +33,13 @@ pub fn processBlockHeader(allocator: Allocator, cached_state: *const CachedBeaco } // verify that the parent matches - var header_parent_root: [32]u8 = undefined; - try types.phase0.BeaconBlockHeader.hashTreeRoot(state.latestBlockHeader(), &header_parent_root); - if (!std.mem.eql(u8, &block.parentRoot(), &header_parent_root)) { + const header_parent_root = try latest_header_view.hashTreeRoot(); + if (!std.mem.eql(u8, &block.parentRoot(), header_parent_root)) { return error.BlockParentRootMismatch; } var body_root: [32]u8 = undefined; try block.beaconBlockBody().hashTreeRoot(allocator, &body_root); // cache current block as the new latest block - const state_latest_block_header = state.latestBlockHeader(); const latest_block_header: BeaconBlockHeader = .{ .slot = slot, .proposer_index = proposer_index, @@ -47,10 +47,13 @@ pub fn processBlockHeader(allocator: Allocator, cached_state: *const CachedBeaco .state_root = ZERO_HASH, .body_root = body_root, }; - state_latest_block_header.* = latest_block_header; + try state.setLatestBlockHeader(&latest_block_header); // verify proposer is not slashed. Only once per block, may use the slower read from tree - if (state.validators().items[proposer_index].slashed) { + var validators_view = try state.validators(); + var proposer_validator_view = try validators_view.get(proposer_index); + const proposer_slashed = try proposer_validator_view.get("slashed"); + if (proposer_slashed) { return error.BlockProposerSlashed; } } diff --git a/src/state_transition/block/process_bls_to_execution_change.zig b/src/state_transition/block/process_bls_to_execution_change.zig index 098cc324d..1ac164a2e 100644 --- a/src/state_transition/block/process_bls_to_execution_change.zig +++ b/src/state_transition/block/process_bls_to_execution_change.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const Root = types.primitive.Root.Type; const SignedBLSToExecutionChange = types.capella.SignedBLSToExecutionChange.Type; @@ -7,32 +7,35 @@ const c = @import("constants"); const digest = @import("../utils/sha256.zig").digest; const verifyBlsToExecutionChangeSignature = @import("../signature_sets/bls_to_execution_change.zig").verifyBlsToExecutionChangeSignature; -pub fn processBlsToExecutionChange(cached_state: *CachedBeaconStateAllForks, signed_bls_to_execution_change: *const SignedBLSToExecutionChange) !void { +pub fn processBlsToExecutionChange(cached_state: *CachedBeaconState, signed_bls_to_execution_change: *const SignedBLSToExecutionChange) !void { const address_change = signed_bls_to_execution_change.message; - const state = cached_state.state; + var state = cached_state.state; try isValidBlsToExecutionChange(cached_state, signed_bls_to_execution_change, true); var new_withdrawal_credentials: Root = [_]u8{0} ** 32; const validator_index = address_change.validator_index; - var validator = &state.validators().items[validator_index]; + var validators = try state.validators(); + var validator = try validators.get(@intCast(validator_index)); new_withdrawal_credentials[0] = c.ETH1_ADDRESS_WITHDRAWAL_PREFIX; @memcpy(new_withdrawal_credentials[12..], &address_change.to_execution_address); // Set the new credentials back - validator.withdrawal_credentials = new_withdrawal_credentials; + try validator.setValue("withdrawal_credentials", &new_withdrawal_credentials); } -pub fn isValidBlsToExecutionChange(cached_state: *CachedBeaconStateAllForks, signed_bls_to_execution_change: *const SignedBLSToExecutionChange, verify_signature: bool) !void { +pub fn isValidBlsToExecutionChange(cached_state: *CachedBeaconState, signed_bls_to_execution_change: *const SignedBLSToExecutionChange, verify_signature: bool) !void { const state = cached_state.state; const address_change = signed_bls_to_execution_change.message; const validator_index = address_change.validator_index; - if (validator_index >= state.validators().items.len) { + var validators = try state.validators(); + const validators_len = try validators.length(); + if (validator_index >= validators_len) { return error.InvalidBlsToExecutionChange; } - const validator = state.validators().items[validator_index]; - const withdrawal_credentials = validator.withdrawal_credentials; + var validator = try validators.get(@intCast(validator_index)); + const withdrawal_credentials = try validator.getRoot("withdrawal_credentials"); if (withdrawal_credentials[0] != c.BLS_WITHDRAWAL_PREFIX) { return error.InvalidWithdrawalCredentialsPrefix; } @@ -41,7 +44,7 @@ pub fn isValidBlsToExecutionChange(cached_state: *CachedBeaconStateAllForks, sig digest(&address_change.from_bls_pubkey, &digest_credentials); // Set the BLS_WITHDRAWAL_PREFIX on the digest_credentials for direct match digest_credentials[0] = c.BLS_WITHDRAWAL_PREFIX; - if (!std.mem.eql(u8, &withdrawal_credentials, &digest_credentials)) { + if (!std.mem.eql(u8, withdrawal_credentials, &digest_credentials)) { return error.InvalidWithdrawalCredentials; } diff --git a/src/state_transition/block/process_consolidation_request.zig b/src/state_transition/block/process_consolidation_request.zig index 171460cdb..8bfce3470 100644 --- a/src/state_transition/block/process_consolidation_request.zig +++ b/src/state_transition/block/process_consolidation_request.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const preset = @import("preset").preset; const FAR_FUTURE_EPOCH = @import("constants").FAR_FUTURE_EPOCH; @@ -15,15 +15,14 @@ const computeConsolidationEpochAndUpdateChurn = @import("../utils/epoch.zig").co const validator_utils = @import("../utils/validator.zig"); const getConsolidationChurnLimit = validator_utils.getConsolidationChurnLimit; const getPendingBalanceToWithdraw = validator_utils.getPendingBalanceToWithdraw; -const isActiveValidator = validator_utils.isActiveValidator; +const isActiveValidatorView = validator_utils.isActiveValidatorView; // TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest pub fn processConsolidationRequest( - allocator: std.mem.Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, consolidation: *const ConsolidationRequest, ) !void { - const state = cached_state.state; + var state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const config = epoch_cache.config; @@ -31,14 +30,14 @@ pub fn processConsolidationRequest( const target_pubkey = consolidation.target_pubkey; const source_address = consolidation.source_address; - if (!isPubkeyKnown(cached_state, source_pubkey)) return; - if (!isPubkeyKnown(cached_state, target_pubkey)) return; + if (!(try isPubkeyKnown(cached_state, source_pubkey))) return; + if (!(try isPubkeyKnown(cached_state, target_pubkey))) return; const source_index = epoch_cache.pubkey_to_index.get(&source_pubkey) orelse return; const target_index = epoch_cache.pubkey_to_index.get(&target_pubkey) orelse return; - if (isValidSwitchToCompoundRequest(cached_state, consolidation)) { - try switchToCompoundingValidator(allocator, cached_state, source_index); + if (try isValidSwitchToCompoundRequest(cached_state, consolidation)) { + try switchToCompoundingValidator(cached_state, source_index); // Early return since we have already switched validator to compounding return; } @@ -49,7 +48,8 @@ pub fn processConsolidationRequest( } // If the pending consolidations queue is full, consolidation requests are ignored - if (state.pendingConsolidations().items.len >= preset.PENDING_CONSOLIDATIONS_LIMIT) { + var pending_consolidations = try state.pendingConsolidations(); + if (try pending_consolidations.length() >= preset.PENDING_CONSOLIDATIONS_LIMIT) { return; } @@ -58,57 +58,64 @@ pub fn processConsolidationRequest( return; } - const source_validator = &state.validators().items[source_index]; - const target_validator = &state.validators().items[target_index]; - const source_withdrawal_address = source_validator.withdrawal_credentials[12..]; + var validators = try state.validators(); + var source_validator = try validators.get(@intCast(source_index)); + var target_validator = try validators.get(@intCast(target_index)); + const source_withdrawal_credentials = try source_validator.getRoot("withdrawal_credentials"); + const target_withdrawal_credentials = try target_validator.getRoot("withdrawal_credentials"); + const source_withdrawal_address = source_withdrawal_credentials[12..]; const current_epoch = epoch_cache.epoch; // Verify source withdrawal credentials - const has_correct_credential = hasExecutionWithdrawalCredential(source_validator.withdrawal_credentials); + const has_correct_credential = hasExecutionWithdrawalCredential(source_withdrawal_credentials); const is_correct_source_address = std.mem.eql(u8, source_withdrawal_address, &source_address); if (!(has_correct_credential and is_correct_source_address)) { return; } // Verify that target has compounding withdrawal credentials - if (!hasCompoundingWithdrawalCredential(target_validator.withdrawal_credentials)) { + if (!hasCompoundingWithdrawalCredential(target_withdrawal_credentials)) { return; } // Verify the source and the target are active - if (!isActiveValidator(source_validator, current_epoch) or !isActiveValidator(target_validator, current_epoch)) { + if (!(try isActiveValidatorView(&source_validator, current_epoch)) or !(try isActiveValidatorView(&target_validator, current_epoch))) { return; } // Verify exits for source and target have not been initiated - if (source_validator.exit_epoch != FAR_FUTURE_EPOCH or target_validator.exit_epoch != FAR_FUTURE_EPOCH) { + const source_exit_epoch = try source_validator.get("exit_epoch"); + const target_exit_epoch = try target_validator.get("exit_epoch"); + if (source_exit_epoch != FAR_FUTURE_EPOCH or target_exit_epoch != FAR_FUTURE_EPOCH) { return; } // Verify the source has been active long enough - if (current_epoch < source_validator.activation_epoch + config.chain.SHARD_COMMITTEE_PERIOD) { + const source_activation_epoch = try source_validator.get("activation_epoch"); + if (current_epoch < source_activation_epoch + config.chain.SHARD_COMMITTEE_PERIOD) { return; } // Verify the source has no pending withdrawals in the queue - if (getPendingBalanceToWithdraw(cached_state.state, source_index) > 0) { + if (try getPendingBalanceToWithdraw(state, source_index) > 0) { return; } // Initiate source validator exit and append pending consolidation // TODO Electra: See if we can get rid of big int - const exit_epoch = computeConsolidationEpochAndUpdateChurn(cached_state, source_validator.effective_balance); - source_validator.exit_epoch = exit_epoch; - source_validator.withdrawable_epoch = exit_epoch + config.chain.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; + const effective_balance = try source_validator.get("effective_balance"); + const exit_epoch = try computeConsolidationEpochAndUpdateChurn(cached_state, effective_balance); + try source_validator.set("exit_epoch", exit_epoch); + try source_validator.set("withdrawable_epoch", exit_epoch + config.chain.MIN_VALIDATOR_WITHDRAWABILITY_DELAY); const pending_consolidation = PendingConsolidation{ .source_index = source_index, .target_index = target_index, }; - try state.pendingConsolidations().append(allocator, pending_consolidation); + try pending_consolidations.pushValue(&pending_consolidation); } -fn isValidSwitchToCompoundRequest(cached_state: *const CachedBeaconStateAllForks, consolidation: *const ConsolidationRequest) bool { +fn isValidSwitchToCompoundRequest(cached_state: *const CachedBeaconState, consolidation: *const ConsolidationRequest) !bool { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); @@ -121,8 +128,10 @@ fn isValidSwitchToCompoundRequest(cached_state: *const CachedBeaconStateAllForks return false; } - const source_validator = state.validators().items[source_index]; - const source_withdrawal_address = source_validator.withdrawal_credentials[12..]; + var validators = try state.validators(); + var source_validator = try validators.get(@intCast(source_index)); + const source_withdrawal_credentials = try source_validator.getRoot("withdrawal_credentials"); + const source_withdrawal_address = source_withdrawal_credentials[12..]; // Verify request has been authorized if (std.mem.eql(u8, source_withdrawal_address, &consolidation.source_address) == false) { @@ -130,17 +139,17 @@ fn isValidSwitchToCompoundRequest(cached_state: *const CachedBeaconStateAllForks } // Verify source withdrawal credentials - if (!hasEth1WithdrawalCredential(source_validator.withdrawal_credentials)) { + if (!hasEth1WithdrawalCredential(source_withdrawal_credentials)) { return false; } // Verify the source is active - if (!isActiveValidator(&source_validator, epoch_cache.epoch)) { + if (!try isActiveValidatorView(&source_validator, epoch_cache.epoch)) { return false; } // Verify exit for source has not been initiated - if (source_validator.exit_epoch != FAR_FUTURE_EPOCH) { + if (try source_validator.get("exit_epoch") != FAR_FUTURE_EPOCH) { return false; } diff --git a/src/state_transition/block/process_deposit.zig b/src/state_transition/block/process_deposit.zig index c3275cc31..bb967c787 100644 --- a/src/state_transition/block/process_deposit.zig +++ b/src/state_transition/block/process_deposit.zig @@ -17,7 +17,7 @@ const computeSigningRoot = @import("../utils/signing_root.zig").computeSigningRo const blst = @import("blst"); const verify = @import("../utils/bls.zig").verify; const ForkSeq = types.primitive.ForkSeq.Type; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const getMaxEffectiveBalance = @import("../utils/validator.zig").getMaxEffectiveBalance; const increaseBalance = @import("../utils/balance.zig").increaseBalance; const verifyMerkleBranch = @import("../utils/verify_merkle_branch.zig").verifyMerkleBranch; @@ -33,10 +33,10 @@ pub const DepositData = union(enum) { }; } - pub fn withdrawalCredentials(self: *const DepositData) WithdrawalCredentials { + pub fn withdrawalCredentials(self: *const DepositData) *const WithdrawalCredentials { return switch (self.*) { - .phase0 => |data| data.withdrawal_credentials, - .electra => |data| data.withdrawal_credentials, + .phase0 => |*data| &data.withdrawal_credentials, + .electra => |*data| &data.withdrawal_credentials, }; } @@ -55,24 +55,26 @@ pub const DepositData = union(enum) { } }; -pub fn processDeposit(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, deposit: *const types.phase0.Deposit.Type) !void { - const state = cached_state.state; +pub fn processDeposit(allocator: Allocator, cached_state: *CachedBeaconState, deposit: *const types.phase0.Deposit.Type) !void { + var state = cached_state.state; // verify the merkle branch var deposit_data_root: Root = undefined; try types.phase0.DepositData.hashTreeRoot(&deposit.data, &deposit_data_root); + + var eth1_data = try state.eth1Data(); + const deposit_root = try eth1_data.getRoot("deposit_root"); if (!verifyMerkleBranch( deposit_data_root, &deposit.proof, c.DEPOSIT_CONTRACT_TREE_DEPTH + 1, - state.eth1DepositIndex(), - state.eth1Data().deposit_root, + @intCast(try state.eth1DepositIndex()), + deposit_root.*, )) { return error.InvalidMerkleProof; } // deposits must be processed in order - const state_eth1_deposit_index = state.eth1DepositIndexPtr(); - state_eth1_deposit_index.* += 1; + try state.incrementEth1DepositIndex(); try applyDeposit(allocator, cached_state, &.{ .phase0 = deposit.data, }); @@ -80,9 +82,9 @@ pub fn processDeposit(allocator: Allocator, cached_state: *CachedBeaconStateAllF /// Adds a new validator into the registry. Or increase balance if already exist. /// Follows applyDeposit() in consensus spec. Will be used by processDeposit() and processDepositRequest() -pub fn applyDeposit(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, deposit: *const DepositData) !void { +pub fn applyDeposit(allocator: Allocator, cached_state: *CachedBeaconState, deposit: *const DepositData) !void { const config = cached_state.config; - const state = cached_state.state; + var state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const pubkey = deposit.pubkey(); const withdrawal_credentials = deposit.withdrawalCredentials(); @@ -90,66 +92,74 @@ pub fn applyDeposit(allocator: Allocator, cached_state: *CachedBeaconStateAllFor const signature = deposit.signature(); const cached_index = epoch_cache.getValidatorIndex(&pubkey); - const is_new_validator = cached_index == null or cached_index.? >= state.validators().items.len; + const is_new_validator = cached_index == null or cached_index.? >= try state.validatorsCount(); - if (state.isPreElectra()) { + if (state.forkSeq().lt(.electra)) { if (is_new_validator) { - if (isValidDepositSignature(config, pubkey, withdrawal_credentials, amount, signature)) { + if (validateDepositSignature(config, pubkey, withdrawal_credentials, amount, signature)) { try addValidatorToRegistry(allocator, cached_state, pubkey, withdrawal_credentials, amount); + } else |_| { + // invalid deposit signature, ignore the deposit + // TODO may be a useful metric to track } } else { // increase balance by deposit amount right away pre-electra const index = cached_index.?; - increaseBalance(state, index, amount); + try increaseBalance(state, index, amount); } } else { const pending_deposit = types.electra.PendingDeposit.Type{ .pubkey = pubkey, - .withdrawal_credentials = withdrawal_credentials, + .withdrawal_credentials = withdrawal_credentials.*, .amount = amount, .signature = signature, .slot = c.GENESIS_SLOT, // Use GENESIS_SLOT to distinguish from a pending deposit request }; + var pending_deposits = try state.pendingDeposits(); if (is_new_validator) { - if (isValidDepositSignature(config, pubkey, withdrawal_credentials, amount, signature)) { + if (validateDepositSignature(config, pubkey, withdrawal_credentials, amount, signature)) { try addValidatorToRegistry(allocator, cached_state, pubkey, withdrawal_credentials, 0); - try state.pendingDeposits().append(allocator, pending_deposit); + try pending_deposits.pushValue(&pending_deposit); + } else |_| { + // invalid deposit signature, ignore the deposit + // TODO may be a useful metric to track } } else { - try state.pendingDeposits().append(allocator, pending_deposit); + try pending_deposits.pushValue(&pending_deposit); } } } pub fn addValidatorToRegistry( allocator: Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, pubkey: BLSPubkey, - withdrawal_credentials: WithdrawalCredentials, + withdrawal_credentials: *const WithdrawalCredentials, amount: u64, ) !void { const epoch_cache = cached_state.getEpochCache(); const state = cached_state.state; - const validators = state.validators(); + var validators = try state.validators(); // add validator and balance entries const effective_balance = @min( amount - (amount % preset.EFFECTIVE_BALANCE_INCREMENT), - if (state.isPreElectra()) preset.MAX_EFFECTIVE_BALANCE else getMaxEffectiveBalance(withdrawal_credentials), + if (state.forkSeq().lt(.electra)) preset.MAX_EFFECTIVE_BALANCE else getMaxEffectiveBalance(withdrawal_credentials), ); - try validators.append(allocator, .{ + const validator: types.phase0.Validator.Type = .{ .pubkey = pubkey, - .withdrawal_credentials = withdrawal_credentials, + .withdrawal_credentials = withdrawal_credentials.*, .activation_eligibility_epoch = c.FAR_FUTURE_EPOCH, .activation_epoch = c.FAR_FUTURE_EPOCH, .exit_epoch = c.FAR_FUTURE_EPOCH, .withdrawable_epoch = c.FAR_FUTURE_EPOCH, .effective_balance = effective_balance, .slashed = false, - }); + }; + try validators.pushValue(&validator); - const validator_index = validators.items.len - 1; + const validator_index = (try validators.length()) - 1; // TODO Electra: Review this // Updating here is better than updating at once on epoch transition // - Simplify genesis fn applyDeposits(): effectiveBalanceIncrements is populated immediately @@ -161,27 +171,32 @@ pub fn addValidatorToRegistry( try epoch_cache.addPubkey(validator_index, pubkey); // Only after altair: - if (state.isPostAltair()) { - const inactivity_scores = state.inactivityScores(); - try inactivity_scores.append(allocator, 0); + if (state.forkSeq().gte(.altair)) { + var inactivity_scores = try state.inactivityScores(); + try inactivity_scores.push(0); // add participation caches - try state.previousEpochParticipations().append(allocator, 0); - const state_current_epoch_participations = state.currentEpochParticipations(); - try state_current_epoch_participations.append(allocator, 0); + var previous_epoch_participation = try state.previousEpochParticipation(); + try previous_epoch_participation.push(0); + var state_current_epoch_participation = try state.currentEpochParticipation(); + try state_current_epoch_participation.push(0); } - const balances = state.balances(); - - try balances.append(allocator, amount); + var balances = try state.balances(); + try balances.push(amount); } /// refer to https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/electra/beacon-chain.md#new-is_valid_deposit_signature -/// no need to return error union since consumer does not care about the reason of failure -pub fn isValidDepositSignature(config: *const BeaconConfig, pubkey: BLSPubkey, withdrawal_credential: WithdrawalCredentials, amount: u64, deposit_signature: BLSSignature) bool { +pub fn validateDepositSignature( + config: *const BeaconConfig, + pubkey: BLSPubkey, + withdrawal_credentials: *const WithdrawalCredentials, + amount: u64, + deposit_signature: BLSSignature, +) !void { // verify the deposit signature (proof of posession) which is not checked by the deposit contract const deposit_message = DepositMessage{ .pubkey = pubkey, - .withdrawal_credentials = withdrawal_credential, + .withdrawal_credentials = withdrawal_credentials.*, .amount = amount, }; @@ -189,14 +204,14 @@ pub fn isValidDepositSignature(config: *const BeaconConfig, pubkey: BLSPubkey, w // fork-agnostic domain since deposits are valid across forks var domain: Domain = undefined; - computeDomain(DOMAIN_DEPOSIT, GENESIS_FORK_VERSION, ZERO_HASH, &domain) catch return false; + try computeDomain(DOMAIN_DEPOSIT, GENESIS_FORK_VERSION, ZERO_HASH, &domain); var signing_root: Root = undefined; - computeSigningRoot(types.phase0.DepositMessage, &deposit_message, &domain, &signing_root) catch return false; + try computeSigningRoot(types.phase0.DepositMessage, &deposit_message, &domain, &signing_root); // Pubkeys must be checked for group + inf. This must be done only once when the validator deposit is processed - const public_key = blst.PublicKey.uncompress(&pubkey) catch return false; - public_key.validate() catch return false; - const signature = blst.Signature.uncompress(&deposit_signature) catch return false; - signature.validate(true) catch return false; - return verify(&signing_root, &public_key, &signature, null, null); + const public_key = try blst.PublicKey.uncompress(&pubkey); + try public_key.validate(); + const signature = try blst.Signature.uncompress(&deposit_signature); + try signature.validate(true); + try verify(&signing_root, &public_key, &signature, null, null); } diff --git a/src/state_transition/block/process_deposit_request.zig b/src/state_transition/block/process_deposit_request.zig index 13bd0b689..72cab6f4d 100644 --- a/src/state_transition/block/process_deposit_request.zig +++ b/src/state_transition/block/process_deposit_request.zig @@ -1,16 +1,15 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const DepositRequest = types.electra.DepositRequest.Type; const PendingDeposit = types.electra.PendingDeposit.Type; -const Root = types.primitive.Root.Type; const c = @import("constants"); -pub fn processDepositRequest(allocator: std.mem.Allocator, cached_state: *CachedBeaconStateAllForks, deposit_request: *const DepositRequest) !void { - const state = cached_state.state; - const deposit_requests_start_index = state.depositRequestsStartIndex(); - if (deposit_requests_start_index.* == c.UNSET_DEPOSIT_REQUESTS_START_INDEX) { - deposit_requests_start_index.* = deposit_request.index; +pub fn processDepositRequest(cached_state: *CachedBeaconState, deposit_request: *const DepositRequest) !void { + var state = cached_state.state; + const deposit_requests_start_index = try state.depositRequestsStartIndex(); + if (deposit_requests_start_index == c.UNSET_DEPOSIT_REQUESTS_START_INDEX) { + try state.setDepositRequestsStartIndex(deposit_request.index); } const pending_deposit = PendingDeposit{ @@ -18,8 +17,9 @@ pub fn processDepositRequest(allocator: std.mem.Allocator, cached_state: *Cached .withdrawal_credentials = deposit_request.withdrawal_credentials, .amount = deposit_request.amount, .signature = deposit_request.signature, - .slot = state.slot(), + .slot = try state.slot(), }; - try state.pendingDeposits().append(allocator, pending_deposit); + var pending_deposits = try state.pendingDeposits(); + try pending_deposits.pushValue(&pending_deposit); } diff --git a/src/state_transition/block/process_eth1_data.zig b/src/state_transition/block/process_eth1_data.zig index b9d035402..2f41e1073 100644 --- a/src/state_transition/block/process_eth1_data.zig +++ b/src/state_transition/block/process_eth1_data.zig @@ -1,41 +1,46 @@ const std = @import("std"); const types = @import("consensus_types"); const Eth1Data = types.phase0.Eth1Data.Type; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const preset = @import("preset").preset; -pub fn processEth1Data(allocator: std.mem.Allocator, cached_state: *const CachedBeaconStateAllForks, eth1_data: *const Eth1Data) !void { +pub fn processEth1Data(allocator: std.mem.Allocator, cached_state: *CachedBeaconState, eth1_data: *const Eth1Data) !void { const state = cached_state.state; - if (becomesNewEth1Data(cached_state, eth1_data)) { - const state_eth1_data = state.eth1Data(); - state_eth1_data.* = eth1_data.*; + if (try becomesNewEth1Data(allocator, state, eth1_data)) { + try state.setEth1Data(eth1_data); } - try state.eth1DataVotes().append(allocator, eth1_data.*); + try state.appendEth1DataVote(eth1_data); } -pub fn becomesNewEth1Data(cached_state: *const CachedBeaconStateAllForks, new_eth1_data: *const Eth1Data) bool { - const state = cached_state.state; +pub fn becomesNewEth1Data(allocator: std.mem.Allocator, state: *BeaconState, new_eth1_data: *const Eth1Data) !bool { const SLOTS_PER_ETH1_VOTING_PERIOD = preset.EPOCHS_PER_ETH1_VOTING_PERIOD * preset.SLOTS_PER_EPOCH; // If there are not more than 50% votes, then we do not have to count to find a winner. - const state_eth1_data_votes = state.eth1DataVotes().items; - if ((state_eth1_data_votes.len + 1) * 2 <= SLOTS_PER_ETH1_VOTING_PERIOD) { - return false; - } + var state_eth1_data_votes_view = try state.eth1DataVotes(); + const state_eth1_data_votes_len = try state_eth1_data_votes_view.length(); + if ((state_eth1_data_votes_len + 1) * 2 <= SLOTS_PER_ETH1_VOTING_PERIOD) return false; // Nothing to do if the state already has this as eth1data (happens a lot after majority vote is in) - if (isEqualEth1DataView(state.eth1Data(), new_eth1_data)) { - return false; - } + var state_eth1_data_view = try state.eth1Data(); + var state_eth1_data: Eth1Data = undefined; + try state_eth1_data_view.toValue(allocator, &state_eth1_data); + if (types.phase0.Eth1Data.equals(&state_eth1_data, new_eth1_data)) return false; + + var new_eth1_data_root: [32]u8 = undefined; + try types.phase0.Eth1Data.hashTreeRoot(new_eth1_data, &new_eth1_data_root); // Close to half the EPOCHS_PER_ETH1_VOTING_PERIOD it can be expensive to do so many comparisions. - // `eth1DataVotes.getAllReadonly()` navigates the tree once to fetch all the LeafNodes efficiently. + // + // `iteratorReadonly` navigates the tree once to fetch all the LeafNodes efficiently. // Then isEqualEth1DataView compares cached roots (HashObject as of Jan 2022) which is much cheaper // than doing structural equality, which requires tree -> value conversions var same_votes_count: usize = 0; - for (state_eth1_data_votes) |state_eth1_data_vote| { - if (isEqualEth1DataView(&state_eth1_data_vote, new_eth1_data)) { + var eth1_data_votes_it = state_eth1_data_votes_view.iteratorReadonly(0); + for (0..state_eth1_data_votes_len) |_| { + const state_eth1_data_vote_root = try eth1_data_votes_it.nextRoot(); + if (std.mem.eql(u8, state_eth1_data_vote_root, &new_eth1_data_root)) { same_votes_count += 1; } } @@ -47,8 +52,3 @@ pub fn becomesNewEth1Data(cached_state: *const CachedBeaconStateAllForks, new_et return false; } - -// TODO: should have a different implement in TreeView -fn isEqualEth1DataView(a: *const Eth1Data, b: *const Eth1Data) bool { - return types.phase0.Eth1Data.equals(a, b); -} diff --git a/src/state_transition/block/process_execution_payload.zig b/src/state_transition/block/process_execution_payload.zig index ab17bd3ac..5b7db7fa8 100644 --- a/src/state_transition/block/process_execution_payload.zig +++ b/src/state_transition/block/process_execution_payload.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const preset = @import("preset").preset; const ForkSeq = @import("config").ForkSeq; @@ -24,7 +24,7 @@ const PartialPayload = struct { pub fn processExecutionPayload( allocator: Allocator, - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, body: Body, external_data: BlockExternalData, ) !void { @@ -54,15 +54,15 @@ pub fn processExecutionPayload( // Verify consistency of the parent hash, block number, base fee per gas and gas limit // with respect to the previous execution payload header if (isMergeTransitionComplete(state)) { - const latest_header = state.latestExecutionPayloadHeader(); - if (!std.mem.eql(u8, &partial_payload.parent_hash, &latest_header.getBlockHash())) { + const latest_block_hash = try state.latestExecutionPayloadHeaderBlockHash(); + if (!std.mem.eql(u8, &partial_payload.parent_hash, latest_block_hash)) { return error.InvalidExecutionPayloadParentHash; } } // Verify random - const expected_random = getRandaoMix(state, epoch_cache.epoch); - if (!std.mem.eql(u8, &partial_payload.prev_randao, &expected_random)) { + const expected_random = try getRandaoMix(state, epoch_cache.epoch); + if (!std.mem.eql(u8, &partial_payload.prev_randao, expected_random)) { return error.InvalidExecutionPayloadRandom; } @@ -72,12 +72,12 @@ pub fn processExecutionPayload( // def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: // slots_since_genesis = slot - GENESIS_SLOT // return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) - if (partial_payload.timestamp != state.genesisTime() + state.slot() * config.chain.SECONDS_PER_SLOT) { + if (partial_payload.timestamp != (try state.genesisTime()) + (try state.slot()) * config.chain.SECONDS_PER_SLOT) { return error.InvalidExecutionPayloadTimestamp; } - if (state.isPostDeneb()) { - const max_blobs_per_block = config.getMaxBlobsPerBlock(computeEpochAtSlot(state.slot())); + if (state.forkSeq().gte(.deneb)) { + const max_blobs_per_block = config.getMaxBlobsPerBlock(computeEpochAtSlot(try state.slot())); if (body.blobKzgCommitmentsLen() > max_blobs_per_block) { return error.BlobKzgCommitmentsExceedsLimit; } @@ -96,12 +96,12 @@ pub fn processExecutionPayload( return error.InvalidExecutionPayload; } - var payload_header: ExecutionPayloadHeader = undefined; + var payload_header = try ExecutionPayloadHeader.init(state.forkSeq()); switch (body) { .regular => |b| try b.executionPayload().createPayloadHeader(allocator, &payload_header), .blinded => |b| try b.executionPayloadHeader().clone(allocator, &payload_header), } - defer payload_header.destroy(allocator); + defer payload_header.deinit(allocator); - state.setLatestExecutionPayloadHeader(allocator, payload_header); + try state.setLatestExecutionPayloadHeader(&payload_header); } diff --git a/src/state_transition/block/process_operations.zig b/src/state_transition/block/process_operations.zig index 25fcb59c6..540ad2480 100644 --- a/src/state_transition/block/process_operations.zig +++ b/src/state_transition/block/process_operations.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const preset = @import("preset").preset; const Body = @import("../types/block.zig").Body; @@ -18,14 +18,14 @@ const ProcessBlockOpts = @import("./process_block.zig").ProcessBlockOpts; pub fn processOperations( allocator: std.mem.Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, body: Body, opts: ProcessBlockOpts, ) !void { const state = cached_state.state; // verify that outstanding deposits are processed up to the maximum number of deposits - const max_deposits = getEth1DepositCount(cached_state, null); + const max_deposits = try getEth1DepositCount(cached_state, null); if (body.deposits().len != max_deposits) { return error.InvalidDepositCount; } @@ -58,23 +58,23 @@ pub fn processOperations( try processVoluntaryExit(cached_state, voluntary_exit, opts.verify_signature); } - if (state.isPostCapella()) { + if (state.forkSeq().gte(.capella)) { for (body.blsToExecutionChanges()) |*bls_to_execution_change| { try processBlsToExecutionChange(cached_state, bls_to_execution_change); } } - if (state.isPostElectra()) { + if (state.forkSeq().gte(.electra)) { for (body.depositRequests()) |*deposit_request| { - try processDepositRequest(allocator, cached_state, deposit_request); + try processDepositRequest(cached_state, deposit_request); } for (body.withdrawalRequests()) |*withdrawal_request| { - try processWithdrawalRequest(allocator, cached_state, withdrawal_request); + try processWithdrawalRequest(cached_state, withdrawal_request); } for (body.consolidationRequests()) |*consolidation_request| { - try processConsolidationRequest(allocator, cached_state, consolidation_request); + try processConsolidationRequest(cached_state, consolidation_request); } } } diff --git a/src/state_transition/block/process_proposer_slashing.zig b/src/state_transition/block/process_proposer_slashing.zig index 85a986de9..57fd45d44 100644 --- a/src/state_transition/block/process_proposer_slashing.zig +++ b/src/state_transition/block/process_proposer_slashing.zig @@ -1,4 +1,4 @@ -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const types = @import("consensus_types"); const ProposerSlashing = types.phase0.ProposerSlashing.Type; @@ -8,7 +8,7 @@ const verifySignature = @import("../utils/signature_sets.zig").verifySingleSigna const slashValidator = @import("./slash_validator.zig").slashValidator; pub fn processProposerSlashing( - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, proposer_slashing: *const ProposerSlashing, verify_signatures: bool, ) !void { @@ -18,7 +18,7 @@ pub fn processProposerSlashing( } pub fn assertValidProposerSlashing( - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, proposer_slashing: *const ProposerSlashing, verify_signature: bool, ) !void { @@ -37,7 +37,9 @@ pub fn assertValidProposerSlashing( return error.InvalidProposerSlashingProposerIndexMismatch; } - if (header_1.proposer_index >= state.validators().items.len) { + var validators_view = try state.validators(); + const validators_len = try validators_view.length(); + if (header_1.proposer_index >= validators_len) { return error.InvalidProposerSlashingProposerIndexOutOfRange; } @@ -47,7 +49,9 @@ pub fn assertValidProposerSlashing( } // verify the proposer is slashable - const proposer = state.validators().items[header_1.proposer_index]; + var proposer_view = try validators_view.get(header_1.proposer_index); + var proposer: types.phase0.Validator.Type = undefined; + try proposer_view.toValue(cached_state.allocator, &proposer); if (!isSlashableValidator(&proposer, epoch_cache.epoch)) { return error.InvalidProposerSlashingProposerNotSlashable; } diff --git a/src/state_transition/block/process_randao.zig b/src/state_transition/block/process_randao.zig index 5917ed066..517128a02 100644 --- a/src/state_transition/block/process_randao.zig +++ b/src/state_transition/block/process_randao.zig @@ -1,10 +1,6 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); -const preset = @import("preset").preset; -const ForkSeq = @import("config").ForkSeq; -const BeaconBlock = @import("../types/beacon_block.zig").BeaconBlock; const Body = @import("../types/block.zig").Body; const Bytes32 = types.primitive.Bytes32.Type; const getRandaoMix = @import("../utils/seed.zig").getRandaoMix; @@ -12,7 +8,7 @@ const verifyRandaoSignature = @import("../signature_sets/randao.zig").verifyRand const digest = @import("../utils/sha256.zig").digest; pub fn processRandao( - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, body: Body, proposer_idx: u64, verify_signature: bool, @@ -24,7 +20,7 @@ pub fn processRandao( // verify RANDAO reveal if (verify_signature) { - if (!try verifyRandaoSignature(cached_state, body, cached_state.state.slot(), proposer_idx)) { + if (!try verifyRandaoSignature(cached_state, body, try cached_state.state.slot(), proposer_idx)) { return error.InvalidRandaoSignature; } } @@ -32,15 +28,15 @@ pub fn processRandao( // mix in RANDAO reveal var randao_reveal_digest: [32]u8 = undefined; digest(&randao_reveal, &randao_reveal_digest); - const randao_mix = xor(getRandaoMix(state, epoch), randao_reveal_digest); - const state_randao_mixes = state.randaoMixes(); - state_randao_mixes[epoch % preset.EPOCHS_PER_HISTORICAL_VECTOR] = randao_mix; + + var randao_mix: [32]u8 = undefined; + const current_mix = try getRandaoMix(state, epoch); + xor(current_mix, &randao_reveal_digest, &randao_mix); + try state.setRandaoMix(epoch, &randao_mix); } -fn xor(a: Bytes32, b: Bytes32) Bytes32 { - var result: Bytes32 = undefined; - for (0..types.primitive.Bytes32.length) |i| { - result[i] = a[i] ^ b[i]; +fn xor(a: *const [32]u8, b: *const [32]u8, out: *[32]u8) void { + inline for (a, b, out) |a_i, b_i, *out_i| { + out_i.* = a_i ^ b_i; } - return result; } diff --git a/src/state_transition/block/process_sync_committee.zig b/src/state_transition/block/process_sync_committee.zig index c46e46906..d23d9481f 100644 --- a/src/state_transition/block/process_sync_committee.zig +++ b/src/state_transition/block/process_sync_committee.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const BeaconBlock = @import("../types/beacon_block.zig").BeaconBlock; const Block = @import("../types/block.zig").Block; const ValidatorIndex = types.primitive.ValidatorIndex.Type; @@ -22,7 +22,7 @@ const decreaseBalance = balance_utils.decreaseBalance; pub fn processSyncAggregate( allocator: Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, sync_aggregate: *const SyncAggregate, verify_signatures: bool, ) !void { @@ -43,9 +43,9 @@ pub fn processSyncAggregate( // When there's no participation we cons ider the signature valid and just ignore it if (participant_indices.items.len > 0) { - const previous_slot = @max(state.slot(), 1) - 1; + const previous_slot = @max(try state.slot(), 1) - 1; const root_signed = try getBlockRootAtSlot(state, previous_slot); - const domain = try cached_state.config.getDomain(state.slot(), c.DOMAIN_SYNC_COMMITTEE, previous_slot); + const domain = try cached_state.config.getDomain(try state.slot(), c.DOMAIN_SYNC_COMMITTEE, previous_slot); const pubkeys = try allocator.alloc(blst.PublicKey, participant_indices.items.len); defer allocator.free(pubkeys); @@ -54,7 +54,7 @@ pub fn processSyncAggregate( } var signing_root: Root = undefined; - try computeSigningRoot(types.primitive.Root, &root_signed, domain, &signing_root); + try computeSigningRoot(types.primitive.Root, root_signed, domain, &signing_root); const signature_set = AggregatedSignatureSet{ .pubkeys = pubkeys, @@ -74,9 +74,9 @@ pub fn processSyncAggregate( const sync_participant_reward = epoch_cache.sync_participant_reward; const sync_proposer_reward = epoch_cache.sync_proposer_reward; - const proposer_index = try cached_state.getBeaconProposer(state.slot()); - const balances = state.balances(); - var proposer_balance = balances.items[proposer_index]; + const proposer_index = try cached_state.getBeaconProposer(try state.slot()); + var balances = try state.balances(); + var proposer_balance = try balances.get(proposer_index); for (0..preset.SYNC_COMMITTEE_SIZE) |i| { const index = committee_indices[i]; @@ -86,7 +86,7 @@ pub fn processSyncAggregate( if (index == proposer_index) { proposer_balance += sync_participant_reward; } else { - increaseBalance(state, index, sync_participant_reward); + try increaseBalance(cached_state.state, index, sync_participant_reward); } // Proposer reward @@ -97,19 +97,19 @@ pub fn processSyncAggregate( if (index == proposer_index) { proposer_balance = @max(0, proposer_balance - sync_participant_reward); } else { - decreaseBalance(state, index, sync_participant_reward); + try decreaseBalance(cached_state.state, index, sync_participant_reward); } } } // Apply proposer balance - balances.items[proposer_index] = proposer_balance; + try balances.set(proposer_index, proposer_balance); } /// Consumers should deinit the returned pubkeys /// this is to be used when we implement getBlockSignatureSets /// see https://github.com/ChainSafe/state-transition-z/issues/72 -pub fn getSyncCommitteeSignatureSet(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, block: Block, participant_indices: ?[]usize) !?AggregatedSignatureSet { +pub fn getSyncCommitteeSignatureSet(allocator: Allocator, cached_state: *CachedBeaconState, block: Block, participant_indices: ?[]usize) !?AggregatedSignatureSet { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const sync_aggregate = block.beaconBlockBody().syncAggregate(); @@ -148,7 +148,7 @@ pub fn getSyncCommitteeSignatureSet(allocator: Allocator, cached_state: *const C // So getSyncCommitteeSignatureSet() can be called with a state in any slot (with the correct shuffling) const root_signed = block.parentRoot(); - const domain = try cached_state.config.getDomain(state.slot(), c.DOMAIN_SYNC_COMMITTEE, previous_slot); + const domain = try cached_state.config.getDomain(try state.slot(), c.DOMAIN_SYNC_COMMITTEE, previous_slot); const pubkeys = try allocator.alloc(blst.PublicKey, participant_indices_.len); for (0..participant_indices_.len) |i| { diff --git a/src/state_transition/block/process_voluntary_exit.zig b/src/state_transition/block/process_voluntary_exit.zig index dc9e96804..9153ed03b 100644 --- a/src/state_transition/block/process_voluntary_exit.zig +++ b/src/state_transition/block/process_voluntary_exit.zig @@ -1,45 +1,51 @@ -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const c = @import("constants"); const SignedVoluntaryExit = types.phase0.SignedVoluntaryExit.Type; -const isActiveValidator = @import("../utils/validator.zig").isActiveValidator; const getPendingBalanceToWithdraw = @import("../utils/validator.zig").getPendingBalanceToWithdraw; +const isActiveValidatorView = @import("../utils/validator.zig").isActiveValidatorView; const verifyVoluntaryExitSignature = @import("../signature_sets/voluntary_exits.zig").verifyVoluntaryExitSignature; const initiateValidatorExit = @import("./initiate_validator_exit.zig").initiateValidatorExit; const FAR_FUTURE_EPOCH = c.FAR_FUTURE_EPOCH; -pub fn processVoluntaryExit(cached_state: *CachedBeaconStateAllForks, signed_voluntary_exit: *const SignedVoluntaryExit, verify_signature: bool) !void { +pub fn processVoluntaryExit(cached_state: *CachedBeaconState, signed_voluntary_exit: *const SignedVoluntaryExit, verify_signature: bool) !void { if (!try isValidVoluntaryExit(cached_state, signed_voluntary_exit, verify_signature)) { return error.InvalidVoluntaryExit; } - const validator = &cached_state.state.validators().items[signed_voluntary_exit.message.validator_index]; - try initiateValidatorExit(cached_state, validator); + + var validators = try cached_state.state.validators(); + var validator = try validators.get(@intCast(signed_voluntary_exit.message.validator_index)); + try initiateValidatorExit(cached_state, &validator); } -pub fn isValidVoluntaryExit(cached_state: *CachedBeaconStateAllForks, signed_voluntary_exit: *const SignedVoluntaryExit, verify_signature: bool) !bool { +pub fn isValidVoluntaryExit(cached_state: *CachedBeaconState, signed_voluntary_exit: *const SignedVoluntaryExit, verify_signature: bool) !bool { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const config = cached_state.config.chain; const voluntary_exit = signed_voluntary_exit.message; - if (voluntary_exit.validator_index >= state.validators().items.len) { + var validators = try state.validators(); + const validators_len = try validators.length(); + if (voluntary_exit.validator_index >= validators_len) { return false; } - const validator = state.validators().items[voluntary_exit.validator_index]; + var validator = try validators.get(@intCast(voluntary_exit.validator_index)); const current_epoch = epoch_cache.epoch; + const activation_epoch = try validator.get("activation_epoch"); + const exit_epoch = try validator.get("exit_epoch"); return ( // verify the validator is active - isActiveValidator(&validator, current_epoch) and + (try isActiveValidatorView(&validator, current_epoch)) and // verify exit has not been initiated - validator.exit_epoch == FAR_FUTURE_EPOCH and + exit_epoch == FAR_FUTURE_EPOCH and // exits must specify an epoch when they become valid; they are not valid before then current_epoch >= voluntary_exit.epoch and // verify the validator had been active long enough - current_epoch >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD and - (if (state.isPostElectra()) getPendingBalanceToWithdraw(cached_state.state, voluntary_exit.validator_index) == 0 else true) and + current_epoch >= activation_epoch + config.SHARD_COMMITTEE_PERIOD and + (if (state.forkSeq().gte(.electra)) try getPendingBalanceToWithdraw(state, voluntary_exit.validator_index) == 0 else true) and // verify signature if (verify_signature) try verifyVoluntaryExitSignature(cached_state, signed_voluntary_exit) else true); } diff --git a/src/state_transition/block/process_withdrawal_request.zig b/src/state_transition/block/process_withdrawal_request.zig index 9006bd449..95a2ee3a3 100644 --- a/src/state_transition/block/process_withdrawal_request.zig +++ b/src/state_transition/block/process_withdrawal_request.zig @@ -1,33 +1,31 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const c = @import("constants"); const types = @import("consensus_types"); const preset = @import("preset").preset; -const Validator = types.phase0.Validator.Type; const WithdrawalRequest = types.electra.WithdrawalRequest.Type; const PendingPartialWithdrawal = types.electra.PendingPartialWithdrawal.Type; -const hasExecutionWithdrawalCredential = @import("../utils/electra.zig").hasExecutionWithdrawalCredential; const hasCompoundingWithdrawalCredential = @import("../utils/electra.zig").hasCompoundingWithdrawalCredential; -const isActiveValidator = @import("../utils/validator.zig").isActiveValidator; +const hasExecutionWithdrawalCredential = @import("../utils/electra.zig").hasExecutionWithdrawalCredential; +const isActiveValidatorView = @import("../utils/validator.zig").isActiveValidatorView; const getPendingBalanceToWithdraw = @import("../utils/validator.zig").getPendingBalanceToWithdraw; const initiateValidatorExit = @import("./initiate_validator_exit.zig").initiateValidatorExit; const computeExitEpochAndUpdateChurn = @import("../utils/epoch.zig").computeExitEpochAndUpdateChurn; -pub fn processWithdrawalRequest(allocator: std.mem.Allocator, cached_state: *CachedBeaconStateAllForks, withdrawal_request: *const WithdrawalRequest) !void { - const state = cached_state.state; +pub fn processWithdrawalRequest(cached_state: *CachedBeaconState, withdrawal_request: *const WithdrawalRequest) !void { + var state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const config = epoch_cache.config; const amount = withdrawal_request.amount; - const pending_partial_withdrawals = state.pendingPartialWithdrawals(); - const validators = state.validators(); - // no need to use unfinalized pubkey cache from 6110 as validator won't be active anyway const pubkey_to_index = epoch_cache.pubkey_to_index; const is_full_exit_request = amount == c.FULL_EXIT_REQUEST_AMOUNT; + var pending_partial_withdrawals = try state.pendingPartialWithdrawals(); + // If partial withdrawal queue is full, only full exits are processed - if (pending_partial_withdrawals.items.len >= preset.PENDING_PARTIAL_WITHDRAWALS_LIMIT and + if (try pending_partial_withdrawals.length() >= preset.PENDING_PARTIAL_WITHDRAWALS_LIMIT and !is_full_exit_request) { return; @@ -37,34 +35,40 @@ pub fn processWithdrawalRequest(allocator: std.mem.Allocator, cached_state: *Cac // note that we don't need to check for 6110 unfinalized vals as they won't be eligible for withdraw/exit anyway const validator_index = pubkey_to_index.get(&withdrawal_request.validator_pubkey) orelse return; - const validator = &validators.items[validator_index]; - if (!isValidatorEligibleForWithdrawOrExit(validator, &withdrawal_request.source_address, cached_state)) { + var validators = try state.validators(); + if (validator_index >= try validators.length()) return; + var validator = try validators.get(@intCast(validator_index)); + if (!(try isValidatorEligibleForWithdrawOrExit(&validator, &withdrawal_request.source_address, cached_state))) { return; } // TODO Electra: Consider caching pendingPartialWithdrawals - const pending_balance_to_withdraw = getPendingBalanceToWithdraw(state, validator_index); - const validator_balance = state.balances().items[validator_index]; + const pending_balance_to_withdraw = try getPendingBalanceToWithdraw(state, validator_index); + var balances = try state.balances(); + const validator_balance = try balances.get(@intCast(validator_index)); if (is_full_exit_request) { // only exit validator if it has no pending withdrawals in the queue if (pending_balance_to_withdraw == 0) { - try initiateValidatorExit(cached_state, validator); + try initiateValidatorExit(cached_state, &validator); } return; } // partial withdrawal request - const has_sufficient_effective_balance = validator.effective_balance >= preset.MIN_ACTIVATION_BALANCE; + const effective_balance = try validator.get("effective_balance"); + const withdrawal_credentials = try validator.getRoot("withdrawal_credentials"); + + const has_sufficient_effective_balance = effective_balance >= preset.MIN_ACTIVATION_BALANCE; const has_excess_balance = validator_balance > preset.MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw; // Only allow partial withdrawals with compounding withdrawal credentials - if (hasExecutionWithdrawalCredential(validator.withdrawal_credentials) and + if (hasCompoundingWithdrawalCredential(withdrawal_credentials) and has_sufficient_effective_balance and has_excess_balance) { const amount_to_withdraw = @min(validator_balance - preset.MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw, amount); - const exit_queue_epoch = computeExitEpochAndUpdateChurn(cached_state, amount_to_withdraw); + const exit_queue_epoch = try computeExitEpochAndUpdateChurn(cached_state, amount_to_withdraw); const withdrawable_epoch = exit_queue_epoch + config.chain.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; const pending_partial_withdrawal = PendingPartialWithdrawal{ @@ -72,20 +76,23 @@ pub fn processWithdrawalRequest(allocator: std.mem.Allocator, cached_state: *Cac .amount = amount_to_withdraw, .withdrawable_epoch = withdrawable_epoch, }; - try state.pendingPartialWithdrawals().append(allocator, pending_partial_withdrawal); + try pending_partial_withdrawals.pushValue(&pending_partial_withdrawal); } } -fn isValidatorEligibleForWithdrawOrExit(validator: *const Validator, source_address: []const u8, cached_state: *const CachedBeaconStateAllForks) bool { - const withdrawal_credentials = validator.withdrawal_credentials; +fn isValidatorEligibleForWithdrawOrExit(validator: *types.phase0.Validator.TreeView, source_address: []const u8, cached_state: *const CachedBeaconState) !bool { + const withdrawal_credentials = try validator.getRoot("withdrawal_credentials"); const address = withdrawal_credentials[12..]; const epoch_cache = cached_state.getEpochCache(); const config = epoch_cache.config; const current_epoch = epoch_cache.epoch; + const activation_epoch = try validator.get("activation_epoch"); + const exit_epoch = try validator.get("exit_epoch"); + return (hasExecutionWithdrawalCredential(withdrawal_credentials) and std.mem.eql(u8, address, source_address) and - isActiveValidator(validator, current_epoch) and - validator.exit_epoch == c.FAR_FUTURE_EPOCH and - current_epoch >= validator.activation_epoch + config.chain.SHARD_COMMITTEE_PERIOD); + (try isActiveValidatorView(validator, current_epoch)) and + exit_epoch == c.FAR_FUTURE_EPOCH and + current_epoch >= activation_epoch + config.chain.SHARD_COMMITTEE_PERIOD); } diff --git a/src/state_transition/block/process_withdrawals.zig b/src/state_transition/block/process_withdrawals.zig index ceab687ab..f0f618502 100644 --- a/src/state_transition/block/process_withdrawals.zig +++ b/src/state_transition/block/process_withdrawals.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const Root = types.primitive.Root.Type; const preset = @import("preset").preset; @@ -29,11 +29,11 @@ pub const WithdrawalsResult = struct { /// refer to https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#modified-process_withdrawals pub fn processWithdrawals( allocator: Allocator, - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, expected_withdrawals_result: WithdrawalsResult, payload_withdrawals_root: Root, ) !void { - const state = cached_state.state; + var state = cached_state.state; // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) const processed_partial_withdrawals_count = expected_withdrawals_result.processed_partial_withdrawals_count; const expected_withdrawals = expected_withdrawals_result.withdrawals.items; @@ -48,34 +48,38 @@ pub fn processWithdrawals( for (0..num_withdrawals) |i| { const withdrawal = expected_withdrawals[i]; - decreaseBalance(state, withdrawal.validator_index, withdrawal.amount); + try decreaseBalance(state, withdrawal.validator_index, withdrawal.amount); } - if (state.isPostElectra()) { - const pending_partial_withdrawals = state.pendingPartialWithdrawals(); - const keep_len = pending_partial_withdrawals.items.len - processed_partial_withdrawals_count; + if (state.forkSeq().gte(.electra)) { + if (processed_partial_withdrawals_count > 0) { + var pending_partial_withdrawals = try state.pendingPartialWithdrawals(); + const truncated = try pending_partial_withdrawals.sliceFrom(processed_partial_withdrawals_count); - std.mem.copyForwards(PendingPartialWithdrawal, pending_partial_withdrawals.items[0..keep_len], pending_partial_withdrawals.items[processed_partial_withdrawals_count..]); - pending_partial_withdrawals.shrinkRetainingCapacity(keep_len); + try state.setPendingPartialWithdrawals(truncated); + } } - const next_withdrawal_index = state.nextWithdrawalIndex(); // Update the nextWithdrawalIndex if (expected_withdrawals.len > 0) { const latest_withdrawal = expected_withdrawals[expected_withdrawals.len - 1]; - next_withdrawal_index.* = latest_withdrawal.index + 1; + try state.setNextWithdrawalIndex(latest_withdrawal.index + 1); } // Update the next_withdrawal_validator_index - const next_withdrawal_validator_index = state.nextWithdrawalValidatorIndex(); + const validators_len: u64 = @intCast(try state.validatorsCount()); + const next_withdrawal_validator_index = try state.nextWithdrawalValidatorIndex(); if (expected_withdrawals.len == preset.MAX_WITHDRAWALS_PER_PAYLOAD) { // All slots filled, next_withdrawal_validator_index should be validatorIndex having next turn - next_withdrawal_validator_index.* = - (expected_withdrawals[expected_withdrawals.len - 1].validator_index + 1) % state.validators().items.len; + try state.setNextWithdrawalValidatorIndex( + (expected_withdrawals[expected_withdrawals.len - 1].validator_index + 1) % validators_len, + ); } else { // expected withdrawals came up short in the bound, so we move next_withdrawal_validator_index to // the next post the bound - next_withdrawal_validator_index.* = (next_withdrawal_validator_index.* + preset.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) % state.validators().items.len; + try state.setNextWithdrawalValidatorIndex( + (next_withdrawal_validator_index + preset.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) % validators_len, + ); } } @@ -84,41 +88,45 @@ pub fn getExpectedWithdrawals( allocator: Allocator, withdrawals_result: *WithdrawalsResult, withdrawal_balances: *std.AutoHashMap(ValidatorIndex, usize), - cached_state: *const CachedBeaconStateAllForks, + cached_state: *const CachedBeaconState, ) !void { - const state = cached_state.state; - if (state.isPreCapella()) { + var state = cached_state.state; + if (state.forkSeq().lt(.capella)) { return error.InvalidForkSequence; } const epoch_cache = cached_state.getEpochCache(); const epoch = epoch_cache.epoch; - var withdrawal_index = state.nextWithdrawalIndex().*; - const validators = state.validators(); - const balances = state.balances(); - const next_withdrawal_validator_index = state.nextWithdrawalValidatorIndex(); + var withdrawal_index = try state.nextWithdrawalIndex(); + var validators = try state.validators(); + var balances = try state.balances(); + const next_withdrawal_validator_index = try state.nextWithdrawalValidatorIndex(); // partial_withdrawals_count is withdrawals coming from EL since electra (EIP-7002) var processed_partial_withdrawals_count: u64 = 0; - if (state.isPostElectra()) { - // TODO: this optimization logic is not needed for TreeView - // MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should only call getAllReadonly() if it makes sense - // pendingPartialWithdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw + if (state.forkSeq().gte(.electra)) { + // MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP = 8, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 so we should just lazily iterate thru state.pending_partial_withdrawals. + // pending_partial_withdrawals comes from EIP-7002 smart contract where it takes fee so it's more likely than not validator is in correct condition to withdraw // also we may break early if withdrawableEpoch > epoch - const pending_partial_withdrawals = state.pendingPartialWithdrawals(); - for (0..pending_partial_withdrawals.items.len) |i| { - const withdrawal = pending_partial_withdrawals.items[i]; + var pending_partial_withdrawals = try state.pendingPartialWithdrawals(); + var pending_partial_withdrawals_it = pending_partial_withdrawals.iteratorReadonly(0); + const pending_partial_withdrawals_len = try pending_partial_withdrawals.length(); + + for (0..pending_partial_withdrawals_len) |_| { + const withdrawal = try pending_partial_withdrawals_it.nextValue(undefined); if (withdrawal.withdrawable_epoch > epoch or withdrawals_result.withdrawals.items.len == preset.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP) { break; } - const validator = validators.items[withdrawal.validator_index]; + var validator: types.phase0.Validator.Type = undefined; + try validators.getValue(undefined, withdrawal.validator_index, &validator); + const total_withdrawn_gop = try withdrawal_balances.getOrPut(withdrawal.validator_index); const total_withdrawn: u64 = if (total_withdrawn_gop.found_existing) total_withdrawn_gop.value_ptr.* else 0; - const balance = balances.items[withdrawal.validator_index] - total_withdrawn; + const balance = try balances.get(withdrawal.validator_index) - total_withdrawn; if (validator.exit_epoch == c.FAR_FUTURE_EPOCH and validator.effective_balance >= preset.MIN_ACTIVATION_BALANCE and @@ -127,7 +135,7 @@ pub fn getExpectedWithdrawals( const balance_over_min_activation_balance = balance - preset.MIN_ACTIVATION_BALANCE; const withdrawable_balance = if (balance_over_min_activation_balance < withdrawal.amount) balance_over_min_activation_balance else withdrawal.amount; var execution_address: ExecutionAddress = undefined; - std.mem.copyForwards(u8, &execution_address, validator.withdrawal_credentials[12..]); + @memcpy(&execution_address, validator.withdrawal_credentials[12..]); try withdrawals_result.withdrawals.append(allocator, .{ .index = withdrawal_index, .validator_index = withdrawal.validator_index, @@ -141,26 +149,28 @@ pub fn getExpectedWithdrawals( } } - const bound = @min(validators.items.len, preset.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); + const validators_count = try validators.length(); + const bound = @min(validators_count, preset.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); // Just run a bounded loop max iterating over all withdrawals // however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD var n: usize = 0; while (n < bound) : (n += 1) { // Get next validator in turn - const validator_index = (next_withdrawal_validator_index.* + n) % validators.items.len; - const validator = validators.items[validator_index]; + const validator_index = (next_withdrawal_validator_index + n) % validators_count; + var validator = try validators.get(validator_index); const withdraw_balance_gop = try withdrawal_balances.getOrPut(validator_index); const withdraw_balance: u64 = if (withdraw_balance_gop.found_existing) withdraw_balance_gop.value_ptr.* else 0; - const balance = if (state.isPostElectra()) + const val_balance = try balances.get(validator_index); + const balance = if (state.forkSeq().gte(.electra)) // Deduct partially withdrawn balance already queued above - if (balances.items[validator_index] > withdraw_balance) balances.items[validator_index] - withdraw_balance else 0 + if (val_balance > withdraw_balance) val_balance - withdraw_balance else 0 else - balances.items[validator_index]; + val_balance; - const withdrawable_epoch = validator.withdrawable_epoch; - const withdrawal_credentials = validator.withdrawal_credentials; - const effective_balance = validator.effective_balance; - const has_withdrawable_credentials = if (state.isPostElectra()) hasExecutionWithdrawalCredential(withdrawal_credentials) else hasEth1WithdrawalCredential(withdrawal_credentials); + const withdrawable_epoch = try validator.get("withdrawable_epoch"); + const withdrawal_credentials = try validator.getRoot("withdrawal_credentials"); + const effective_balance = try validator.get("effective_balance"); + const has_withdrawable_credentials = if (state.forkSeq().gte(.electra)) hasExecutionWithdrawalCredential(withdrawal_credentials) else hasEth1WithdrawalCredential(withdrawal_credentials); // early skip for balance = 0 as its now more likely that validator has exited/slashed with // balance zero than not have withdrawal credentials set if (balance == 0 or !has_withdrawable_credentials) { @@ -170,7 +180,7 @@ pub fn getExpectedWithdrawals( // capella full withdrawal if (withdrawable_epoch <= epoch) { var execution_address: ExecutionAddress = undefined; - std.mem.copyForwards(u8, &execution_address, validator.withdrawal_credentials[12..]); + @memcpy(&execution_address, withdrawal_credentials[12..]); try withdrawals_result.withdrawals.append(allocator, .{ .index = withdrawal_index, .validator_index = validator_index, @@ -178,7 +188,7 @@ pub fn getExpectedWithdrawals( .amount = balance, }); withdrawal_index += 1; - } else if ((effective_balance == if (state.isPostElectra()) + } else if ((effective_balance == if (state.forkSeq().gte(.electra)) getMaxEffectiveBalance(withdrawal_credentials) else preset.MAX_EFFECTIVE_BALANCE) and balance > effective_balance) @@ -186,7 +196,7 @@ pub fn getExpectedWithdrawals( // capella partial withdrawal const partial_amount = balance - effective_balance; var execution_address: ExecutionAddress = undefined; - std.mem.copyForwards(u8, &execution_address, validator.withdrawal_credentials[12..]); + @memcpy(&execution_address, withdrawal_credentials[12..]); try withdrawals_result.withdrawals.append(allocator, .{ .index = withdrawal_index, .validator_index = validator_index, @@ -203,6 +213,8 @@ pub fn getExpectedWithdrawals( } } + try state.setNextWithdrawalIndex(withdrawal_index); + withdrawals_result.sampled_validators = n; withdrawals_result.processed_partial_withdrawals_count = processed_partial_withdrawals_count; } diff --git a/src/state_transition/block/slash_validator.zig b/src/state_transition/block/slash_validator.zig index 4fcbd7bea..7bcdec18d 100644 --- a/src/state_transition/block/slash_validator.zig +++ b/src/state_transition/block/slash_validator.zig @@ -2,34 +2,42 @@ const ForkSeq = @import("config").ForkSeq; const types = @import("consensus_types"); const preset = @import("preset").preset; const c = @import("constants"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ValidatorIndex = types.primitive.ValidatorIndex.Type; const decreaseBalance = @import("../utils/balance.zig").decreaseBalance; const increaseBalance = @import("../utils/balance.zig").increaseBalance; const initiateValidatorExit = @import("./initiate_validator_exit.zig").initiateValidatorExit; +const computePreviousEpoch = @import("../utils/epoch.zig").computePreviousEpoch; +const isActiveValidatorView = @import("../utils/validator.zig").isActiveValidatorView; /// Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag const TIMELY_TARGET = 1 << c.TIMELY_TARGET_FLAG_INDEX; pub fn slashValidator( - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, slashed_index: ValidatorIndex, whistle_blower_index: ?ValidatorIndex, ) !void { const epoch_cache = cached_state.getEpochCache(); - const state = cached_state.state; + var state = cached_state.state; const epoch = epoch_cache.epoch; const effective_balance_increments = epoch_cache.effective_balance_increment; + const slashed_effective_balance_increments = effective_balance_increments.get().items[@intCast(slashed_index)]; - var validator = &state.validators().items[slashed_index]; + var validators = try state.validators(); + var validator = try validators.get(@intCast(slashed_index)); // TODO: Bellatrix initiateValidatorExit validators.update() with the one below - try initiateValidatorExit(cached_state, validator); + try initiateValidatorExit(cached_state, &validator); - validator.slashed = true; - validator.withdrawable_epoch = @max(validator.withdrawable_epoch, epoch + preset.EPOCHS_PER_SLASHINGS_VECTOR); + try validator.set("slashed", true); + const cur_withdrawable_epoch = try validator.get("withdrawable_epoch"); + try validator.set( + "withdrawable_epoch", + @max(cur_withdrawable_epoch, epoch + preset.EPOCHS_PER_SLASHINGS_VECTOR), + ); - const effective_balance = validator.effective_balance; + const effective_balance = try validator.get("effective_balance"); // state.slashings is initially a Gwei (BigInt) vector, however since Nov 2023 it's converted to UintNum64 (number) vector in the state transition because: // - state.slashings[nextEpoch % EPOCHS_PER_SLASHINGS_VECTOR] is reset per epoch in processSlashingsReset() @@ -37,9 +45,10 @@ pub fn slashValidator( // - with that and 32_000_000_000 MAX_EFFECTIVE_BALANCE or 2048_000_000_000 MAX_EFFECTIVE_BALANCE_ELECTRA, it still fits in a number given that Math.floor(Number.MAX_SAFE_INTEGER / 32_000_000_000) = 281474 // - we don't need to compute the total slashings from state.slashings, it's handled by totalSlashingsByIncrement in EpochCache const slashing_index = epoch % preset.EPOCHS_PER_SLASHINGS_VECTOR; - const slashings = state.slashings(); - slashings[slashing_index] = state.slashings()[slashing_index] + effective_balance; - epoch_cache.total_slashings_by_increment += effective_balance_increments.get().items[slashed_index]; + var slashings = try state.slashings(); + const cur_slashings = try slashings.get(@intCast(slashing_index)); + try slashings.set(@intCast(slashing_index), cur_slashings + effective_balance); + epoch_cache.total_slashings_by_increment += slashed_effective_balance_increments; // TODO(ct): define MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA const min_slashing_penalty_quotient: usize = switch (state.*) { @@ -49,7 +58,7 @@ pub fn slashValidator( .electra, .fulu => preset.MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA, }; - decreaseBalance(state, slashed_index, @divFloor(effective_balance, min_slashing_penalty_quotient)); + try decreaseBalance(state, slashed_index, @divFloor(effective_balance, min_slashing_penalty_quotient)); // apply proposer and whistleblower rewards // TODO(ct): define WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA @@ -63,26 +72,38 @@ pub fn slashValidator( else => @divFloor(whistleblower_reward * c.PROPOSER_WEIGHT, c.WEIGHT_DENOMINATOR), }; - const proposer_index = try cached_state.getBeaconProposer(state.slot()); + const proposer_index = try cached_state.getBeaconProposer(try state.slot()); if (whistle_blower_index) |_whistle_blower_index| { - increaseBalance(state, proposer_index, proposer_reward); - increaseBalance(state, _whistle_blower_index, whistleblower_reward - proposer_reward); + try increaseBalance(state, proposer_index, proposer_reward); + try increaseBalance(state, _whistle_blower_index, whistleblower_reward - proposer_reward); // TODO: implement RewardCache // state.proposer_rewards.slashing += proposer_reward; } else { - increaseBalance(state, proposer_index, whistleblower_reward); + try increaseBalance(state, proposer_index, whistleblower_reward); // TODO: implement RewardCache // state.proposerRewards.slashing += whistleblowerReward; } - if (state.isPostAltair()) { - if (state.previousEpochParticipations().items[slashed_index] & TIMELY_TARGET == TIMELY_TARGET) { - epoch_cache.previous_target_unslashed_balance_increments -= @divFloor(effective_balance, preset.EFFECTIVE_BALANCE_INCREMENT); + if (state.forkSeq().gte(.altair)) { + const previous_epoch = computePreviousEpoch(epoch); + const is_active_previous_epoch = try isActiveValidatorView(&validator, previous_epoch); + const is_active_current_epoch = try isActiveValidatorView(&validator, epoch); + + var previous_participation = try state.previousEpochParticipation(); + if (is_active_previous_epoch and (try previous_participation.get(@intCast(slashed_index))) & TIMELY_TARGET == TIMELY_TARGET) { + if (epoch_cache.previous_target_unslashed_balance_increments < slashed_effective_balance_increments) { + return error.PreviousTargetUnslashedBalanceUnderflow; + } + epoch_cache.previous_target_unslashed_balance_increments -= slashed_effective_balance_increments; } - if (state.currentEpochParticipations().items[slashed_index] & TIMELY_TARGET == TIMELY_TARGET) { - epoch_cache.current_target_unslashed_balance_increments -= @divFloor(effective_balance, preset.EFFECTIVE_BALANCE_INCREMENT); + var current_participation = try state.currentEpochParticipation(); + if (is_active_current_epoch and (try current_participation.get(@intCast(slashed_index))) & TIMELY_TARGET == TIMELY_TARGET) { + if (epoch_cache.current_target_unslashed_balance_increments < slashed_effective_balance_increments) { + return error.CurrentTargetUnslashedBalanceUnderflow; + } + epoch_cache.current_target_unslashed_balance_increments -= slashed_effective_balance_increments; } } } diff --git a/src/state_transition/block_test_root.zig b/src/state_transition/block_test_root.zig new file mode 100644 index 000000000..a83500857 --- /dev/null +++ b/src/state_transition/block_test_root.zig @@ -0,0 +1,31 @@ +// Root file to run only state_transition/block tests. +// +// This exists because `zig test` compiles the entire root module before applying +// `--test-filter`. Keeping a small root lets us iterate on block processing +// without fixing unrelated compilation errors across the whole state_transition module. + +test "state_transition block" { + _ = @import("block/initiate_validator_exit.zig"); + _ = @import("block/is_valid_indexed_attestation.zig"); + _ = @import("block/process_attestation_altair.zig"); + _ = @import("block/process_attestation_phase0.zig"); + _ = @import("block/process_attestations.zig"); + _ = @import("block/process_attester_slashing.zig"); + _ = @import("block/process_blob_kzg_commitments.zig"); + _ = @import("block/process_block.zig"); + _ = @import("block/process_block_header.zig"); + _ = @import("block/process_bls_to_execution_change.zig"); + _ = @import("block/process_consolidation_request.zig"); + _ = @import("block/process_deposit.zig"); + _ = @import("block/process_deposit_request.zig"); + _ = @import("block/process_eth1_data.zig"); + _ = @import("block/process_execution_payload.zig"); + _ = @import("block/process_operations.zig"); + _ = @import("block/process_proposer_slashing.zig"); + _ = @import("block/process_randao.zig"); + _ = @import("block/process_sync_committee.zig"); + _ = @import("block/process_voluntary_exit.zig"); + _ = @import("block/process_withdrawal_request.zig"); + _ = @import("block/process_withdrawals.zig"); + _ = @import("block/slash_validator.zig"); +} diff --git a/src/state_transition/cache/effective_balance_increments.zig b/src/state_transition/cache/effective_balance_increments.zig index af919599a..9ca374b62 100644 --- a/src/state_transition/cache/effective_balance_increments.zig +++ b/src/state_transition/cache/effective_balance_increments.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const types = @import("consensus_types"); const preset = @import("preset").preset; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const ReferenceCount = @import("../utils/reference_count.zig").ReferenceCount; const EFFECTIVE_BALANCE_INCREMENT = preset.EFFECTIVE_BALANCE_INCREMENT; @@ -23,13 +23,14 @@ pub fn getEffectiveBalanceIncrementsWithLen(allocator: Allocator, validator_coun return getEffectiveBalanceIncrementsZeroed(allocator, len); } -pub fn getEffectiveBalanceIncrements(allocator: Allocator, state: BeaconStateAllForks) !EffectiveBalanceIncrements { - const validator_count = state.validators().items.len; - var increments = try EffectiveBalanceIncrements.initCapacity(allocator, validator_count); - try increments.resize(validator_count); +pub fn getEffectiveBalanceIncrements(allocator: Allocator, state: BeaconState) !EffectiveBalanceIncrements { + const validators = try state.validatorsSlice(allocator); + defer allocator.free(validators); - for (0..validator_count) |i| { - const validator = state.validators()[i]; + var increments = try EffectiveBalanceIncrements.initCapacity(allocator, validators.len); + try increments.resize(validators.len); + + for (validators, 0..) |validator, i| { increments.items[i] = @divFloor(validator.effective_balance, preset.EFFECTIVE_BALANCE_INCREMENT); } } diff --git a/src/state_transition/cache/epoch_cache.zig b/src/state_transition/cache/epoch_cache.zig index a476d2a05..2f42ecc6c 100644 --- a/src/state_transition/cache/epoch_cache.zig +++ b/src/state_transition/cache/epoch_cache.zig @@ -19,8 +19,8 @@ const EpochShuffling = @import("../utils//epoch_shuffling.zig").EpochShuffling; const EpochShufflingRc = @import("../utils/epoch_shuffling.zig").EpochShufflingRc; const EffectiveBalanceIncrementsRc = @import("./effective_balance_increments.zig").EffectiveBalanceIncrementsRc; const EffectiveBalanceIncrements = @import("./effective_balance_increments.zig").EffectiveBalanceIncrements; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const computeEpochAtSlot = @import("../utils/epoch.zig").computeEpochAtSlot; const computePreviousEpoch = @import("../utils/epoch.zig").computePreviousEpoch; @@ -31,7 +31,7 @@ const computeEpochShuffling = @import("../utils/epoch_shuffling.zig").computeEpo const getSeed = @import("../utils/seed.zig").getSeed; const computeProposers = @import("../utils/seed.zig").computeProposers; const SyncCommitteeCacheRc = @import("./sync_committee_cache.zig").SyncCommitteeCacheRc; -const SyncCommitteeCacheAllForks = @import("./sync_committee_cache.zig").SyncCommitteeCacheAllForks; +const SyncCommitteeCacheAllForks = @import("./sync_committee_cache.zig").SyncCommitteeCache; const computeSyncParticipantReward = @import("../utils/sync_committee.zig").computeSyncParticipantReward; const computeBaseRewardPerIncrement = @import("../utils/sync_committee.zig").computeBaseRewardPerIncrement; const computeSyncPeriodAtEpoch = @import("../utils/epoch.zig").computeSyncPeriodAtEpoch; @@ -66,8 +66,8 @@ const weight_denominator: f64 = @floatFromInt(c.WEIGHT_DENOMINATOR); pub const proposer_weight_factor: f64 = proposer_weight / (weight_denominator - proposer_weight); -/// an EpochCache is shared by multiple CachedBeaconStateAllForks instances -/// a CachedBeaconStateAllForks should increase the reference count of EpochCache when it is created +/// an EpochCache is shared by multiple CachedBeaconState instances +/// a CachedBeaconState should increase the reference count of EpochCache when it is created /// and decrease the reference count when it is deinitialized pub const EpochCacheRc = ReferenceCount(*EpochCache); @@ -142,12 +142,12 @@ pub const EpochCache = struct { epoch: Epoch, - pub fn createFromState(allocator: Allocator, state: *const BeaconStateAllForks, immutable_data: EpochCacheImmutableData, option: ?EpochCacheOpts) !*EpochCache { + pub fn createFromState(allocator: Allocator, state: *BeaconState, immutable_data: EpochCacheImmutableData, option: ?EpochCacheOpts) !*EpochCache { const config = immutable_data.config; const pubkey_to_index = immutable_data.pubkey_to_index; const index_to_pubkey = immutable_data.index_to_pubkey; - const current_epoch = computeEpochAtSlot(state.slot()); + const current_epoch = computeEpochAtSlot(try state.slot()); const is_genesis = current_epoch == GENESIS_EPOCH; const previous_epoch = if (is_genesis) GENESIS_EPOCH else current_epoch - 1; const next_epoch = current_epoch + 1; @@ -156,7 +156,9 @@ pub const EpochCache = struct { var exit_queue_epoch = computeActivationExitEpoch(current_epoch); var exit_queue_churn: u64 = 0; - const validators = state.validators().items; + const validators = try state.validatorsSlice(allocator); + defer allocator.free(validators); + const validator_count = validators.len; // syncPubkeys here to ensure EpochCacheImmutableData is popualted before computing the rest of caches @@ -167,7 +169,7 @@ pub const EpochCache = struct { } const effective_balance_increment = try getEffectiveBalanceIncrementsWithLen(allocator, validator_count); - const total_slashings_by_increment = getTotalSlashingsByIncrement(state); + const total_slashings_by_increment = try getTotalSlashingsByIncrement(state); var previous_active_indices_array_list = std.ArrayList(ValidatorIndex).init(allocator); defer previous_active_indices_array_list.deinit(); try previous_active_indices_array_list.ensureTotalCapacity(validator_count); @@ -248,8 +250,20 @@ pub const EpochCache = struct { const sync_proposer_reward: u64 = @intFromFloat(std.math.floor(sync_participant_reward_f64 * proposer_weight_factor)); const base_reward_pre_increment = computeBaseRewardPerIncrement(total_active_balance_increments); const skip_sync_committee_cache = if (option) |opt| opt.skip_sync_committee_cache else !after_altair_fork; - var current_sync_committee_indexed = if (skip_sync_committee_cache) SyncCommitteeCacheAllForks.initEmpty() else try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, state.currentSyncCommittee(), pubkey_to_index); - var next_sync_committee_indexed = if (skip_sync_committee_cache) SyncCommitteeCacheAllForks.initEmpty() else try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, state.nextSyncCommittee(), pubkey_to_index); + var current_sync_committee_indexed = blk: { + if (skip_sync_committee_cache) break :blk SyncCommitteeCacheAllForks.initEmpty(); + var current_sc_view = try state.currentSyncCommittee(); + var current_sc: types.altair.SyncCommittee.Type = undefined; + try current_sc_view.toValue(allocator, ¤t_sc); + break :blk try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, ¤t_sc, pubkey_to_index); + }; + var next_sync_committee_indexed = blk: { + if (skip_sync_committee_cache) break :blk SyncCommitteeCacheAllForks.initEmpty(); + var next_sc_view = try state.nextSyncCommittee(); + var next_sc: types.altair.SyncCommittee.Type = undefined; + try next_sc_view.toValue(allocator, &next_sc); + break :blk try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, &next_sc, pubkey_to_index); + }; errdefer { current_sync_committee_indexed.deinit(); @@ -283,8 +297,13 @@ pub const EpochCache = struct { var current_target_unslashed_balance_increments: u64 = 0; if (fork_seq.gte(.altair)) { - const previous_epoch_participation = state.previousEpochParticipations().items; - const current_epoch_participation = state.currentEpochParticipations().items; + var previous_epoch_participation_view = try state.previousEpochParticipation(); + const previous_epoch_participation = try previous_epoch_participation_view.getAll(allocator); + defer allocator.free(previous_epoch_participation); + + var current_epoch_participation_view = try state.currentEpochParticipation(); + const current_epoch_participation = try current_epoch_participation_view.getAll(allocator); + defer allocator.free(current_epoch_participation); previous_target_unslashed_balance_increments = sumTargetUnslashedBalanceIncrements(previous_epoch_participation, previous_epoch, validators); current_target_unslashed_balance_increments = sumTargetUnslashedBalanceIncrements(current_epoch_participation, current_epoch, validators); @@ -408,7 +427,7 @@ pub const EpochCache = struct { return self.effective_balance_increment.get(); } - pub fn afterProcessEpoch(self: *EpochCache, cached_state: *const CachedBeaconStateAllForks, epoch_transition_cache: *const EpochTransitionCache) !void { + pub fn afterProcessEpoch(self: *EpochCache, cached_state: *const CachedBeaconState, epoch_transition_cache: *const EpochTransitionCache) !void { const state = cached_state.state; const upcoming_epoch = self.epoch + 1; const epoch_after_upcoming = upcoming_epoch + 1; @@ -430,7 +449,7 @@ pub const EpochCache = struct { self.next_shuffling = try EpochShufflingRc.init(self.allocator, next_shuffling); self.churn_limit = getChurnLimit(self.config, self.current_shuffling.get().active_indices.len); - self.activation_churn_limit = getActivationChurnLimit(self.config, self.config.forkSeq(state.slot()), self.current_shuffling.get().active_indices.len); + self.activation_churn_limit = getActivationChurnLimit(self.config, self.config.forkSeq(try state.slot()), self.current_shuffling.get().active_indices.len); const exit_queue_epoch = computeActivationExitEpoch(upcoming_epoch); if (exit_queue_epoch > self.exit_queue_epoch) { @@ -448,12 +467,12 @@ pub const EpochCache = struct { self.previous_target_unslashed_balance_increments = self.current_target_unslashed_balance_increments; self.current_target_unslashed_balance_increments = 0; - self.epoch = computeEpochAtSlot(state.slot()); + self.epoch = computeEpochAtSlot(try state.slot()); self.sync_period = computeSyncPeriodAtEpoch(self.epoch); } /// At fork boundary, this runs post-fork logic and after `upgradeState*`. - pub fn finalProcessEpoch(self: *EpochCache, cached_state: *const CachedBeaconStateAllForks) !void { + pub fn finalProcessEpoch(self: *EpochCache, cached_state: *const CachedBeaconState) !void { const state = cached_state.state; self.proposers_prev_epoch = self.proposers; @@ -461,8 +480,12 @@ pub const EpochCache = struct { // field which we already processed in `processProposerLookahead`. // Proposers are to be computed pre-fulu to be cached within `self`. if (self.epoch >= self.config.chain.FULU_FORK_EPOCH) { - self.proposers = state.proposerLookahead()[0..preset.SLOTS_PER_EPOCH].*; - self.proposers_next_epoch = state.proposerLookahead()[preset.SLOTS_PER_EPOCH .. preset.SLOTS_PER_EPOCH * 2].*; + var proposer_lookahead = try state.proposerLookahead(); + self.proposers_next_epoch = undefined; + for (0..preset.SLOTS_PER_EPOCH) |i| { + self.proposers[i] = @intCast(try proposer_lookahead.get(i)); + self.proposers_next_epoch.?[i] = @intCast(try proposer_lookahead.get(preset.SLOTS_PER_EPOCH + i)); + } } else { var upcoming_proposer_seed: [32]u8 = undefined; try getSeed(state, self.epoch, c.DOMAIN_BEACON_PROPOSER, &upcoming_proposer_seed); @@ -511,7 +534,7 @@ pub const EpochCache = struct { } /// Gets the beacon proposer for a slot. This is for pre-Fulu forks only. - /// NOTE: For the Fulu fork, use `CachedBeaconStateAllForks.getBeaconProposer()` instead, + /// NOTE: For the Fulu fork, use `CachedBeaconState.getBeaconProposer()` instead, /// which properly accesses `proposer_lookahead` from the state. pub fn getBeaconProposer(self: *const EpochCache, slot: Slot) !ValidatorIndex { const epoch = computeEpochAtSlot(slot); diff --git a/src/state_transition/cache/epoch_transition_cache.zig b/src/state_transition/cache/epoch_transition_cache.zig index 969ad38bd..028100651 100644 --- a/src/state_transition/cache/epoch_transition_cache.zig +++ b/src/state_transition/cache/epoch_transition_cache.zig @@ -6,9 +6,9 @@ const ValidatorIndex = types.primitive.ValidatorIndex.Type; const ForkSeq = @import("config").ForkSeq; const Epoch = types.primitive.Epoch.Type; const preset = @import("preset").preset; -const CachedBeaconStateAllForks = @import("./state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("./state_cache.zig").CachedBeaconState; -const TestCachedBeaconStateAllForks = @import("../test_utils/root.zig").TestCachedBeaconStateAllForks; +const TestCachedBeaconState = @import("../test_utils/root.zig").TestCachedBeaconState; const upgradeStateToFulu = @import("../slot/upgrade_state_to_fulu.zig").upgradeStateToFulu; const deinitStateTransition = @import("../root.zig").deinitStateTransition; @@ -194,11 +194,11 @@ pub const EpochTransitionCache = struct { // TODO: no need EpochTransitionCacheOpts for zig version // this is the same to beforeProcessEpoch in typesript version - pub fn init(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !*EpochTransitionCache { + pub fn init(allocator: Allocator, cached_state: *CachedBeaconState) !*EpochTransitionCache { const config = cached_state.config; var epoch_cache = cached_state.getEpochCache(); const state = cached_state.state; - const fork_seq = config.forkSeq(state.slot()); + const fork_seq = config.forkSeq(try state.slot()); const current_epoch = epoch_cache.epoch; const prev_epoch = epoch_cache.getPreviousShuffling().epoch; const next_epoch = current_epoch + 1; @@ -215,7 +215,9 @@ pub const EpochTransitionCache = struct { var indices_to_eject = std.ArrayList(ValidatorIndex).init(allocator); var total_active_stake_by_increment: u64 = 0; - const validator_count = state.validators().items.len; + const validators = try state.validatorsSlice(allocator); + defer allocator.free(validators); + const validator_count = validators.len; // Clone before being mutated in processEffectiveBalanceUpdates try epoch_cache.beforeEpochTransition(); @@ -225,8 +227,7 @@ pub const EpochTransitionCache = struct { var next_epoch_shuffling_active_indices_length: usize = 0; var reused_cache = try getReusedEpochTransitionCache(allocator, validator_count); - for (0..validator_count) |i| { - const validator = state.validators().items[i]; + for (validators, 0..) |validator, i| { var flag: u8 = 0; if (validator.slashed) { @@ -258,7 +259,7 @@ pub const EpochTransitionCache = struct { reused_cache.flags.items[i] = flag; if (fork_seq.gte(.electra)) { - reused_cache.is_compounding_validator_arr.items[i] = hasCompoundingWithdrawalCredential(validator.withdrawal_credentials); + reused_cache.is_compounding_validator_arr.items[i] = hasCompoundingWithdrawalCredential(&validator.withdrawal_credentials); } if (is_active_curr) { @@ -352,6 +353,24 @@ pub const EpochTransitionCache = struct { @memset(reused_cache.proposer_indices.items, validator_count); try reused_cache.inclusion_delays.resize(validator_count); @memset(reused_cache.inclusion_delays.items, 0); + + var previous_epoch_pending_attestations_view = try state.previousEpochPendingAttestations(); + const previous_epoch_pending_attestations = try previous_epoch_pending_attestations_view.getAllReadonlyValues(allocator); + defer { + for (previous_epoch_pending_attestations) |*att| { + types.phase0.PendingAttestation.deinit(allocator, att); + } + allocator.free(previous_epoch_pending_attestations); + } + var current_epoch_pending_attestations_view = try state.currentEpochPendingAttestations(); + const current_epoch_pending_attestations = try current_epoch_pending_attestations_view.getAllReadonlyValues(allocator); + defer { + for (current_epoch_pending_attestations) |*att| { + types.phase0.PendingAttestation.deinit(allocator, att); + } + allocator.free(current_epoch_pending_attestations); + } + try processPendingAttestations( allocator, cached_state, @@ -359,7 +378,7 @@ pub const EpochTransitionCache = struct { validator_count, reused_cache.inclusion_delays.items, reused_cache.flags.items, - state.previousEpochPendingAttestations().items, + previous_epoch_pending_attestations, prev_epoch, FLAG_PREV_SOURCE_ATTESTER, FLAG_PREV_TARGET_ATTESTER, @@ -372,7 +391,7 @@ pub const EpochTransitionCache = struct { validator_count, reused_cache.inclusion_delays.items, reused_cache.flags.items, - state.currentEpochPendingAttestations().items, + current_epoch_pending_attestations, current_epoch, FLAG_CURR_SOURCE_ATTESTER, FLAG_CURR_TARGET_ATTESTER, @@ -381,9 +400,17 @@ pub const EpochTransitionCache = struct { } else { try reused_cache.previous_epoch_participation.resize(validator_count); try reused_cache.current_epoch_participation.resize(validator_count); - // TODO: does not work for TreeView - @memcpy(reused_cache.previous_epoch_participation.items[0..validator_count], state.previousEpochParticipations().items); - @memcpy(reused_cache.current_epoch_participation.items[0..validator_count], state.currentEpochParticipations().items); + + var previous_epoch_participation_view = try state.previousEpochParticipation(); + const previous_epoch_participation = try previous_epoch_participation_view.getAll(allocator); + defer allocator.free(previous_epoch_participation); + var current_epoch_participation_view = try state.currentEpochParticipation(); + const current_epoch_participation = try current_epoch_participation_view.getAll(allocator); + defer allocator.free(current_epoch_participation); + + @memcpy(reused_cache.previous_epoch_participation.items[0..validator_count], previous_epoch_participation); + @memcpy(reused_cache.current_epoch_participation.items[0..validator_count], current_epoch_participation); + for (0..validator_count) |i| { reused_cache.flags.items[i] |= // checking active status first is required to pass random spec tests in altair @@ -511,7 +538,12 @@ pub const EpochTransitionCache = struct { test "EpochTransitionCache - finalProcessEpoch" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + + const Node = @import("persistent_merkle_tree").Node; + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); try upgradeStateToFulu(allocator, test_state.cached_state); @@ -525,7 +557,11 @@ test "EpochTransitionCache.beforeProcessEpoch" { const validator_count_arr = &.{ 256, 10_000 }; inline for (validator_count_arr) |validator_count| { - var test_state = try TestCachedBeaconStateAllForks.init(allocator, validator_count); + const Node = @import("persistent_merkle_tree").Node; + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, validator_count); defer test_state.deinit(); var epoch_transition_cache = try EpochTransitionCache.init(allocator, test_state.cached_state); diff --git a/src/state_transition/cache/pubkey_cache.zig b/src/state_transition/cache/pubkey_cache.zig index 6c13a8f21..6a5c41faa 100644 --- a/src/state_transition/cache/pubkey_cache.zig +++ b/src/state_transition/cache/pubkey_cache.zig @@ -13,7 +13,7 @@ pub const Index2PubkeyCache = std.ArrayList(PublicKey); /// consumers should deinit each item inside Index2PubkeyCache pub fn syncPubkeys( - validators: []Validator, + validators: []const Validator, pubkey_to_index: *PubkeyIndexMap, index_to_pubkey: *Index2PubkeyCache, ) !void { diff --git a/src/state_transition/cache/state_cache.zig b/src/state_transition/cache/state_cache.zig index b9a290db7..481a9aed3 100644 --- a/src/state_transition/cache/state_cache.zig +++ b/src/state_transition/cache/state_cache.zig @@ -2,17 +2,18 @@ const std = @import("std"); const types = @import("consensus_types"); const Allocator = std.mem.Allocator; const BeaconConfig = @import("config").BeaconConfig; -const TestCachedBeaconStateAllForks = @import("../test_utils/root.zig").TestCachedBeaconStateAllForks; +const TestCachedBeaconState = @import("../test_utils/root.zig").TestCachedBeaconState; const EpochCacheRc = @import("./epoch_cache.zig").EpochCacheRc; const EpochCache = @import("./epoch_cache.zig").EpochCache; const EpochCacheImmutableData = @import("./epoch_cache.zig").EpochCacheImmutableData; const EpochCacheOpts = @import("./epoch_cache.zig").EpochCacheOpts; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const ValidatorIndex = types.primitive.ValidatorIndex.Type; const PubkeyIndexMap = @import("pubkey_cache.zig").PubkeyIndexMap(ValidatorIndex); const Index2PubkeyCache = @import("pubkey_cache.zig").Index2PubkeyCache; +const CloneOpts = @import("ssz").BaseTreeView.CloneOpts; -pub const CachedBeaconStateAllForks = struct { +pub const CachedBeaconState = struct { allocator: Allocator, /// only a reference to the singleton BeaconConfig config: *const BeaconConfig, @@ -20,19 +21,20 @@ pub const CachedBeaconStateAllForks = struct { /// TODO: before an epoch transition, need to release() epoch_cache before using a new one epoch_cache_ref: *EpochCacheRc, /// this takes ownership of the state, it is expected to be deinitialized by this struct - state: *BeaconStateAllForks, + state: *BeaconState, // TODO: cloned_count properties, implement this once we switch to TreeView // TODO: proposer_rewards, looks like this is not a great place to put in, it's a result of a block state transition instead /// This class takes ownership of state after this function and has responsibility to deinit it - pub fn createCachedBeaconState(allocator: Allocator, state: *BeaconStateAllForks, immutable_data: EpochCacheImmutableData, option: ?EpochCacheOpts) !*CachedBeaconStateAllForks { + pub fn createCachedBeaconState(allocator: Allocator, state: *BeaconState, immutable_data: EpochCacheImmutableData, option: ?EpochCacheOpts) !*CachedBeaconState { const epoch_cache = try EpochCache.createFromState(allocator, state, immutable_data, option); errdefer epoch_cache.deinit(); const epoch_cache_ref = try EpochCacheRc.init(allocator, epoch_cache); errdefer epoch_cache_ref.release(); - const cached_state = try allocator.create(CachedBeaconStateAllForks); + const cached_state = try allocator.create(CachedBeaconState); errdefer allocator.destroy(cached_state); + cached_state.* = .{ .allocator = allocator, .config = immutable_data.config, @@ -44,29 +46,33 @@ pub const CachedBeaconStateAllForks = struct { } // TODO: do we need another getConst()? - pub fn getEpochCache(self: *const CachedBeaconStateAllForks) *EpochCache { + pub fn getEpochCache(self: *const CachedBeaconState) *EpochCache { return self.epoch_cache_ref.get(); } - pub fn clone(self: *CachedBeaconStateAllForks, allocator: Allocator) !*CachedBeaconStateAllForks { - const cached_state = try allocator.create(CachedBeaconStateAllForks); + pub fn clone(self: *CachedBeaconState, allocator: Allocator, opts: CloneOpts) !*CachedBeaconState { + const cached_state = try allocator.create(CachedBeaconState); errdefer allocator.destroy(cached_state); const epoch_cache_ref = self.epoch_cache_ref.acquire(); errdefer epoch_cache_ref.release(); + const state = try allocator.create(BeaconState); + errdefer allocator.destroy(state); + state.* = try self.state.clone(opts); + cached_state.* = .{ .allocator = allocator, .config = self.config, .epoch_cache_ref = epoch_cache_ref, - .state = try self.state.clone(allocator), + .state = state, }; return cached_state; } - pub fn deinit(self: *CachedBeaconStateAllForks) void { + pub fn deinit(self: *CachedBeaconState) void { // should not deinit config since we don't take ownership of it, it's singleton across applications self.epoch_cache_ref.release(); - self.state.deinit(self.allocator); + self.state.deinit(); self.allocator.destroy(self.state); } @@ -75,19 +81,19 @@ pub const CachedBeaconStateAllForks = struct { // need to do this once we switch to TreeView // TODO: implement getCachedBeaconState - // this is used to create a CachedBeaconStateAllForks based on a tree and an exising CachedBeaconStateAllForks at fork transition + // this is used to create a CachedBeaconState based on a tree and an exising CachedBeaconState at fork transition // implement this once we switch to TreeView /// Gets the beacon proposer index for a given slot. /// For the Fulu fork, this uses `proposer_lookahead` from the state. /// For earlier forks, this uses `EpochCache.getBeaconProposer()`. - pub fn getBeaconProposer(self: *const CachedBeaconStateAllForks, slot: types.primitive.Slot.Type) !ValidatorIndex { + pub fn getBeaconProposer(self: *const CachedBeaconState, slot: types.primitive.Slot.Type) !ValidatorIndex { const preset_import = @import("preset").preset; const computeEpochAtSlot = @import("../utils/epoch.zig").computeEpochAtSlot; // For Fulu, use proposer_lookahead from state - if (self.state.isFulu()) { - const current_epoch = computeEpochAtSlot(self.state.slot()); + if (self.state.forkSeq().gte(.fulu)) { + const current_epoch = computeEpochAtSlot(try self.state.slot()); const slot_epoch = computeEpochAtSlot(slot); // proposer_lookahead covers current_epoch through current_epoch + MIN_SEED_LOOKAHEAD @@ -98,23 +104,27 @@ pub const CachedBeaconStateAllForks = struct { return error.SlotOutsideProposerLookahead; } - const proposer_lookahead = self.state.proposerLookahead(); + var proposer_lookahead = try self.state.proposerLookahead(); const epoch_offset = slot_epoch - lookahead_start_epoch; const slot_in_epoch = slot % preset_import.SLOTS_PER_EPOCH; const index = epoch_offset * preset_import.SLOTS_PER_EPOCH + slot_in_epoch; - return proposer_lookahead[index]; + return try proposer_lookahead.get(index); } return self.getEpochCache().getBeaconProposer(slot); } }; -test "CachedBeaconStateAllForks.clone()" { +test "CachedBeaconState.clone()" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + const Node = @import("persistent_merkle_tree").Node; + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); // test clone() api works fine with no memory leak - const cloned_cached_state = try test_state.cached_state.clone(allocator); + const cloned_cached_state = try test_state.cached_state.clone(allocator, .{}); defer { cloned_cached_state.deinit(); allocator.destroy(cloned_cached_state); diff --git a/src/state_transition/cache/sync_committee_cache.zig b/src/state_transition/cache/sync_committee_cache.zig index 62351a4aa..0e8063290 100644 --- a/src/state_transition/cache/sync_committee_cache.zig +++ b/src/state_transition/cache/sync_committee_cache.zig @@ -11,46 +11,46 @@ const SyncCommitteeIndices = std.ArrayList(u32); const SyncComitteeValidatorIndexMap = std.AutoHashMap(ValidatorIndex, SyncCommitteeIndices); const ReferenceCount = @import("../utils/reference_count.zig").ReferenceCount; -pub const SyncCommitteeCacheRc = ReferenceCount(SyncCommitteeCacheAllForks); +pub const SyncCommitteeCacheRc = ReferenceCount(SyncCommitteeCache); /// EpochCache is the only consumer of this cache but an instance of SyncCommitteeCacheAllForks is shared across EpochCache instances /// no EpochCache instance takes the ownership of SyncCommitteeCacheAllForks instance /// instead of that, we count on reference counting to deallocate the memory, see ReferenceCount() utility -pub const SyncCommitteeCacheAllForks = union(enum) { +pub const SyncCommitteeCache = union(enum) { phase0: void, - altair: *SyncCommitteeCache, + altair: *SyncCommitteeCacheAltair, - pub fn getValidatorIndices(self: *const SyncCommitteeCacheAllForks) []ValidatorIndex { + pub fn getValidatorIndices(self: *const SyncCommitteeCache) []ValidatorIndex { return switch (self.*) { .phase0 => @panic("phase0 does not have sync_committee"), .altair => |sync_committee| sync_committee.validator_indices, }; } - pub fn getValidatorIndexMap(self: *const SyncCommitteeCacheAllForks) SyncComitteeValidatorIndexMap { - return switch (self) { + pub fn getValidatorIndexMap(self: *const SyncCommitteeCache) *const SyncComitteeValidatorIndexMap { + return switch (self.*) { .phase0 => @panic("phase0 does not have sync_committee"), - .altair => self.altair.validator_index_map, + .altair => |sync_committee| sync_committee.validator_index_map, }; } - pub fn initEmpty() SyncCommitteeCacheAllForks { - return SyncCommitteeCacheAllForks{ .phase0 = {} }; + pub fn initEmpty() SyncCommitteeCache { + return SyncCommitteeCache{ .phase0 = {} }; } - pub fn initSyncCommittee(allocator: Allocator, sync_committee: *const SyncCommittee, pubkey_to_index: *const PubkeyIndexMap) !SyncCommitteeCacheAllForks { - const cache = try SyncCommitteeCache.initSyncCommittee(allocator, sync_committee, pubkey_to_index); - return SyncCommitteeCacheAllForks{ .altair = cache }; + pub fn initSyncCommittee(allocator: Allocator, sync_committee: *const SyncCommittee, pubkey_to_index: *const PubkeyIndexMap) !SyncCommitteeCache { + const cache = try SyncCommitteeCacheAltair.initSyncCommittee(allocator, sync_committee, pubkey_to_index); + return SyncCommitteeCache{ .altair = cache }; } - pub fn initValidatorIndices(allocator: Allocator, indices: []const ValidatorIndex) !SyncCommitteeCacheAllForks { + pub fn initValidatorIndices(allocator: Allocator, indices: []const ValidatorIndex) !SyncCommitteeCache { const cloned_indices = try allocator.alloc(ValidatorIndex, indices.len); std.mem.copyForwards(ValidatorIndex, cloned_indices, indices); - const cache = try SyncCommitteeCache.initValidatorIndices(allocator, cloned_indices); - return SyncCommitteeCacheAllForks{ .altair = cache }; + const cache = try SyncCommitteeCacheAltair.initValidatorIndices(allocator, cloned_indices); + return SyncCommitteeCache{ .altair = cache }; } - pub fn deinit(self: *SyncCommitteeCacheAllForks) void { + pub fn deinit(self: *SyncCommitteeCache) void { switch (self.*) { .phase0 => {}, .altair => |sync_committee_cache| sync_committee_cache.deinit(), @@ -59,7 +59,7 @@ pub const SyncCommitteeCacheAllForks = union(enum) { }; /// this is for post-altair -const SyncCommitteeCache = struct { +const SyncCommitteeCacheAltair = struct { allocator: Allocator, // this takes ownership of validator_indices, consumer needs to transfer ownership to this cache @@ -67,13 +67,13 @@ const SyncCommitteeCache = struct { validator_index_map: *SyncComitteeValidatorIndexMap, - pub fn initSyncCommittee(allocator: Allocator, sync_committee: *const SyncCommittee, pubkey_to_index: *const PubkeyIndexMap) !*SyncCommitteeCache { + pub fn initSyncCommittee(allocator: Allocator, sync_committee: *const SyncCommittee, pubkey_to_index: *const PubkeyIndexMap) !*SyncCommitteeCacheAltair { const validator_indices = try allocator.alloc(ValidatorIndex, sync_committee.pubkeys.len); try computeSyncCommitteeIndices(sync_committee, pubkey_to_index, validator_indices); - return SyncCommitteeCache.initValidatorIndices(allocator, validator_indices); + return SyncCommitteeCacheAltair.initValidatorIndices(allocator, validator_indices); } - pub fn initValidatorIndices(allocator: Allocator, validator_indices: []ValidatorIndex) !*SyncCommitteeCache { + pub fn initValidatorIndices(allocator: Allocator, validator_indices: []ValidatorIndex) !*SyncCommitteeCacheAltair { const validator_index_map = try allocator.create(SyncComitteeValidatorIndexMap); errdefer allocator.destroy(validator_index_map); @@ -88,10 +88,10 @@ const SyncCommitteeCache = struct { try computeSyncCommitteeMap(allocator, validator_indices, validator_index_map); - const cache_ptr = try allocator.create(SyncCommitteeCache); + const cache_ptr = try allocator.create(SyncCommitteeCacheAltair); errdefer allocator.destroy(cache_ptr); - cache_ptr.* = SyncCommitteeCache{ + cache_ptr.* = SyncCommitteeCacheAltair{ .allocator = allocator, .validator_indices = validator_indices, .validator_index_map = validator_index_map, @@ -99,7 +99,7 @@ const SyncCommitteeCache = struct { return cache_ptr; } - pub fn deinit(self: *SyncCommitteeCache) void { + pub fn deinit(self: *SyncCommitteeCacheAltair) void { self.allocator.free(self.validator_indices); var value_iterator = self.validator_index_map.valueIterator(); while (value_iterator.next()) |value| { @@ -124,7 +124,7 @@ test "initSyncCommittee - sanity" { defer pubkey_index_map.deinit(); try pubkey_index_map.set(&sync_committee.pubkeys[0], 1000); - var cache = try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, &sync_committee, pubkey_index_map); + var cache = try SyncCommitteeCache.initSyncCommittee(allocator, &sync_committee, pubkey_index_map); defer cache.deinit(); try std.testing.expectEqualSlices( diff --git a/src/state_transition/epoch/get_attestation_deltas.zig b/src/state_transition/epoch/get_attestation_deltas.zig index 525b45f28..7c38cf344 100644 --- a/src/state_transition/epoch/get_attestation_deltas.zig +++ b/src/state_transition/epoch/get_attestation_deltas.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const attester_status = @import("../utils/attester_status.zig"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const preset = @import("preset").preset; const c = @import("constants"); @@ -34,7 +34,7 @@ const RewardPenaltyItem = struct { finality_delay_penalty: u64, }; -pub fn getAttestationDeltas(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, cache: *const EpochTransitionCache, rewards: []u64, penalties: []u64) !void { +pub fn getAttestationDeltas(allocator: Allocator, cached_state: *const CachedBeaconState, cache: *const EpochTransitionCache, rewards: []u64, penalties: []u64) !void { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); @@ -63,7 +63,7 @@ pub fn getAttestationDeltas(allocator: Allocator, cached_state: *const CachedBea const total_balance_in_gwei_f64: f64 = @floatFromInt(total_balance_in_gwei); const total_balance_in_gwei_sqrt: f64 = @sqrt(total_balance_in_gwei_f64); const balance_sq_root: u64 = @intFromFloat(total_balance_in_gwei_sqrt); - const finality_delay = cache.prev_epoch - state.finalizedCheckpoint().epoch; + const finality_delay = cache.prev_epoch - try state.finalizedEpoch(); const BASE_REWARDS_PER_EPOCH = BASE_REWARDS_PER_EPOCH_CONST; const proposer_reward_quotient = PROPOSER_REWARD_QUOTIENT; @@ -77,7 +77,6 @@ pub fn getAttestationDeltas(allocator: Allocator, cached_state: *const CachedBea defer reward_penalty_item_cache.deinit(); const effective_balance_increments = epoch_cache.getEffectiveBalanceIncrements(); - std.debug.assert(flags.len == state.validators().items.len); std.debug.assert(flags.len <= effective_balance_increments.items.len); for (0..flags.len) |i| { const flag = flags[i]; diff --git a/src/state_transition/epoch/get_rewards_and_penalties.zig b/src/state_transition/epoch/get_rewards_and_penalties.zig index 025892da0..3b686e078 100644 --- a/src/state_transition/epoch/get_rewards_and_penalties.zig +++ b/src/state_transition/epoch/get_rewards_and_penalties.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const attester_status = @import("../utils/attester_status.zig"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const preset = @import("preset").preset; const c = @import("constants"); @@ -34,9 +34,9 @@ const RewardPenaltyItem = struct { }; /// consumer should deinit `rewards` and `penalties` arrays -pub fn getRewardsAndPenaltiesAltair(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, cache: *const EpochTransitionCache, rewards: []u64, penalties: []u64) !void { +pub fn getRewardsAndPenaltiesAltair(allocator: Allocator, cached_state: *const CachedBeaconState, cache: *const EpochTransitionCache, rewards: []u64, penalties: []u64) !void { const state = cached_state.state; - const validator_count = state.validators().items.len; + const validator_count = try state.validatorsCount(); const active_increments = cache.total_active_stake_by_increment; if (rewards.len != validator_count or penalties.len != validator_count) { return error.InvalidArrayLength; @@ -44,7 +44,7 @@ pub fn getRewardsAndPenaltiesAltair(allocator: Allocator, cached_state: *const C @memset(rewards, 0); @memset(penalties, 0); - const is_in_inactivity_leak = isInInactivityLeak(cached_state); + const is_in_inactivity_leak = try isInInactivityLeak(cached_state); // effectiveBalance is multiple of EFFECTIVE_BALANCE_INCREMENT and less than MAX_EFFECTIVE_BALANCE // so there are limited values of them like 32, 31, 30 var reward_penalty_item_cache = std.AutoHashMap(u64, RewardPenaltyItem).init(allocator); @@ -52,7 +52,7 @@ pub fn getRewardsAndPenaltiesAltair(allocator: Allocator, cached_state: *const C const config = cached_state.config; const epoch_cache = cached_state.getEpochCache(); - const fork = config.forkSeq(state.slot()); + const fork = config.forkSeq(try state.slot()); const inactivity_penality_multiplier: u64 = if (fork == ForkSeq.altair) INACTIVITY_PENALTY_QUOTIENT_ALTAIR else INACTIVITY_PENALTY_QUOTIENT_BELLATRIX; @@ -60,7 +60,7 @@ pub fn getRewardsAndPenaltiesAltair(allocator: Allocator, cached_state: *const C const flags = cache.flags; const effective_balance_increments = epoch_cache.getEffectiveBalanceIncrements().items; - const inactivity_scores = state.inactivityScores(); + var inactivity_scores = try state.inactivityScores(); for (flags, 0..) |flag, i| { if (!hasMarkers(flag, FLAG_ELIGIBLE_ATTESTER)) { continue; @@ -121,7 +121,7 @@ pub fn getRewardsAndPenaltiesAltair(allocator: Allocator, cached_state: *const C // Same logic to getInactivityPenaltyDeltas // TODO: if we have limited value in inactivityScores we can provide a cache too if (!hasMarkers(flag, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) { - const penalty_numerator: u64 = @as(u64, effective_balance_increment) * EFFECTIVE_BALANCE_INCREMENT * inactivity_scores.items[i]; + const penalty_numerator: u64 = @as(u64, effective_balance_increment) * EFFECTIVE_BALANCE_INCREMENT * (try inactivity_scores.get(i)); penalties[i] += @divFloor(penalty_numerator, penalty_denominator); } } diff --git a/src/state_transition/epoch/process_effective_balance_updates.zig b/src/state_transition/epoch/process_effective_balance_updates.zig index 493928e36..7b60ed604 100644 --- a/src/state_transition/epoch/process_effective_balance_updates.zig +++ b/src/state_transition/epoch/process_effective_balance_updates.zig @@ -1,5 +1,6 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const Allocator = std.mem.Allocator; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const types = @import("consensus_types"); @@ -14,10 +15,11 @@ const DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * preset.HYSTERESIS_DOWNWARD_MUL const UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * preset.HYSTERESIS_UPWARD_MULTIPLIER; /// this function also update EpochTransitionCache -pub fn processEffectiveBalanceUpdates(cached_state: *CachedBeaconStateAllForks, cache: *EpochTransitionCache) !usize { +pub fn processEffectiveBalanceUpdates(allocator: Allocator, cached_state: *CachedBeaconState, cache: *EpochTransitionCache) !usize { const state = cached_state.state; + const fork = state.forkSeq(); const epoch_cache = cached_state.getEpochCache(); - const validators = state.validators(); + var validators = try state.validators(); const effective_balance_increments = epoch_cache.getEffectiveBalanceIncrements().items; var next_epoch_total_active_balance_by_increment: u64 = 0; @@ -26,15 +28,27 @@ pub fn processEffectiveBalanceUpdates(cached_state: *CachedBeaconStateAllForks, // epochTransitionCache.balances is initialized in processRewardsAndPenalties() // and updated in processPendingDeposits() and processPendingConsolidations() // so it's recycled here for performance. - const balances = if (cache.balances) |balances_arr| balances_arr.items else state.balances().items; + const balances = if (cache.balances) |balances_arr| + balances_arr.items + else + try state.balancesSlice(allocator); + defer if (cache.balances == null) { + allocator.free(balances); + }; const is_compounding_validator_arr = cache.is_compounding_validator_arr.items; + var previous_epoch_participation: types.altair.EpochParticipation.TreeView = undefined; + var current_epoch_participation: types.altair.EpochParticipation.TreeView = undefined; + if (fork.gte(.altair)) { + previous_epoch_participation = try state.previousEpochParticipation(); + current_epoch_participation = try state.currentEpochParticipation(); + } + var num_update: usize = 0; for (balances, 0..) |balance, i| { - // PERF: It's faster to access to get() every single element (4ms) than to convert to regular array then loop (9ms) var effective_balance_increment = effective_balance_increments[i]; var effective_balance = @as(u64, effective_balance_increment) * preset.EFFECTIVE_BALANCE_INCREMENT; - const effective_balance_limit: u64 = if (state.isPreElectra()) preset.MAX_EFFECTIVE_BALANCE else blk: { + const effective_balance_limit: u64 = if (fork.lt(.electra)) preset.MAX_EFFECTIVE_BALANCE else blk: { // from electra, effectiveBalanceLimit is per validator if (is_compounding_validator_arr[i]) { break :blk preset.MAX_EFFECTIVE_BALANCE_ELECTRA; @@ -51,30 +65,28 @@ pub fn processEffectiveBalanceUpdates(cached_state: *CachedBeaconStateAllForks, { // Update the state tree // Should happen rarely, so it's fine to update the tree - var validator = &validators.items[i]; + var validator = try validators.get(i); effective_balance = @min( balance - (balance % preset.EFFECTIVE_BALANCE_INCREMENT), effective_balance_limit, ); - validator.effective_balance = effective_balance; + try validator.set("effective_balance", effective_balance); // Also update the fast cached version const new_effective_balance_increment: u16 = @intCast(@divFloor(effective_balance, preset.EFFECTIVE_BALANCE_INCREMENT)); // TODO: describe issue. Compute progressive target balances // Must update target balances for consistency, see comments below - if (state.isPostAltair()) { - if (!validator.slashed) { - const previous_epoch_participation = state.previousEpochParticipations().items; - const current_epoch_participation = state.currentEpochParticipations().items; - - if (previous_epoch_participation[i] & TIMELY_TARGET == TIMELY_TARGET) { + if (fork.gte(.altair)) { + const slashed = try validator.get("slashed"); + if (!slashed) { + if ((try previous_epoch_participation.get(i)) & TIMELY_TARGET == TIMELY_TARGET) { // Use += then -= to avoid underflow when new_effective_balance_increment < effective_balance_increment epoch_cache.previous_target_unslashed_balance_increments += new_effective_balance_increment; epoch_cache.previous_target_unslashed_balance_increments -= effective_balance_increment; } // currentTargetUnslashedBalanceIncrements is transferred to previousTargetUnslashedBalanceIncrements in afterEpochTransitionCache // at epoch transition of next epoch (in EpochTransitionCache), prevTargetUnslStake is calculated based on newEffectiveBalanceIncrement - if (current_epoch_participation[i] & TIMELY_TARGET == TIMELY_TARGET) { + if ((try current_epoch_participation.get(i)) & TIMELY_TARGET == TIMELY_TARGET) { // Use += then -= to avoid underflow when new_effective_balance_increment < effective_balance_increment epoch_cache.current_target_unslashed_balance_increments += new_effective_balance_increment; epoch_cache.current_target_unslashed_balance_increments -= effective_balance_increment; diff --git a/src/state_transition/epoch/process_epoch.zig b/src/state_transition/epoch/process_epoch.zig index dfca10136..78655eeb9 100644 --- a/src/state_transition/epoch/process_epoch.zig +++ b/src/state_transition/epoch/process_epoch.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const processJustificationAndFinalization = @import("./process_justification_and_finalization.zig").processJustificationAndFinalization; @@ -21,11 +21,11 @@ const processSyncCommitteeUpdates = @import("./process_sync_committee_updates.zi const processProposerLookahead = @import("./process_proposer_lookahead.zig").processProposerLookahead; // TODO: add metrics -pub fn processEpoch(allocator: std.mem.Allocator, cached_state: *CachedBeaconStateAllForks, cache: *EpochTransitionCache) !void { +pub fn processEpoch(allocator: std.mem.Allocator, cached_state: *CachedBeaconState, cache: *EpochTransitionCache) !void { const state = cached_state.state; try processJustificationAndFinalization(cached_state, cache); - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { try processInactivityUpdates(cached_state, cache); } @@ -36,36 +36,36 @@ pub fn processEpoch(allocator: std.mem.Allocator, cached_state: *CachedBeaconSta try processRewardsAndPenalties(allocator, cached_state, cache); - processEth1DataReset(allocator, cached_state, cache); + try processEth1DataReset(cached_state, cache); - if (state.isPostElectra()) { + if (state.forkSeq().gte(.electra)) { try processPendingDeposits(allocator, cached_state, cache); - try processPendingConsolidations(allocator, cached_state, cache); + try processPendingConsolidations(cached_state, cache); } // const numUpdate = processEffectiveBalanceUpdates(fork, state, cache); - _ = try processEffectiveBalanceUpdates(cached_state, cache); + _ = try processEffectiveBalanceUpdates(allocator, cached_state, cache); - processSlashingsReset(cached_state, cache); - processRandaoMixesReset(cached_state, cache); + try processSlashingsReset(cached_state, cache); + try processRandaoMixesReset(cached_state, cache); - if (state.isPostCapella()) { - try processHistoricalSummariesUpdate(allocator, cached_state, cache); + if (state.forkSeq().gte(.capella)) { + try processHistoricalSummariesUpdate(cached_state, cache); } else { - try processHistoricalRootsUpdate(allocator, cached_state, cache); + try processHistoricalRootsUpdate(cached_state, cache); } - if (state.isPhase0()) { - processParticipationRecordUpdates(allocator, cached_state); + if (state.forkSeq() == .phase0) { + try processParticipationRecordUpdates(cached_state); } else { - try processParticipationFlagUpdates(allocator, cached_state); + try processParticipationFlagUpdates(cached_state); } - if (state.isPostAltair()) { + if (state.forkSeq().gte(.altair)) { try processSyncCommitteeUpdates(allocator, cached_state); } - if (state.isFulu()) { + if (state.forkSeq().gte(.fulu)) { try processProposerLookahead(allocator, cached_state, cache); } } diff --git a/src/state_transition/epoch/process_eth1_data_reset.zig b/src/state_transition/epoch/process_eth1_data_reset.zig index e1a21cb1e..0e8bfda2c 100644 --- a/src/state_transition/epoch/process_eth1_data_reset.zig +++ b/src/state_transition/epoch/process_eth1_data_reset.zig @@ -1,18 +1,16 @@ -const Allocator = @import("std").mem.Allocator; const types = @import("consensus_types"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const preset = @import("preset").preset; const EPOCHS_PER_ETH1_VOTING_PERIOD = preset.EPOCHS_PER_ETH1_VOTING_PERIOD; /// Reset eth1DataVotes tree every `EPOCHS_PER_ETH1_VOTING_PERIOD`. -pub fn processEth1DataReset(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) void { +pub fn processEth1DataReset(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { const next_epoch = cache.current_epoch + 1; // reset eth1 data votes if (next_epoch % EPOCHS_PER_ETH1_VOTING_PERIOD == 0) { - const state = cached_state.state; - const state_eth1_data_votes = state.eth1DataVotes(); - state_eth1_data_votes.clearAndFree(allocator); + var state = cached_state.state; + try state.resetEth1DataVotes(); } } diff --git a/src/state_transition/epoch/process_historical_roots_update.zig b/src/state_transition/epoch/process_historical_roots_update.zig index 9e4d0cc75..a374ea8ac 100644 --- a/src/state_transition/epoch/process_historical_roots_update.zig +++ b/src/state_transition/epoch/process_historical_roots_update.zig @@ -1,30 +1,26 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const types = @import("consensus_types"); const preset = @import("preset").preset; const Root = types.primitive.Root.Type; -pub fn processHistoricalRootsUpdate(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) !void { +pub fn processHistoricalRootsUpdate(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { const state = cached_state.state; const next_epoch = cache.current_epoch + 1; // set historical root accumulator if (next_epoch % @divFloor(preset.SLOTS_PER_HISTORICAL_ROOT, preset.SLOTS_PER_EPOCH) == 0) { - var block_roots: Root = undefined; - try types.phase0.HistoricalBlockRoots.hashTreeRoot(state.blockRoots(), &block_roots); - var state_roots: Root = undefined; - try types.phase0.HistoricalStateRoots.hashTreeRoot(state.stateRoots(), &state_roots); + const block_roots = try state.blockRootsRoot(); + const state_roots = try state.stateRootsRoot(); var root: Root = undefined; // HistoricalBatchRoots = Non-spec'ed helper type to allow efficient hashing in epoch transition. // This type is like a 'Header' of HistoricalBatch where its fields are hashed. try types.phase0.HistoricalBatchRoots.hashTreeRoot(&.{ - .block_roots = block_roots, - .state_roots = state_roots, + .block_roots = block_roots.*, + .state_roots = state_roots.*, }, &root); - const historical_roots = state.historicalRoots(); - try historical_roots.append(allocator, root); + var historical_roots = try state.historicalRoots(); + try historical_roots.pushValue(&root); } } diff --git a/src/state_transition/epoch/process_historical_summaries_update.zig b/src/state_transition/epoch/process_historical_summaries_update.zig index 34534a81d..e5a5b0af5 100644 --- a/src/state_transition/epoch/process_historical_summaries_update.zig +++ b/src/state_transition/epoch/process_historical_summaries_update.zig @@ -1,26 +1,23 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const types = @import("consensus_types"); const Root = types.primitive.Root.Type; const preset = @import("preset").preset; -pub fn processHistoricalSummariesUpdate(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) !void { +pub fn processHistoricalSummariesUpdate(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { const state = cached_state.state; const next_epoch = cache.current_epoch + 1; // set historical root accumulator if (next_epoch % @divFloor(preset.SLOTS_PER_HISTORICAL_ROOT, preset.SLOTS_PER_EPOCH) == 0) { - var block_summary_root: Root = undefined; - try types.phase0.HistoricalBlockRoots.hashTreeRoot(state.blockRoots(), &block_summary_root); - var state_summary_root: Root = undefined; - try types.phase0.HistoricalStateRoots.hashTreeRoot(state.stateRoots(), &state_summary_root); - const historical_summaries = state.historicalSummaries(); - try historical_summaries.append(allocator, .{ - .block_summary_root = block_summary_root, - .state_summary_root = state_summary_root, - }); + const block_summary_root = try state.blockRootsRoot(); + const state_summary_root = try state.stateRootsRoot(); + var historical_summaries = try state.historicalSummaries(); + const new_historical_summary: types.capella.HistoricalSummary.Type = .{ + .block_summary_root = block_summary_root.*, + .state_summary_root = state_summary_root.*, + }; + try historical_summaries.pushValue(&new_historical_summary); } } diff --git a/src/state_transition/epoch/process_inactivity_updates.zig b/src/state_transition/epoch/process_inactivity_updates.zig index d22e87805..1e326279f 100644 --- a/src/state_transition/epoch/process_inactivity_updates.zig +++ b/src/state_transition/epoch/process_inactivity_updates.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const GENESIS_EPOCH = @import("preset").GENESIS_EPOCH; @@ -7,7 +7,7 @@ const isInInactivityLeak = @import("../utils/finality.zig").isInInactivityLeak; const attester_status_utils = @import("../utils/attester_status.zig"); const hasMarkers = attester_status_utils.hasMarkers; -pub fn processInactivityUpdates(cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) !void { +pub fn processInactivityUpdates(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { if (cached_state.getEpochCache().epoch == GENESIS_EPOCH) { return; } @@ -17,20 +17,18 @@ pub fn processInactivityUpdates(cached_state: *CachedBeaconStateAllForks, cache: const INACTIVITY_SCORE_BIAS = config.INACTIVITY_SCORE_BIAS; const INACTIVITY_SCORE_RECOVERY_RATE = config.INACTIVITY_SCORE_RECOVERY_RATE; const flags = cache.flags; - const is_in_activity_leak = isInInactivityLeak(cached_state); + const is_in_activity_leak = try isInInactivityLeak(cached_state); // this avoids importing FLAG_ELIGIBLE_ATTESTER inside the for loop, check the compiled code const FLAG_PREV_TARGET_ATTESTER_UNSLASHED = attester_status_utils.FLAG_PREV_TARGET_ATTESTER_UNSLASHED; const FLAG_ELIGIBLE_ATTESTER = attester_status_utils.FLAG_ELIGIBLE_ATTESTER; - // for TreeView, we may need a reused inactivityScoresArr - // TODO: assert this once https://github.com/ChainSafe/state-transition-z/issues/33 - - const inactivity_scores = state.inactivityScores(); + // TODO for TreeView, we may want to convert to value and back + var inactivity_scores = try state.inactivityScores(); for (0..flags.len) |i| { const flag = flags[i]; if (hasMarkers(flag, FLAG_ELIGIBLE_ATTESTER)) { - var inactivity_score = inactivity_scores.items[i]; + var inactivity_score = try inactivity_scores.get(i); const prev_inactivity_score = inactivity_score; if (hasMarkers(flag, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) { @@ -42,7 +40,7 @@ pub fn processInactivityUpdates(cached_state: *CachedBeaconStateAllForks, cache: inactivity_score -= @min(INACTIVITY_SCORE_RECOVERY_RATE, inactivity_score); } if (inactivity_score != prev_inactivity_score) { - inactivity_scores.items[i] = inactivity_score; + try inactivity_scores.set(i, inactivity_score); } } } diff --git a/src/state_transition/epoch/process_justification_and_finalization.zig b/src/state_transition/epoch/process_justification_and_finalization.zig index 3657d8c6a..44c46ca70 100644 --- a/src/state_transition/epoch/process_justification_and_finalization.zig +++ b/src/state_transition/epoch/process_justification_and_finalization.zig @@ -1,4 +1,4 @@ -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const Epoch = types.primitive.Epoch.Type; const Checkpoint = types.phase0.Checkpoint.Type; @@ -11,7 +11,7 @@ const getBlockRoot = @import("../utils/block_root.zig").getBlockRoot; /// Update justified and finalized checkpoints depending on network participation. /// /// PERF: Very low (constant) cost. Persist small objects to the tree. -pub fn processJustificationAndFinalization(cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) !void { +pub fn processJustificationAndFinalization(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { // Initial FFG checkpoint values have a `0x00` stub for `root`. // Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub. if (cache.current_epoch <= GENESIS_EPOCH + 1) { @@ -20,64 +20,67 @@ pub fn processJustificationAndFinalization(cached_state: *CachedBeaconStateAllFo try weighJustificationAndFinalization(cached_state, cache.total_active_stake_by_increment, cache.prev_epoch_unslashed_stake_target_by_increment, cache.curr_epoch_unslashed_target_stake_by_increment); } -pub fn weighJustificationAndFinalization(cached_state: *CachedBeaconStateAllForks, total_active_balance: u64, previous_epoch_target_balance: u64, current_epoch_target_balance: u64) !void { - const state = cached_state.state; - const current_epoch = computeEpochAtSlot(state.slot()); +pub fn weighJustificationAndFinalization(cached_state: *CachedBeaconState, total_active_balance: u64, previous_epoch_target_balance: u64, current_epoch_target_balance: u64) !void { + var state = cached_state.state; + const current_epoch = computeEpochAtSlot(try state.slot()); const previous_epoch = if (current_epoch == GENESIS_EPOCH) GENESIS_EPOCH else current_epoch - 1; - const old_previous_justified_checkpoint = state.previousJustifiedCheckpoint().*; - const old_current_justified_checkpoint = state.currentJustifiedCheckpoint().*; + var old_previous_justified_checkpoint: types.phase0.Checkpoint.Type = undefined; + try state.previousJustifiedCheckpoint(&old_previous_justified_checkpoint); + var old_current_justified_checkpoint: types.phase0.Checkpoint.Type = undefined; + try state.currentJustifiedCheckpoint(&old_current_justified_checkpoint); + + const old_previous_justified_checkpoint_epoch = old_previous_justified_checkpoint.epoch; + const old_current_justified_checkpoint_epoch = old_current_justified_checkpoint.epoch; // Process justifications - state.previousJustifiedCheckpoint().* = old_current_justified_checkpoint; - const justification_bits = state.justificationBits(); - var bits = [_]bool{false} ** JustificationBits.length; - justification_bits.toBoolArray(&bits); + try state.setPreviousJustifiedCheckpoint(&old_current_justified_checkpoint); + var justification_bits = try state.justificationBits(); + var bits = try justification_bits.toBoolArray(); // Rotate bits - var i: usize = bits.len - 1; - while (i > 0) : (i -= 1) { - bits[i] = bits[i - 1]; + var idx: usize = bits.len - 1; + while (idx > 0) : (idx -= 1) { + bits[idx] = bits[idx - 1]; } bits[0] = false; - const current_justified_checkpoint = state.currentJustifiedCheckpoint(); if (previous_epoch_target_balance * 3 > total_active_balance * 2) { - current_justified_checkpoint.* = Checkpoint{ + const new_current_justified_checkpoint = Checkpoint{ .epoch = previous_epoch, - .root = try getBlockRoot(state, previous_epoch), + .root = (try getBlockRoot(state, previous_epoch)).*, }; + try state.setCurrentJustifiedCheckpoint(&new_current_justified_checkpoint); bits[1] = true; } if (current_epoch_target_balance * 3 > total_active_balance * 2) { - current_justified_checkpoint.* = Checkpoint{ + const new_current_justified_checkpoint = Checkpoint{ .epoch = current_epoch, - .root = try getBlockRoot(state, current_epoch), + .root = (try getBlockRoot(state, current_epoch)).*, }; + try state.setCurrentJustifiedCheckpoint(&new_current_justified_checkpoint); bits[0] = true; } - justification_bits.* = try JustificationBits.fromBoolArray(bits); - - // TODO: Consider rendering bits as array of boolean for faster repeated access here + const new_justification_bits = try JustificationBits.fromBoolArray(bits); + try state.setJustificationBits(&new_justification_bits); - const finalized_checkpoint = state.finalizedCheckpoint(); // Process finalizations // The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source - if (bits[1] and bits[2] and bits[3] and old_previous_justified_checkpoint.epoch + 3 == current_epoch) { - finalized_checkpoint.* = old_previous_justified_checkpoint; + if (bits[1] and bits[2] and bits[3] and old_previous_justified_checkpoint_epoch + 3 == current_epoch) { + try state.setFinalizedCheckpoint(&old_previous_justified_checkpoint); } // The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source - if (bits[1] and bits[2] and old_previous_justified_checkpoint.epoch + 2 == current_epoch) { - finalized_checkpoint.* = old_previous_justified_checkpoint; + if (bits[1] and bits[2] and old_previous_justified_checkpoint_epoch + 2 == current_epoch) { + try state.setFinalizedCheckpoint(&old_previous_justified_checkpoint); } // The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3rd as source - if (bits[0] and bits[1] and bits[2] and old_current_justified_checkpoint.epoch + 2 == current_epoch) { - finalized_checkpoint.* = old_current_justified_checkpoint; + if (bits[0] and bits[1] and bits[2] and old_current_justified_checkpoint_epoch + 2 == current_epoch) { + try state.setFinalizedCheckpoint(&old_current_justified_checkpoint); } // The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source - if (bits[0] and bits[1] and old_current_justified_checkpoint.epoch + 1 == current_epoch) { - finalized_checkpoint.* = old_current_justified_checkpoint; + if (bits[0] and bits[1] and old_current_justified_checkpoint_epoch + 1 == current_epoch) { + try state.setFinalizedCheckpoint(&old_current_justified_checkpoint); } } diff --git a/src/state_transition/epoch/process_participation_flag_updates.zig b/src/state_transition/epoch/process_participation_flag_updates.zig index 9ce1701f4..2f7f009fe 100644 --- a/src/state_transition/epoch/process_participation_flag_updates.zig +++ b/src/state_transition/epoch/process_participation_flag_updates.zig @@ -1,27 +1,9 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ForkSeq = @import("config").ForkSeq; -const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; -const types = @import("consensus_types"); -const preset = @import("preset").preset; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; -pub fn processParticipationFlagUpdates(allocator: std.mem.Allocator, cached_state: *CachedBeaconStateAllForks) !void { +pub fn processParticipationFlagUpdates(cached_state: *CachedBeaconState) !void { const state = cached_state.state; - // rotate EpochParticipation - try state.rotateEpochParticipations(allocator); - // We need to replace the node of currentEpochParticipation with a node that represents an empty list of some length. - // SSZ represents a list as = new BranchNode(chunksNode, lengthNode). - // Since the chunks represent all zero'ed data we can re-use the pre-computed zeroNode at chunkDepth to skip any - // data transformation and create the required tree almost for free. - - // TODO(ct) implement this using TreeView - // const currentEpochParticipationNode = types.altair.EpochParticipation.tree_setChunksNode( - // state.currentEpochParticipation.node, - // zeroNode(types.altair.EpochParticipation.chunkDepth), - // state.currentEpochParticipation.length - // ); - - // state.currentEpochParticipation = types.altair.EpochParticipation.getViewDU(currentEpochParticipationNode); + if (state.forkSeq().lt(.altair)) return; + try state.rotateEpochParticipation(); } diff --git a/src/state_transition/epoch/process_participation_record_updates.zig b/src/state_transition/epoch/process_participation_record_updates.zig index 2c19d58ea..c279fcf97 100644 --- a/src/state_transition/epoch/process_participation_record_updates.zig +++ b/src/state_transition/epoch/process_participation_record_updates.zig @@ -1,13 +1,7 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ForkSeq = @import("config").ForkSeq; -const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; -const types = @import("consensus_types"); -const preset = @import("preset").preset; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; -pub fn processParticipationRecordUpdates(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) void { - const state = cached_state.state; +pub fn processParticipationRecordUpdates(cached_state: *CachedBeaconState) !void { + var state = cached_state.state; // rotate current/previous epoch attestations - state.rotateEpochPendingAttestations(allocator); + try state.rotateEpochPendingAttestations(); } diff --git a/src/state_transition/epoch/process_pending_attestations.zig b/src/state_transition/epoch/process_pending_attestations.zig index 58729a30b..24b336661 100644 --- a/src/state_transition/epoch/process_pending_attestations.zig +++ b/src/state_transition/epoch/process_pending_attestations.zig @@ -1,8 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const types = @import("consensus_types"); -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const computeStartSlotAtEpoch = @import("../utils/epoch.zig").computeStartSlotAtEpoch; const getBlockRootAtSlot = @import("../utils/block_root.zig").getBlockRootAtSlot; @@ -12,7 +12,7 @@ const PendingAttestation = types.phase0.PendingAttestation.Type; pub fn processPendingAttestations( allocator: Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, proposer_indices: []usize, validator_count: usize, inclusion_delays: []usize, @@ -25,7 +25,7 @@ pub fn processPendingAttestations( ) !void { const epoch_cache = cached_state.getEpochCache(); const state = cached_state.state; - const state_slot = state.slot(); + const state_slot = try state.slot(); const prev_epoch = epoch_cache.getPreviousShuffling().epoch; if (attestations.len == 0) { return; @@ -45,7 +45,10 @@ pub fn processPendingAttestations( const proposer_index = att.proposer_index; const att_slot = att_data.slot; const att_voted_target_root = std.mem.eql(u8, att_data.target.root[0..], actual_target_block_root[0..]); - const att_voted_head_root = att_slot < state_slot and std.mem.eql(u8, att_data.beacon_block_root[0..], &(try getBlockRootAtSlot(state, att_slot))); + const att_voted_head_root = if (att_slot < state_slot) blk: { + const head_root = try getBlockRootAtSlot(state, att_slot); + break :blk std.mem.eql(u8, att_data.beacon_block_root[0..], head_root[0..]); + } else false; const committee = @as([]const u64, try epoch_cache.getBeaconCommittee(att_slot, att_data.index)); var participants = try att.aggregation_bits.intersectValues(ValidatorIndex, allocator, committee); defer participants.deinit(); diff --git a/src/state_transition/epoch/process_pending_consolidations.zig b/src/state_transition/epoch/process_pending_consolidations.zig index 4174d7ade..31b8c1749 100644 --- a/src/state_transition/epoch/process_pending_consolidations.zig +++ b/src/state_transition/epoch/process_pending_consolidations.zig @@ -1,65 +1,53 @@ -const std = @import("std"); +const Allocator = @import("std").mem.Allocator; const types = @import("consensus_types"); -const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ForkSeq = @import("config").ForkSeq; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const decreaseBalance = @import("../utils/balance.zig").decreaseBalance; const increaseBalance = @import("../utils/balance.zig").increaseBalance; /// also modify balances inside EpochTransitionCache -pub fn processPendingConsolidations(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, cache: *EpochTransitionCache) !void { +pub fn processPendingConsolidations(cached_state: *CachedBeaconState, cache: *EpochTransitionCache) !void { const epoch_cache = cached_state.getEpochCache(); - const state = cached_state.state; + var state = cached_state.state; const next_epoch = epoch_cache.epoch + 1; var next_pending_consolidation: usize = 0; - const validators = state.validators(); - - var chunk_start_index: usize = 0; - const chunk_size = 100; - const pending_consolidations = state.pendingConsolidations(); - const pending_consolidations_length = pending_consolidations.items.len; - outer: while (chunk_start_index < pending_consolidations_length) : (chunk_start_index += chunk_size) { - // TODO(ssz): implement getReadonlyByRange api for TreeView - const consolidation_chunk = state.pendingConsolidations().items[chunk_start_index..@min(chunk_start_index + chunk_size, pending_consolidations_length)]; - for (consolidation_chunk) |pending_consolidation| { - const source_index = pending_consolidation.source_index; - const target_index = pending_consolidation.target_index; - const source_validator = validators.items[source_index]; - - if (source_validator.slashed) { - next_pending_consolidation += 1; - continue; - } - - if (source_validator.withdrawable_epoch > next_epoch) { - break :outer; - } + var validators = try state.validators(); + var balances = try state.balances(); + + var pending_consolidations = try state.pendingConsolidations(); + var pending_consolidations_it = pending_consolidations.iteratorReadonly(0); + const pending_consolidations_length = try pending_consolidations.length(); + for (0..pending_consolidations_length) |_| { + const pending_consolidation = try pending_consolidations_it.nextValue(undefined); + const source_index = pending_consolidation.source_index; + const target_index = pending_consolidation.target_index; + var source_validator = try validators.get(source_index); + + if (try source_validator.get("slashed")) { + next_pending_consolidation += 1; + continue; + } - // Calculate the consolidated balance - const source_effective_balance = @min(state.balances().items[source_index], source_validator.effective_balance); + if ((try source_validator.get("withdrawable_epoch")) > next_epoch) { + break; + } - // Move active balance to target. Excess balance is withdrawable. - decreaseBalance(state, source_index, source_effective_balance); - increaseBalance(state, target_index, source_effective_balance); - if (cache.balances) |cached_balances| { - cached_balances.items[source_index] -= source_effective_balance; - cached_balances.items[target_index] += source_effective_balance; - } + // Calculate the consolidated balance + const source_effective_balance = @min(try balances.get(source_index), try source_validator.get("effective_balance")); - next_pending_consolidation += 1; + // Move active balance to target. Excess balance is withdrawable. + try decreaseBalance(state, source_index, source_effective_balance); + try increaseBalance(state, target_index, source_effective_balance); + if (cache.balances) |cached_balances| { + cached_balances.items[source_index] -= source_effective_balance; + cached_balances.items[target_index] += source_effective_balance; } + + next_pending_consolidation += 1; } if (next_pending_consolidation > 0) { - const new_len = pending_consolidations.items.len - next_pending_consolidation; - - std.mem.copyForwards( - types.electra.PendingConsolidation.Type, - pending_consolidations.items[0..new_len], - pending_consolidations.items[next_pending_consolidation .. next_pending_consolidation + new_len], - ); - - try pending_consolidations.resize(allocator, new_len); + const new_pending_consolidations = try pending_consolidations.sliceFrom(next_pending_consolidation); + try state.setPendingConsolidations(new_pending_consolidations); } } diff --git a/src/state_transition/epoch/process_pending_deposits.zig b/src/state_transition/epoch/process_pending_deposits.zig index e5abdd1af..69a357feb 100644 --- a/src/state_transition/epoch/process_pending_deposits.zig +++ b/src/state_transition/epoch/process_pending_deposits.zig @@ -1,13 +1,13 @@ const std = @import("std"); const types = @import("consensus_types"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const getActivationExitChurnLimit = @import("../utils/validator.zig").getActivationExitChurnLimit; const preset = @import("preset").preset; const isValidatorKnown = @import("../utils/electra.zig").isValidatorKnown; const ForkSeq = @import("config").ForkSeq; -const isValidDepositSignature = @import("../block/process_deposit.zig").isValidDepositSignature; +const validateDepositSignature = @import("../block/process_deposit.zig").validateDepositSignature; const addValidatorToRegistry = @import("../block/process_deposit.zig").addValidatorToRegistry; const hasCompoundingWithdrawalCredential = @import("../utils/electra.zig").hasCompoundingWithdrawalCredential; const increaseBalance = @import("../utils/balance.zig").increaseBalance; @@ -17,130 +17,130 @@ const GENESIS_SLOT = @import("preset").GENESIS_SLOT; const c = @import("constants"); /// we append EpochTransitionCache.is_compounding_validator_arr in this flow -pub fn processPendingDeposits(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, cache: *EpochTransitionCache) !void { +pub fn processPendingDeposits(allocator: Allocator, cached_state: *CachedBeaconState, cache: *EpochTransitionCache) !void { const epoch_cache = cached_state.getEpochCache(); - const state = cached_state.state; + var state = cached_state.state; + const next_epoch = epoch_cache.epoch + 1; - const deposit_balance_to_consume = state.depositBalanceToConsume(); - const available_for_processing = deposit_balance_to_consume.* + getActivationExitChurnLimit(epoch_cache); + const deposit_balance_to_consume = try state.depositBalanceToConsume(); + const available_for_processing = deposit_balance_to_consume + getActivationExitChurnLimit(epoch_cache); + const finalized_slot = computeStartSlotAtEpoch(try state.finalizedEpoch()); + const eth1_deposit_index = try state.eth1DepositIndex(); + const deposit_requests_start_index = try state.depositRequestsStartIndex(); + var processed_amount: u64 = 0; var next_deposit_index: u64 = 0; var deposits_to_postpone = std.ArrayList(PendingDeposit).init(allocator); defer deposits_to_postpone.deinit(); var is_churn_limit_reached = false; - const finalized_slot = computeStartSlotAtEpoch(state.finalizedCheckpoint().epoch); - - var start_index: usize = 0; - // TODO: is this a good number? - const chunk = 100; - const pending_deposits = state.pendingDeposits(); - const pending_deposits_len = pending_deposits.items.len; - outer: while (start_index < pending_deposits.items.len) : (start_index += chunk) { - // TODO(types.primitive): implement getReadonlyByRange api for TreeView - // const deposits: []PendingDeposit = state.getPendingDeposits().getReadonlyByRange(start_index, chunk); - const deposits: []PendingDeposit = pending_deposits.items[start_index..@min(start_index + chunk, pending_deposits_len)]; - for (deposits) |deposit| { - // Do not process deposit requests if Eth1 bridge deposits are not yet applied. - if ( - // Is deposit request - deposit.slot > GENESIS_SLOT and - // There are pending Eth1 bridge deposits - state.eth1DepositIndex() < state.depositRequestsStartIndex().*) - { - break :outer; - } - // Check if deposit has been finalized, otherwise, stop processing. - if (deposit.slot > finalized_slot) { - break :outer; - } + var pending_deposits = try state.pendingDeposits(); + var pending_deposits_it = pending_deposits.iteratorReadonly(0); + const pending_deposits_len = try pending_deposits.length(); - // Check if number of processed deposits has not reached the limit, otherwise, stop processing. - // TODO(ct): define MAX_PENDING_DEPOSITS_PER_EPOCH in preset - if (next_deposit_index >= preset.MAX_PENDING_DEPOSITS_PER_EPOCH) { - break :outer; - } + for (0..pending_deposits_len) |_| { + const deposit = try pending_deposits_it.nextValue(undefined); + // Do not process deposit requests if Eth1 bridge deposits are not yet applied. + if ( + // Is deposit request + deposit.slot > GENESIS_SLOT and + // There are pending Eth1 bridge deposits + eth1_deposit_index < deposit_requests_start_index) + { + break; + } + + // Check if deposit has been finalized, otherwise, stop processing. + if (deposit.slot > finalized_slot) { + break; + } - // Read validator state - var is_validator_exited = false; - var is_validator_withdrawn = false; - const validator_index = epoch_cache.getValidatorIndex(&deposit.pubkey); + // Check if number of processed deposits has not reached the limit, otherwise, stop processing. + if (next_deposit_index >= preset.MAX_PENDING_DEPOSITS_PER_EPOCH) { + break; + } - if (isValidatorKnown(state, validator_index)) { - const validator = state.validators().items[validator_index.?]; - is_validator_exited = validator.exit_epoch < c.FAR_FUTURE_EPOCH; - is_validator_withdrawn = validator.withdrawable_epoch < next_epoch; - } + // Read validator state + var is_validator_exited = false; + var is_validator_withdrawn = false; + const validator_index = epoch_cache.getValidatorIndex(&deposit.pubkey); - if (is_validator_withdrawn) { - // Deposited balance will never become active. Increase balance but do not consume churn - try applyPendingDeposit(allocator, cached_state, deposit, cache); - } else if (is_validator_exited) { - // Validator is exiting, postpone the deposit until after withdrawable epoch - try deposits_to_postpone.append(deposit); - } else { - // Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. - is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing; - if (is_churn_limit_reached) { - break :outer; - } - // Consume churn and apply deposit. - processed_amount += deposit.amount; - try applyPendingDeposit(allocator, cached_state, deposit, cache); - } + if (try isValidatorKnown(state, validator_index)) { + var validators = try state.validators(); + var validator = try validators.get(validator_index.?); + is_validator_exited = try validator.get("exit_epoch") < c.FAR_FUTURE_EPOCH; + is_validator_withdrawn = try validator.get("withdrawable_epoch") < next_epoch; + } - // Regardless of how the deposit was handled, we move on in the queue. - next_deposit_index += 1; + if (is_validator_withdrawn) { + // Deposited balance will never become active. Increase balance but do not consume churn + try applyPendingDeposit(allocator, cached_state, deposit, cache); + } else if (is_validator_exited) { + // Validator is exiting, postpone the deposit until after withdrawable epoch + try deposits_to_postpone.append(deposit); + } else { + // Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing; + if (is_churn_limit_reached) { + break; + } + // Consume churn and apply deposit. + processed_amount += deposit.amount; + try applyPendingDeposit(allocator, cached_state, deposit, cache); } + + // Regardless of how the deposit was handled, we move on in the queue. + next_deposit_index += 1; } if (next_deposit_index > 0) { - // TODO: implement sliceFrom for TreeView api - const new_len = pending_deposits_len - next_deposit_index; - std.mem.copyForwards(types.electra.PendingDeposit.Type, pending_deposits.items[0..new_len], pending_deposits.items[next_deposit_index..pending_deposits_len]); - try pending_deposits.resize(allocator, new_len); + const new_pending_deposits = try pending_deposits.sliceFrom(next_deposit_index); + try state.setPendingDeposits(new_pending_deposits); + pending_deposits = new_pending_deposits; } for (deposits_to_postpone.items) |deposit| { - try pending_deposits.append(allocator, deposit); + try pending_deposits.pushValue(&deposit); } // Accumulate churn only if the churn limit has been hit. - deposit_balance_to_consume.* = - if (is_churn_limit_reached) - available_for_processing - processed_amount - else - 0; + try state.setDepositBalanceToConsume(if (is_churn_limit_reached) + available_for_processing - processed_amount + else + 0); } /// we append EpochTransitionCache.is_compounding_validator_arr in this flow -fn applyPendingDeposit(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, deposit: PendingDeposit, cache: *EpochTransitionCache) !void { +fn applyPendingDeposit(allocator: Allocator, cached_state: *CachedBeaconState, deposit: PendingDeposit, cache: *EpochTransitionCache) !void { const epoch_cache = cached_state.getEpochCache(); const state = cached_state.state; const validator_index = epoch_cache.getValidatorIndex(&deposit.pubkey) orelse null; const pubkey = deposit.pubkey; // TODO: is this withdrawal_credential(s) the same to spec? - const withdrawal_credential = deposit.withdrawal_credentials; + const withdrawal_credentials = &deposit.withdrawal_credentials; const amount = deposit.amount; const signature = deposit.signature; - const is_validator_known = isValidatorKnown(state, validator_index); + const is_validator_known = try isValidatorKnown(state, validator_index); if (!is_validator_known) { // Verify the deposit signature (proof of possession) which is not checked by the deposit contract - if (isValidDepositSignature(cached_state.config, pubkey, withdrawal_credential, amount, signature)) { - try addValidatorToRegistry(allocator, cached_state, pubkey, withdrawal_credential, amount); - try cache.is_compounding_validator_arr.append(hasCompoundingWithdrawalCredential(withdrawal_credential)); + if (validateDepositSignature(cached_state.config, pubkey, withdrawal_credentials, amount, signature)) { + try addValidatorToRegistry(allocator, cached_state, pubkey, withdrawal_credentials, amount); + try cache.is_compounding_validator_arr.append(hasCompoundingWithdrawalCredential(withdrawal_credentials)); // set balance, so that the next deposit of same pubkey will increase the balance correctly // this is to fix the double deposit issue found in mekong // see https://github.com/ChainSafe/lodestar/pull/7255 if (cache.balances) |*balances| { try balances.append(amount); } + } else |_| { + // invalid deposit signature, ignore the deposit + // TODO may be a useful metric to track } } else { if (validator_index) |val_idx| { // Increase balance - increaseBalance(state, val_idx, amount); + try increaseBalance(state, val_idx, amount); if (cache.balances) |*balances| { balances.items[val_idx] += amount; } diff --git a/src/state_transition/epoch/process_proposer_lookahead.zig b/src/state_transition/epoch/process_proposer_lookahead.zig index 059be451a..378d12a90 100644 --- a/src/state_transition/epoch/process_proposer_lookahead.zig +++ b/src/state_transition/epoch/process_proposer_lookahead.zig @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator; const ssz = @import("consensus_types"); const preset = @import("preset").preset; const c = @import("constants"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const ValidatorIndex = ssz.primitive.ValidatorIndex.Type; const computeEpochAtSlot = @import("../utils/epoch.zig").computeEpochAtSlot; @@ -16,17 +16,13 @@ const computeProposers = seed_utils.computeProposers; /// Uses active indices from the epoch transition cache for the new epoch. pub fn processProposerLookahead( allocator: Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, epoch_transition_cache: *const EpochTransitionCache, ) !void { const state = cached_state.state; - const fulu_state = switch (state.*) { - .fulu => |s| s, - // We already check for `state.isFulu()` in `processEpoch` - // but if we do get in here we simply return. - else => return, - }; + const proposer_lookahead: *[ssz.fulu.ProposerLookahead.length]u64 = try state.proposerLookaheadSlice(allocator); + defer allocator.free(proposer_lookahead); const epoch_cache = cached_state.epoch_cache_ref.get(); const lookahead_epochs = preset.MIN_SEED_LOOKAHEAD + 1; @@ -35,13 +31,13 @@ pub fn processProposerLookahead( // Shift out proposers in the first epoch std.mem.copyForwards( ValidatorIndex, - fulu_state.proposer_lookahead[0..last_epoch_start], - fulu_state.proposer_lookahead[preset.SLOTS_PER_EPOCH..], + proposer_lookahead[0..last_epoch_start], + proposer_lookahead[preset.SLOTS_PER_EPOCH..], ); // Fill in the last epoch with new proposer indices // The new epoch is current_epoch + MIN_SEED_LOOKAHEAD + 1 = current_epoch + 2 - const current_epoch = computeEpochAtSlot(state.slot()); + const current_epoch = computeEpochAtSlot(try state.slot()); const new_epoch = current_epoch + preset.MIN_SEED_LOOKAHEAD + 1; // Active indices for the new epoch come from the epoch transition cache @@ -59,6 +55,8 @@ pub fn processProposerLookahead( new_epoch, active_indices, effective_balance_increments, - fulu_state.proposer_lookahead[last_epoch_start..], + proposer_lookahead[last_epoch_start..], ); + + try state.setProposerLookahead(proposer_lookahead); } diff --git a/src/state_transition/epoch/process_randao_mixes_reset.zig b/src/state_transition/epoch/process_randao_mixes_reset.zig index 4614bd9e7..6834138dd 100644 --- a/src/state_transition/epoch/process_randao_mixes_reset.zig +++ b/src/state_transition/epoch/process_randao_mixes_reset.zig @@ -1,16 +1,20 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const types = @import("consensus_types"); const preset = @import("preset").preset; -pub fn processRandaoMixesReset(cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) void { +pub fn processRandaoMixesReset(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { const state = cached_state.state; const current_epoch = cache.current_epoch; const next_epoch = current_epoch + 1; - const state_randao_mixes = state.randaoMixes(); - state_randao_mixes[next_epoch % preset.EPOCHS_PER_HISTORICAL_VECTOR] = - state_randao_mixes[current_epoch % preset.EPOCHS_PER_HISTORICAL_VECTOR]; + var randao_mixes = try state.randaoMixes(); + var old = try randao_mixes.get(current_epoch % preset.EPOCHS_PER_HISTORICAL_VECTOR); + try randao_mixes.set( + next_epoch % preset.EPOCHS_PER_HISTORICAL_VECTOR, + // TODO inspect why this clone was needed + try old.clone(.{}), + ); } diff --git a/src/state_transition/epoch/process_registry_updates.zig b/src/state_transition/epoch/process_registry_updates.zig index 207188b81..a39e58b75 100644 --- a/src/state_transition/epoch/process_registry_updates.zig +++ b/src/state_transition/epoch/process_registry_updates.zig @@ -1,4 +1,4 @@ -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const Epoch = types.primitive.Epoch.Type; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; @@ -6,44 +6,45 @@ const ForkSeq = @import("config").ForkSeq; const computeActivationExitEpoch = @import("../utils/epoch.zig").computeActivationExitEpoch; const initiateValidatorExit = @import("../block/initiate_validator_exit.zig").initiateValidatorExit; -pub fn processRegistryUpdates(cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) !void { +pub fn processRegistryUpdates(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { const epoch_cache = cached_state.getEpochCache(); const state = cached_state.state; // Get the validators sub tree once for all the loop - const validators = state.validators(); + var validators = try state.validators(); // TODO: Batch set this properties in the tree at once with setMany() or setNodes() // process ejections - for (cache.indices_to_eject.items) |index| { + for (cache.indices_to_eject.items) |i| { // set validator exit epoch and withdrawable epoch // TODO: Figure out a way to quickly set properties on the validators tree - const validator = &validators.items[index]; - try initiateValidatorExit(cached_state, validator); + var validator = try validators.get(i); + try initiateValidatorExit(cached_state, &validator); } // set new activation eligibilities - for (cache.indices_eligible_for_activation_queue.items) |index| { - validators.items[index].activation_eligibility_epoch = epoch_cache.epoch + 1; + for (cache.indices_eligible_for_activation_queue.items) |i| { + var validator = try validators.get(i); + try validator.set("activation_eligibility_epoch", epoch_cache.epoch + 1); } - const finality_epoch = state.finalizedCheckpoint().epoch; - const len = if (state.isPreElectra()) @min(cache.indices_eligible_for_activation.items.len, epoch_cache.activation_churn_limit) else cache.indices_eligible_for_activation.items.len; + const finalized_epoch = try state.finalizedEpoch(); + const len = if (state.forkSeq().lt(.electra)) @min(cache.indices_eligible_for_activation.items.len, epoch_cache.activation_churn_limit) else cache.indices_eligible_for_activation.items.len; const activation_epoch = computeActivationExitEpoch(cache.current_epoch); // dequeue validators for activation up to churn limit for (0..len) |i| { const validator_index = cache.indices_eligible_for_activation.items[i]; - const validator = &validators.items[validator_index]; + var validator = try validators.get(validator_index); // placement in queue is finalized - if (validator.activation_eligibility_epoch > finality_epoch) { + if ((try validator.get("activation_eligibility_epoch")) > finalized_epoch) { // remaining validators all have an activationEligibilityEpoch that is higher anyway, break early // activationEligibilityEpoch has been sorted in epoch process in ascending order. // At that point the finalityEpoch was not known because processJustificationAndFinalization() wasn't called yet. // So we need to filter by finalityEpoch here to comply with the spec. break; } - validator.activation_epoch = activation_epoch; + try validator.set("activation_epoch", activation_epoch); } } diff --git a/src/state_transition/epoch/process_rewards_and_penalties.zig b/src/state_transition/epoch/process_rewards_and_penalties.zig index 54fb88a58..c2dfd91a8 100644 --- a/src/state_transition/epoch/process_rewards_and_penalties.zig +++ b/src/state_transition/epoch/process_rewards_and_penalties.zig @@ -1,14 +1,14 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ForkSeq = @import("config").ForkSeq; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const preset = @import("preset").preset; const GENESIS_EPOCH = @import("preset").GENESIS_EPOCH; const getAttestationDeltas = @import("./get_attestation_deltas.zig").getAttestationDeltas; const getRewardsAndPenaltiesAltair = @import("./get_rewards_and_penalties.zig").getRewardsAndPenaltiesAltair; -pub fn processRewardsAndPenalties(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) !void { +pub fn processRewardsAndPenalties(allocator: Allocator, cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { // No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch if (cache.current_epoch == GENESIS_EPOCH) { return; @@ -20,18 +20,27 @@ pub fn processRewardsAndPenalties(allocator: Allocator, cached_state: *CachedBea const penalties = cache.penalties; try getRewardsAndPenalties(allocator, cached_state, cache, rewards, penalties); - for (rewards, 0..) |reward, i| { - const balance = &state.balances().items[i]; - const result = balance.* + reward - penalties[i]; - balance.* = @max(result, 0); + const balances = try state.balancesSlice(allocator); + defer allocator.free(balances); + + for (rewards, penalties, balances) |reward, penalty, *balance| { + const result = balance.* + reward -| penalty; + balance.* = result; } - // TODO this is naive version, consider caching balances here when switching to TreeView + var balances_arraylist: std.ArrayListUnmanaged(u64) = .fromOwnedSlice(balances); + try state.setBalances(&balances_arraylist); } -pub fn getRewardsAndPenalties(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, cache: *const EpochTransitionCache, rewards: []u64, penalties: []u64) !void { +pub fn getRewardsAndPenalties( + allocator: Allocator, + cached_state: *const CachedBeaconState, + cache: *const EpochTransitionCache, + rewards: []u64, + penalties: []u64, +) !void { const state = cached_state.state; - const fork = cached_state.config.forkSeq(state.slot()); + const fork = cached_state.config.forkSeq(try state.slot()); return if (fork == ForkSeq.phase0) try getAttestationDeltas(allocator, cached_state, cache, rewards, penalties) else diff --git a/src/state_transition/epoch/process_slashings.zig b/src/state_transition/epoch/process_slashings.zig index 2d2d9fc8e..7691ed1bc 100644 --- a/src/state_transition/epoch/process_slashings.zig +++ b/src/state_transition/epoch/process_slashings.zig @@ -1,9 +1,9 @@ const std = @import("std"); const types = @import("consensus_types"); const preset = @import("preset").preset; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const decreaseBalance = @import("../utils//balance.zig").decreaseBalance; const EFFECTIVE_BALANCE_INCREMENT = preset.EFFECTIVE_BALANCE_INCREMENT; @@ -14,7 +14,7 @@ const PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX = preset.PROPORTIONAL_SLASHING_ /// TODO: consider returning number[] when we switch to TreeView pub fn processSlashings( allocator: std.mem.Allocator, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, cache: *const EpochTransitionCache, ) !void { // Return early if there no index to slash @@ -23,10 +23,10 @@ pub fn processSlashings( } const config = cached_state.config; const epoch_cache = cached_state.getEpochCache(); - const state = cached_state.state; + var state = cached_state.state; const total_balance_by_increment = cache.total_active_stake_by_increment; - const fork = config.forkSeq(state.slot()); + const fork = config.forkSeq(try state.slot()); const proportional_slashing_multiplier: u64 = if (fork == ForkSeq.phase0) PROPORTIONAL_SLASHING_MULTIPLIER else if (fork == ForkSeq.altair) PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR @@ -34,7 +34,7 @@ pub fn processSlashings( PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX; const effective_balance_increments = epoch_cache.getEffectiveBalanceIncrements().items; - const adjusted_total_slashing_balance_by_increment = @min(getTotalSlashingsByIncrement(state) * proportional_slashing_multiplier, total_balance_by_increment); + const adjusted_total_slashing_balance_by_increment = @min((try getTotalSlashingsByIncrement(state)) * proportional_slashing_multiplier, total_balance_by_increment); const increment = EFFECTIVE_BALANCE_INCREMENT; const penalty_per_effective_balance_increment = @divFloor((adjusted_total_slashing_balance_by_increment * increment), total_balance_by_increment); @@ -52,16 +52,16 @@ pub fn processSlashings( try penalties_by_effective_balance_increment.put(effective_balance_increment, p); break :blk p; }; - decreaseBalance(state, index, penalty); + try decreaseBalance(state, index, penalty); } } -pub fn getTotalSlashingsByIncrement(state: *const BeaconStateAllForks) u64 { +pub fn getTotalSlashingsByIncrement(state: *BeaconState) !u64 { var total_slashings_by_increment: u64 = 0; - const count = state.slashings().len; - - for (0..count) |i| { - const slashing = state.slashings()[i]; + var slashings = try state.slashings(); + const slashings_len = @TypeOf(slashings).length; + for (0..slashings_len) |i| { + const slashing = try slashings.get(i); total_slashings_by_increment += @divFloor(slashing, preset.EFFECTIVE_BALANCE_INCREMENT); } diff --git a/src/state_transition/epoch/process_slashings_reset.zig b/src/state_transition/epoch/process_slashings_reset.zig index 72bd6aefd..97334c268 100644 --- a/src/state_transition/epoch/process_slashings_reset.zig +++ b/src/state_transition/epoch/process_slashings_reset.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const types = @import("consensus_types"); @@ -7,16 +7,16 @@ const preset = @import("preset").preset; /// Resets slashings for the next epoch. /// PERF: Almost no (constant) cost -pub fn processSlashingsReset(cached_state: *CachedBeaconStateAllForks, cache: *const EpochTransitionCache) void { +pub fn processSlashingsReset(cached_state: *CachedBeaconState, cache: *const EpochTransitionCache) !void { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const next_epoch = cache.current_epoch + 1; // reset slashings const slash_index = next_epoch % preset.EPOCHS_PER_SLASHINGS_VECTOR; - const slashings = state.slashings(); - const slashing = slashings[slash_index]; + var slashings = try state.slashings(); + const slashing = try slashings.get(slash_index); const old_slashing_value_by_increment = slashing / preset.EFFECTIVE_BALANCE_INCREMENT; - slashings[slash_index] = 0; + try slashings.set(slash_index, 0); epoch_cache.total_slashings_by_increment = @max(0, epoch_cache.total_slashings_by_increment - old_slashing_value_by_increment); } diff --git a/src/state_transition/epoch/process_sync_committee_updates.zig b/src/state_transition/epoch/process_sync_committee_updates.zig index 95c4ebc98..b58377e40 100644 --- a/src/state_transition/epoch/process_sync_committee_updates.zig +++ b/src/state_transition/epoch/process_sync_committee_updates.zig @@ -1,45 +1,30 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ForkSeq = @import("config").ForkSeq; const EpochTransitionCache = @import("../cache/epoch_transition_cache.zig").EpochTransitionCache; const types = @import("consensus_types"); const preset = @import("preset").preset; -const ValidatorIndex = types.primitive.ValidatorIndex.Type; -const BLSPubkey = types.primitive.BLSPubkey.Type; -const getNextSyncCommitteeIndices = @import("../utils/sync_committee.zig").getNextSyncCommitteeIndices; -const blst = @import("blst"); +const getNextSyncCommittee = @import("../utils/sync_committee.zig").getNextSyncCommittee; +const SyncCommitteeInfo = @import("../utils/sync_committee.zig").SyncCommitteeInfo; -pub fn processSyncCommitteeUpdates(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { +pub fn processSyncCommitteeUpdates(allocator: Allocator, cached_state: *CachedBeaconState) !void { const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const next_epoch = epoch_cache.epoch + 1; if (next_epoch % preset.EPOCHS_PER_SYNC_COMMITTEE_PERIOD == 0) { const active_validator_indices = epoch_cache.getNextEpochShuffling().active_indices; const effective_balance_increments = epoch_cache.getEffectiveBalanceIncrements(); - var next_sync_committee_indices: [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex = undefined; - try getNextSyncCommitteeIndices(allocator, state, active_validator_indices, effective_balance_increments, &next_sync_committee_indices); - const validators = state.validators(); - // Using the index2pubkey cache is slower because it needs the serialized pubkey. - var next_sync_committee_pubkeys: [preset.SYNC_COMMITTEE_SIZE]BLSPubkey = undefined; - var next_sync_committee_pubkeys_slices: [preset.SYNC_COMMITTEE_SIZE]blst.PublicKey = undefined; - for (next_sync_committee_indices, 0..next_sync_committee_indices.len) |index, i| { - next_sync_committee_pubkeys[i] = validators.items[index].pubkey; - next_sync_committee_pubkeys_slices[i] = try blst.PublicKey.uncompress(&next_sync_committee_pubkeys[i]); - } + // Compute next + var next_sync_committee_info: SyncCommitteeInfo = undefined; + try getNextSyncCommittee(allocator, state, active_validator_indices, effective_balance_increments, &next_sync_committee_info); - const current_sync_committee = state.currentSyncCommittee(); - const next_sync_committee = state.nextSyncCommittee(); - current_sync_committee.* = next_sync_committee.*; // Rotate syncCommittee in state - next_sync_committee.* = .{ - .pubkeys = next_sync_committee_pubkeys, - .aggregate_pubkey = (try blst.AggregatePublicKey.aggregate(&next_sync_committee_pubkeys_slices, false)).toPublicKey().compress(), - }; + try state.rotateSyncCommittees(&next_sync_committee_info.sync_committee); // Rotate syncCommittee cache // next_sync_committee_indices ownership is transferred to epoch_cache - try epoch_cache.rotateSyncCommitteeIndexed(allocator, &next_sync_committee_indices); + try epoch_cache.rotateSyncCommitteeIndexed(allocator, &next_sync_committee_info.indices); } } diff --git a/src/state_transition/root.zig b/src/state_transition/root.zig index e5a398ef2..712bcd2e4 100644 --- a/src/state_transition/root.zig +++ b/src/state_transition/root.zig @@ -4,8 +4,8 @@ const testing = std.testing; pub const computeSigningRoot = @import("./utils/signing_root.zig").computeSigningRoot; pub const BeaconBlock = @import("./types/beacon_block.zig").BeaconBlock; pub const BeaconBlockBody = @import("./types/beacon_block.zig").BeaconBlockBody; -pub const BeaconStateAllForks = @import("./types/beacon_state.zig").BeaconStateAllForks; -pub const CachedBeaconStateAllForks = @import("./cache/state_cache.zig").CachedBeaconStateAllForks; +pub const BeaconState = @import("./types/beacon_state.zig").BeaconState; +pub const CachedBeaconState = @import("./cache/state_cache.zig").CachedBeaconState; pub const EffectiveBalanceIncrements = @import("./cache/effective_balance_increments.zig").EffectiveBalanceIncrements; pub const EpochCacheImmutableData = @import("./cache/epoch_cache.zig").EpochCacheImmutableData; diff --git a/src/state_transition/signature_sets/block.zig b/src/state_transition/signature_sets/block.zig index 1bbe90b8a..64e5f4883 100644 --- a/src/state_transition/signature_sets/block.zig +++ b/src/state_transition/signature_sets/block.zig @@ -2,15 +2,15 @@ const std = @import("std"); const types = @import("consensus_types"); const SingleSignatureSet = @import("../utils/signature_sets.zig").SingleSignatureSet; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const SignedBlock = @import("../types/block.zig").SignedBlock; const SignedBeaconBlock = @import("../state_transition.zig").SignedBeaconBlock; -const TestCachedBeaconStateAllForks = @import("../test_utils/root.zig").TestCachedBeaconStateAllForks; +const TestCachedBeaconState = @import("../test_utils/root.zig").TestCachedBeaconState; const randaoRevealSignatureSet = @import("./randao.zig").randaoRevealSignatureSet; const proposerSlashingsSignatureSets = @import("./proposer_slashings.zig").proposerSlashingsSignatureSets; pub fn blockSignatureSets( - state: CachedBeaconStateAllForks, + state: CachedBeaconState, signed_block: SignedBlock, ) SingleSignatureSet { _ = state; @@ -20,7 +20,7 @@ pub fn blockSignatureSets( test "blockSignatureSets" { const allocator = std.testing.allocator; const validator_count = 256; - const test_state = try TestCachedBeaconStateAllForks.init(allocator, validator_count); + const test_state = try TestCachedBeaconState.init(allocator, validator_count); const signature_sets = try std.ArrayList(SingleSignatureSet).init(allocator); diff --git a/src/state_transition/signature_sets/bls_to_execution_change.zig b/src/state_transition/signature_sets/bls_to_execution_change.zig index 6a474c896..f84aa5c9f 100644 --- a/src/state_transition/signature_sets/bls_to_execution_change.zig +++ b/src/state_transition/signature_sets/bls_to_execution_change.zig @@ -1,6 +1,6 @@ const std = @import("std"); const types = @import("consensus_types"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const SignedBLSToExecutionChange = types.capella.SignedBLSToExecutionChange.Type; const BeaconConfig = @import("config").BeaconConfig; const SingleSignatureSet = @import("../utils/signature_sets.zig").SingleSignatureSet; @@ -12,7 +12,7 @@ const Root = types.primitive.Root.Type; const SignedBeaconBlock = @import("../types/beacon_block.zig").SignedBeaconBlock; const verifySingleSignatureSet = @import("../utils/signature_sets.zig").verifySingleSignatureSet; -pub fn verifyBlsToExecutionChangeSignature(cached_state: *const CachedBeaconStateAllForks, signed_bls_to_execution_change: *const SignedBLSToExecutionChange) !bool { +pub fn verifyBlsToExecutionChangeSignature(cached_state: *const CachedBeaconState, signed_bls_to_execution_change: *const SignedBLSToExecutionChange) !bool { const config = cached_state.config; const signature_set = try getBlsToExecutionChangeSignatureSet(config, signed_bls_to_execution_change); return verifySingleSignatureSet(&signature_set); diff --git a/src/state_transition/signature_sets/indexed_attestation.zig b/src/state_transition/signature_sets/indexed_attestation.zig index 1efc402c3..e32c89725 100644 --- a/src/state_transition/signature_sets/indexed_attestation.zig +++ b/src/state_transition/signature_sets/indexed_attestation.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const blst = @import("blst"); const PublicKey = blst.PublicKey; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const BeaconBlock = @import("../types/beacon_block.zig").BeaconBlock; const SignedBeaconBlock = @import("../types/beacon_block.zig").SignedBeaconBlock; const c = @import("constants"); @@ -17,11 +17,11 @@ const AggregatedSignatureSet = @import("../utils/signature_sets.zig").Aggregated const createAggregateSignatureSetFromComponents = @import("../utils/signature_sets.zig").createAggregateSignatureSetFromComponents; const IndexedAttestation = @import("../types/attestation.zig").IndexedAttestation; -pub fn getAttestationDataSigningRoot(cached_state: *const CachedBeaconStateAllForks, data: *const AttestationData, out: *[32]u8) !void { +pub fn getAttestationDataSigningRoot(cached_state: *const CachedBeaconState, data: *const AttestationData, out: *[32]u8) !void { const slot = computeStartSlotAtEpoch(data.target.epoch); const config = cached_state.config; const state = cached_state.state; - const domain = try config.getDomain(state.slot(), c.DOMAIN_BEACON_ATTESTER, slot); + const domain = try config.getDomain(try state.slot(), c.DOMAIN_BEACON_ATTESTER, slot); try computeSigningRoot(types.phase0.AttestationData, data, domain, out); } @@ -29,7 +29,7 @@ pub fn getAttestationDataSigningRoot(cached_state: *const CachedBeaconStateAllFo /// Consumer need to free the returned pubkeys array pub fn getAttestationWithIndicesSignatureSet( allocator: Allocator, - cached_state: *const CachedBeaconStateAllForks, + cached_state: *const CachedBeaconState, data: *const AttestationData, signature: BLSSignature, attesting_indices: []u64, @@ -48,14 +48,14 @@ pub fn getAttestationWithIndicesSignatureSet( } /// Consumer need to free the returned pubkeys array -pub fn getIndexedAttestationSignatureSet(comptime IA: type, allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, indexed_attestation: *const IA) !AggregatedSignatureSet { +pub fn getIndexedAttestationSignatureSet(comptime IA: type, allocator: Allocator, cached_state: *const CachedBeaconState, indexed_attestation: *const IA) !AggregatedSignatureSet { return try getAttestationWithIndicesSignatureSet(allocator, cached_state, &indexed_attestation.data, indexed_attestation.signature, indexed_attestation.attesting_indices.items); } /// Appends to out all the AggregatedSignatureSet for each attestation in the signed_block /// Consumer need to free the pubkeys arrays in each AggregatedSignatureSet in out /// TODO: consume in https://github.com/ChainSafe/state-transition-z/issues/72 -pub fn attestationsSignatureSets(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, signed_block: *const SignedBeaconBlock, out: std.ArrayList(AggregatedSignatureSet)) !void { +pub fn attestationsSignatureSets(allocator: Allocator, cached_state: *const CachedBeaconState, signed_block: *const SignedBeaconBlock, out: std.ArrayList(AggregatedSignatureSet)) !void { const epoch_cache = cached_state.getEpochCache(); const attestation_items = signed_block.beaconBlock().beaconBlockBody().attestations().items(); diff --git a/src/state_transition/signature_sets/proposer.zig b/src/state_transition/signature_sets/proposer.zig index a33208ab2..ead206cea 100644 --- a/src/state_transition/signature_sets/proposer.zig +++ b/src/state_transition/signature_sets/proposer.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const SignedBeaconBlock = @import("../types/beacon_block.zig").SignedBeaconBlock; const SingleSignatureSet = @import("../utils/signature_sets.zig").SingleSignatureSet; const c = @import("constants"); @@ -11,18 +11,18 @@ const computeSigningRoot = @import("../utils/signing_root.zig").computeSigningRo const verifySignatureSet = @import("../utils/signature_sets.zig").verifySingleSignatureSet; const SignedBlock = @import("../types/block.zig").SignedBlock; -pub fn verifyProposerSignature(cached_state: *CachedBeaconStateAllForks, signed_block: SignedBlock) !bool { +pub fn verifyProposerSignature(cached_state: *CachedBeaconState, signed_block: SignedBlock) !bool { const signature_set = try getBlockProposerSignatureSet(cached_state.allocator, cached_state, signed_block); return try verifySignatureSet(&signature_set); } // TODO: support SignedBlindedBeaconBlock -pub fn getBlockProposerSignatureSet(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, signed_block: SignedBlock) !SingleSignatureSet { +pub fn getBlockProposerSignatureSet(allocator: Allocator, cached_state: *CachedBeaconState, signed_block: SignedBlock) !SingleSignatureSet { const config = cached_state.config; const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const block = signed_block.message(); - const domain = try config.getDomain(state.slot(), c.DOMAIN_BEACON_PROPOSER, block.slot()); + const domain = try config.getDomain(try state.slot(), c.DOMAIN_BEACON_PROPOSER, block.slot()); // var signing_root: Root = undefined; var signing_root_buf: [32]u8 = undefined; try computeBlockSigningRoot(allocator, block, domain, &signing_root_buf); @@ -35,7 +35,7 @@ pub fn getBlockProposerSignatureSet(allocator: Allocator, cached_state: *CachedB }; } -pub fn getBlockHeaderProposerSignatureSet(cached_state: *const CachedBeaconStateAllForks, signed_block_header: *const types.phase0.SignedBeaconBlockHeader.Type) SingleSignatureSet { +pub fn getBlockHeaderProposerSignatureSet(cached_state: *const CachedBeaconState, signed_block_header: *const types.phase0.SignedBeaconBlockHeader.Type) SingleSignatureSet { const config = cached_state.config; const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); diff --git a/src/state_transition/signature_sets/proposer_slashings.zig b/src/state_transition/signature_sets/proposer_slashings.zig index 0a36655ac..6d18599ba 100644 --- a/src/state_transition/signature_sets/proposer_slashings.zig +++ b/src/state_transition/signature_sets/proposer_slashings.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const SignedBeaconBlock = @import("../types/beacon_block.zig").SignedBeaconBlock; const SingleSignatureSet = @import("../utils/signature_sets.zig").SingleSignatureSet; const c = @import("constants"); @@ -10,7 +10,7 @@ const computeBlockSigningRoot = @import("../utils/signing_root.zig").computeBloc const computeSigningRoot = @import("../utils/signing_root.zig").computeSigningRoot; const verifySignatureSet = @import("../utils/signature_sets.zig").verifySingleSignatureSet; -pub fn getProposerSlashingSignatureSets(cached_state: *const CachedBeaconStateAllForks, proposer_slashing: *const types.phase0.ProposerSlashing.Type) ![2]SingleSignatureSet { +pub fn getProposerSlashingSignatureSets(cached_state: *const CachedBeaconState, proposer_slashing: *const types.phase0.ProposerSlashing.Type) ![2]SingleSignatureSet { const config = cached_state.config; const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); @@ -20,8 +20,8 @@ pub fn getProposerSlashingSignatureSets(cached_state: *const CachedBeaconStateAl // In state transition, ProposerSlashing headers are only partially validated. Their slot could be higher than the // clock and the slashing would still be valid. Must use bigint variants to hash correctly to all possible values var result: [2]SingleSignatureSet = undefined; - const domain_1 = try config.getDomain(state.slot(), c.DOMAIN_BEACON_PROPOSER, signed_header_1.message.slot); - const domain_2 = try config.getDomain(state.slot(), c.DOMAIN_BEACON_PROPOSER, signed_header_2.message.slot); + const domain_1 = try config.getDomain(try state.slot(), c.DOMAIN_BEACON_PROPOSER, signed_header_1.message.slot); + const domain_2 = try config.getDomain(try state.slot(), c.DOMAIN_BEACON_PROPOSER, signed_header_2.message.slot); var signing_root_1: [32]u8 = undefined; try computeSigningRoot(types.phase0.BeaconBlockHeader, &signed_header_1.message, domain_1, &signing_root_1); var signing_root_2: [32]u8 = undefined; @@ -42,10 +42,10 @@ pub fn getProposerSlashingSignatureSets(cached_state: *const CachedBeaconStateAl return result; } -pub fn proposerSlashingsSignatureSets(cached_state: *const CachedBeaconStateAllForks, signed_block: *const SignedBeaconBlock, out: std.ArrayList(SingleSignatureSet)) !void { +pub fn proposerSlashingsSignatureSets(cached_state: *const CachedBeaconState, signed_block: *const SignedBeaconBlock, out: std.ArrayList(SingleSignatureSet)) !void { const proposer_slashings = signed_block.beaconBlock().beaconBlockBody().proposerSlashings().items; for (proposer_slashings) |proposer_slashing| { - const signature_sets = getProposerSlashingSignatureSets(cached_state, proposer_slashing); + const signature_sets = try getProposerSlashingSignatureSets(cached_state, proposer_slashing); try out.append(signature_sets[0]); try out.append(signature_sets[1]); } diff --git a/src/state_transition/signature_sets/randao.zig b/src/state_transition/signature_sets/randao.zig index f60333e6b..d1f687904 100644 --- a/src/state_transition/signature_sets/randao.zig +++ b/src/state_transition/signature_sets/randao.zig @@ -1,6 +1,6 @@ const types = @import("consensus_types"); const Slot = types.primitive.Slot.Type; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const Root = types.primitive.Root.Type; const Epoch = types.primitive.Epoch.Type; const Body = @import("../types/block.zig").Body; @@ -11,7 +11,7 @@ const computeSigningRoot = @import("../utils/signing_root.zig").computeSigningRo const verifySingleSignatureSet = @import("../utils/signature_sets.zig").verifySingleSignatureSet; pub fn verifyRandaoSignature( - state: *const CachedBeaconStateAllForks, + state: *const CachedBeaconState, body: Body, slot: Slot, proposer_idx: u64, @@ -21,7 +21,7 @@ pub fn verifyRandaoSignature( } pub fn randaoRevealSignatureSet( - cached_state: *const CachedBeaconStateAllForks, + cached_state: *const CachedBeaconState, body: Body, slot: Slot, proposer_idx: u64, @@ -32,7 +32,7 @@ pub fn randaoRevealSignatureSet( // should not get epoch from epoch_cache const epoch = computeEpochAtSlot(slot); - const domain = try config.getDomain(state.slot(), c.DOMAIN_RANDAO, slot); + const domain = try config.getDomain(try state.slot(), c.DOMAIN_RANDAO, slot); var signing_root: Root = undefined; try computeSigningRoot(types.primitive.Epoch, &epoch, domain, &signing_root); return .{ diff --git a/src/state_transition/signature_sets/voluntary_exits.zig b/src/state_transition/signature_sets/voluntary_exits.zig index b04c79111..00b8667db 100644 --- a/src/state_transition/signature_sets/voluntary_exits.zig +++ b/src/state_transition/signature_sets/voluntary_exits.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const SignedBeaconBlock = @import("../types/beacon_block.zig").SignedBeaconBlock; const SingleSignatureSet = @import("../utils/signature_sets.zig").SingleSignatureSet; const types = @import("consensus_types"); @@ -10,18 +10,18 @@ const computeStartSlotAtEpoch = @import("../utils/epoch.zig").computeStartSlotAt const computeSigningRoot = @import("../utils/signing_root.zig").computeSigningRoot; const verifySingleSignatureSet = @import("../utils/signature_sets.zig").verifySingleSignatureSet; -pub fn verifyVoluntaryExitSignature(cached_state: *const CachedBeaconStateAllForks, signed_voluntary_exit: *const SignedVoluntaryExit) !bool { +pub fn verifyVoluntaryExitSignature(cached_state: *const CachedBeaconState, signed_voluntary_exit: *const SignedVoluntaryExit) !bool { const signature_set = try getVoluntaryExitSignatureSet(cached_state, signed_voluntary_exit); return try verifySingleSignatureSet(&signature_set); } -pub fn getVoluntaryExitSignatureSet(cached_state: *const CachedBeaconStateAllForks, signed_voluntary_exit: *const SignedVoluntaryExit) !SingleSignatureSet { +pub fn getVoluntaryExitSignatureSet(cached_state: *const CachedBeaconState, signed_voluntary_exit: *const SignedVoluntaryExit) !SingleSignatureSet { const config = cached_state.config; const state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); const slot = computeStartSlotAtEpoch(signed_voluntary_exit.message.epoch); - const domain = try config.getDomainForVoluntaryExit(state.slot(), slot); + const domain = try config.getDomainForVoluntaryExit(try state.slot(), slot); var signing_root: [32]u8 = undefined; try computeSigningRoot(types.phase0.VoluntaryExit, &signed_voluntary_exit.message, domain, &signing_root); @@ -32,10 +32,10 @@ pub fn getVoluntaryExitSignatureSet(cached_state: *const CachedBeaconStateAllFor }; } -pub fn voluntaryExitsSignatureSets(cached_state: *const CachedBeaconStateAllForks, signed_block: *const SignedBeaconBlock, out: std.ArrayList(SingleSignatureSet)) !void { +pub fn voluntaryExitsSignatureSets(cached_state: *const CachedBeaconState, signed_block: *const SignedBeaconBlock, out: std.ArrayList(SingleSignatureSet)) !void { const voluntary_exits = signed_block.beaconBlock().beaconBlockBody().voluntaryExits().items; for (voluntary_exits) |signed_voluntary_exit| { - const signature_set = getVoluntaryExitSignatureSet(cached_state, &signed_voluntary_exit); + const signature_set = try getVoluntaryExitSignatureSet(cached_state, &signed_voluntary_exit); try out.append(signature_set); } } diff --git a/src/state_transition/slot/process_slot.zig b/src/state_transition/slot/process_slot.zig index 53b384b0d..7247aa497 100644 --- a/src/state_transition/slot/process_slot.zig +++ b/src/state_transition/slot/process_slot.zig @@ -1,29 +1,30 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const preset = @import("preset").preset; const Root = types.primitive.Root.Type; const ZERO_HASH = @import("constants").ZERO_HASH; -pub fn processSlot(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { +pub fn processSlot(cached_state: *CachedBeaconState) !void { const state = cached_state.state; // Cache state root - var previous_state_root: Root = undefined; - try state.hashTreeRoot(allocator, &previous_state_root); - const state_roots = state.stateRoots(); - @memcpy(state_roots[state.slot() % preset.SLOTS_PER_HISTORICAL_ROOT][0..], previous_state_root[0..]); + const previous_state_root = try state.hashTreeRoot(); + var state_roots = try state.stateRoots(); + try state_roots.setValue(try state.slot() % preset.SLOTS_PER_HISTORICAL_ROOT, previous_state_root); // Cache latest block header state root - var latest_block_header = state.latestBlockHeader(); - if (std.mem.eql(u8, &latest_block_header.state_root, &ZERO_HASH)) { - latest_block_header.state_root = previous_state_root; + var latest_block_header = try state.latestBlockHeader(); + var latest_header_state_root: [32]u8 = undefined; + try latest_block_header.getValue(cached_state.allocator, "state_root", &latest_header_state_root); + + if (std.mem.eql(u8, latest_header_state_root[0..], ZERO_HASH[0..])) { + try latest_block_header.setValue("state_root", previous_state_root); } // Cache block root - var previous_block_root: Root = undefined; - try types.phase0.BeaconBlockHeader.hashTreeRoot(latest_block_header, &previous_block_root); - const block_roots = state.blockRoots(); - @memcpy(block_roots[state.slot() % preset.SLOTS_PER_HISTORICAL_ROOT][0..], previous_block_root[0..]); + const previous_block_root = try latest_block_header.hashTreeRoot(); + var block_roots = try state.blockRoots(); + try block_roots.setValue(try state.slot() % preset.SLOTS_PER_HISTORICAL_ROOT, previous_block_root[0..]); } diff --git a/src/state_transition/slot/upgrade_state_to_altair.zig b/src/state_transition/slot/upgrade_state_to_altair.zig index 923c0a16f..4d237e5fb 100644 --- a/src/state_transition/slot/upgrade_state_to_altair.zig +++ b/src/state_transition/slot/upgrade_state_to_altair.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; 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; @@ -11,64 +11,86 @@ const ValidatorIndex = types.primitive.ValidatorIndex.Type; const RootCache = @import("../utils/root_cache.zig").RootCache; const getAttestationParticipationStatus = @import("../block//process_attestation_altair.zig").getAttestationParticipationStatus; -pub fn upgradeStateToAltair(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { - var state = cached_state.state; - if (!state.isPhase0()) { +pub fn upgradeStateToAltair(allocator: Allocator, cached_state: *CachedBeaconState) !void { + var phase0_state = cached_state.state; + if (phase0_state.forkSeq() != .phase0) { // already altair return error.StateIsNotPhase0; } - const phase0_state = state.phase0; - defer { - types.phase0.BeaconState.deinit(allocator, phase0_state); - allocator.destroy(phase0_state); - } - _ = try state.upgradeUnsafe(allocator); - state.forkPtr().* = .{ - .previous_version = phase0_state.fork.current_version, + var altair_state = try phase0_state.upgradeUnsafe(); + errdefer altair_state.deinit(); + + const new_fork: types.altair.Fork.Type = .{ + .previous_version = try phase0_state.forkCurrentVersion(), .current_version = cached_state.config.chain.ALTAIR_FORK_VERSION, .epoch = cached_state.getEpochCache().epoch, }; - const validator_count = state.validators().items.len; - const previous_epoch_participations = state.previousEpochParticipations(); - try previous_epoch_participations.resize(allocator, validator_count); - @memset(previous_epoch_participations.items, 0); + try altair_state.setFork(&new_fork); + + const validator_count = try altair_state.validatorsCount(); + var previous_epoch_participations = try altair_state.previousEpochParticipation(); + try previous_epoch_participations.setLength(validator_count); - const current_epoch_participations = state.currentEpochParticipations(); - try state.currentEpochParticipations().resize(allocator, validator_count); - @memset(current_epoch_participations.items, 0); + var current_epoch_participations = try altair_state.currentEpochParticipation(); + try current_epoch_participations.setLength(validator_count); - const inactivity_scores = state.inactivityScores(); - try inactivity_scores.resize(allocator, validator_count); - @memset(inactivity_scores.items, 0); + var inactivity_scores = try altair_state.inactivityScores(); + try inactivity_scores.setLength(validator_count); const epoch_cache = cached_state.getEpochCache(); const active_indices = epoch_cache.next_shuffling.get().active_indices; + var sync_committee_info: SyncCommitteeInfo = undefined; - try getNextSyncCommittee(allocator, state, active_indices, epoch_cache.getEffectiveBalanceIncrements(), &sync_committee_info); - defer sync_committee_info.deinit(allocator); - state.currentSyncCommittee().* = sync_committee_info.sync_committee.*; - state.nextSyncCommittee().* = sync_committee_info.sync_committee.*; + try getNextSyncCommittee(allocator, &altair_state, active_indices, epoch_cache.getEffectiveBalanceIncrements(), &sync_committee_info); + + try altair_state.setCurrentSyncCommittee(&sync_committee_info.sync_committee); + try altair_state.setNextSyncCommittee(&sync_committee_info.sync_committee); + + try cached_state.epoch_cache_ref.get().setSyncCommitteesIndexed(&sync_committee_info.indices); + + var previous_epoch_participation = try translateParticipation(allocator, cached_state, try phase0_state.previousEpochPendingAttestations()); + defer previous_epoch_participation.deinit(allocator); + try altair_state.setPreviousEpochParticipation(&previous_epoch_participation); - try cached_state.epoch_cache_ref.get().setSyncCommitteesIndexed(sync_committee_info.indices.items); - try translateParticipation(allocator, cached_state, phase0_state.previous_epoch_attestations); + var current_epoch_participation = try translateParticipation(allocator, cached_state, try phase0_state.currentEpochPendingAttestations()); + defer current_epoch_participation.deinit(allocator); + try altair_state.setCurrentEpochParticipation(¤t_epoch_participation); const previous_epoch = computePreviousEpoch(epoch_cache.epoch); - epoch_cache.previous_target_unslashed_balance_increments = sumTargetUnslashedBalanceIncrements(state.previousEpochParticipations().items, previous_epoch, state.validators().items); + try altair_state.commit(); + const validators = try altair_state.validatorsSlice(allocator); + defer allocator.free(validators); + epoch_cache.previous_target_unslashed_balance_increments = sumTargetUnslashedBalanceIncrements(previous_epoch_participation.items, previous_epoch, validators); + epoch_cache.current_target_unslashed_balance_increments = sumTargetUnslashedBalanceIncrements(current_epoch_participation.items, epoch_cache.epoch, validators); + + phase0_state.deinit(); + cached_state.state.* = altair_state; } /// Translate_participation in https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/fork.md -fn translateParticipation(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, pending_attestations: types.phase0.EpochAttestations.Type) !void { +/// Caller must free returned value +fn translateParticipation(allocator: Allocator, cached_state: *CachedBeaconState, pending_attestations_tree: types.phase0.EpochAttestations.TreeView) !types.altair.EpochParticipation.Type { const epoch_cache = cached_state.getEpochCache(); const root_cache = try RootCache.init(allocator, cached_state); defer root_cache.deinit(); - const state = cached_state.state; - const epoch_participation = state.previousEpochParticipations(); - try epoch_participation.resize(allocator, state.validators().items.len); + + const pending_attestations = try @constCast(&pending_attestations_tree).getAllReadonlyValues(allocator); + defer { + for (pending_attestations) |*attestation| { + types.phase0.PendingAttestation.deinit(allocator, attestation); + } + allocator.free(pending_attestations); + } + + // translate all participations into a flat array, then convert to tree view the end + var epoch_participation = types.altair.EpochParticipation.default_value; + const validator_count = try cached_state.state.validatorsCount(); + try epoch_participation.resize(allocator, validator_count); @memset(epoch_participation.items, 0); - for (pending_attestations.items) |*attestation| { - const data = attestation.data; + for (pending_attestations) |*attestation| { + const data = &attestation.data; const attestation_flag = try getAttestationParticipationStatus(cached_state.state, data, attestation.inclusion_delay, epoch_cache.epoch, root_cache); const committee_indices = try epoch_cache.getBeaconCommittee(data.slot, data.index); const attesting_indices = try attestation.aggregation_bits.intersectValues(ValidatorIndex, allocator, committee_indices); @@ -77,4 +99,6 @@ fn translateParticipation(allocator: Allocator, cached_state: *CachedBeaconState epoch_participation.items[validator_index] |= attestation_flag; } } + + return epoch_participation; } diff --git a/src/state_transition/slot/upgrade_state_to_bellatrix.zig b/src/state_transition/slot/upgrade_state_to_bellatrix.zig index d468728be..9aaaa0ec7 100644 --- a/src/state_transition/slot/upgrade_state_to_bellatrix.zig +++ b/src/state_transition/slot/upgrade_state_to_bellatrix.zig @@ -1,11 +1,11 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ssz = @import("consensus_types"); +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const ct = @import("consensus_types"); -pub fn upgradeStateToBellatrix(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { - var state = cached_state.state; - if (!state.isAltair()) { +pub fn upgradeStateToBellatrix(_: Allocator, cached_state: *CachedBeaconState) !void { + var altair_state = cached_state.state; + if (altair_state.forkSeq() != .altair) { return error.StateIsNotAltair; } @@ -42,16 +42,16 @@ pub fn upgradeStateToBellatrix(allocator: Allocator, cached_state: *CachedBeacon // next_sync_committee | - | next_sync_committee // - | new | latest_execution_payload_header - const altair_state = state.altair; - defer { - ssz.altair.BeaconState.deinit(allocator, altair_state); - allocator.destroy(altair_state); - } + var state = try altair_state.upgradeUnsafe(); + errdefer state.deinit(); - _ = try state.upgradeUnsafe(allocator); - state.forkPtr().* = .{ - .previous_version = altair_state.fork.current_version, + const new_fork: ct.phase0.Fork.Type = .{ + .previous_version = try altair_state.forkCurrentVersion(), .current_version = cached_state.config.chain.BELLATRIX_FORK_VERSION, .epoch = cached_state.getEpochCache().epoch, }; + try state.setFork(&new_fork); + + altair_state.deinit(); + cached_state.state.* = state; } diff --git a/src/state_transition/slot/upgrade_state_to_capella.zig b/src/state_transition/slot/upgrade_state_to_capella.zig index 739749d35..a987bb7ad 100644 --- a/src/state_transition/slot/upgrade_state_to_capella.zig +++ b/src/state_transition/slot/upgrade_state_to_capella.zig @@ -1,9 +1,10 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ssz = @import("consensus_types"); +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const ct = @import("consensus_types"); +const ExecutionPayloadHeader = @import("../types/execution_payload.zig").ExecutionPayloadHeader; -pub fn upgradeStateToCapella(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { +pub fn upgradeStateToCapella(allocator: Allocator, cached_state: *CachedBeaconState) !void { // Get underlying node and cast bellatrix tree to capella tree // // An bellatrix BeaconState tree can be safely casted to a capella BeaconState tree because: @@ -41,30 +42,39 @@ pub fn upgradeStateToCapella(allocator: Allocator, cached_state: *CachedBeaconSt // - | new | next_withdrawal_validator_index // - | new | historical_summaries - var state = cached_state.state; - if (!state.isBellatrix()) { + var bellatrix_state = cached_state.state; + if (bellatrix_state.forkSeq() != .bellatrix) { return error.StateIsNotBellatrix; } - const bellatrix_state = state.bellatrix; - defer { - ssz.bellatrix.BeaconState.deinit(allocator, bellatrix_state); - allocator.destroy(bellatrix_state); - } - _ = try state.upgradeUnsafe(allocator); - state.forkPtr().* = .{ - .previous_version = bellatrix_state.fork.current_version, + var state = try bellatrix_state.upgradeUnsafe(); + errdefer state.deinit(); + + const new_fork: ct.phase0.Fork.Type = .{ + .previous_version = try bellatrix_state.forkCurrentVersion(), .current_version = cached_state.config.chain.CAPELLA_FORK_VERSION, .epoch = cached_state.getEpochCache().epoch, }; + try state.setFork(&new_fork); - var capella_latest_execution_payload_header = ssz.capella.ExecutionPayloadHeader.default_value; - const bellatrix_latest_execution_payload_header = bellatrix_state.latest_execution_payload_header; - try ssz.bellatrix.ExecutionPayloadHeader.clone(allocator, &bellatrix_latest_execution_payload_header, &capella_latest_execution_payload_header); + var new_latest_execution_payload_header: ExecutionPayloadHeader = .{ .capella = ct.capella.ExecutionPayloadHeader.default_value }; + var bellatrix_latest_execution_payload_header: ExecutionPayloadHeader = undefined; + try bellatrix_state.latestExecutionPayloadHeader(allocator, &bellatrix_latest_execution_payload_header); + defer bellatrix_latest_execution_payload_header.deinit(allocator); + if (bellatrix_latest_execution_payload_header != .bellatrix) { + return error.UnexpectedLatestExecutionPayloadHeaderType; + } + + try ct.bellatrix.ExecutionPayloadHeader.clone( + allocator, + &bellatrix_latest_execution_payload_header.bellatrix, + &new_latest_execution_payload_header.capella, + ); // new in capella - capella_latest_execution_payload_header.withdrawals_root = [_]u8{0} ** 32; + new_latest_execution_payload_header.capella.withdrawals_root = [_]u8{0} ** 32; + + try state.setLatestExecutionPayloadHeader(&new_latest_execution_payload_header); - state.setLatestExecutionPayloadHeader(allocator, .{ - .capella = &capella_latest_execution_payload_header, - }); + bellatrix_state.deinit(); + cached_state.state.* = state; } diff --git a/src/state_transition/slot/upgrade_state_to_deneb.zig b/src/state_transition/slot/upgrade_state_to_deneb.zig index cdc7ec277..abb613b62 100644 --- a/src/state_transition/slot/upgrade_state_to_deneb.zig +++ b/src/state_transition/slot/upgrade_state_to_deneb.zig @@ -1,35 +1,46 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ssz = @import("consensus_types"); +const ExecutionPayloadHeader = @import("../types/execution_payload.zig").ExecutionPayloadHeader; -pub fn upgradeStateToDeneb(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { - var state = cached_state.state; - if (!state.isCapella()) { +pub fn upgradeStateToDeneb(allocator: Allocator, cached_state: *CachedBeaconState) !void { + var capella_state = cached_state.state; + if (capella_state.forkSeq() != .capella) { return error.StateIsNotCapella; } - const capella_state = state.capella; - defer { - ssz.capella.BeaconState.deinit(allocator, capella_state); - allocator.destroy(capella_state); - } - _ = try state.upgradeUnsafe(allocator); - state.forkPtr().* = .{ - .previous_version = capella_state.fork.current_version, + var state = try capella_state.upgradeUnsafe(); + errdefer state.deinit(); + + const new_fork: ssz.phase0.Fork.Type = .{ + .previous_version = try capella_state.forkCurrentVersion(), .current_version = cached_state.config.chain.DENEB_FORK_VERSION, .epoch = cached_state.getEpochCache().epoch, }; + try state.setFork(&new_fork); // ownership is transferred to BeaconState - var deneb_latest_execution_payload_header = ssz.deneb.ExecutionPayloadHeader.default_value; - const capella_latest_execution_payload_header = capella_state.latest_execution_payload_header; - try ssz.capella.ExecutionPayloadHeader.clone(allocator, &capella_latest_execution_payload_header, &deneb_latest_execution_payload_header); - // add excessBlobGas and blobGasUsed to latestExecutionPayloadHeader - deneb_latest_execution_payload_header.excess_blob_gas = 0; - deneb_latest_execution_payload_header.blob_gas_used = 0; - - state.setLatestExecutionPayloadHeader(allocator, .{ - .deneb = &deneb_latest_execution_payload_header, - }); + var new_latest_execution_payload_header: ExecutionPayloadHeader = .{ .deneb = ssz.deneb.ExecutionPayloadHeader.default_value }; + var capella_latest_execution_payload_header: ExecutionPayloadHeader = undefined; + try capella_state.latestExecutionPayloadHeader(allocator, &capella_latest_execution_payload_header); + defer capella_latest_execution_payload_header.deinit(allocator); + if (capella_latest_execution_payload_header != .capella) { + return error.UnexpectedLatestExecutionPayloadHeaderType; + } + + try ssz.capella.ExecutionPayloadHeader.clone( + allocator, + &capella_latest_execution_payload_header.capella, + &new_latest_execution_payload_header.deneb, + ); + + // new in deneb + new_latest_execution_payload_header.deneb.excess_blob_gas = 0; + new_latest_execution_payload_header.deneb.blob_gas_used = 0; + + try state.setLatestExecutionPayloadHeader(&new_latest_execution_payload_header); + + capella_state.deinit(); + cached_state.state.* = state; } diff --git a/src/state_transition/slot/upgrade_state_to_electra.zig b/src/state_transition/slot/upgrade_state_to_electra.zig index e8d279b53..cfb0562f5 100644 --- a/src/state_transition/slot/upgrade_state_to_electra.zig +++ b/src/state_transition/slot/upgrade_state_to_electra.zig @@ -1,8 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ssz = @import("consensus_types"); -const ValidatorIndex = ssz.primitive.ValidatorIndex.Type; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const ct = @import("consensus_types"); +const ValidatorIndex = ct.primitive.ValidatorIndex.Type; const constants = @import("constants"); const computeActivationExitEpoch = @import("../utils/epoch.zig").computeActivationExitEpoch; const getActivationExitChurnLimit = @import("../utils/validator.zig").getActivationExitChurnLimit; @@ -10,35 +10,39 @@ const getConsolidationChurnLimit = @import("../utils/validator.zig").getConsolid const hasCompoundingWithdrawalCredential = @import("../utils/electra.zig").hasCompoundingWithdrawalCredential; const queueExcessActiveBalance = @import("../utils/electra.zig").queueExcessActiveBalance; -pub fn upgradeStateToElectra(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { - var state = cached_state.state; - if (!state.isDeneb()) { +pub fn upgradeStateToElectra(allocator: Allocator, cached_state: *CachedBeaconState) !void { + var deneb_state = cached_state.state.*; + if (deneb_state.forkSeq() != .deneb) { return error.StateIsNotDeneb; } - const deneb_state = state.deneb; - defer { - ssz.deneb.BeaconState.deinit(allocator, deneb_state); - allocator.destroy(deneb_state); + var state = try deneb_state.upgradeUnsafe(); + cached_state.state.* = state; + errdefer { + state.deinit(); + cached_state.state.* = deneb_state; } - _ = try state.upgradeUnsafe(allocator); - state.forkPtr().* = .{ - .previous_version = deneb_state.fork.current_version, + + const new_fork: ct.phase0.Fork.Type = .{ + .previous_version = try deneb_state.forkCurrentVersion(), .current_version = cached_state.config.chain.ELECTRA_FORK_VERSION, .epoch = cached_state.getEpochCache().epoch, }; + try state.setFork(&new_fork); - state.depositRequestsStartIndex().* = constants.UNSET_DEPOSIT_REQUESTS_START_INDEX; - state.depositBalanceToConsume().* = 0; - state.exitBalanceToConsume().* = 0; + try state.setDepositRequestsStartIndex(constants.UNSET_DEPOSIT_REQUESTS_START_INDEX); + // default values are already 0, don't need to be set explicitly + // try state.setDepositBalanceToConsume(0); + // try state.setExitBalanceToConsume(0); const current_epoch_pre = cached_state.getEpochCache().epoch; var earliest_exit_epoch = computeActivationExitEpoch(current_epoch_pre); // [EIP-7251]: add validators that are not yet active to pending balance deposits - var pre_activation = std.ArrayList(ssz.primitive.ValidatorIndex.Type).init(allocator); + var pre_activation = std.ArrayList(ct.primitive.ValidatorIndex.Type).init(allocator); defer pre_activation.deinit(); - const validators = state.validators().items; - for (validators, 0..) |validator, validator_index| { + const validators_slice = try state.validatorsSlice(allocator); + defer allocator.free(validators_slice); + for (validators_slice, 0..) |validator, validator_index| { const activation_epoch = validator.activation_epoch; const exit_epoch = validator.exit_epoch; if (activation_epoch == constants.FAR_FUTURE_EPOCH) { @@ -49,46 +53,51 @@ pub fn upgradeStateToElectra(allocator: Allocator, cached_state: *CachedBeaconSt } } - state.earliestExitEpoch().* = earliest_exit_epoch + 1; - state.earliestConsolidationEpoch().* = computeActivationExitEpoch(current_epoch_pre); - state.exitBalanceToConsume().* = getActivationExitChurnLimit(cached_state.getEpochCache()); - state.consolidationBalanceToConsume().* = getConsolidationChurnLimit(cached_state.getEpochCache()); + try state.setEarliestExitEpoch(earliest_exit_epoch + 1); + try state.setEarliestConsolidationEpoch(computeActivationExitEpoch(current_epoch_pre)); + try state.setExitBalanceToConsume(getActivationExitChurnLimit(cached_state.getEpochCache())); + try state.setConsolidationBalanceToConsume(getConsolidationChurnLimit(cached_state.getEpochCache())); const sort_fn = struct { - pub fn sort(validator_arr: []ssz.phase0.Validator.Type, a: ValidatorIndex, b: ValidatorIndex) bool { + pub fn sort(validator_arr: []const ct.phase0.Validator.Type, a: ValidatorIndex, b: ValidatorIndex) bool { const activation_eligibility_epoch_a = validator_arr[a].activation_eligibility_epoch; const activation_eligibility_epoch_b = validator_arr[b].activation_eligibility_epoch; return if (activation_eligibility_epoch_a != activation_eligibility_epoch_b) activation_eligibility_epoch_a < activation_eligibility_epoch_b else a < b; } }.sort; - std.mem.sort(ValidatorIndex, pre_activation.items, validators, sort_fn); + std.mem.sort(ValidatorIndex, pre_activation.items, validators_slice, sort_fn); // const electra_state = state.electra; - const balances = state.balances().items; + var balances = try state.balances(); + var validators = try state.validators(); const effective_balance_increments = cached_state.getEpochCache().getEffectiveBalanceIncrements(); + var pending_deposits = try state.pendingDeposits(); for (pre_activation.items) |validator_index| { - const balance = balances[validator_index]; - state.balances().items[validator_index] = 0; + const balance = try balances.get(validator_index); + try balances.set(validator_index, 0); - const validator = &state.validators().items[validator_index]; - validator.effective_balance = 0; + var validator = try validators.get(validator_index); + try validator.set("effective_balance", 0); effective_balance_increments.items[validator_index] = 0; - validator.activation_eligibility_epoch = constants.FAR_FUTURE_EPOCH; + try validator.set("activation_eligibility_epoch", constants.FAR_FUTURE_EPOCH); - try state.pendingDeposits().append(allocator, .{ - .pubkey = validator.pubkey, - .withdrawal_credentials = validator.withdrawal_credentials, + const pending_deposit: ct.electra.PendingDeposit.Type = .{ + .pubkey = validators_slice[validator_index].pubkey, + .withdrawal_credentials = validators_slice[validator_index].withdrawal_credentials, .amount = balance, .signature = constants.G2_POINT_AT_INFINITY, .slot = constants.GENESIS_SLOT, - }); + }; + try pending_deposits.pushValue(&pending_deposit); } - for (validators, 0..) |validator, validator_index| { + for (validators_slice, 0..) |validator, validator_index| { // [EIP-7251]: Ensure early adopters of compounding credentials go through the activation churn - const withdrawal_credential = validator.withdrawal_credentials; - if (hasCompoundingWithdrawalCredential(withdrawal_credential)) { - try queueExcessActiveBalance(allocator, cached_state, validator_index); + const withdrawal_credentials = validator.withdrawal_credentials; + if (hasCompoundingWithdrawalCredential(&withdrawal_credentials)) { + try queueExcessActiveBalance(cached_state, validator_index, &withdrawal_credentials, validator.pubkey); } } + + deneb_state.deinit(); } diff --git a/src/state_transition/slot/upgrade_state_to_fulu.zig b/src/state_transition/slot/upgrade_state_to_fulu.zig index ec3e45383..cae1881bc 100644 --- a/src/state_transition/slot/upgrade_state_to_fulu.zig +++ b/src/state_transition/slot/upgrade_state_to_fulu.zig @@ -1,34 +1,33 @@ const Allocator = @import("std").mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const ssz = @import("consensus_types"); +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const ct = @import("consensus_types"); const initializeProposerLookahead = @import("../utils/process_proposer_lookahead.zig").initializeProposerLookahead; -pub fn upgradeStateToFulu(allocator: Allocator, cached_state: *CachedBeaconStateAllForks) !void { - var state = cached_state.state; - if (!state.isElectra()) { +pub fn upgradeStateToFulu(allocator: Allocator, cached_state: *CachedBeaconState) !void { + var electra_state = cached_state.state; + if (electra_state.forkSeq() != .electra) { return error.StateIsNotElectra; } - const electra_state = state.electra; - const previous_fork_version = electra_state.fork.current_version; - - defer { - ssz.electra.BeaconState.deinit(allocator, electra_state); - allocator.destroy(electra_state); - } - - _ = try state.upgradeUnsafe(allocator); + var state = try electra_state.upgradeUnsafe(); + errdefer state.deinit(); // Update fork version - state.forkPtr().* = .{ - .previous_version = previous_fork_version, + const new_fork = ct.phase0.Fork.Type{ + .previous_version = try electra_state.forkCurrentVersion(), .current_version = cached_state.config.chain.FULU_FORK_VERSION, .epoch = cached_state.getEpochCache().epoch, }; + try state.setFork(&new_fork); + var proposer_lookahead = ct.fulu.ProposerLookahead.default_value; try initializeProposerLookahead( allocator, cached_state, - &state.fulu.proposer_lookahead, + &proposer_lookahead, ); + try state.setProposerLookahead(&proposer_lookahead); + + electra_state.deinit(); + cached_state.state.* = state; } diff --git a/src/state_transition/state_transition.zig b/src/state_transition/state_transition.zig index a09b789e3..547489d32 100644 --- a/src/state_transition/state_transition.zig +++ b/src/state_transition/state_transition.zig @@ -11,7 +11,7 @@ const ExecutionPayload = @import("types/execution_payload.zig").ExecutionPayload const Slot = types.primitive.Slot.Type; -const CachedBeaconStateAllForks = @import("cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("cache/state_cache.zig").CachedBeaconState; pub const SignedBeaconBlock = @import("types/beacon_block.zig").SignedBeaconBlock; const verifyProposerSignature = @import("./signature_sets/proposer.zig").verifyProposerSignature; pub const processBlock = @import("./block/process_block.zig").processBlock; @@ -56,19 +56,20 @@ pub const BlockExternalData = struct { }, }; -pub fn processSlotsWithTransientCache( +pub fn processSlots( allocator: std.mem.Allocator, - post_state: *CachedBeaconStateAllForks, + post_state: *CachedBeaconState, slot: Slot, _: EpochTransitionCacheOpts, ) !void { var state = post_state.state; - if (state.slot() > slot) return error.outdatedSlot; + if (try state.slot() > slot) return error.outdatedSlot; - while (state.slot() < slot) { - try processSlot(allocator, post_state); + while (try state.slot() < slot) { + try processSlot(post_state); - if ((state.slot() + 1) % preset.SLOTS_PER_EPOCH == 0) { + const next_slot = try state.slot() + 1; + if (next_slot % preset.SLOTS_PER_EPOCH == 0) { // TODO(bing): metrics // const epochTransitionTimer = metrics?.epochTransitionTime.startTimer(); @@ -81,12 +82,12 @@ pub fn processSlotsWithTransientCache( try processEpoch(allocator, post_state, epoch_transition_cache); // TODO(bing): registerValidatorStatuses - state.slotPtr().* += 1; + try state.setSlot(next_slot); try post_state.epoch_cache_ref.get().afterProcessEpoch(post_state, epoch_transition_cache); // post_state.commit - const state_epoch = computeEpochAtSlot(state.slot()); + const state_epoch = computeEpochAtSlot(next_slot); const config = post_state.config; if (state_epoch == config.chain.ALTAIR_FORK_EPOCH) { @@ -110,7 +111,7 @@ pub fn processSlotsWithTransientCache( try post_state.epoch_cache_ref.get().finalProcessEpoch(post_state); } else { - state.slotPtr().* += 1; + try state.setSlot(next_slot); } //epochTransitionTimer @@ -126,17 +127,17 @@ pub const TransitionOpt = struct { pub fn stateTransition( allocator: std.mem.Allocator, - state: *CachedBeaconStateAllForks, + state: *CachedBeaconState, signed_block: SignedBlock, opts: TransitionOpt, -) !*CachedBeaconStateAllForks { +) !*CachedBeaconState { const block = signed_block.message(); const block_slot = switch (block) { .regular => |b| b.slot(), .blinded => |b| b.slot(), }; - const post_state = try state.clone(allocator); + const post_state = try state.clone(allocator, .{ .transfer_cache = !opts.do_not_transfer_cache }); errdefer { post_state.deinit(); @@ -148,7 +149,7 @@ pub fn stateTransition( // onStateCloneMetrics(postState, metrics, StateCloneSource.stateTransition); //} - try processSlotsWithTransientCache(allocator, post_state, block_slot, .{}); + try processSlots(allocator, post_state, block_slot, .{}); // Verify proposer signature only if (opts.verify_proposer and !try verifyProposerSignature(post_state, signed_block)) { @@ -183,20 +184,22 @@ pub fn stateTransition( // Verify state root if (opts.verify_state_root) { - var post_state_root: [32]u8 = undefined; // const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({ // source: StateHashTreeRootSource.stateTransition, // }); - try post_state.state.hashTreeRoot(allocator, &post_state_root); + const post_state_root = try post_state.state.hashTreeRoot(); // hashTreeRootTimer?.(); const block_state_root = switch (block) { .regular => |b| b.stateRoot(), .blinded => |b| b.stateRoot(), }; - if (!std.mem.eql(u8, &post_state_root, &block_state_root)) { + if (!std.mem.eql(u8, post_state_root, &block_state_root)) { return error.InvalidStateRoot; } + } else { + // Even if we don't verify the state_root, commit the tree changes + try post_state.state.commit(); } return post_state; diff --git a/src/state_transition/test_utils/generate_block.zig b/src/state_transition/test_utils/generate_block.zig index b1eb2e389..cc73dab47 100644 --- a/src/state_transition/test_utils/generate_block.zig +++ b/src/state_transition/test_utils/generate_block.zig @@ -8,28 +8,32 @@ const preset = @import("preset").preset; const state_transition = @import("../root.zig"); const Root = types.primitive.Root.Type; const ZERO_HASH = @import("constants").ZERO_HASH; -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; +const CachedBeaconState = state_transition.CachedBeaconState; const computeStartSlotAtEpoch = state_transition.computeStartSlotAtEpoch; const getBlockRootAtSlot = state_transition.getBlockRootAtSlot; /// Generate a valid electra block for the given pre-state. -pub fn generateElectraBlock(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks, out: *types.electra.SignedBeaconBlock.Type) !void { +pub fn generateElectraBlock(allocator: Allocator, cached_state: *CachedBeaconState, out: *types.electra.SignedBeaconBlock.Type) !void { const state = cached_state.state; var attestations = types.electra.Attestations.default_value; // no need to fill up to MAX_ATTESTATIONS_ELECTRA - const att_slot: Slot = state.slot() - 2; + const att_slot: Slot = (try state.slot()) - 2; const att_index = 0; const att_block_root = try getBlockRootAtSlot(state, att_slot); const target_epoch = cached_state.getEpochCache().epoch; const target_epoch_slot = computeStartSlotAtEpoch(target_epoch); + var source_checkpoint: types.phase0.Checkpoint.Type = undefined; + try state.currentJustifiedCheckpoint(&source_checkpoint); + + const att_target_root = try getBlockRootAtSlot(state, target_epoch_slot); const att_data: types.phase0.AttestationData.Type = .{ .slot = att_slot, .index = att_index, - .beacon_block_root = att_block_root, - .source = state.currentJustifiedCheckpoint().*, + .beacon_block_root = att_block_root.*, + .source = source_checkpoint, .target = .{ .epoch = target_epoch, - .root = try getBlockRootAtSlot(state, target_epoch_slot), + .root = att_target_root.*, }, }; const committee_count = try cached_state.getEpochCache().getCommitteeCountPerSlot(target_epoch); @@ -64,7 +68,7 @@ pub fn generateElectraBlock(allocator: Allocator, cached_state: *const CachedBea out.* = .{ .message = .{ - .slot = state.slot() + 1, + .slot = (try state.slot()) + 1, // value is generated after running real state transition int test .proposer_index = 41, .parent_root = try hex.hexToRoot("0x4e647394b6f96c1cd44938483ddf14d89b35d3f67586a59cbfd410a56efbb2b1"), diff --git a/src/state_transition/test_utils/generate_state.zig b/src/state_transition/test_utils/generate_state.zig index 25e99c5fb..d8e5e058d 100644 --- a/src/state_transition/test_utils/generate_state.zig +++ b/src/state_transition/test_utils/generate_state.zig @@ -8,15 +8,17 @@ const types = @import("consensus_types"); const hex = @import("hex"); const Epoch = types.primitive.Epoch.Type; const ElectraBeaconState = types.electra.BeaconState.Type; +const Validator = types.phase0.Validator.Type; const BLSPubkey = types.primitive.BLSPubkey.Type; const ValidatorIndex = types.primitive.ValidatorIndex.Type; const preset = @import("preset").preset; const active_preset = @import("preset").active_preset; const BeaconConfig = @import("config").BeaconConfig; const ChainConfig = @import("config").ChainConfig; +const Node = @import("persistent_merkle_tree").Node; const state_transition = @import("../root.zig"); -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const CachedBeaconState = state_transition.CachedBeaconState; +const BeaconState = state_transition.BeaconState; const PubkeyIndexMap = state_transition.PubkeyIndexMap(ValidatorIndex); const Index2PubkeyCache = state_transition.Index2PubkeyCache; const EffectiveBalanceIncrements = state_transition.EffectiveBalanceIncrements; @@ -27,11 +29,17 @@ const EFFECTIVE_BALANCE_INCREMENT = 32; const EFFECTIVE_BALANCE = 32 * 1e9; const active_chain_config = if (active_preset == .mainnet) mainnet_chain_config else minimal_chain_config; -/// generate, allocate BeaconStateAllForks -/// consumer has responsibility to deinit it -pub fn generateElectraState(allocator: Allocator, chain_config: ChainConfig, validator_count: usize) !*BeaconStateAllForks { +/// generate, allocate BeaconState +/// consumer has responsibility to deinit and destroy it +pub fn generateElectraState(allocator: Allocator, pool: *Node.Pool, chain_config: ChainConfig, validator_count: usize) !*BeaconState { + const beacon_state = try allocator.create(BeaconState); + errdefer allocator.destroy(beacon_state); + const electra_state = try allocator.create(ElectraBeaconState); - errdefer allocator.destroy(electra_state); + defer { + types.electra.BeaconState.deinit(allocator, electra_state); + allocator.destroy(electra_state); + } electra_state.* = types.electra.BeaconState.default_value; electra_state.genesis_time = 1596546008; electra_state.genesis_validators_root = try hex.hexToRoot("0x8a8b3f1f1e2d3c4b5a697887766554433221100ffeeddccbbaa9988776655443"); @@ -117,54 +125,58 @@ pub fn generateElectraState(allocator: Allocator, chain_config: ChainConfig, val }; // the same logic to processSyncCommitteeUpdates - const beacon_state = try allocator.create(BeaconStateAllForks); - errdefer allocator.destroy(beacon_state); - beacon_state.* = .{ .electra = electra_state }; - const validators = beacon_state.validators(); + beacon_state.* = try BeaconState.fromValue(allocator, pool, .electra, electra_state); + errdefer beacon_state.deinit(); + var next_sync_committee_indices: [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex = undefined; try getNextSyncCommitteeIndices(allocator, beacon_state, active_validator_indices.items, effective_balance_increments, &next_sync_committee_indices); var next_sync_committee_pubkeys: [preset.SYNC_COMMITTEE_SIZE]BLSPubkey = undefined; var next_sync_committee_pubkeys_slices: [preset.SYNC_COMMITTEE_SIZE]blst.PublicKey = undefined; + var validators = try beacon_state.validators(); for (next_sync_committee_indices, 0..next_sync_committee_indices.len) |index, i| { - next_sync_committee_pubkeys[i] = validators.items[index].pubkey; + var validator = try validators.get(@intCast(index)); + var pubkey_view = try validator.get("pubkey"); + _ = try pubkey_view.getAllInto(next_sync_committee_pubkeys[i][0..]); next_sync_committee_pubkeys_slices[i] = try blst.PublicKey.uncompress(&next_sync_committee_pubkeys[i]); } - const current_sync_committee = beacon_state.currentSyncCommittee(); - const next_sync_committee = beacon_state.nextSyncCommittee(); + var current_sync_committee = try beacon_state.currentSyncCommittee(); + var next_sync_committee = try beacon_state.nextSyncCommittee(); // Rotate syncCommittee in state - next_sync_committee.* = .{ - .pubkeys = next_sync_committee_pubkeys, - .aggregate_pubkey = (try blst.AggregatePublicKey.aggregate(&next_sync_committee_pubkeys_slices, false)).toPublicKey().compress(), - }; + const aggregate_pubkey = (try blst.AggregatePublicKey.aggregate(&next_sync_committee_pubkeys_slices, false)).toPublicKey().compress(); + try next_sync_committee.setValue("pubkeys", &next_sync_committee_pubkeys); + try next_sync_committee.setValue("aggregate_pubkey", &aggregate_pubkey); // initialize current sync committee to be the same as next sync committee - current_sync_committee.* = next_sync_committee.*; + try current_sync_committee.setValue("pubkeys", &next_sync_committee_pubkeys); + try current_sync_committee.setValue("aggregate_pubkey", &aggregate_pubkey); + + try beacon_state.commit(); return beacon_state; } -pub const TestCachedBeaconStateAllForks = struct { +pub const TestCachedBeaconState = struct { allocator: Allocator, config: *BeaconConfig, pubkey_index_map: *PubkeyIndexMap, index_pubkey_cache: *Index2PubkeyCache, - cached_state: *CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, - pub fn init(allocator: Allocator, validator_count: usize) !TestCachedBeaconStateAllForks { - const state = try generateElectraState(allocator, active_chain_config, validator_count); - errdefer state.deinit(allocator); - defer allocator.destroy(state); + pub fn init(allocator: Allocator, pool: *Node.Pool, validator_count: usize) !TestCachedBeaconState { + var state = try generateElectraState(allocator, pool, active_chain_config, validator_count); + errdefer { + state.deinit(); + allocator.destroy(state); + } - return initFromState(allocator, state, ForkSeq.electra, state.fork().epoch); + var fork_view = try state.fork(); + const fork_epoch = try fork_view.get("epoch"); + return initFromState(allocator, state, ForkSeq.electra, fork_epoch); } - pub fn initFromState(allocator: Allocator, state: *BeaconStateAllForks, fork: ForkSeq, fork_epoch: Epoch) !TestCachedBeaconStateAllForks { - const owned_state = try allocator.create(BeaconStateAllForks); - errdefer allocator.destroy(owned_state); - owned_state.* = state.*; - + pub fn initFromState(allocator: Allocator, state: *BeaconState, fork: ForkSeq, fork_epoch: Epoch) !TestCachedBeaconState { const pubkey_index_map = try PubkeyIndexMap.init(allocator); errdefer pubkey_index_map.deinit(); const index_pubkey_cache = try allocator.create(Index2PubkeyCache); @@ -176,9 +188,12 @@ pub const TestCachedBeaconStateAllForks = struct { const chain_config = getConfig(active_chain_config, fork, fork_epoch); const config = try allocator.create(BeaconConfig); errdefer allocator.destroy(config); - config.* = BeaconConfig.init(chain_config, owned_state.genesisValidatorsRoot()); + config.* = BeaconConfig.init(chain_config, (try state.genesisValidatorsRoot()).*); + + const validators = try state.validatorsSlice(allocator); + defer allocator.free(validators); - try syncPubkeys(owned_state.validators().items, pubkey_index_map, index_pubkey_cache); + try syncPubkeys(validators, pubkey_index_map, index_pubkey_cache); const immutable_data = state_transition.EpochCacheImmutableData{ .config = config, @@ -186,12 +201,12 @@ pub const TestCachedBeaconStateAllForks = struct { .pubkey_to_index = pubkey_index_map, }; // cached_state takes ownership of state and will deinit there - const cached_state = try CachedBeaconStateAllForks.createCachedBeaconState(allocator, owned_state, immutable_data, .{ - .skip_sync_committee_cache = owned_state.isPhase0(), + const cached_state = try CachedBeaconState.createCachedBeaconState(allocator, state, immutable_data, .{ + .skip_sync_committee_cache = state.forkSeq() == .phase0, .skip_sync_pubkeys = false, }); - return TestCachedBeaconStateAllForks{ + return TestCachedBeaconState{ .allocator = allocator, .config = config, .pubkey_index_map = pubkey_index_map, @@ -200,7 +215,7 @@ pub const TestCachedBeaconStateAllForks = struct { }; } - pub fn deinit(self: *TestCachedBeaconStateAllForks) void { + pub fn deinit(self: *TestCachedBeaconState) void { self.cached_state.deinit(); self.allocator.destroy(self.cached_state); self.pubkey_index_map.deinit(); @@ -250,8 +265,11 @@ pub fn getConfig(config: ChainConfig, fork: ForkSeq, fork_epoch: Epoch) ChainCon } } -test TestCachedBeaconStateAllForks { +test TestCachedBeaconState { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); } diff --git a/src/state_transition/test_utils/root.zig b/src/state_transition/test_utils/root.zig index b6d1c3e21..a4ff2d5f5 100644 --- a/src/state_transition/test_utils/root.zig +++ b/src/state_transition/test_utils/root.zig @@ -2,7 +2,7 @@ const std = @import("std"); const testing = std.testing; /// Utils that could be used for different kinds of tests like int, perf -pub const TestCachedBeaconStateAllForks = @import("./generate_state.zig").TestCachedBeaconStateAllForks; +pub const TestCachedBeaconState = @import("./generate_state.zig").TestCachedBeaconState; pub const generateElectraBlock = @import("./generate_block.zig").generateElectraBlock; pub const interopSign = @import("./interop_pubkeys.zig").interopSign; diff --git a/src/state_transition/types/beacon_block.zig b/src/state_transition/types/beacon_block.zig index 3d047af31..b71a97c44 100644 --- a/src/state_transition/types/beacon_block.zig +++ b/src/state_transition/types/beacon_block.zig @@ -428,11 +428,11 @@ pub const BeaconBlockBody = union(enum) { // bellatrix fields pub fn executionPayload(self: *const BeaconBlockBody) ExecutionPayload { return switch (self.*) { - .bellatrix => |body| .{ .bellatrix = &body.execution_payload }, - .capella => |body| .{ .capella = &body.execution_payload }, - .deneb => |body| .{ .deneb = &body.execution_payload }, - .electra => |body| .{ .electra = &body.execution_payload }, - .fulu => |body| .{ .electra = &body.execution_payload }, + .bellatrix => |body| .{ .bellatrix = body.execution_payload }, + .capella => |body| .{ .capella = body.execution_payload }, + .deneb => |body| .{ .deneb = body.execution_payload }, + .electra => |body| .{ .electra = body.execution_payload }, + .fulu => |body| .{ .fulu = body.execution_payload }, else => panic("ExecutionPayload is not available in {}", .{self}), }; } @@ -557,9 +557,9 @@ pub const BlindedBeaconBlockBody = union(enum) { // bellatrix fields pub fn executionPayloadHeader(self: *const BlindedBeaconBlockBody) ExecutionPayloadHeader { return switch (self.*) { - .capella => |body| .{ .capella = &body.execution_payload_header }, - .deneb => |body| .{ .deneb = &body.execution_payload_header }, - .electra => |body| .{ .electra = &body.execution_payload_header }, + .capella => |body| .{ .capella = body.execution_payload_header }, + .deneb => |body| .{ .deneb = body.execution_payload_header }, + .electra => |body| .{ .electra = body.execution_payload_header }, }; } @@ -667,11 +667,9 @@ fn testBlockSanity(Block: type) !void { if (is_blinded) { // Blinded blocks do not have the execution payload in plain try std.testing.expectEqualSlices(u8, &[_]u8{0} ** 32, &block_body.executionPayloadHeader().electra.parent_hash); - // another way to access the parent_hash try std.testing.expectEqualSlices(u8, &[_]u8{0} ** 32, &block_body.executionPayloadHeader().getParentHash()); } else { try std.testing.expectEqualSlices(u8, &[_]u8{0} ** 32, &block_body.executionPayload().electra.parent_hash); - // another way to access the parent_hash try std.testing.expectEqualSlices(u8, &[_]u8{0} ** 32, &block_body.executionPayload().getParentHash()); } diff --git a/src/state_transition/types/beacon_state.zig b/src/state_transition/types/beacon_state.zig index 84c2847cf..629155e76 100644 --- a/src/state_transition/types/beacon_state.zig +++ b/src/state_transition/types/beacon_state.zig @@ -1,198 +1,128 @@ const std = @import("std"); -const panic = std.debug.panic; const Allocator = std.mem.Allocator; const expect = std.testing.expect; -const types = @import("consensus_types"); const preset = @import("preset").preset; -const BeaconStatePhase0 = types.phase0.BeaconState.Type; -const BeaconStateAltair = types.altair.BeaconState.Type; -const BeaconStateBellatrix = types.bellatrix.BeaconState.Type; -const BeaconStateCapella = types.capella.BeaconState.Type; -const BeaconStateDeneb = types.deneb.BeaconState.Type; -const BeaconStateElectra = types.electra.BeaconState.Type; -const BeaconStateFulu = types.fulu.BeaconState.Type; -const ExecutionPayloadHeader = @import("./execution_payload.zig").ExecutionPayloadHeader; -const Root = types.primitive.Root.Type; -const Fork = types.phase0.Fork.Type; -const BeaconBlockHeader = types.phase0.BeaconBlockHeader.Type; -const Eth1Data = types.phase0.Eth1Data.Type; -const Eth1DataVotes = types.phase0.Eth1DataVotes.Type; -const Validator = types.phase0.Validator.Type; -const Validators = types.phase0.Validators.Type; -const PendingAttestation = types.phase0.PendingAttestation.Type; -const JustificationBits = types.phase0.JustificationBits.Type; -const Checkpoint = types.phase0.Checkpoint.Type; -const SyncCommittee = types.altair.SyncCommittee.Type; -const HistoricalSummary = types.capella.HistoricalSummary.Type; -const PendingDeposit = types.electra.PendingDeposit.Type; -const PendingPartialWithdrawal = types.electra.PendingPartialWithdrawal.Type; -const PendingConsolidation = types.electra.PendingConsolidation.Type; -const Bytes32 = types.primitive.Bytes32.Type; -const Gwei = types.primitive.Gwei.Type; -const Epoch = types.primitive.Epoch.Type; -const ValidatorIndex = types.primitive.ValidatorIndex.Type; const ForkSeq = @import("config").ForkSeq; +const Node = @import("persistent_merkle_tree").Node; +const isBasicType = @import("ssz").isBasicType; const isFixedType = @import("ssz").isFixedType; +const BaseTreeView = @import("ssz").BaseTreeView; +const CloneOpts = @import("ssz").BaseTreeView.CloneOpts; +const ct = @import("consensus_types"); +const ExecutionPayloadHeader = @import("./execution_payload.zig").ExecutionPayloadHeader; /// wrapper for all BeaconState types across forks so that we don't have to do switch/case for all methods -/// right now this works with regular types -/// TODO: migrate this to TreeView and implement the same set of methods here because TreeView objects does not have a great Devex APIs -pub const BeaconStateAllForks = union(enum) { - phase0: *BeaconStatePhase0, - altair: *BeaconStateAltair, - bellatrix: *BeaconStateBellatrix, - capella: *BeaconStateCapella, - deneb: *BeaconStateDeneb, - electra: *BeaconStateElectra, - fulu: *BeaconStateFulu, - - pub fn init(f: ForkSeq, state_any: anytype) !@This() { - var state: @This() = undefined; - - switch (f) { - .phase0 => { - const T = types.phase0.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .phase0 = src }; - }, - .altair => { - const T = types.altair.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .altair = src }; - }, - .bellatrix => { - const T = types.bellatrix.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .bellatrix = src }; - }, - .capella => { - const T = types.capella.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .capella = src }; - }, - .deneb => { - const T = types.deneb.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .deneb = src }; - }, - .electra => { - const T = types.electra.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .electra = src }; - }, - .fulu => { - const T = types.fulu.BeaconState; - const src: *T.Type = @ptrCast(@alignCast(state_any)); - state = .{ .fulu = src }; +pub const BeaconState = union(ForkSeq) { + phase0: ct.phase0.BeaconState.TreeView, + altair: ct.altair.BeaconState.TreeView, + bellatrix: ct.bellatrix.BeaconState.TreeView, + capella: ct.capella.BeaconState.TreeView, + deneb: ct.deneb.BeaconState.TreeView, + electra: ct.electra.BeaconState.TreeView, + fulu: ct.fulu.BeaconState.TreeView, + + pub fn fromValue(allocator: Allocator, pool: *Node.Pool, comptime fork_seq: ForkSeq, value: anytype) !BeaconState { + return switch (fork_seq) { + .phase0 => .{ + .phase0 = try ct.phase0.BeaconState.TreeView.fromValue(allocator, pool, value), }, - } + .altair => .{ + .altair = try ct.altair.BeaconState.TreeView.fromValue(allocator, pool, value), + }, + .bellatrix => .{ + .bellatrix = try ct.bellatrix.BeaconState.TreeView.fromValue(allocator, pool, value), + }, + .capella => .{ + .capella = try ct.capella.BeaconState.TreeView.fromValue(allocator, pool, value), + }, + .deneb => .{ + .deneb = try ct.deneb.BeaconState.TreeView.fromValue(allocator, pool, value), + }, + .electra => .{ + .electra = try ct.electra.BeaconState.TreeView.fromValue(allocator, pool, value), + }, + .fulu => .{ + .fulu = try ct.fulu.BeaconState.TreeView.fromValue(allocator, pool, value), + }, + }; + } - return state; - } - - pub fn deserialize(allocator: Allocator, fork_seq: ForkSeq, bytes: []const u8) !BeaconStateAllForks { - switch (fork_seq) { - .phase0 => { - const state = try allocator.create(BeaconStatePhase0); - errdefer allocator.destroy(state); - state.* = types.phase0.BeaconState.default_value; - try types.phase0.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .phase0 = state }; - }, - .altair => { - const state = try allocator.create(BeaconStateAltair); - errdefer allocator.destroy(state); - state.* = types.altair.BeaconState.default_value; - try types.altair.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .altair = state }; - }, - .bellatrix => { - const state = try allocator.create(BeaconStateBellatrix); - errdefer allocator.destroy(state); - state.* = types.bellatrix.BeaconState.default_value; - try types.bellatrix.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .bellatrix = state }; - }, - .capella => { - const state = try allocator.create(BeaconStateCapella); - errdefer allocator.destroy(state); - state.* = types.capella.BeaconState.default_value; - try types.capella.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .capella = state }; - }, - .deneb => { - const state = try allocator.create(BeaconStateDeneb); - errdefer allocator.destroy(state); - state.* = types.deneb.BeaconState.default_value; - try types.deneb.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .deneb = state }; - }, - .electra => { - const state = try allocator.create(BeaconStateElectra); - errdefer allocator.destroy(state); - state.* = types.electra.BeaconState.default_value; - try types.electra.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .electra = state }; - }, - .fulu => { - const state = try allocator.create(BeaconStateFulu); - errdefer allocator.destroy(state); - state.* = types.fulu.BeaconState.default_value; - try types.fulu.BeaconState.deserializeFromBytes(allocator, bytes, state); - return .{ .fulu = state }; + pub fn deserialize(allocator: Allocator, pool: *Node.Pool, fork_seq: ForkSeq, bytes: []const u8) !BeaconState { + return switch (fork_seq) { + .phase0 => .{ + .phase0 = try ct.phase0.BeaconState.TreeView.deserialize(allocator, pool, bytes), }, - } + .altair => .{ + .altair = try ct.altair.BeaconState.TreeView.deserialize(allocator, pool, bytes), + }, + .bellatrix => .{ + .bellatrix = try ct.bellatrix.BeaconState.TreeView.deserialize(allocator, pool, bytes), + }, + .capella => .{ + .capella = try ct.capella.BeaconState.TreeView.deserialize(allocator, pool, bytes), + }, + .deneb => .{ + .deneb = try ct.deneb.BeaconState.TreeView.deserialize(allocator, pool, bytes), + }, + .electra => .{ + .electra = try ct.electra.BeaconState.TreeView.deserialize(allocator, pool, bytes), + }, + .fulu => .{ + .fulu = try ct.fulu.BeaconState.TreeView.deserialize(allocator, pool, bytes), + }, + }; } - pub fn serialize(self: BeaconStateAllForks, allocator: Allocator) ![]u8 { - switch (self) { - .phase0 => |state| { - const out = try allocator.alloc(u8, types.phase0.BeaconState.serializedSize(state)); + pub fn serialize(self: BeaconState, allocator: Allocator) ![]u8 { + var s = self; + switch (s) { + .phase0 => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.phase0.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, - .altair => |state| { - const out = try allocator.alloc(u8, types.altair.BeaconState.serializedSize(state)); + .altair => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.altair.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, - .bellatrix => |state| { - const out = try allocator.alloc(u8, types.bellatrix.BeaconState.serializedSize(state)); + .bellatrix => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.bellatrix.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, - .capella => |state| { - const out = try allocator.alloc(u8, types.capella.BeaconState.serializedSize(state)); + .capella => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.capella.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, - .deneb => |state| { - const out = try allocator.alloc(u8, types.deneb.BeaconState.serializedSize(state)); + .deneb => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.deneb.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, - .electra => |state| { - const out = try allocator.alloc(u8, types.electra.BeaconState.serializedSize(state)); + .electra => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.electra.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, - .fulu => |state| { - const out = try allocator.alloc(u8, types.fulu.BeaconState.serializedSize(state)); + .fulu => |*state| { + const out = try allocator.alloc(u8, try state.serializedSize()); errdefer allocator.free(out); - _ = types.fulu.BeaconState.serializeIntoBytes(state, out); + _ = try state.serializeIntoBytes(out); return out; }, } } pub fn format( - self: BeaconStateAllForks, + self: BeaconState, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, @@ -206,613 +136,706 @@ pub const BeaconStateAllForks = union(enum) { }; } - pub fn clone(self: *const BeaconStateAllForks, allocator: std.mem.Allocator) !*BeaconStateAllForks { - const out = try allocator.create(BeaconStateAllForks); - errdefer allocator.destroy(out); + pub fn baseView(self: *BeaconState) BaseTreeView { + return switch (self.*) { + inline else => |*state| state.base_view, + }; + } + + pub fn clone(self: *BeaconState, opts: CloneOpts) !BeaconState { + return switch (self.*) { + .phase0 => |*state| .{ .phase0 = try state.clone(opts) }, + .altair => |*state| .{ .altair = try state.clone(opts) }, + .bellatrix => |*state| .{ .bellatrix = try state.clone(opts) }, + .capella => |*state| .{ .capella = try state.clone(opts) }, + .deneb => |*state| .{ .deneb = try state.clone(opts) }, + .electra => |*state| .{ .electra = try state.clone(opts) }, + .fulu => |*state| .{ .fulu = try state.clone(opts) }, + }; + } + + pub fn commit(self: *BeaconState) !void { switch (self.*) { - .phase0 => |state| { - const cloned_state = try allocator.create(BeaconStatePhase0); - errdefer allocator.destroy(cloned_state); - out.* = .{ .phase0 = cloned_state }; - try types.phase0.BeaconState.clone(allocator, state, cloned_state); - }, - .altair => |state| { - const cloned_state = try allocator.create(BeaconStateAltair); - errdefer allocator.destroy(cloned_state); - out.* = .{ .altair = cloned_state }; - try types.altair.BeaconState.clone(allocator, state, cloned_state); - }, - .bellatrix => |state| { - const cloned_state = try allocator.create(BeaconStateBellatrix); - errdefer allocator.destroy(cloned_state); - out.* = .{ .bellatrix = cloned_state }; - try types.bellatrix.BeaconState.clone(allocator, state, cloned_state); - }, - .capella => |state| { - const cloned_state = try allocator.create(BeaconStateCapella); - errdefer allocator.destroy(cloned_state); - out.* = .{ .capella = cloned_state }; - try types.capella.BeaconState.clone(allocator, state, cloned_state); - }, - .deneb => |state| { - const cloned_state = try allocator.create(BeaconStateDeneb); - errdefer allocator.destroy(cloned_state); - out.* = .{ .deneb = cloned_state }; - try types.deneb.BeaconState.clone(allocator, state, cloned_state); - }, - .electra => |state| { - const cloned_state = try allocator.create(BeaconStateElectra); - errdefer allocator.destroy(cloned_state); - out.* = .{ .electra = cloned_state }; - try types.electra.BeaconState.clone(allocator, state, cloned_state); - }, - .fulu => |state| { - const cloned_state = try allocator.create(BeaconStateFulu); - errdefer allocator.destroy(cloned_state); - out.* = .{ .fulu = cloned_state }; - try types.fulu.BeaconState.clone(allocator, state, cloned_state); - }, + inline else => |*state| try state.commit(), + } + } + + pub fn hashTreeRoot(self: *BeaconState) !*const [32]u8 { + return switch (self.*) { + inline else => |*state| try state.hashTreeRoot(), + }; + } + + pub fn deinit(self: *BeaconState) void { + switch (self.*) { + inline else => |*state| state.deinit(), } + } + + pub fn forkSeq(self: *BeaconState) ForkSeq { + return (self.*); + } - return out; + pub fn genesisTime(self: *BeaconState) !u64 { + return switch (self.*) { + inline else => |*state| try state.get("genesis_time"), + }; + } + + pub fn genesisValidatorsRoot(self: *BeaconState) !*const [32]u8 { + return switch (self.*) { + inline else => |*state| try state.getRoot("genesis_validators_root"), + }; } - pub fn hashTreeRoot(self: *const BeaconStateAllForks, allocator: std.mem.Allocator, out: *[32]u8) !void { + pub fn slot(self: *BeaconState) !u64 { return switch (self.*) { - .phase0 => |state| try types.phase0.BeaconState.hashTreeRoot(allocator, state, out), - .altair => |state| try types.altair.BeaconState.hashTreeRoot(allocator, state, out), - .bellatrix => |state| try types.bellatrix.BeaconState.hashTreeRoot(allocator, state, out), - .capella => |state| try types.capella.BeaconState.hashTreeRoot(allocator, state, out), - .deneb => |state| try types.deneb.BeaconState.hashTreeRoot(allocator, state, out), - .electra => |state| try types.electra.BeaconState.hashTreeRoot(allocator, state, out), - .fulu => |state| try types.fulu.BeaconState.hashTreeRoot(allocator, state, out), + inline else => |*state| try state.get("slot"), }; } - pub fn deinit(self: *BeaconStateAllForks, allocator: Allocator) void { + pub fn setSlot(self: *BeaconState, s: u64) !void { switch (self.*) { - .phase0 => |state| { - types.phase0.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, - .altair => |state| { - types.altair.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, - .capella => |state| { - types.capella.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, - .bellatrix => |state| { - types.bellatrix.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, - .deneb => |state| { - types.deneb.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, - .electra => |state| { - types.electra.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, - .fulu => |state| { - types.fulu.BeaconState.deinit(allocator, state); - allocator.destroy(state); - }, + inline else => |*state| try state.set("slot", s), } } - pub fn forkSeq(self: *const BeaconStateAllForks) ForkSeq { + pub fn fork(self: *BeaconState) !ct.phase0.Fork.TreeView { return switch (self.*) { - .phase0 => .phase0, - .altair => .altair, - .bellatrix => .bellatrix, - .capella => .capella, - .deneb => .deneb, - .electra => .electra, - .fulu => .fulu, + inline else => |*state| try state.get("fork"), + }; + } + + pub fn forkCurrentVersion(self: *BeaconState) ![4]u8 { + var f = switch (self.*) { + inline else => |*state| try state.getReadonly("fork"), }; + const current_version_root = try f.getRoot("current_version"); + var version: [4]u8 = undefined; + @memcpy(&version, current_version_root[0..4]); + return version; + } + + pub fn setFork(self: *BeaconState, f: *const ct.phase0.Fork.Type) !void { + switch (self.*) { + inline else => |*state| try state.setValue("fork", f), + } } - pub fn isPhase0(self: *const BeaconStateAllForks) bool { + pub fn latestBlockHeader(self: *BeaconState) !ct.phase0.BeaconBlockHeader.TreeView { return switch (self.*) { - .phase0 => true, - else => false, + inline else => |*state| try state.get("latest_block_header"), }; } - pub fn isAltair(self: *const BeaconStateAllForks) bool { + pub fn setLatestBlockHeader(self: *BeaconState, header: *const ct.phase0.BeaconBlockHeader.Type) !void { + switch (self.*) { + inline else => |*state| try state.setValue("latest_block_header", header), + } + } + + pub fn blockRoots(self: *BeaconState) !ct.phase0.HistoricalBlockRoots.TreeView { return switch (self.*) { - .altair => true, - else => false, + inline else => |*state| try state.get("block_roots"), }; } - pub fn isPreAltair(self: *const BeaconStateAllForks) bool { + pub fn blockRootsRoot(self: *BeaconState) !*const [32]u8 { return switch (self.*) { - .phase0 => true, - else => false, + inline else => |*state| try state.getRoot("block_roots"), }; } - pub fn isPostAltair(self: *const BeaconStateAllForks) bool { + pub fn stateRoots(self: *BeaconState) !ct.phase0.HistoricalStateRoots.TreeView { return switch (self.*) { - .phase0 => false, - else => true, + inline else => |*state| try state.get("state_roots"), }; } - pub fn isBellatrix(self: *const BeaconStateAllForks) bool { + pub fn stateRootsRoot(self: *BeaconState) !*const [32]u8 { return switch (self.*) { - .bellatrix => true, - else => false, + inline else => |*state| try state.getRoot("state_roots"), }; } - pub fn isPreBellatrix(self: *const BeaconStateAllForks) bool { + pub fn historicalRoots(self: *BeaconState) !ct.phase0.HistoricalRoots.TreeView { return switch (self.*) { - inline .phase0, .altair => false, - else => true, + inline else => |*state| try state.get("historical_roots"), }; } - pub fn isPostBellatrix(self: *const BeaconStateAllForks) bool { + pub fn eth1Data(self: *BeaconState) !ct.phase0.Eth1Data.TreeView { return switch (self.*) { - inline .phase0, .altair => false, - else => true, + inline else => |*state| try state.get("eth1_data"), }; } - pub fn isCapella(self: *const BeaconStateAllForks) bool { + pub fn setEth1Data(self: *BeaconState, eth1_data: *const ct.phase0.Eth1Data.Type) !void { + switch (self.*) { + inline else => |*state| try state.setValue("eth1_data", eth1_data), + } + } + + pub fn eth1DataVotes(self: *BeaconState) !ct.phase0.Eth1DataVotes.TreeView { return switch (self.*) { - .capella => true, - else => false, + inline else => |*state| try state.get("eth1_data_votes"), }; } - pub fn isPreCapella(self: *const BeaconStateAllForks) bool { + pub fn setEth1DataVotes(self: *BeaconState, eth1_data_votes: ct.phase0.Eth1DataVotes.TreeView) !void { + switch (self.*) { + inline else => |*state| try state.set("eth1_data_votes", eth1_data_votes), + } + } + + pub fn appendEth1DataVote(self: *BeaconState, eth1_data: *const ct.phase0.Eth1Data.Type) !void { + var votes = try self.eth1DataVotes(); + try votes.pushValue(eth1_data); + } + + pub fn resetEth1DataVotes(self: *BeaconState) !void { + switch (self.*) { + inline else => |*state| try state.setValue("eth1_data_votes", &ct.phase0.Eth1DataVotes.default_value), + } + } + + pub fn eth1DepositIndex(self: *BeaconState) !u64 { return switch (self.*) { - inline .phase0, .altair, .bellatrix => true, - else => false, + inline else => |*state| try state.get("eth1_deposit_index"), }; } - pub fn isPostCapella(self: *const BeaconStateAllForks) bool { + pub fn setEth1DepositIndex(self: *BeaconState, index: u64) !void { return switch (self.*) { - inline .phase0, .altair, .bellatrix => false, - else => true, + inline else => |*state| try state.set("eth1_deposit_index", index), }; } - pub fn isDeneb(self: *const BeaconStateAllForks) bool { + pub fn incrementEth1DepositIndex(self: *BeaconState) !void { + try self.setEth1DepositIndex(try self.eth1DepositIndex() + 1); + } + + pub fn validators(self: *BeaconState) !ct.phase0.Validators.TreeView { return switch (self.*) { - .deneb => true, - else => false, + inline else => |*state| try state.get("validators"), }; } - pub fn isPreDeneb(self: *const BeaconStateAllForks) bool { + pub fn validatorsCount(self: *BeaconState) !usize { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella => true, - else => false, + inline else => |*state| { + var validators_view = try state.getReadonly("validators"); + return validators_view.length(); + }, }; } - pub fn isPostDeneb(self: *const BeaconStateAllForks) bool { + /// Returns a read-only slice of validators. + /// This is read-only in the sense that modifications will not be reflected back to the state. + /// Caller owns the returned slice and must free it with the same allocator. + pub fn validatorsSlice(self: *BeaconState, allocator: Allocator) ![]ct.phase0.Validator.Type { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella => false, - else => true, + inline else => |*state| { + var validators_view = try state.getReadonly("validators"); + return validators_view.getAllReadonlyValues(allocator); + }, }; } - pub fn isElectra(self: *const BeaconStateAllForks) bool { + pub fn balances(self: *BeaconState) !ct.phase0.Balances.TreeView { return switch (self.*) { - .electra => true, - else => false, + inline else => |*state| try state.get("balances"), }; } - pub fn isPreElectra(self: *const BeaconStateAllForks) bool { + /// Returns a read-only slice of balances. + /// This is read-only in the sense that modifications will not be reflected back to the state. + /// Caller owns the returned slice and must free it with the same allocator. + pub fn balancesSlice(self: *BeaconState, allocator: Allocator) ![]u64 { return switch (self.*) { - .phase0, .altair, .bellatrix, .capella, .deneb => true, - else => false, + inline else => |*state| { + var balances_view = try state.get("balances"); + try balances_view.commit(); + return balances_view.getAll(allocator); + }, }; } - pub fn isPostElectra(self: *const BeaconStateAllForks) bool { + pub fn setBalances(self: *BeaconState, b: *const ct.phase0.Balances.Type) !void { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => false, - else => true, + inline else => |*state| try state.setValue("balances", b), }; } - pub fn isFulu(self: *const BeaconStateAllForks) bool { + pub fn randaoMixes(self: *BeaconState) !ct.phase0.RandaoMixes.TreeView { return switch (self.*) { - .fulu => true, - else => false, + inline else => |*state| try state.get("randao_mixes"), }; } - pub fn isPreFulu(self: *const BeaconStateAllForks) bool { + pub fn setRandaoMix(self: *BeaconState, epoch: u64, randao_mix: *const ct.primitive.Bytes32.Type) !void { + var mixes = try self.randaoMixes(); + try mixes.setValue(epoch % preset.EPOCHS_PER_HISTORICAL_VECTOR, randao_mix); + } + + pub fn slashings(self: *BeaconState) !ct.phase0.Slashings.TreeView { return switch (self.*) { - .phase0, .altair, .bellatrix, .capella, .deneb, .electra => true, - else => false, + inline else => |*state| try state.get("slashings"), }; } - pub fn isPostFulu(self: *const BeaconStateAllForks) bool { + pub fn previousEpochPendingAttestations(self: *BeaconState) !ct.phase0.EpochAttestations.TreeView { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb, .electra => false, - else => true, + .phase0 => |*state| try state.get("previous_epoch_attestations"), + else => error.InvalidAtFork, }; } - pub fn genesisTime(self: *const BeaconStateAllForks) u64 { + pub fn currentEpochPendingAttestations(self: *BeaconState) !ct.phase0.EpochAttestations.TreeView { return switch (self.*) { - inline else => |state| state.genesis_time, + .phase0 => |*state| try state.get("current_epoch_attestations"), + else => error.InvalidAtFork, }; } - pub fn genesisValidatorsRoot(self: *const BeaconStateAllForks) Root { + pub fn rotateEpochPendingAttestations(self: *BeaconState) !void { return switch (self.*) { - inline else => |state| state.genesis_validators_root, + .phase0 => |*state| { + const current_root = try state.getRootNode("current_epoch_attestations"); + try state.setRootNode("previous_epoch_attestations", current_root); + try state.setValue("current_epoch_attestations", &ct.phase0.EpochAttestations.default_value); + }, + else => error.InvalidAtFork, }; } - pub fn slot(self: *const BeaconStateAllForks) u64 { + pub fn previousEpochParticipation(self: *BeaconState) !ct.altair.EpochParticipation.TreeView { return switch (self.*) { - inline else => |state| state.slot, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.get("previous_epoch_participation"), }; } - pub fn slotPtr(self: *const BeaconStateAllForks) *u64 { + pub fn setPreviousEpochParticipation(self: *BeaconState, participations: *const ct.altair.EpochParticipation.Type) !void { return switch (self.*) { - inline else => |state| &state.slot, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.setValue("previous_epoch_participation", participations), }; } - pub fn fork(self: *const BeaconStateAllForks) Fork { + pub fn currentEpochParticipation(self: *BeaconState) !ct.altair.EpochParticipation.TreeView { return switch (self.*) { - inline else => |state| state.fork, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.get("current_epoch_participation"), }; } - pub fn forkPtr(self: *const BeaconStateAllForks) *Fork { + pub fn setCurrentEpochParticipation(self: *BeaconState, participations: *const ct.altair.EpochParticipation.Type) !void { return switch (self.*) { - inline else => |state| &state.fork, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.setValue("current_epoch_participation", participations), }; } - pub fn latestBlockHeader(self: *const BeaconStateAllForks) *BeaconBlockHeader { + pub fn rotateEpochParticipation(self: *BeaconState) !void { return switch (self.*) { - inline else => |state| &state.latest_block_header, + .phase0 => error.InvalidAtFork, + inline else => |*state| { + var current_epoch_participation = try state.get("current_epoch_participation"); + try current_epoch_participation.commit(); + const length = try current_epoch_participation.length(); + try state.set( + "previous_epoch_participation", + // cannot set without cloning because the original is owned by the tree + // we need to clone it to create an owned tree + try current_epoch_participation.clone(.{ .transfer_cache = true }), + ); + + // Reset current_epoch_participation by rebuilding a zeroed SSZ List of the same length. + const new_current_root = try ct.altair.EpochParticipation.tree.zeros( + state.base_view.pool, + length, + ); + errdefer state.base_view.pool.unref(new_current_root); + try state.setRootNode("current_epoch_participation", new_current_root); + }, }; } - pub fn blockRoots(self: *const BeaconStateAllForks) *[preset.SLOTS_PER_HISTORICAL_ROOT]Root { + pub fn justificationBits(self: *BeaconState) !ct.phase0.JustificationBits.TreeView { return switch (self.*) { - inline else => |state| &state.block_roots, + inline else => |*state| try state.get("justification_bits"), }; } - pub fn stateRoots(self: *const BeaconStateAllForks) *[preset.SLOTS_PER_HISTORICAL_ROOT]Root { + pub fn setJustificationBits(self: *BeaconState, bits: *const ct.phase0.JustificationBits.Type) !void { return switch (self.*) { - inline else => |state| &state.state_roots, + inline else => |*state| try state.setValue("justification_bits", bits), }; } - pub fn historicalRoots(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(Root) { + pub fn previousJustifiedCheckpoint(self: *BeaconState, out: *ct.phase0.Checkpoint.Type) !void { return switch (self.*) { - inline else => |state| &state.historical_roots, + inline else => |*state| try state.getValue(undefined, "previous_justified_checkpoint", out), }; } - pub fn eth1Data(self: *const BeaconStateAllForks) *Eth1Data { + pub fn setPreviousJustifiedCheckpoint(self: *BeaconState, checkpoint: *const ct.phase0.Checkpoint.Type) !void { return switch (self.*) { - inline else => |state| &state.eth1_data, + inline else => |*state| try state.setValue("previous_justified_checkpoint", checkpoint), }; } - pub fn eth1DataVotes(self: *const BeaconStateAllForks) *Eth1DataVotes { + pub fn currentJustifiedCheckpoint(self: *BeaconState, out: *ct.phase0.Checkpoint.Type) !void { return switch (self.*) { - inline else => |state| &state.eth1_data_votes, + inline else => |*state| try state.getValue(undefined, "current_justified_checkpoint", out), }; } - pub fn eth1DepositIndex(self: *const BeaconStateAllForks) u64 { + pub fn setCurrentJustifiedCheckpoint(self: *BeaconState, checkpoint: *const ct.phase0.Checkpoint.Type) !void { return switch (self.*) { - inline else => |state| state.eth1_deposit_index, + inline else => |*state| try state.setValue("current_justified_checkpoint", checkpoint), }; } - pub fn eth1DepositIndexPtr(self: *const BeaconStateAllForks) *u64 { + pub fn finalizedCheckpoint(self: *BeaconState, out: *ct.phase0.Checkpoint.Type) !void { return switch (self.*) { - inline else => |state| &state.eth1_deposit_index, + inline else => |*state| try state.getValue(undefined, "finalized_checkpoint", out), }; } - pub fn increaseEth1DepositIndex(self: *BeaconStateAllForks) void { - switch (self.*) { - inline else => |state| state.eth1_deposit_index += 1, - } + pub fn setFinalizedCheckpoint(self: *BeaconState, checkpoint: *const ct.phase0.Checkpoint.Type) !void { + return switch (self.*) { + inline else => |*state| try state.setValue("finalized_checkpoint", checkpoint), + }; } - // TODO: change to []Validator - pub fn validators(self: *const BeaconStateAllForks) *Validators { + pub fn finalizedEpoch(self: *BeaconState) !u64 { return switch (self.*) { - inline else => |state| &state.validators, + inline else => |*state| { + var checkpoint_view = try state.getReadonly("finalized_checkpoint"); + return try checkpoint_view.get("epoch"); + }, }; } - pub fn balances(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(u64) { + pub fn inactivityScores(self: *BeaconState) !ct.altair.InactivityScores.TreeView { return switch (self.*) { - inline else => |state| &state.balances, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.get("inactivity_scores"), }; } - pub fn randaoMixes(self: *const BeaconStateAllForks) []Bytes32 { + pub fn currentSyncCommittee(self: *BeaconState) !ct.altair.SyncCommittee.TreeView { return switch (self.*) { - inline else => |state| &state.randao_mixes, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.get("current_sync_committee"), }; } - pub fn slashings(self: *const BeaconStateAllForks) []u64 { + pub fn setCurrentSyncCommittee(self: *BeaconState, sync_committee: *const ct.altair.SyncCommittee.Type) !void { return switch (self.*) { - inline else => |state| &state.slashings, + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.setValue("current_sync_committee", sync_committee), }; } - pub fn previousEpochPendingAttestations(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(PendingAttestation) { + pub fn nextSyncCommittee(self: *BeaconState) !ct.altair.SyncCommittee.TreeView { return switch (self.*) { - .phase0 => |state| &state.previous_epoch_attestations, - else => @panic("current_epoch_pending_attestations is not available post phase0"), + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.get("next_sync_committee"), }; } - pub fn currentEpochPendingAttestations(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(PendingAttestation) { + pub fn setNextSyncCommittee(self: *BeaconState, sync_committee: *const ct.altair.SyncCommittee.Type) !void { return switch (self.*) { - .phase0 => |state| &state.current_epoch_attestations, - else => @panic("current_epoch_pending_attestations is not available post phase0"), + .phase0 => error.InvalidAtFork, + inline else => |*state| try state.setValue("next_sync_committee", sync_committee), }; } - pub fn rotateEpochPendingAttestations(self: *BeaconStateAllForks, allocator: Allocator) void { - switch (self.*) { - .phase0 => |state| { - for (state.previous_epoch_attestations.items) |*attestation| { - types.phase0.PendingAttestation.deinit(allocator, attestation); - } - state.previous_epoch_attestations.deinit(allocator); - state.previous_epoch_attestations = state.current_epoch_attestations; - state.current_epoch_attestations = types.phase0.EpochAttestations.default_value; + pub fn rotateSyncCommittees(self: *BeaconState, next_sync_committee: *const ct.altair.SyncCommittee.Type) !void { + return switch (self.*) { + .phase0 => error.InvalidAtFork, + inline else => |*state| { + const next_sync_committee_root = try state.getRootNode("next_sync_committee"); + try state.setRootNode("current_sync_committee", next_sync_committee_root); + try state.setValue("next_sync_committee", next_sync_committee); }, - else => @panic("shift_epoch_pending_attestations is not available post phase0"), - } + }; } - pub fn previousEpochParticipations(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(u8) { + pub fn latestExecutionPayloadHeader(self: *BeaconState, allocator: Allocator, out: *ExecutionPayloadHeader) !void { return switch (self.*) { - .phase0 => @panic("previous_epoch_participation is not available in phase0"), - inline .altair, .bellatrix, .capella, .deneb, .electra, .fulu => |state| &state.previous_epoch_participation, + .phase0, .altair => error.InvalidAtFork, + .bellatrix => |*state| { + out.* = .{ .bellatrix = undefined }; + try state.getValue(allocator, "latest_execution_payload_header", &out.bellatrix); + }, + .capella => |*state| { + out.* = .{ .capella = undefined }; + try state.getValue(allocator, "latest_execution_payload_header", &out.capella); + }, + .deneb => |*state| { + out.* = .{ .deneb = undefined }; + try state.getValue(allocator, "latest_execution_payload_header", &out.deneb); + }, + .electra => |*state| { + out.* = .{ .electra = undefined }; + try state.getValue(allocator, "latest_execution_payload_header", &out.electra); + }, + .fulu => |*state| { + out.* = .{ .fulu = undefined }; + try state.getValue(allocator, "latest_execution_payload_header", &out.fulu); + }, }; } - pub fn currentEpochParticipations(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(u8) { + pub fn latestExecutionPayloadHeaderBlockHash(self: *BeaconState) !*const [32]u8 { return switch (self.*) { - .phase0 => @panic("current_epoch_participation is not available in phase0"), - inline else => |state| &state.current_epoch_participation, + .phase0, .altair => error.InvalidAtFork, + inline else => |*state| { + var header = try state.get("latest_execution_payload_header"); + return try header.getRoot("block_hash"); + }, }; } - pub fn rotateEpochParticipations(self: *BeaconStateAllForks, allocator: Allocator) !void { + pub fn setLatestExecutionPayloadHeader(self: *BeaconState, header: *const ExecutionPayloadHeader) !void { switch (self.*) { - .phase0 => @panic("rotate_epoch_participations is not available in phase0"), - inline else => |state| { - state.previous_epoch_participation.clearRetainingCapacity(); - try state.previous_epoch_participation.appendSlice(allocator, state.current_epoch_participation.items); - @memset(state.current_epoch_participation.items, 0); - }, + .bellatrix => |*state| try state.setValue("latest_execution_payload_header", &header.bellatrix), + .capella => |*state| try state.setValue("latest_execution_payload_header", &header.capella), + .deneb => |*state| try state.setValue("latest_execution_payload_header", &header.deneb), + .electra => |*state| try state.setValue("latest_execution_payload_header", &header.electra), + .fulu => |*state| try state.setValue("latest_execution_payload_header", &header.fulu), + else => return error.InvalidAtFork, } } - pub fn justificationBits(self: *const BeaconStateAllForks) *JustificationBits { + pub fn nextWithdrawalIndex(self: *BeaconState) !u64 { return switch (self.*) { - inline else => |state| &state.justification_bits, + .phase0, .altair, .bellatrix => error.InvalidAtFork, + inline else => |*state| try state.get("next_withdrawal_index"), }; } - pub fn previousJustifiedCheckpoint(self: *const BeaconStateAllForks) *Checkpoint { + pub fn setNextWithdrawalIndex(self: *BeaconState, next_withdrawal_index: u64) !void { return switch (self.*) { - inline else => |state| &state.previous_justified_checkpoint, + .phase0, .altair, .bellatrix => error.InvalidAtFork, + inline else => |*state| try state.set("next_withdrawal_index", next_withdrawal_index), }; } - pub fn currentJustifiedCheckpoint(self: *const BeaconStateAllForks) *Checkpoint { + pub fn nextWithdrawalValidatorIndex(self: *BeaconState) !u64 { return switch (self.*) { - inline else => |state| &state.current_justified_checkpoint, + .phase0, .altair, .bellatrix => error.InvalidAtFork, + inline else => |*state| try state.get("next_withdrawal_validator_index"), }; } - pub fn finalizedCheckpoint(self: *const BeaconStateAllForks) *Checkpoint { + pub fn setNextWithdrawalValidatorIndex(self: *BeaconState, next_withdrawal_validator_index: u64) !void { return switch (self.*) { - inline else => |state| &state.finalized_checkpoint, + .phase0, .altair, .bellatrix => error.InvalidAtFork, + inline else => |*state| try state.set("next_withdrawal_validator_index", next_withdrawal_validator_index), }; } - pub fn inactivityScores(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(u64) { + pub fn historicalSummaries(self: *BeaconState) !ct.capella.HistoricalSummaries.TreeView { return switch (self.*) { - .phase0 => @panic("inactivity_scores is not available in phase0"), - inline else => |state| &state.inactivity_scores, + .phase0, .altair, .bellatrix => error.InvalidAtFork, + inline else => |*state| try state.get("historical_summaries"), }; } - pub fn currentSyncCommittee(self: *const BeaconStateAllForks) *SyncCommittee { + pub fn depositRequestsStartIndex(self: *BeaconState) !u64 { return switch (self.*) { - .phase0 => @panic("current_sync_committee is not available in phase0"), - inline else => |state| &state.current_sync_committee, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("deposit_requests_start_index"), }; } - pub fn nextSyncCommittee(self: *const BeaconStateAllForks) *SyncCommittee { + pub fn setDepositRequestsStartIndex(self: *BeaconState, index: u64) !void { return switch (self.*) { - .phase0 => @panic("next_sync_committee is not available in phase0"), - inline else => |state| &state.next_sync_committee, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("deposit_requests_start_index", index), }; } - pub fn setNextSyncCommittee(self: *BeaconStateAllForks, sync_committee: *const SyncCommittee) void { - switch (self.*) { - .phase0 => @panic("next_sync_committee is not available in phase0"), - inline else => |state| state.next_sync_committee = sync_committee.*, - } + pub fn depositBalanceToConsume(self: *BeaconState) !u64 { + return switch (self.*) { + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("deposit_balance_to_consume"), + }; } - pub fn latestExecutionPayloadHeader(self: *const BeaconStateAllForks) ExecutionPayloadHeader { + pub fn setDepositBalanceToConsume(self: *BeaconState, balance: u64) !void { return switch (self.*) { - .bellatrix => |state| .{ .bellatrix = &state.latest_execution_payload_header }, - .capella => |state| .{ .capella = &state.latest_execution_payload_header }, - .deneb => |state| .{ .deneb = &state.latest_execution_payload_header }, - .electra => |state| .{ .electra = &state.latest_execution_payload_header }, - .fulu => |state| .{ .electra = &state.latest_execution_payload_header }, - else => panic("latest_execution_payload_header is not available in {}", .{self}), + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("deposit_balance_to_consume", balance), }; } - // `header` ownership is transferred to BeaconState and will be deinit when state is deinit - // caller must guarantee that `header` is properly initialized and allocated/cloned with `allocator` and no longer used after this call - pub fn setLatestExecutionPayloadHeader(self: *BeaconStateAllForks, allocator: Allocator, header: ExecutionPayloadHeader) void { - const current_header = self.latestExecutionPayloadHeader(); - current_header.deinit(allocator); + pub fn exitBalanceToConsume(self: *BeaconState) !u64 { + return switch (self.*) { + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("exit_balance_to_consume"), + }; + } - switch (self.*) { - .bellatrix => |state| state.latest_execution_payload_header = header.bellatrix.*, - .capella => |state| state.latest_execution_payload_header = header.capella.*, - .deneb => |state| state.latest_execution_payload_header = header.deneb.*, - .electra => |state| state.latest_execution_payload_header = header.electra.*, - .fulu => |state| state.latest_execution_payload_header = header.electra.*, - else => panic("latest_execution_payload_header is not available in {}", .{self}), - } + pub fn setExitBalanceToConsume(self: *BeaconState, balance: u64) !void { + return switch (self.*) { + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("exit_balance_to_consume", balance), + }; } - pub fn nextWithdrawalIndex(self: *const BeaconStateAllForks) *u64 { + pub fn earliestExitEpoch(self: *BeaconState) !u64 { return switch (self.*) { - inline .phase0, .altair, .bellatrix => panic("next_withdrawal_index is not available in {}", .{self}), - inline else => |state| &state.next_withdrawal_index, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("earliest_exit_epoch"), }; } - pub fn nextWithdrawalValidatorIndex(self: *const BeaconStateAllForks) *u64 { + pub fn setEarliestExitEpoch(self: *BeaconState, epoch: u64) !void { return switch (self.*) { - inline .phase0, .altair, .bellatrix => panic("next_withdrawal_validator_index is not available in {}", .{self}), - inline else => |state| &state.next_withdrawal_validator_index, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("earliest_exit_epoch", epoch), }; } - pub fn historicalSummaries(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(HistoricalSummary) { + pub fn consolidationBalanceToConsume(self: *BeaconState) !u64 { return switch (self.*) { - inline .phase0, .altair, .bellatrix => panic("historical_summaries is not available in {}", .{self}), - inline else => |state| &state.historical_summaries, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("consolidation_balance_to_consume"), }; } - pub fn depositRequestsStartIndex(self: *const BeaconStateAllForks) *u64 { + pub fn setConsolidationBalanceToConsume(self: *BeaconState, balance: u64) !void { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => panic("deposit_requests_start_index is not available in {}", .{self}), - inline else => |state| &state.deposit_requests_start_index, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("consolidation_balance_to_consume", balance), }; } - pub fn depositBalanceToConsume(self: *const BeaconStateAllForks) *Gwei { + pub fn earliestConsolidationEpoch(self: *BeaconState) !u64 { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => panic("deposit_balance_to_consume is not available in {}", .{self}), - inline else => |state| &state.deposit_balance_to_consume, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("earliest_consolidation_epoch"), }; } - pub fn exitBalanceToConsume(self: *const BeaconStateAllForks) *Gwei { + pub fn setEarliestConsolidationEpoch(self: *BeaconState, epoch: u64) !void { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => panic("exit_balance_to_consume is not available in {}", .{self}), - inline else => |state| &state.exit_balance_to_consume, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("earliest_consolidation_epoch", epoch), }; } - pub fn earliestExitEpoch(self: *const BeaconStateAllForks) *Epoch { + pub fn pendingDeposits(self: *BeaconState) !ct.electra.PendingDeposits.TreeView { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => panic("earliest_exit_epoch is not available in {}", .{self}), - inline else => |state| &state.earliest_exit_epoch, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("pending_deposits"), }; } - pub fn consolidationBalanceToConsume(self: *const BeaconStateAllForks) *Gwei { + pub fn setPendingDeposits(self: *BeaconState, deposits: ct.electra.PendingDeposits.TreeView) !void { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => panic("consolidation_balance_to_consume is not available in {}", .{self}), - inline else => |state| &state.consolidation_balance_to_consume, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("pending_deposits", deposits), }; } - pub fn earliestConsolidationEpoch(self: *const BeaconStateAllForks) *Epoch { + pub fn pendingPartialWithdrawals(self: *BeaconState) !ct.electra.PendingPartialWithdrawals.TreeView { return switch (self.*) { - inline .phase0, .altair, .bellatrix, .capella, .deneb => panic("earliest_consolidation_epoch is not available in {}", .{self}), - inline else => |state| &state.earliest_consolidation_epoch, + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("pending_partial_withdrawals"), }; } - pub fn pendingDeposits(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(PendingDeposit) { + pub fn setPendingPartialWithdrawals(self: *BeaconState, pending_partial_withdrawals: ct.electra.PendingPartialWithdrawals.TreeView) !void { return switch (self.*) { - inline .electra, .fulu => |state| &state.pending_deposits, - else => panic("pending_deposits is not available in {}", .{self}), + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("pending_partial_withdrawals", pending_partial_withdrawals), }; } - pub fn pendingPartialWithdrawals(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(PendingPartialWithdrawal) { + pub fn pendingConsolidations(self: *BeaconState) !ct.electra.PendingConsolidations.TreeView { return switch (self.*) { - inline .electra, .fulu => |state| &state.pending_partial_withdrawals, - else => panic("pending_partial_withdrawals is not available in {}", .{self}), + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.get("pending_consolidations"), }; } - pub fn pendingConsolidations(self: *const BeaconStateAllForks) *std.ArrayListUnmanaged(PendingConsolidation) { + pub fn setPendingConsolidations(self: *BeaconState, consolidations: ct.electra.PendingConsolidations.TreeView) !void { return switch (self.*) { - inline .electra, .fulu => |state| &state.pending_consolidations, - else => panic("pending_consolidations is not available in {}", .{self}), + .phase0, .altair, .bellatrix, .capella, .deneb => error.InvalidAtFork, + inline else => |*state| try state.set("pending_consolidations", consolidations), }; } /// Get proposer_lookahead - /// Returns a slice of ValidatorIndex with length (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH - pub fn proposerLookahead(self: *const BeaconStateAllForks) []ValidatorIndex { + pub fn proposerLookahead(self: *BeaconState) !ct.fulu.ProposerLookahead.TreeView { return switch (self.*) { - .fulu => |state| &state.proposer_lookahead, - else => panic("proposer_lookahead is not available in {}", .{self}), + .phase0, .altair, .bellatrix, .capella, .deneb, .electra => error.InvalidAtFork, + inline else => |*state| try state.get("proposer_lookahead"), + }; + } + + /// Returns a read-only slice of proposer_lookahead values. + /// Caller owns the returned slice and must free it with the same allocator. + pub fn proposerLookaheadSlice(self: *BeaconState, allocator: Allocator) !*[ct.fulu.ProposerLookahead.length]u64 { + var lookahead_view = try self.proposerLookahead(); + return @ptrCast(try lookahead_view.getAll(allocator)); + } + + pub fn setProposerLookahead(self: *BeaconState, proposer_lookahead: *const [ct.fulu.ProposerLookahead.length]u64) !void { + return switch (self.*) { + .phase0, .altair, .bellatrix, .capella, .deneb, .electra => error.InvalidAtFork, + inline else => |*state| try state.setValue("proposer_lookahead", proposer_lookahead), }; } /// Copies fields of `BeaconState` from type `F` to type `T`, provided they have the same field name. + /// The cache of original state is cleared after the copy is complete. fn populateFields( comptime F: type, comptime T: type, allocator: Allocator, - state: *F.Type, - ) !*T.Type { - var upgraded = try allocator.create(T.Type); - errdefer allocator.destroy(upgraded); - upgraded.* = T.default_value; + pool: *Node.Pool, + state: F.TreeView, + ) !T.TreeView { + // first ensure that the source state is committed + var committed_state = state; + try committed_state.commit(); + + var upgraded = try T.TreeView.fromValue(allocator, pool, &T.default_value); + errdefer upgraded.deinit(); + inline for (F.fields) |f| { - if (@hasField(T.Type, f.name)) { - if (comptime isFixedType(f.type)) { - try f.type.clone(&@field(state, f.name), &@field(upgraded, f.name)); + if (comptime T.hasField(f.name)) { + if (T.getFieldType(f.name) != f.type) { + // BeaconState of prev_fork and cur_fork has the same field name but different types + // for example latest_execution_payload_header changed from Bellatrix to Capella + // In this case we just skip copying this field and leave it to caller to set properly + continue; + } + + if (comptime isBasicType(f.type)) { + // For basic fields, get() returns a copy, so we can directly set it. + try upgraded.set(f.name, try committed_state.get(f.name)); } else { - if (@TypeOf(@field(upgraded, f.name)) != @TypeOf(f.type.default_value)) { - // 2 BeaconState of prev_fork and cur_fork has same field name but different types - // for example latest_execution_payload_header changed from Bellatrix to Capella - } else { - @field(upgraded, f.name) = f.type.default_value; - try f.type.clone(allocator, &@field(state, f.name), &@field(upgraded, f.name)); - } + // For composite fields, get() returns a borrowed TreeView backed by committed_state caches. + // Clone it to create an owned view, then transfer ownership to upgraded. + var field_view = try committed_state.get(f.name); + var owned_field_view = try field_view.clone(.{ .transfer_cache = true }); + errdefer owned_field_view.deinit(); + try upgraded.set(f.name, owned_field_view); } } } + try upgraded.commit(); + return upgraded; } @@ -820,167 +843,176 @@ pub const BeaconStateAllForks = union(enum) { /// Allocates a new `state` of the next fork, clones all fields of the current `state` to it and assigns `self` to it. /// Caller must make sure an upgrade is needed by checking BeaconConfig then free upgraded state. /// Caller needs to deinit the old state - pub fn upgradeUnsafe(self: *BeaconStateAllForks, allocator: std.mem.Allocator) !*BeaconStateAllForks { - switch (self.*) { - .phase0 => |state| { - self.* = .{ - .altair = try populateFields( - types.phase0.BeaconState, - types.altair.BeaconState, - allocator, - state, - ), - }; - return self; - }, - .altair => |state| { - self.* = .{ - .bellatrix = try populateFields( - types.altair.BeaconState, - types.bellatrix.BeaconState, - allocator, - state, - ), - }; - return self; - }, - .bellatrix => |state| { - self.* = .{ - .capella = try populateFields( - types.bellatrix.BeaconState, - types.capella.BeaconState, - allocator, - state, - ), - }; - return self; - }, - .capella => |state| { - self.* = .{ - .deneb = try populateFields( - types.capella.BeaconState, - types.deneb.BeaconState, - allocator, - state, - ), - }; - return self; - }, - .deneb => |state| { - self.* = .{ - .electra = try populateFields( - types.deneb.BeaconState, - types.electra.BeaconState, - allocator, - state, - ), - }; - return self; - }, - .electra => |state| { - self.* = .{ - .fulu = try populateFields( - types.electra.BeaconState, - types.fulu.BeaconState, - allocator, - state, - ), - }; - return self; - }, - .fulu => { - @panic("upgrade state from fulu to glaos unimplemented"); - }, - } + pub fn upgradeUnsafe(self: *BeaconState) !BeaconState { + return switch (self.*) { + .phase0 => |state| .{ + .altair = try populateFields( + ct.phase0.BeaconState, + ct.altair.BeaconState, + state.base_view.allocator, + state.base_view.pool, + state, + ), + }, + .altair => |state| .{ + .bellatrix = try populateFields( + ct.altair.BeaconState, + ct.bellatrix.BeaconState, + state.base_view.allocator, + state.base_view.pool, + state, + ), + }, + .bellatrix => |state| .{ + .capella = try populateFields( + ct.bellatrix.BeaconState, + ct.capella.BeaconState, + state.base_view.allocator, + state.base_view.pool, + state, + ), + }, + .capella => |state| .{ + .deneb = try populateFields( + ct.capella.BeaconState, + ct.deneb.BeaconState, + state.base_view.allocator, + state.base_view.pool, + state, + ), + }, + .deneb => |state| .{ + .electra = try populateFields( + ct.deneb.BeaconState, + ct.electra.BeaconState, + state.base_view.allocator, + state.base_view.pool, + state, + ), + }, + .electra => |state| .{ + .fulu = try populateFields( + ct.electra.BeaconState, + ct.fulu.BeaconState, + state.base_view.allocator, + state.base_view.pool, + state, + ), + }, + .fulu => error.InvalidAtFork, + }; } }; test "electra - sanity" { const allocator = std.testing.allocator; - var electra_state = types.electra.BeaconState.default_value; - electra_state.slot = 12345; - var beacon_state = BeaconStateAllForks{ - .electra = &electra_state, - }; + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var beacon_state = try BeaconState.fromValue(allocator, &pool, .electra, &ct.electra.BeaconState.default_value); + defer beacon_state.deinit(); - try std.testing.expect(beacon_state.genesisTime() == 0); - try std.testing.expectEqualSlices(u8, &[_]u8{0} ** 32, &beacon_state.genesisValidatorsRoot()); - try std.testing.expect(beacon_state.slot() == 12345); - const slot = beacon_state.slotPtr(); - slot.* = 2025; - try std.testing.expect(beacon_state.slot() == 2025); + try beacon_state.setSlot(12345); - var out: [32]u8 = undefined; - try beacon_state.hashTreeRoot(allocator, &out); - try expect(!std.mem.eql(u8, &[_]u8{0} ** 32, &out)); + try std.testing.expect((try beacon_state.genesisTime()) == 0); + try std.testing.expectEqualSlices(u8, &[_]u8{0} ** 32, (try beacon_state.genesisValidatorsRoot())[0..]); + try std.testing.expect((try beacon_state.slot()) == 12345); + try beacon_state.setSlot(2025); + try std.testing.expect((try beacon_state.slot()) == 2025); + + const out: *const [32]u8 = try beacon_state.hashTreeRoot(); + try expect(!std.mem.eql(u8, (&[_]u8{0} ** 32)[0..], out.*[0..])); // TODO: more tests } test "clone - sanity" { const allocator = std.testing.allocator; - var electra_state = types.electra.BeaconState.default_value; - electra_state.slot = 12345; - var beacon_state = BeaconStateAllForks{ - .electra = &electra_state, - }; + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var beacon_state = try BeaconState.fromValue(allocator, &pool, .electra, &ct.electra.BeaconState.default_value); + defer beacon_state.deinit(); + + try beacon_state.setSlot(12345); + try beacon_state.commit(); // test the clone() and deinit() works fine without memory leak - const cloned_state = try beacon_state.clone(allocator); - try expect(cloned_state.slot() == 12345); - defer { - cloned_state.deinit(allocator); - allocator.destroy(cloned_state); + var cloned_state = try beacon_state.clone(.{}); + defer cloned_state.deinit(); + + try expect((try cloned_state.slot()) == 12345); +} + +test "clone - cases" { + const allocator = std.testing.allocator; + + const TestCase = struct { + name: []const u8, + slot_set: u64, + commit_before_clone: bool, + expected_slot: u64, + }; + + const test_Case = [_]TestCase{ + .{ .name = "commit before clone", .slot_set = 12345, .commit_before_clone = true, .expected_slot = 12345 }, + .{ .name = "no commit before clone", .slot_set = 12345, .commit_before_clone = false, .expected_slot = 0 }, + }; + + inline for (test_Case) |tc| { + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); + + var beacon_state = try BeaconState.fromValue(allocator, &pool, .electra, &ct.electra.BeaconState.default_value); + defer beacon_state.deinit(); + + try beacon_state.setSlot(tc.slot_set); + try expect((try beacon_state.slot()) == tc.slot_set); + + if (tc.commit_before_clone) { + try beacon_state.commit(); + } + + var cloned_state = try beacon_state.clone(.{}); + defer cloned_state.deinit(); + + const got = try cloned_state.slot(); + if (got != tc.expected_slot) { + std.debug.print("clone case '{s}' failed: got slot {}, expected {}\n", .{ tc.name, got, tc.expected_slot }); + return error.TestExpectedEqual; + } } } test "upgrade state - sanity" { const allocator = std.testing.allocator; - const phase0_state = try allocator.create(types.phase0.BeaconState.Type); - phase0_state.* = types.phase0.BeaconState.default_value; + var pool = try Node.Pool.init(allocator, 500_000); + defer pool.deinit(); - var phase0 = BeaconStateAllForks{ .phase0 = phase0_state }; - const old_phase0_state = phase0.phase0; - defer { - types.phase0.BeaconState.deinit(allocator, old_phase0_state); - allocator.destroy(old_phase0_state); - } + var phase0_state = try BeaconState.fromValue(allocator, &pool, .phase0, &ct.phase0.BeaconState.default_value); + defer phase0_state.deinit(); - var altair = try phase0.upgradeUnsafe(allocator); - const old_altair_state = altair.altair; - defer { - types.altair.BeaconState.deinit(allocator, old_altair_state); - allocator.destroy(old_altair_state); - } + var altair_state = try phase0_state.upgradeUnsafe(); + defer altair_state.deinit(); + try expect(altair_state.forkSeq() == .altair); - var bellatrix = try altair.upgradeUnsafe(allocator); - const old_bellatrix_state = bellatrix.bellatrix; - defer { - types.bellatrix.BeaconState.deinit(allocator, old_bellatrix_state); - allocator.destroy(old_bellatrix_state); - } + var bellatrix_state = try altair_state.upgradeUnsafe(); + defer bellatrix_state.deinit(); + try expect(bellatrix_state.forkSeq() == .bellatrix); - var capella = try bellatrix.upgradeUnsafe(allocator); - const old_capella_state = capella.capella; - defer { - types.capella.BeaconState.deinit(allocator, old_capella_state); - allocator.destroy(old_capella_state); - } + var capella_state = try bellatrix_state.upgradeUnsafe(); + defer capella_state.deinit(); + try expect(capella_state.forkSeq() == .capella); - var deneb = try capella.upgradeUnsafe(allocator); - const old_deneb_state = deneb.deneb; - defer { - types.deneb.BeaconState.deinit(allocator, old_deneb_state); - allocator.destroy(old_deneb_state); - } + var deneb_state = try capella_state.upgradeUnsafe(); + defer deneb_state.deinit(); + try expect(deneb_state.forkSeq() == .deneb); - var electra = try deneb.upgradeUnsafe(allocator); - const old_electra_state = electra.electra; - defer { - types.electra.BeaconState.deinit(allocator, old_electra_state); - allocator.destroy(old_electra_state); - } + var electra_state = try deneb_state.upgradeUnsafe(); + defer electra_state.deinit(); + try expect(electra_state.forkSeq() == .electra); - var fulu = try electra.upgradeUnsafe(allocator); - defer fulu.deinit(allocator); + var fulu_state = try electra_state.upgradeUnsafe(); + defer fulu_state.deinit(); + try expect(fulu_state.forkSeq() == .fulu); } diff --git a/src/state_transition/types/execution_payload.zig b/src/state_transition/types/execution_payload.zig index 5e55940c3..309f50e6b 100644 --- a/src/state_transition/types/execution_payload.zig +++ b/src/state_transition/types/execution_payload.zig @@ -1,21 +1,17 @@ const std = @import("std"); -const types = @import("consensus_types"); +const ct = @import("consensus_types"); +const ForkSeq = @import("config").ForkSeq; const Allocator = std.mem.Allocator; -const Root = types.primitive.Root.Type; -const ExecutionAddress = types.primitive.ExecutionAddress; +const Root = ct.primitive.Root.Type; +const ExecutionAddress = ct.primitive.ExecutionAddress; +// TODO Either make this a union(ForkSeq) or else remove the duplicate union branches pub const ExecutionPayload = union(enum) { - bellatrix: *const types.bellatrix.ExecutionPayload.Type, - capella: *const types.capella.ExecutionPayload.Type, - deneb: *const types.deneb.ExecutionPayload.Type, - electra: *const types.electra.ExecutionPayload.Type, - - pub fn isCapellaPayload(self: *const ExecutionPayload) bool { - return switch (self.*) { - .bellatrix => false, - else => true, - }; - } + bellatrix: ct.bellatrix.ExecutionPayload.Type, + capella: ct.capella.ExecutionPayload.Type, + deneb: ct.deneb.ExecutionPayload.Type, + electra: ct.electra.ExecutionPayload.Type, + fulu: ct.fulu.ExecutionPayload.Type, /// Converts ExecutionPayload to an owned ExecutionPayloadHeader. pub fn toPayloadHeader(self: *const ExecutionPayload, allocator: Allocator) !ExecutionPayloadHeader { @@ -28,320 +24,374 @@ pub const ExecutionPayload = union(enum) { pub fn createPayloadHeader(self: *const ExecutionPayload, allocator: Allocator, out: *ExecutionPayloadHeader) !void { switch (self.*) { .bellatrix => |payload| { - const header = try allocator.create(types.bellatrix.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(header); - out.* = .{ .bellatrix = header }; - try toExecutionPayloadHeader(allocator, types.bellatrix.ExecutionPayloadHeader.Type, payload, header); + out.* = .{ .bellatrix = undefined }; + try toExecutionPayloadHeader( + allocator, + ct.bellatrix.ExecutionPayloadHeader.Type, + &payload, + &out.bellatrix, + ); errdefer out.deinit(allocator); - try types.bellatrix.Transactions.hashTreeRoot(allocator, &payload.transactions, &header.transactions_root); + try ct.bellatrix.Transactions.hashTreeRoot( + allocator, + &payload.transactions, + &out.bellatrix.transactions_root, + ); }, .capella => |payload| { - const header = try allocator.create(types.capella.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(header); - out.* = .{ .capella = header }; - try toExecutionPayloadHeader(allocator, types.capella.ExecutionPayloadHeader.Type, payload, header); + out.* = .{ .capella = undefined }; + try toExecutionPayloadHeader( + allocator, + ct.capella.ExecutionPayloadHeader.Type, + &payload, + &out.capella, + ); errdefer out.deinit(allocator); - try types.bellatrix.Transactions.hashTreeRoot(allocator, &payload.transactions, &header.transactions_root); - try types.capella.Withdrawals.hashTreeRoot(allocator, &payload.withdrawals, &header.withdrawals_root); + try ct.bellatrix.Transactions.hashTreeRoot( + allocator, + &payload.transactions, + &out.capella.transactions_root, + ); + try ct.capella.Withdrawals.hashTreeRoot( + allocator, + &payload.withdrawals, + &out.capella.withdrawals_root, + ); }, .deneb => |payload| { - const header = try allocator.create(types.deneb.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(header); - out.* = .{ .deneb = header }; - try toExecutionPayloadHeader(allocator, types.deneb.ExecutionPayloadHeader.Type, payload, header); + out.* = .{ .deneb = undefined }; + try toExecutionPayloadHeader( + allocator, + ct.deneb.ExecutionPayloadHeader.Type, + &payload, + &out.deneb, + ); errdefer out.deinit(allocator); - try types.bellatrix.Transactions.hashTreeRoot(allocator, &payload.transactions, &header.transactions_root); - try types.capella.Withdrawals.hashTreeRoot(allocator, &payload.withdrawals, &header.withdrawals_root); - header.blob_gas_used = payload.blob_gas_used; - header.excess_blob_gas = payload.excess_blob_gas; + try ct.bellatrix.Transactions.hashTreeRoot( + allocator, + &payload.transactions, + &out.deneb.transactions_root, + ); + try ct.capella.Withdrawals.hashTreeRoot( + allocator, + &payload.withdrawals, + &out.deneb.withdrawals_root, + ); + out.deneb.blob_gas_used = payload.blob_gas_used; + out.deneb.excess_blob_gas = payload.excess_blob_gas; }, .electra => |payload| { - const header = try allocator.create(types.electra.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(header); - out.* = .{ .electra = header }; - try toExecutionPayloadHeader(allocator, types.electra.ExecutionPayloadHeader.Type, payload, header); + out.* = .{ .electra = undefined }; + // Electra reuses Deneb execution payload types. + try toExecutionPayloadHeader( + allocator, + ct.electra.ExecutionPayloadHeader.Type, + &payload, + &out.electra, + ); errdefer out.deinit(allocator); - try types.bellatrix.Transactions.hashTreeRoot(allocator, &payload.transactions, &header.transactions_root); - try types.capella.Withdrawals.hashTreeRoot(allocator, &payload.withdrawals, &header.withdrawals_root); - header.blob_gas_used = payload.blob_gas_used; - header.excess_blob_gas = payload.excess_blob_gas; + try ct.bellatrix.Transactions.hashTreeRoot( + allocator, + &payload.transactions, + &out.electra.transactions_root, + ); + try ct.capella.Withdrawals.hashTreeRoot( + allocator, + &payload.withdrawals, + &out.electra.withdrawals_root, + ); + out.electra.blob_gas_used = payload.blob_gas_used; + out.electra.excess_blob_gas = payload.excess_blob_gas; + }, + .fulu => |payload| { + out.* = .{ .fulu = undefined }; + // Fulu reuses Electra (which reuses Deneb) execution payload types. + try toExecutionPayloadHeader( + allocator, + ct.fulu.ExecutionPayloadHeader.Type, + &payload, + &out.fulu, + ); + errdefer out.deinit(allocator); + try ct.bellatrix.Transactions.hashTreeRoot( + allocator, + &payload.transactions, + &out.fulu.transactions_root, + ); + try ct.capella.Withdrawals.hashTreeRoot( + allocator, + &payload.withdrawals, + &out.fulu.withdrawals_root, + ); + out.fulu.blob_gas_used = payload.blob_gas_used; + out.fulu.excess_blob_gas = payload.excess_blob_gas; }, } } pub fn getParentHash(self: *const ExecutionPayload) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.parent_hash, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.parent_hash, }; } pub fn getFeeRecipient(self: *const ExecutionPayload) ExecutionAddress { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.fee_recipient, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.fee_recipient, }; } pub fn stateRoot(self: *const ExecutionPayload) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.state_root, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.state_root, }; } pub fn getReceiptsRoot(self: *const ExecutionPayload) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.receipts_root, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.receipts_root, }; } - pub fn getLogsBloom(self: *const ExecutionPayload) types.bellatrix.LogsBoom.Type { + pub fn getLogsBloom(self: *const ExecutionPayload) ct.bellatrix.LogsBoom.Type { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.logs_bloom, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.logs_bloom, }; } pub fn getPrevRandao(self: *const ExecutionPayload) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.prev_randao, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.prev_randao, }; } pub fn getBlockNumber(self: *const ExecutionPayload) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.block_number, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.block_number, }; } pub fn getGasLimit(self: *const ExecutionPayload) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.gas_limit, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.gas_limit, }; } pub fn getGasUsed(self: *const ExecutionPayload) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.gas_used, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.gas_used, }; } pub fn getTimestamp(self: *const ExecutionPayload) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.timestamp, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.timestamp, }; } - pub fn getExtraData(self: *const ExecutionPayload) types.bellatrix.ExtraData.Type { + pub fn getExtraData(self: *const ExecutionPayload) ct.bellatrix.ExtraData.Type { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.extra_data, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.extra_data, }; } pub fn getBaseFeePerGas(self: *const ExecutionPayload) u256 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.base_fee_per_gas, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.base_fee_per_gas, }; } pub fn getBlockHash(self: *const ExecutionPayload) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.block_hash, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.block_hash, }; } - pub fn getTransactions(self: *const ExecutionPayload) types.bellatrix.Transactions.Type { + pub fn getTransactions(self: *const ExecutionPayload) ct.bellatrix.Transactions.Type { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload| payload.transactions, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload| payload.transactions, }; } - pub fn getWithdrawals(self: *const ExecutionPayload) types.capella.Withdrawals.Type { + pub fn getWithdrawals(self: *const ExecutionPayload) ct.capella.Withdrawals.Type { return switch (self.*) { .bellatrix => @panic("Withdrawals are not available in bellatrix"), - inline .capella, .deneb, .electra => |payload| payload.withdrawals, + inline .capella, .deneb, .electra, .fulu => |payload| payload.withdrawals, }; } pub fn getBlobGasUsed(self: *const ExecutionPayload) u64 { return switch (self.*) { inline .bellatrix, .capella => @panic("Blob gas used is not available in bellatrix or capella"), - inline .deneb, .electra => |payload| payload.blob_gas_used, + inline .deneb, .electra, .fulu => |payload| payload.blob_gas_used, }; } pub fn getExcessBlobGas(self: *const ExecutionPayload) u64 { return switch (self.*) { inline .bellatrix, .capella => @panic("Excess blob gas is not available in bellatrix or capella"), - inline .deneb, .electra => |payload| payload.excess_blob_gas, + inline .deneb, .electra, .fulu => |payload| payload.excess_blob_gas, }; } }; +// TODO Either make this a union(ForkSeq) or else remove the duplicate union branches pub const ExecutionPayloadHeader = union(enum) { - bellatrix: *const types.bellatrix.ExecutionPayloadHeader.Type, - capella: *const types.capella.ExecutionPayloadHeader.Type, - deneb: *const types.deneb.ExecutionPayloadHeader.Type, - electra: *const types.electra.ExecutionPayloadHeader.Type, + bellatrix: ct.bellatrix.ExecutionPayloadHeader.Type, + capella: ct.capella.ExecutionPayloadHeader.Type, + deneb: ct.deneb.ExecutionPayloadHeader.Type, + electra: ct.electra.ExecutionPayloadHeader.Type, + fulu: ct.fulu.ExecutionPayloadHeader.Type, - pub fn isCapellaPayloadHeader(self: *const ExecutionPayloadHeader) bool { - return switch (self.*) { - .bellatrix => false, - else => true, + pub fn init(fork_seq: ForkSeq) !ExecutionPayloadHeader { + return switch (fork_seq) { + .bellatrix => .{ .bellatrix = ct.bellatrix.ExecutionPayloadHeader.default_value }, + .capella => .{ .capella = ct.capella.ExecutionPayloadHeader.default_value }, + .deneb => .{ .deneb = ct.deneb.ExecutionPayloadHeader.default_value }, + .electra => .{ .electra = ct.electra.ExecutionPayloadHeader.default_value }, + .fulu => .{ .fulu = ct.fulu.ExecutionPayloadHeader.default_value }, + else => error.UnexpectedForkSeq, }; } pub fn getParentHash(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.parent_hash, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.parent_hash, }; } pub fn getFeeRecipient(self: *const ExecutionPayloadHeader) ExecutionAddress { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.fee_recipient, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.fee_recipient, }; } pub fn stateRoot(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.state_root, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.state_root, }; } pub fn getReceiptsRoot(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.receipts_root, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.receipts_root, }; } - pub fn getLogsBloom(self: *const ExecutionPayloadHeader) types.bellatrix.LogsBoom.Type { + pub fn getLogsBloom(self: *const ExecutionPayloadHeader) ct.bellatrix.LogsBoom.Type { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.logs_bloom, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.logs_bloom, }; } pub fn getPrevRandao(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.prev_randao, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.prev_randao, }; } pub fn getBlockNumber(self: *const ExecutionPayloadHeader) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.block_number, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.block_number, }; } pub fn getGasLimit(self: *const ExecutionPayloadHeader) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.gas_limit, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.gas_limit, }; } pub fn getGasUsed(self: *const ExecutionPayloadHeader) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.gas_used, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.gas_used, }; } pub fn getTimestamp(self: *const ExecutionPayloadHeader) u64 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.timestamp, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.timestamp, }; } - pub fn getExtraData(self: *const ExecutionPayloadHeader) types.bellatrix.ExtraData.Type { + pub fn getExtraData(self: *const ExecutionPayloadHeader) ct.bellatrix.ExtraData.Type { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.extra_data, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.extra_data, }; } pub fn getBaseFeePerGas(self: *const ExecutionPayloadHeader) u256 { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.base_fee_per_gas, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.base_fee_per_gas, }; } pub fn getBlockHash(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.block_hash, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.block_hash, }; } pub fn getTransactionsRoot(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { - inline .bellatrix, .capella, .deneb, .electra => |payload_header| payload_header.transactions_root, + inline .bellatrix, .capella, .deneb, .electra, .fulu => |payload_header| payload_header.transactions_root, }; } pub fn getWithdrawalsRoot(self: *const ExecutionPayloadHeader) Root { return switch (self.*) { .bellatrix => @panic("Withdrawals are not available in bellatrix"), - inline .capella, .deneb, .electra => |payload_header| payload_header.withdrawals_root, + inline .capella, .deneb, .electra, .fulu => |payload_header| payload_header.withdrawals_root, }; } pub fn getBlobGasUsed(self: *const ExecutionPayloadHeader) u64 { return switch (self.*) { inline .bellatrix, .capella => @panic("Blob gas used is not available in bellatrix or capella"), - inline .deneb, .electra => |payload_header| payload_header.blob_gas_used, + inline .deneb, .electra, .fulu => |payload_header| payload_header.blob_gas_used, }; } pub fn getExcessBlobGas(self: *const ExecutionPayloadHeader) u64 { return switch (self.*) { inline .bellatrix, .capella => @panic("Excess blob gas is not available in bellatrix or capella"), - inline .deneb, .electra => |payload_header| payload_header.excess_blob_gas, + inline .deneb, .electra, .fulu => |payload_header| payload_header.excess_blob_gas, }; } pub fn clone(self: *const ExecutionPayloadHeader, allocator: Allocator, out: *ExecutionPayloadHeader) !void { switch (self.*) { .bellatrix => |header| { - const cloned_header = try allocator.create(types.bellatrix.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(cloned_header); - out.* = .{ .bellatrix = cloned_header }; - try types.bellatrix.ExecutionPayloadHeader.clone(allocator, header, cloned_header); + try ct.bellatrix.ExecutionPayloadHeader.clone(allocator, &header, &out.bellatrix); }, .capella => |header| { - const cloned_header = try allocator.create(types.capella.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(cloned_header); - out.* = .{ .capella = cloned_header }; - try types.capella.ExecutionPayloadHeader.clone(allocator, header, cloned_header); + try ct.capella.ExecutionPayloadHeader.clone(allocator, &header, &out.capella); }, .deneb => |header| { - const cloned_header = try allocator.create(types.deneb.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(cloned_header); - out.* = .{ .deneb = cloned_header }; - try types.deneb.ExecutionPayloadHeader.clone(allocator, header, cloned_header); + try ct.deneb.ExecutionPayloadHeader.clone(allocator, &header, &out.deneb); }, .electra => |header| { - const cloned_header = try allocator.create(types.electra.ExecutionPayloadHeader.Type); - errdefer allocator.destroy(cloned_header); - out.* = .{ .electra = cloned_header }; - try types.electra.ExecutionPayloadHeader.clone(allocator, header, cloned_header); + try ct.electra.ExecutionPayloadHeader.clone(allocator, &header, &out.electra); + }, + .fulu => |header| { + try ct.fulu.ExecutionPayloadHeader.clone(allocator, &header, &out.fulu); }, } } - pub fn deinit(self: *const ExecutionPayloadHeader, allocator: Allocator) void { - switch (self.*) { - .bellatrix => |header| types.bellatrix.ExecutionPayloadHeader.deinit(allocator, @constCast(header)), - .capella => |header| types.capella.ExecutionPayloadHeader.deinit(allocator, @constCast(header)), - .deneb => |header| types.deneb.ExecutionPayloadHeader.deinit(allocator, @constCast(header)), - .electra => |header| types.electra.ExecutionPayloadHeader.deinit(allocator, @constCast(header)), - } - } - - pub fn destroy(self: *const ExecutionPayloadHeader, allocator: Allocator) void { + pub fn deinit(self: *ExecutionPayloadHeader, allocator: Allocator) void { switch (self.*) { - .bellatrix => |header| allocator.destroy(@constCast(header)), - .capella => |header| allocator.destroy(@constCast(header)), - .deneb => |header| allocator.destroy(@constCast(header)), - .electra => |header| allocator.destroy(@constCast(header)), + .bellatrix => |*header| ct.bellatrix.ExecutionPayloadHeader.deinit(allocator, header), + .capella => |*header| ct.capella.ExecutionPayloadHeader.deinit(allocator, header), + .deneb => |*header| ct.deneb.ExecutionPayloadHeader.deinit(allocator, header), + .electra => |*header| ct.electra.ExecutionPayloadHeader.deinit(allocator, header), + .fulu => |*header| ct.fulu.ExecutionPayloadHeader.deinit(allocator, header), } } }; /// Converts some basic fields of ExecutionPayload to ExecutionPayloadHeader. +/// Can also be used to upgrade between different ExecutionPayloadHeader versions. /// Writes the fields directly into the provided result pointer. pub fn toExecutionPayloadHeader( allocator: Allocator, @@ -362,40 +412,44 @@ pub fn toExecutionPayloadHeader( result.extra_data = try payload.extra_data.clone(allocator); result.base_fee_per_gas = payload.base_fee_per_gas; result.block_hash = payload.block_hash; + if (@hasField(@TypeOf(payload.*), "transactions_root")) { + result.transactions_root = payload.transactions_root; + } + if (@hasField(@TypeOf(payload.*), "withdrawals_root")) { + result.withdrawals_root = payload.withdrawals_root; + } // remaining fields are left unset } test "electra - sanity" { - const payload = types.electra.ExecutionPayload.Type{ - .parent_hash = types.primitive.Root.default_value, - .fee_recipient = types.primitive.Bytes20.default_value, - .state_root = types.primitive.Root.default_value, - .receipts_root = types.primitive.Root.default_value, - .logs_bloom = types.bellatrix.LogsBloom.default_value, - .prev_randao = types.primitive.Root.default_value, + const payload = ct.electra.ExecutionPayload.Type{ + .parent_hash = ct.primitive.Root.default_value, + .fee_recipient = ct.primitive.Bytes20.default_value, + .state_root = ct.primitive.Root.default_value, + .receipts_root = ct.primitive.Root.default_value, + .logs_bloom = ct.bellatrix.LogsBloom.default_value, + .prev_randao = ct.primitive.Root.default_value, .block_number = 12345, .gas_limit = 0, .gas_used = 0, .timestamp = 0, - .extra_data = types.bellatrix.ExtraData.default_value, + .extra_data = ct.bellatrix.ExtraData.default_value, .base_fee_per_gas = 0, - .block_hash = types.primitive.Root.default_value, - .transactions = types.bellatrix.Transactions.Type{}, - .withdrawals = types.capella.Withdrawals.Type{}, + .block_hash = ct.primitive.Root.default_value, + .transactions = ct.bellatrix.Transactions.Type{}, + .withdrawals = ct.capella.Withdrawals.Type{}, .blob_gas_used = 0, .excess_blob_gas = 0, }; - const electra_payload: ExecutionPayload = .{ .electra = &payload }; - var header_out = types.electra.ExecutionPayloadHeader.default_value; - var header: ExecutionPayloadHeader = .{ .electra = &header_out }; + const electra_payload: ExecutionPayload = .{ .electra = payload }; + const header_out = ct.electra.ExecutionPayloadHeader.default_value; + var header: ExecutionPayloadHeader = .{ .electra = header_out }; try electra_payload.createPayloadHeader(std.testing.allocator, &header); - defer std.testing.allocator.destroy(header.electra); defer header.deinit(std.testing.allocator); _ = header.getGasUsed(); try std.testing.expect(header.electra.block_number == payload.block_number); var owned_header = try electra_payload.toPayloadHeader(std.testing.allocator); - defer owned_header.destroy(std.testing.allocator); defer owned_header.deinit(std.testing.allocator); try std.testing.expect(owned_header.electra.block_number == payload.block_number); } diff --git a/src/state_transition/types_test_root.zig b/src/state_transition/types_test_root.zig new file mode 100644 index 000000000..a776c711f --- /dev/null +++ b/src/state_transition/types_test_root.zig @@ -0,0 +1,10 @@ +// Root file to run only state_transition/types tests. +// +// This exists because `zig test` compiles the entire root module before applying +// `--test-filter`. Keeping a small root lets us iterate on a subset without +// fixing unrelated compilation errors across the whole state_transition module. + +test "state_transition types" { + _ = @import("types/beacon_state.zig"); + _ = @import("types/execution_payload.zig"); +} diff --git a/src/state_transition/utils/balance.zig b/src/state_transition/utils/balance.zig index f620f0149..7499b056f 100644 --- a/src/state_transition/utils/balance.zig +++ b/src/state_transition/utils/balance.zig @@ -1,17 +1,21 @@ -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const std = @import("std"); +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const types = @import("consensus_types"); const ValidatorIndex = types.primitive.ValidatorIndex.Type; /// Increase the balance for a validator with the given ``index`` by ``delta``. -pub fn increaseBalance(state: *BeaconStateAllForks, index: ValidatorIndex, delta: u64) void { - const balance = &state.balances().items[index]; - balance.* = state.balances().items[index] + delta; +pub fn increaseBalance(state: *BeaconState, index: ValidatorIndex, delta: u64) !void { + var balances = try state.balances(); + const current = try balances.get(index); + const next = try std.math.add(u64, current, delta); + try balances.set(index, next); } /// Decrease the balance for a validator with the given ``index`` by ``delta``. /// Set to 0 when underflow. -pub fn decreaseBalance(state: *BeaconStateAllForks, index: ValidatorIndex, delta: u64) void { - const balance = &state.balances().items[index]; - const new_balance = if (balance.* > delta) balance.* - delta else 0; - balance.* = @max(0, new_balance); +pub fn decreaseBalance(state: *BeaconState, index: ValidatorIndex, delta: u64) !void { + var balances = try state.balances(); + const current = try balances.get(index); + const next = if (current > delta) current - delta else 0; + try balances.set(index, next); } diff --git a/src/state_transition/utils/block_root.zig b/src/state_transition/utils/block_root.zig index c0cdbb4ca..286b2a5e3 100644 --- a/src/state_transition/utils/block_root.zig +++ b/src/state_transition/utils/block_root.zig @@ -3,12 +3,12 @@ const preset = @import("preset").preset; const Root = types.primitive.Root.Type; const Slot = types.primitive.Slot.Type; const Epoch = types.primitive.Epoch.Type; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const SLOTS_PER_HISTORICAL_ROOT = preset.SLOTS_PER_HISTORICAL_ROOT; const computeStartSlotAtEpoch = @import("./epoch.zig").computeStartSlotAtEpoch; -pub fn getBlockRootAtSlot(state: *const BeaconStateAllForks, slot: Slot) !Root { - const state_slot = state.slot(); +pub fn getBlockRootAtSlot(state: *BeaconState, slot: Slot) !*const [32]u8 { + const state_slot = try state.slot(); if (slot >= state_slot) { return error.SlotTooBig; } @@ -19,10 +19,11 @@ pub fn getBlockRootAtSlot(state: *const BeaconStateAllForks, slot: Slot) !Root { return error.SlotTooSmall; } - return state.blockRoots()[slot % SLOTS_PER_HISTORICAL_ROOT]; + var block_roots = try state.blockRoots(); + return try block_roots.getRoot(slot % SLOTS_PER_HISTORICAL_ROOT); } -pub fn getBlockRoot(state: *const BeaconStateAllForks, epoch: Epoch) !Root { +pub fn getBlockRoot(state: *BeaconState, epoch: Epoch) !*const [32]u8 { return getBlockRootAtSlot(state, computeStartSlotAtEpoch(epoch)); } diff --git a/src/state_transition/utils/bls.zig b/src/state_transition/utils/bls.zig index 2b1bfbe9d..fc4343623 100644 --- a/src/state_transition/utils/bls.zig +++ b/src/state_transition/utils/bls.zig @@ -17,16 +17,15 @@ pub fn sign(secret_key: SecretKey, msg: []const u8) Signature { /// If `pk_validate` is `true`, the public key will be infinity and group checked. /// /// If `sig_groupcheck` is `true`, the signature will be group checked. -pub fn verify(msg: []const u8, pk: *const PublicKey, sig: *const Signature, in_pk_validate: ?bool, in_sig_groupcheck: ?bool) bool { +pub fn verify(msg: []const u8, pk: *const PublicKey, sig: *const Signature, in_pk_validate: ?bool, in_sig_groupcheck: ?bool) blst.BlstError!void { const sig_groupcheck = in_sig_groupcheck orelse false; const pk_validate = in_pk_validate orelse false; - sig.verify(sig_groupcheck, msg, DST, null, pk, pk_validate) catch return false; - return true; + try sig.verify(sig_groupcheck, msg, DST, null, pk, pk_validate); } -threadlocal var pairing_buf: [blst.Pairing.sizeOf()]u8 = undefined; - pub fn fastAggregateVerify(msg: []const u8, pks: []const PublicKey, sig: *const Signature, in_pk_validate: ?bool, in_sigs_group_check: ?bool) !bool { + var pairing_buf: [blst.Pairing.sizeOf()]u8 = undefined; + const sigs_groupcheck = in_sigs_group_check orelse false; const pks_validate = in_pk_validate orelse false; return sig.fastAggregateVerify(sigs_groupcheck, &pairing_buf, msg[0..32], DST, pks, pks_validate) catch return false; @@ -44,7 +43,7 @@ test "bls - sanity" { const msg = [_]u8{1} ** 32; const sig = sign(sk, &msg); const pk = sk.toPublicKey(); - try std.testing.expect(verify(&msg, &pk, &sig, null, null)); + try verify(&msg, &pk, &sig, null, null); var pks = [_]PublicKey{pk}; var pks_slice: []const PublicKey = pks[0..1]; diff --git a/src/state_transition/utils/capella.zig b/src/state_transition/utils/capella.zig index b4f8f5d9b..720d90e40 100644 --- a/src/state_transition/utils/capella.zig +++ b/src/state_transition/utils/capella.zig @@ -5,6 +5,6 @@ const ETH1_ADDRESS_WITHDRAWAL_PREFIX = c.ETH1_ADDRESS_WITHDRAWAL_PREFIX; pub const WithdrawalCredentials = types.primitive.Root.Type; /// https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/capella/beacon-chain.md#has_eth1_withdrawal_credential -pub fn hasEth1WithdrawalCredential(withdrawal_credentials: WithdrawalCredentials) bool { +pub fn hasEth1WithdrawalCredential(withdrawal_credentials: *const WithdrawalCredentials) bool { return withdrawal_credentials[0] == ETH1_ADDRESS_WITHDRAWAL_PREFIX; } diff --git a/src/state_transition/utils/deposit.zig b/src/state_transition/utils/deposit.zig index 8d46cc94e..1115a8ae4 100644 --- a/src/state_transition/utils/deposit.zig +++ b/src/state_transition/utils/deposit.zig @@ -2,27 +2,30 @@ const types = @import("consensus_types"); const preset = @import("preset").preset; const Eth1Data = types.phase0.Eth1Data.Type; const MAX_DEPOSITS = preset.MAX_DEPOSITS; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; -pub fn getEth1DepositCount(cached_state: *const CachedBeaconStateAllForks, eth1_data: ?*const Eth1Data) u64 { +pub fn getEth1DepositCount(cached_state: *CachedBeaconState, eth1_data: ?*const Eth1Data) !u64 { const state = cached_state.state; - const eth1_data_to_use = eth1_data orelse state.eth1Data(); + const deposit_count: u64 = if (eth1_data) |d| d.deposit_count else blk: { + var eth1_data_view = try state.eth1Data(); + break :blk try eth1_data_view.get("deposit_count"); + }; - if (state.isPostElectra()) { - const deposit_requests_start_index = state.depositRequestsStartIndex(); - // eth1DataIndexLimit = min(UintNum64, UintBn64) can be safely casted as UintNum64 - // since the result lies within upper and lower bound of UintNum64 - const eth1_data_index_limit: u64 = if (eth1_data_to_use.deposit_count < deposit_requests_start_index.*) - eth1_data_to_use.deposit_count + const eth1_deposit_index = try state.eth1DepositIndex(); + + if (state.forkSeq().gte(.electra)) { + const deposit_requests_start_index = try state.depositRequestsStartIndex(); + const eth1_data_index_limit: u64 = if (deposit_count < deposit_requests_start_index) + deposit_count else - deposit_requests_start_index.*; + deposit_requests_start_index; - return if (state.eth1DepositIndex() < eth1_data_index_limit) - @min(MAX_DEPOSITS, eth1_data_index_limit - state.eth1DepositIndex()) + return if (eth1_deposit_index < eth1_data_index_limit) + @min(MAX_DEPOSITS, eth1_data_index_limit - eth1_deposit_index) else 0; } - return @min(MAX_DEPOSITS, eth1_data_to_use.deposit_count - state.eth1DepositIndex()); + return @min(MAX_DEPOSITS, deposit_count - eth1_deposit_index); } diff --git a/src/state_transition/utils/electra.zig b/src/state_transition/utils/electra.zig index aafa7d2c4..4ba368b0e 100644 --- a/src/state_transition/utils/electra.zig +++ b/src/state_transition/utils/electra.zig @@ -1,69 +1,83 @@ const std = @import("std"); const c = @import("constants"); const COMPOUNDING_WITHDRAWAL_PREFIX = c.COMPOUNDING_WITHDRAWAL_PREFIX; -const types = @import("consensus_types"); +const ct = @import("consensus_types"); const MIN_ACTIVATION_BALANCE = @import("preset").preset.MIN_ACTIVATION_BALANCE; const GENESIS_SLOT = @import("preset").GENESIS_SLOT; -pub const WithdrawalCredentials = types.primitive.Root.Type; -pub const WithdrawalCredentialsLength = types.primitive.Root.length; -const BLSPubkey = types.primitive.BLSPubkey.Type; -const ValidatorIndex = types.primitive.ValidatorIndex.Type; +pub const WithdrawalCredentials = ct.primitive.Root.Type; +const BLSPubkey = ct.primitive.BLSPubkey.Type; +const ValidatorIndex = ct.primitive.ValidatorIndex.Type; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const hasEth1WithdrawalCredential = @import("./capella.zig").hasEth1WithdrawalCredential; const G2_POINT_AT_INFINITY = @import("constants").G2_POINT_AT_INFINITY; const Allocator = std.mem.Allocator; -pub fn hasCompoundingWithdrawalCredential(withdrawal_credentials: WithdrawalCredentials) bool { +pub fn hasCompoundingWithdrawalCredential(withdrawal_credentials: *const WithdrawalCredentials) bool { return withdrawal_credentials[0] == COMPOUNDING_WITHDRAWAL_PREFIX; } -pub fn hasExecutionWithdrawalCredential(withdrawal_credentials: WithdrawalCredentials) bool { +pub fn hasExecutionWithdrawalCredential(withdrawal_credentials: *const WithdrawalCredentials) bool { return hasCompoundingWithdrawalCredential(withdrawal_credentials) or hasEth1WithdrawalCredential(withdrawal_credentials); } -pub fn switchToCompoundingValidator(allocator: Allocator, state_cache: *CachedBeaconStateAllForks, index: ValidatorIndex) !void { - var validator = &state_cache.state.validators().items[index]; +pub fn switchToCompoundingValidator(state_cache: *CachedBeaconState, index: ValidatorIndex) !void { + var validators = try state_cache.state.validators(); + var validator = try validators.get(index); + const old_withdrawal_credentials = try validator.getRoot("withdrawal_credentials"); - // directly modifying the byte leads to types.primitive missing the modification resulting into - // wrong root compute, although slicing can be avoided but anyway this is not going - // to be a hot path so its better to clean slice and avoid side effects - var new_withdrawal_credentials = [_]u8{0} ** WithdrawalCredentialsLength; - std.mem.copyForwards(u8, new_withdrawal_credentials[0..], validator.withdrawal_credentials[0..]); + var new_withdrawal_credentials: [32]u8 = undefined; + @memcpy(new_withdrawal_credentials[0..], old_withdrawal_credentials[0..]); new_withdrawal_credentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; - @memcpy(validator.withdrawal_credentials[0..], new_withdrawal_credentials[0..]); - try queueExcessActiveBalance(allocator, state_cache, index); + + try validator.setValue("withdrawal_credentials", &new_withdrawal_credentials); + + var pubkey: ct.primitive.BLSPubkey.Type = undefined; + try validator.getValue(undefined, "pubkey", &pubkey); + + try queueExcessActiveBalance( + state_cache, + index, + &new_withdrawal_credentials, + pubkey, + ); } -pub fn queueExcessActiveBalance(allocator: Allocator, cached_state: *CachedBeaconStateAllForks, index: ValidatorIndex) !void { +pub fn queueExcessActiveBalance( + cached_state: *CachedBeaconState, + index: ValidatorIndex, + withdrawal_credentials: *const WithdrawalCredentials, + pubkey: ct.primitive.BLSPubkey.Type, +) !void { const state = cached_state.state; - const balance = &state.balances().items[index]; - if (balance.* > MIN_ACTIVATION_BALANCE) { - const validator = state.validators().items[index]; - const excess_balance = balance.* - MIN_ACTIVATION_BALANCE; - balance.* = MIN_ACTIVATION_BALANCE; + var balances = try state.balances(); + const balance = try balances.get(index); + if (balance > MIN_ACTIVATION_BALANCE) { + const excess_balance = balance - MIN_ACTIVATION_BALANCE; + try balances.set(index, MIN_ACTIVATION_BALANCE); - const pending_deposit = types.electra.PendingDeposit.Type{ - .pubkey = validator.pubkey, - .withdrawal_credentials = validator.withdrawal_credentials, + const pending_deposit = ct.electra.PendingDeposit.Type{ + .pubkey = pubkey, + .withdrawal_credentials = withdrawal_credentials.*, .amount = excess_balance, // Use bls.G2_POINT_AT_INFINITY as a signature field placeholder .signature = G2_POINT_AT_INFINITY, // Use GENESIS_SLOT to distinguish from a pending deposit request .slot = GENESIS_SLOT, }; - - try state.pendingDeposits().append(allocator, pending_deposit); + var pending_deposits = try state.pendingDeposits(); + try pending_deposits.pushValue(&pending_deposit); } } -pub fn isPubkeyKnown(cached_state: *const CachedBeaconStateAllForks, pubkey: BLSPubkey) bool { - return isValidatorKnown(cached_state.state, cached_state.getEpochCache().getValidatorIndex(&pubkey)); +pub fn isPubkeyKnown(cached_state: *CachedBeaconState, pubkey: BLSPubkey) !bool { + return try isValidatorKnown(cached_state.state, cached_state.getEpochCache().getValidatorIndex(&pubkey)); } -pub fn isValidatorKnown(state: *const BeaconStateAllForks, index: ?ValidatorIndex) bool { +pub fn isValidatorKnown(state: *BeaconState, index: ?ValidatorIndex) !bool { const validator_index = index orelse return false; - return validator_index < state.validators().items.len; + const validators_count = try state.validatorsCount(); + return validator_index < validators_count; } diff --git a/src/state_transition/utils/epoch.zig b/src/state_transition/utils/epoch.zig index e0f01824e..555503a24 100644 --- a/src/state_transition/utils/epoch.zig +++ b/src/state_transition/utils/epoch.zig @@ -1,11 +1,11 @@ -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const types = @import("consensus_types"); const preset = @import("preset").preset; const GENESIS_EPOCH = @import("preset").GENESIS_EPOCH; const Slot = types.primitive.Slot.Type; const Epoch = types.primitive.Epoch.Type; const SyncPeriod = types.primitive.SyncPeriod.Type; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const Gwei = types.primitive.Gwei.Type; const getActivationExitChurnLimit = @import("../utils/validator.zig").getActivationExitChurnLimit; const getConsolidationChurnLimit = @import("../utils/validator.zig").getConsolidationChurnLimit; @@ -34,16 +34,19 @@ pub fn computeActivationExitEpoch(epoch: Epoch) Epoch { return epoch + 1 + preset.MAX_SEED_LOOKAHEAD; } -pub fn computeExitEpochAndUpdateChurn(cached_state: *const CachedBeaconStateAllForks, exit_balance: Gwei) u64 { - const state = cached_state.state; +pub fn computeExitEpochAndUpdateChurn(cached_state: *const CachedBeaconState, exit_balance: Gwei) !u64 { + var state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); - const state_earliest_exit_epoch = state.earliestExitEpoch(); - var earliest_exit_epoch = @max(state_earliest_exit_epoch.*, computeActivationExitEpoch(epoch_cache.epoch)); + const state_earliest_exit_epoch = try state.earliestExitEpoch(); + var earliest_exit_epoch = @max(state_earliest_exit_epoch, computeActivationExitEpoch(epoch_cache.epoch)); const per_epoch_churn = getActivationExitChurnLimit(epoch_cache); - const state_exit_balance_to_consume = state.exitBalanceToConsume(); + const state_exit_balance_to_consume = try state.exitBalanceToConsume(); // New epoch for exits. - var exit_balance_to_consume = if (state_earliest_exit_epoch.* < earliest_exit_epoch) per_epoch_churn else state_exit_balance_to_consume.*; + var exit_balance_to_consume = if (state_earliest_exit_epoch < earliest_exit_epoch) + per_epoch_churn + else + state_exit_balance_to_consume; // Exit doesn't fit in the current earliest epoch. if (exit_balance > exit_balance_to_consume) { @@ -54,27 +57,27 @@ pub fn computeExitEpochAndUpdateChurn(cached_state: *const CachedBeaconStateAllF } // Consume the balance and update state variables. - state_exit_balance_to_consume.* = exit_balance_to_consume - exit_balance; - state_earliest_exit_epoch.* = earliest_exit_epoch; + try state.setExitBalanceToConsume(exit_balance_to_consume - exit_balance); + try state.setEarliestExitEpoch(earliest_exit_epoch); - return state_earliest_exit_epoch.*; + return earliest_exit_epoch; } -pub fn computeConsolidationEpochAndUpdateChurn(cached_state: *const CachedBeaconStateAllForks, consolidation_balance: Gwei) u64 { - const state = cached_state.state; +pub fn computeConsolidationEpochAndUpdateChurn(cached_state: *const CachedBeaconState, consolidation_balance: Gwei) !u64 { + var state = cached_state.state; const epoch_cache = cached_state.getEpochCache(); - const state_earliest_consolidation_epoch = state.earliestConsolidationEpoch(); - var earliest_consolidation_epoch = @max(state_earliest_consolidation_epoch.*, computeActivationExitEpoch(epoch_cache.epoch)); + const state_earliest_consolidation_epoch = try state.earliestConsolidationEpoch(); + var earliest_consolidation_epoch = @max(state_earliest_consolidation_epoch, computeActivationExitEpoch(epoch_cache.epoch)); const per_epoch_consolidation_churn = getConsolidationChurnLimit(epoch_cache); - const state_consolidation_balance_to_consume = state.consolidationBalanceToConsume(); + const state_consolidation_balance_to_consume = try state.consolidationBalanceToConsume(); // New epoch for consolidations - var consolidation_balance_to_consume = if (state_earliest_consolidation_epoch.* < earliest_consolidation_epoch) + var consolidation_balance_to_consume = if (state_earliest_consolidation_epoch < earliest_consolidation_epoch) per_epoch_consolidation_churn else - state_consolidation_balance_to_consume.*; + state_consolidation_balance_to_consume; // Consolidation doesn't fit in the current earliest epoch. if (consolidation_balance > consolidation_balance_to_consume) { @@ -85,13 +88,13 @@ pub fn computeConsolidationEpochAndUpdateChurn(cached_state: *const CachedBeacon } // Consume the balance and update state variables. - state_consolidation_balance_to_consume.* = consolidation_balance_to_consume - consolidation_balance; - state_earliest_consolidation_epoch.* = earliest_consolidation_epoch; + try state.setConsolidationBalanceToConsume(consolidation_balance_to_consume - consolidation_balance); + try state.setEarliestConsolidationEpoch(earliest_consolidation_epoch); - return state_earliest_consolidation_epoch.*; + return earliest_consolidation_epoch; } -pub fn getCurrentEpoch(state: BeaconStateAllForks) Epoch { +pub fn getCurrentEpoch(state: BeaconState) Epoch { return computeEpochAtSlot(state.slot()); } @@ -99,7 +102,7 @@ pub fn computePreviousEpoch(epoch: Epoch) Epoch { return if (epoch == GENESIS_EPOCH) GENESIS_EPOCH else epoch - 1; } -pub fn getPreviousEpoch(state: BeaconStateAllForks) Epoch { +pub fn getPreviousEpoch(state: BeaconState) Epoch { return computePreviousEpoch(getCurrentEpoch(state)); } diff --git a/src/state_transition/utils/epoch_shuffling.zig b/src/state_transition/utils/epoch_shuffling.zig index 884880ea2..6b270fe6e 100644 --- a/src/state_transition/utils/epoch_shuffling.zig +++ b/src/state_transition/utils/epoch_shuffling.zig @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator; const types = @import("consensus_types"); const ValidatorIndex = types.primitive.ValidatorIndex.Type; const preset = @import("preset").preset; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const getSeed = @import("./seed.zig").getSeed; const c = @import("constants"); const innerShuffleList = @import("./shuffle.zig").innerShuffleList; @@ -103,7 +103,7 @@ test EpochShuffling { } /// active_indices is allocated at consumer side and transfer ownership to EpochShuffling -pub fn computeEpochShuffling(allocator: Allocator, state: *const BeaconStateAllForks, active_indices: []ValidatorIndex, epoch: Epoch) !*EpochShuffling { +pub fn computeEpochShuffling(allocator: Allocator, state: *BeaconState, active_indices: []ValidatorIndex, epoch: Epoch) !*EpochShuffling { var seed = [_]u8{0} ** 32; try getSeed(state, epoch, c.DOMAIN_BEACON_ATTESTER, &seed); return EpochShuffling.init(allocator, seed, epoch, active_indices); diff --git a/src/state_transition/utils/execution.zig b/src/state_transition/utils/execution.zig index 94f50f5d6..e4a8e91da 100644 --- a/src/state_transition/utils/execution.zig +++ b/src/state_transition/utils/execution.zig @@ -6,11 +6,12 @@ const Block = @import("../types/block.zig").Block; const BeaconBlockBody = @import("../types/beacon_block.zig").BeaconBlockBody; const ExecutionPayload = @import("../types/beacon_block.zig").ExecutionPayload; // const ExecutionPayloadHeader -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; +const ZERO_HASH = @import("constants").ZERO_HASH; -pub fn isExecutionEnabled(state: *const BeaconStateAllForks, block: Block) bool { - if (!state.isPostBellatrix()) return false; +pub fn isExecutionEnabled(state: *BeaconState, block: Block) bool { + if (state.forkSeq().lt(.bellatrix)) return false; if (isMergeTransitionComplete(state)) return true; // TODO(bing): in lodestar prod, state root comparison should be enough but spec tests were failing. This switch block is a failsafe for that. @@ -40,28 +41,21 @@ pub fn isExecutionEnabled(state: *const BeaconStateAllForks, block: Block) bool } } -pub fn isMergeTransitionBlock(state: *const BeaconStateAllForks, body: *const BeaconBlockBody) bool { - if (!state.isBellatrix()) { +pub fn isMergeTransitionBlock(state: *BeaconState, body: *const BeaconBlockBody) bool { + if (state.forkSeq() != .bellatrix) { return false; } - return (!isMergeTransitionComplete(state) and - !types.bellatrix.ExecutionPayload.equals(body.getExecutionPayload().bellatrix, types.bellatrix.ExecutionPayload.default_value)); + return (!isMergeTransitionComplete(state) and switch (body.*) { + .bellatrix => |bd| !types.bellatrix.ExecutionPayload.equals(&bd.execution_payload, &types.bellatrix.ExecutionPayload.default_value), + else => false, + }); } -pub fn isMergeTransitionComplete(state: *const BeaconStateAllForks) bool { - if (!state.isPostCapella()) { - return switch (state.*) { - .bellatrix => |s| !types.bellatrix.ExecutionPayloadHeader.equals(&s.latest_execution_payload_header, &types.bellatrix.ExecutionPayloadHeader.default_value), - else => false, - }; +pub fn isMergeTransitionComplete(state: *BeaconState) bool { + if (state.forkSeq().lt(.bellatrix)) { + return false; } - - return switch (state.*) { - .capella => |s| !types.capella.ExecutionPayloadHeader.equals(&s.latest_execution_payload_header, &types.capella.ExecutionPayloadHeader.default_value), - .deneb => |s| !types.deneb.ExecutionPayloadHeader.equals(&s.latest_execution_payload_header, &types.deneb.ExecutionPayloadHeader.default_value), - .electra => |s| !types.electra.ExecutionPayloadHeader.equals(&s.latest_execution_payload_header, &types.electra.ExecutionPayloadHeader.default_value), - .fulu => |s| !types.electra.ExecutionPayloadHeader.equals(&s.latest_execution_payload_header, &types.electra.ExecutionPayloadHeader.default_value), - else => false, - }; + const block_hash = state.latestExecutionPayloadHeaderBlockHash() catch return false; + return !std.mem.eql(u8, block_hash[0..], ZERO_HASH[0..]); } diff --git a/src/state_transition/utils/finality.zig b/src/state_transition/utils/finality.zig index 90f5b414e..891ac20a8 100644 --- a/src/state_transition/utils/finality.zig +++ b/src/state_transition/utils/finality.zig @@ -1,21 +1,22 @@ const std = @import("std"); -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; 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 { +pub fn getFinalityDelay(cached_state: *const CachedBeaconState) !u64 { const previous_epoch = computePreviousEpoch(cached_state.getEpochCache().epoch); - std.debug.assert(previous_epoch >= cached_state.state.finalizedCheckpoint().epoch); + const finalized_epoch = try cached_state.state.finalizedEpoch(); + std.debug.assert(previous_epoch >= finalized_epoch); // previous_epoch = epoch - 1 - return previous_epoch - cached_state.state.finalizedCheckpoint().epoch; + return previous_epoch - finalized_epoch; } /// If the chain has not been finalized for >4 epochs, the chain enters an "inactivity leak" mode, /// where inactive validators get progressively penalized more and more, to reduce their influence /// until blocks get finalized again. See here (https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#inactivity-quotient) for what the inactivity leak is, what it's for and how /// it works. -pub fn isInInactivityLeak(state: *const CachedBeaconStateAllForks) bool { - return getFinalityDelay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY; +pub fn isInInactivityLeak(state: *const CachedBeaconState) !bool { + return (try getFinalityDelay(state)) > MIN_EPOCHS_TO_INACTIVITY_PENALTY; } diff --git a/src/state_transition/utils/process_proposer_lookahead.zig b/src/state_transition/utils/process_proposer_lookahead.zig index db27cf84f..b96a595b7 100644 --- a/src/state_transition/utils/process_proposer_lookahead.zig +++ b/src/state_transition/utils/process_proposer_lookahead.zig @@ -3,8 +3,8 @@ const Allocator = std.mem.Allocator; const ssz = @import("consensus_types"); const preset = @import("preset").preset; const c = @import("constants"); -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; const ValidatorIndex = ssz.primitive.ValidatorIndex.Type; const computeEpochAtSlot = @import("./epoch.zig").computeEpochAtSlot; const seed_utils = @import("./seed.zig"); @@ -16,7 +16,7 @@ const computeProposers = seed_utils.computeProposers; /// Uses active indices from the epoch cache shufflings. pub fn initializeProposerLookahead( allocator: Allocator, - cached_state: *const CachedBeaconStateAllForks, + cached_state: *CachedBeaconState, out: []ValidatorIndex, ) !void { const lookahead_epochs = preset.MIN_SEED_LOOKAHEAD + 1; @@ -26,7 +26,7 @@ pub fn initializeProposerLookahead( const epoch_cache = cached_state.epoch_cache_ref.get(); const state = cached_state.state; - const current_epoch = computeEpochAtSlot(state.slot()); + const current_epoch = computeEpochAtSlot(try state.slot()); const effective_balance_increments = epoch_cache.getEffectiveBalanceIncrements(); const fork_seq = state.forkSeq(); diff --git a/src/state_transition/utils/root_cache.zig b/src/state_transition/utils/root_cache.zig index 7dc969957..531c11efc 100644 --- a/src/state_transition/utils/root_cache.zig +++ b/src/state_transition/utils/root_cache.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const CachedBeaconStateAllForks = @import("../cache/state_cache.zig").CachedBeaconStateAllForks; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const CachedBeaconState = @import("../cache/state_cache.zig").CachedBeaconState; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const getBlockRootFn = @import("../utils/block_root.zig").getBlockRoot; const getBlockRootAtSlotFn = @import("../utils/block_root.zig").getBlockRootAtSlot; const types = @import("consensus_types"); @@ -14,27 +14,32 @@ pub const RootCache = struct { allocator: Allocator, current_justified_checkpoint: Checkpoint, previous_justified_checkpoint: Checkpoint, - state: *const BeaconStateAllForks, - block_root_epoch_cache: std.AutoHashMap(Epoch, Root), - block_root_slot_cache: std.AutoHashMap(Slot, Root), + state: *BeaconState, + block_root_epoch_cache: std.AutoHashMap(Epoch, *const Root), + block_root_slot_cache: std.AutoHashMap(Slot, *const Root), - pub fn init(allocator: Allocator, cached_state: *const CachedBeaconStateAllForks) !*RootCache { + pub fn init(allocator: Allocator, cached_state: *CachedBeaconState) !*RootCache { const instance = try allocator.create(RootCache); errdefer allocator.destroy(instance); const state = cached_state.state; + + var current_justified_checkpoint: Checkpoint = undefined; + var previous_justified_checkpoint: Checkpoint = undefined; + try state.currentJustifiedCheckpoint(¤t_justified_checkpoint); + try state.previousJustifiedCheckpoint(&previous_justified_checkpoint); instance.* = RootCache{ .allocator = allocator, - .current_justified_checkpoint = state.currentJustifiedCheckpoint().*, - .previous_justified_checkpoint = state.previousJustifiedCheckpoint().*, + .current_justified_checkpoint = current_justified_checkpoint, + .previous_justified_checkpoint = previous_justified_checkpoint, .state = state, - .block_root_epoch_cache = std.AutoHashMap(Epoch, Root).init(allocator), - .block_root_slot_cache = std.AutoHashMap(Slot, Root).init(allocator), + .block_root_epoch_cache = std.AutoHashMap(Epoch, *const Root).init(allocator), + .block_root_slot_cache = std.AutoHashMap(Slot, *const Root).init(allocator), }; return instance; } - pub fn getBlockRoot(self: *RootCache, epoch: Epoch) !Root { + pub fn getBlockRoot(self: *RootCache, epoch: Epoch) !*const Root { if (self.block_root_epoch_cache.get(epoch)) |root| { return root; } else { @@ -44,7 +49,7 @@ pub const RootCache = struct { } } - pub fn getBlockRootAtSlot(self: *RootCache, slot: Slot) !Root { + pub fn getBlockRootAtSlot(self: *RootCache, slot: Slot) !*const Root { if (self.block_root_slot_cache.get(slot)) |root| { return root; } else { diff --git a/src/state_transition/utils/seed.zig b/src/state_transition/utils/seed.zig index 7bc08898d..bc29983ea 100644 --- a/src/state_transition/utils/seed.zig +++ b/src/state_transition/utils/seed.zig @@ -2,7 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const types = @import("consensus_types"); const preset = @import("preset").preset; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const digest = @import("./sha256.zig").digest; const Epoch = types.primitive.Epoch.Type; const Bytes32 = types.primitive.Bytes32.Type; @@ -53,21 +53,23 @@ test "computeProposers - sanity" { try computeProposers(allocator, ForkSeq.electra, epoch_seed, 0, active_indices[0..], effective_balance_increments, &out); } -pub fn getNextSyncCommitteeIndices(allocator: Allocator, state: *const BeaconStateAllForks, active_indices: []const ValidatorIndex, effective_balance_increments: EffectiveBalanceIncrements, out: []ValidatorIndex) !void { - const rand_byte_count: ByteCount = if (state.isPostElectra()) ByteCount.Two else ByteCount.One; - const max_effective_balance: u64 = if (state.isPostElectra()) preset.MAX_EFFECTIVE_BALANCE_ELECTRA else preset.MAX_EFFECTIVE_BALANCE; - const epoch = computeEpochAtSlot(state.slot()) + 1; +pub fn getNextSyncCommitteeIndices(allocator: Allocator, state: *BeaconState, active_indices: []const ValidatorIndex, effective_balance_increments: EffectiveBalanceIncrements, out: []ValidatorIndex) !void { + const fork_seq = state.forkSeq(); + const rand_byte_count: ByteCount = if (fork_seq.gte(.electra)) ByteCount.Two else ByteCount.One; + const max_effective_balance: u64 = if (fork_seq.gte(.electra)) preset.MAX_EFFECTIVE_BALANCE_ELECTRA else preset.MAX_EFFECTIVE_BALANCE; + const epoch = computeEpochAtSlot(try state.slot()) + 1; var seed: [32]u8 = undefined; try getSeed(state, epoch, c.DOMAIN_SYNC_COMMITTEE, &seed); try computeSyncCommitteeIndices(allocator, &seed, active_indices, effective_balance_increments.items, rand_byte_count, max_effective_balance, preset.EFFECTIVE_BALANCE_INCREMENT, preset.SHUFFLE_ROUND_COUNT, out); } -pub fn getRandaoMix(state: *const BeaconStateAllForks, epoch: Epoch) Bytes32 { - return state.randaoMixes()[epoch % EPOCHS_PER_HISTORICAL_VECTOR]; +pub fn getRandaoMix(state: *BeaconState, epoch: Epoch) !*const [32]u8 { + var randao_mixes = try state.randaoMixes(); + return try randao_mixes.getRoot(epoch % EPOCHS_PER_HISTORICAL_VECTOR); } -pub fn getSeed(state: *const BeaconStateAllForks, epoch: Epoch, domain_type: DomainType, out: *[32]u8) !void { - const mix = getRandaoMix(state, epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1); +pub fn getSeed(state: *BeaconState, epoch: Epoch, domain_type: DomainType, out: *[32]u8) !void { + const mix = try getRandaoMix(state, epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1); var epoch_buf: [8]u8 = undefined; std.mem.writeInt(u64, &epoch_buf, epoch, .little); var buffer = [_]u8{0} ** (types.primitive.DomainType.length + 8 + types.primitive.Bytes32.length); diff --git a/src/state_transition/utils/signature_sets.zig b/src/state_transition/utils/signature_sets.zig index 79b011240..377ca90b0 100644 --- a/src/state_transition/utils/signature_sets.zig +++ b/src/state_transition/utils/signature_sets.zig @@ -28,7 +28,11 @@ pub const AggregatedSignatureSet = struct { pub fn verifySingleSignatureSet(set: *const SingleSignatureSet) !bool { // All signatures are not trusted and must be group checked (p2.subgroup_check) const signature = try Signature.uncompress(&set.signature); - return verify(&set.signing_root, &set.pubkey, &signature, null, null); + if (verify(&set.signing_root, &set.pubkey, &signature, null, null)) { + return true; + } else |_| { + return false; + } } pub fn verifyAggregatedSignatureSet(set: *const AggregatedSignatureSet) !bool { diff --git a/src/state_transition/utils/sync_committee.zig b/src/state_transition/utils/sync_committee.zig index 3afa29431..5f3d414bd 100644 --- a/src/state_transition/utils/sync_committee.zig +++ b/src/state_transition/utils/sync_committee.zig @@ -2,7 +2,7 @@ const std = @import("std"); const blst = @import("blst"); const AggregatePublicKey = blst.AggregatePublicKey; const Allocator = std.mem.Allocator; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const EffiectiveBalanceIncrements = @import("../cache/effective_balance_increments.zig").EffectiveBalanceIncrements; const types = @import("consensus_types"); const preset = @import("preset").preset; @@ -14,42 +14,37 @@ const ForkSeq = @import("config").ForkSeq; const intSqrt = @import("../utils/math.zig").intSqrt; pub const getNextSyncCommitteeIndices = @import("./seed.zig").getNextSyncCommitteeIndices; -pub const SyncCommitteeInfo = struct { - // TODO: switch to fixed-size array since preset.SYNC_COMMITTEE_SIZE is constant - indices: std.ArrayList(ValidatorIndex), - sync_committee: *SyncCommittee, - pub fn deinit(self: *SyncCommitteeInfo, allocator: Allocator) void { - allocator.destroy(self.sync_committee); - self.indices.deinit(); - } +pub const SyncCommitteeInfo = struct { + indices: [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex, + sync_committee: SyncCommittee, }; /// Consumer must deallocate the returned `SyncCommitteeInfo` struct -pub fn getNextSyncCommittee(allocator: Allocator, state: *const BeaconStateAllForks, active_validators_indices: []const ValidatorIndex, effective_balance_increment: EffiectiveBalanceIncrements, out: *SyncCommitteeInfo) !void { - var indices = std.ArrayList(ValidatorIndex).init(allocator); - try indices.resize(preset.SYNC_COMMITTEE_SIZE); - try getNextSyncCommitteeIndices(allocator, state, active_validators_indices, effective_balance_increment, indices.items); +pub fn getNextSyncCommittee( + allocator: Allocator, + state: *BeaconState, + active_validator_indices: []const ValidatorIndex, + effective_balance_increments: EffiectiveBalanceIncrements, + out: *SyncCommitteeInfo, +) !void { + const indices = &out.indices; + try getNextSyncCommitteeIndices(allocator, state, active_validator_indices, effective_balance_increments, indices); + var validators_view = try state.validators(); // Using the index2pubkey cache is slower because it needs the serialized pubkey. - var pubkeys: [preset.SYNC_COMMITTEE_SIZE]PublicKey = undefined; - var blst_pubkeys: [preset.SYNC_COMMITTEE_SIZE]blst.PublicKey = undefined; - for (indices.items, 0..) |index, i| { - pubkeys[i] = state.validators().items[index].pubkey; - blst_pubkeys[i] = try blst.PublicKey.uncompress(&pubkeys[i]); + const pubkeys = &out.sync_committee.pubkeys; + var pubkeys_uncompressed: [preset.SYNC_COMMITTEE_SIZE]blst.PublicKey = undefined; + for (indices, 0..indices.len) |index, i| { + var validator_view = try validators_view.get(index); + var validator: types.phase0.Validator.Type = undefined; + try validator_view.toValue(allocator, &validator); + pubkeys[i] = validator.pubkey; + pubkeys_uncompressed[i] = try blst.PublicKey.uncompress(&pubkeys[i]); } - const aggregated_pk = try AggregatePublicKey.aggregate(&blst_pubkeys, false); - const sync_committee = try allocator.create(SyncCommittee); - errdefer allocator.destroy(sync_committee); - sync_committee.* = .{ - .pubkeys = pubkeys, - .aggregate_pubkey = aggregated_pk.toPublicKey().compress(), - }; - out.* = .{ - .indices = indices, - .sync_committee = sync_committee, - }; + const aggregated_pk = try AggregatePublicKey.aggregate(&pubkeys_uncompressed, false); + out.sync_committee.aggregate_pubkey = aggregated_pk.toPublicKey().compress(); } pub fn computeSyncParticipantReward(total_active_balance_increments: u64) u64 { diff --git a/src/state_transition/utils/target_unslashed_balance.zig b/src/state_transition/utils/target_unslashed_balance.zig index bc93f4efc..d4d5177c5 100644 --- a/src/state_transition/utils/target_unslashed_balance.zig +++ b/src/state_transition/utils/target_unslashed_balance.zig @@ -8,7 +8,7 @@ const isActiveValidator = @import("./validator.zig").isActiveValidator; const TIMELY_TARGET = 1 << c.TIMELY_TARGET_FLAG_INDEX; -pub fn sumTargetUnslashedBalanceIncrements(participations: []const u8, epoch: Epoch, validators: []Validator) u64 { +pub fn sumTargetUnslashedBalanceIncrements(participations: []const u8, epoch: Epoch, validators: []const Validator) u64 { var total: u64 = 0; for (participations, 0..) |participation, i| { if ((participation & TIMELY_TARGET) == TIMELY_TARGET) { diff --git a/src/state_transition/utils/validator.zig b/src/state_transition/utils/validator.zig index 45ca2399e..14f0d42d5 100644 --- a/src/state_transition/utils/validator.zig +++ b/src/state_transition/utils/validator.zig @@ -2,32 +2,42 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const types = @import("consensus_types"); const preset = @import("preset").preset; -const Validator = types.phase0.Validator.Type; +const Validator = types.phase0.Validator; const Epoch = types.primitive.Epoch.Type; const ValidatorIndex = types.primitive.ValidatorIndex.Type; -const BeaconStateAllForks = @import("../types/beacon_state.zig").BeaconStateAllForks; +const BeaconState = @import("../types/beacon_state.zig").BeaconState; const BeaconConfig = @import("config").BeaconConfig; const ForkSeq = @import("config").ForkSeq; const EpochCache = @import("../cache/epoch_cache.zig").EpochCache; const WithdrawalCredentials = types.primitive.Root.Type; const hasCompoundingWithdrawalCredential = @import("./electra.zig").hasCompoundingWithdrawalCredential; -pub fn isActiveValidator(validator: *const Validator, epoch: Epoch) bool { +pub fn isActiveValidator(validator: *const Validator.Type, epoch: Epoch) bool { return validator.activation_epoch <= epoch and epoch < validator.exit_epoch; } -pub fn isSlashableValidator(validator: *const Validator, epoch: Epoch) bool { +pub fn isActiveValidatorView(validator: *Validator.TreeView, epoch: Epoch) !bool { + const activation_epoch: Epoch = @intCast(try validator.get("activation_epoch")); + const exit_epoch: Epoch = @intCast(try validator.get("exit_epoch")); + return activation_epoch <= epoch and epoch < exit_epoch; +} + +pub fn isSlashableValidator(validator: *const Validator.Type, epoch: Epoch) bool { return !validator.slashed and validator.activation_epoch <= epoch and epoch < validator.withdrawable_epoch; } -pub fn getActiveValidatorIndices(allocator: Allocator, state: *const BeaconStateAllForks, epoch: Epoch) !std.ArrayList(ValidatorIndex) { +pub fn getActiveValidatorIndices(allocator: Allocator, state: *BeaconState, epoch: Epoch) !std.ArrayList(ValidatorIndex) { var indices = std.ArrayList(ValidatorIndex).init(allocator); - const validators = state.validators(); - for (0..validators.items.len) |i| { - const validator = &validators.items[i]; - if (isActiveValidator(validator, epoch)) { + var validators = try state.validators(); + var validators_it = validators.iteratorReadonly(); + const validators_len = try validators.length(); + for (0..validators_len) |i| { + var validator = try validators_it.next(); + defer validator.deinit(); + + if (try isActiveValidatorView(&validator, epoch)) { try indices.append(@intCast(i)); } } @@ -67,7 +77,7 @@ pub fn getConsolidationChurnLimit(epoch_cache: *const EpochCache) u64 { return getBalanceChurnLimitFromCache(epoch_cache) - getActivationExitChurnLimit(epoch_cache); } -pub fn getMaxEffectiveBalance(withdrawal_credentials: WithdrawalCredentials) u64 { +pub fn getMaxEffectiveBalance(withdrawal_credentials: *const WithdrawalCredentials) u64 { // Compounding withdrawal credential only available since Electra if (hasCompoundingWithdrawalCredential(withdrawal_credentials)) { return preset.MAX_EFFECTIVE_BALANCE_ELECTRA; @@ -75,13 +85,16 @@ pub fn getMaxEffectiveBalance(withdrawal_credentials: WithdrawalCredentials) u64 return preset.MIN_ACTIVATION_BALANCE; } -pub fn getPendingBalanceToWithdraw(state: *const BeaconStateAllForks, validator_index: ValidatorIndex) u64 { +pub fn getPendingBalanceToWithdraw(state: *BeaconState, validator_index: ValidatorIndex) !u64 { var total: u64 = 0; - const pending_partial_withdrawals = state.pendingPartialWithdrawals(); - for (0..pending_partial_withdrawals.items.len) |i| { - const pending_partial_withdrawal = pending_partial_withdrawals.items[i]; - if (pending_partial_withdrawal.validator_index == validator_index) { - total += pending_partial_withdrawal.amount; + + var pending_partial_withdrawals = try state.pendingPartialWithdrawals(); + const len = try pending_partial_withdrawals.length(); + for (0..len) |i| { + var pending_partial_withdrawal = try pending_partial_withdrawals.get(i); + const idx = try pending_partial_withdrawal.get("validator_index"); + if (idx == validator_index) { + total += try pending_partial_withdrawal.get("amount"); } } return total; diff --git a/src/state_transition/utils_test_root.zig b/src/state_transition/utils_test_root.zig new file mode 100644 index 000000000..0235d46e8 --- /dev/null +++ b/src/state_transition/utils_test_root.zig @@ -0,0 +1,37 @@ +// Root file to run only state_transition/utils tests. +// +// This exists because `zig test` compiles the entire root module before applying +// `--test-filter`. Keeping a small root lets us iterate on a subset without +// fixing unrelated compilation errors across the whole state_transition module. + +test "state_transition utils" { + _ = @import("utils/aggregator.zig"); + _ = @import("utils/attestation.zig"); + _ = @import("utils/attester_status.zig"); + _ = @import("utils/balance.zig"); + _ = @import("utils/block_root.zig"); + _ = @import("utils/bls.zig"); + _ = @import("utils/capella.zig"); + _ = @import("utils/committee_indices.zig"); + _ = @import("utils/deposit.zig"); + _ = @import("utils/domain.zig"); + _ = @import("utils/electra.zig"); + _ = @import("utils/epoch.zig"); + _ = @import("utils/epoch_shuffling.zig"); + _ = @import("utils/execution.zig"); + _ = @import("utils/finality.zig"); + _ = @import("utils/math.zig"); + _ = @import("utils/process_proposer_lookahead.zig"); + _ = @import("utils/pubkey_index_map.zig"); + _ = @import("utils/reference_count.zig"); + _ = @import("utils/root_cache.zig"); + _ = @import("utils/seed.zig"); + _ = @import("utils/sha256.zig"); + _ = @import("utils/shuffle.zig"); + _ = @import("utils/signature_sets.zig"); + _ = @import("utils/signing_root.zig"); + _ = @import("utils/sync_committee.zig"); + _ = @import("utils/target_unslashed_balance.zig"); + _ = @import("utils/validator.zig"); + _ = @import("utils/verify_merkle_branch.zig"); +} diff --git a/test/int/epoch/process_effective_balance_updates.zig b/test/int/epoch/process_effective_balance_updates.zig index 42c960422..d6e49379d 100644 --- a/test/int/epoch/process_effective_balance_updates.zig +++ b/test/int/epoch/process_effective_balance_updates.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; @@ -10,7 +10,7 @@ test "processEffectiveBalanceUpdates - sanity" { try TestRunner( state_transition.processEffectiveBalanceUpdates, .{ - .alloc = false, + .alloc = true, .err_return = true, .void_return = false, }, diff --git a/test/int/epoch/process_epoch.zig b/test/int/epoch/process_epoch.zig index 8f461d504..19d8ada28 100644 --- a/test/int/epoch/process_epoch.zig +++ b/test/int/epoch/process_epoch.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_eth1_data_reset.zig b/test/int/epoch/process_eth1_data_reset.zig index 41d2c4d07..08a15586a 100644 --- a/test/int/epoch/process_eth1_data_reset.zig +++ b/test/int/epoch/process_eth1_data_reset.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; @@ -8,8 +8,8 @@ const TestRunner = @import("./test_runner.zig").TestRunner; test "processEth1DataReset - sanity" { try TestRunner(state_transition.processEth1DataReset, .{ - .alloc = true, - .err_return = false, + .alloc = false, + .err_return = true, .void_return = true, }).testProcessEpochFn(); defer state_transition.deinitStateTransition(); diff --git a/test/int/epoch/process_historical_summaries_update.zig b/test/int/epoch/process_historical_summaries_update.zig index 0402c057c..d97ec814e 100644 --- a/test/int/epoch/process_historical_summaries_update.zig +++ b/test/int/epoch/process_historical_summaries_update.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; @@ -8,7 +8,7 @@ const TestRunner = @import("./test_runner.zig").TestRunner; test "processHistoricalSummariesUpdate - sanity" { try TestRunner(state_transition.processHistoricalSummariesUpdate, .{ - .alloc = true, + .alloc = false, .err_return = true, .void_return = true, }).testProcessEpochFn(); diff --git a/test/int/epoch/process_inactivity_updates.zig b/test/int/epoch/process_inactivity_updates.zig index 2ae88127f..cd8035328 100644 --- a/test/int/epoch/process_inactivity_updates.zig +++ b/test/int/epoch/process_inactivity_updates.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_justification_and_finalization.zig b/test/int/epoch/process_justification_and_finalization.zig index b737a40bd..e374a1a99 100644 --- a/test/int/epoch/process_justification_and_finalization.zig +++ b/test/int/epoch/process_justification_and_finalization.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_participation_flag_updates.zig b/test/int/epoch/process_participation_flag_updates.zig index 3c1289e77..7c046afe3 100644 --- a/test/int/epoch/process_participation_flag_updates.zig +++ b/test/int/epoch/process_participation_flag_updates.zig @@ -1,19 +1,21 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; -const EpochTransitionCache = state_transition.EpochTransitionCache; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const processParticipationFlagUpdates = state_transition.processParticipationFlagUpdates; +const Node = @import("persistent_merkle_tree").Node; // this function runs without EpochTransionCache so cannot use getTestProcessFn test "processParticipationFlagUpdates - sanity" { const allocator = std.testing.allocator; const validator_count_arr = &.{ 256, 10_000 }; + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + inline for (validator_count_arr) |validator_count| { - var test_state = try TestCachedBeaconStateAllForks.init(allocator, validator_count); + var test_state = try TestCachedBeaconState.init(allocator, &pool, validator_count); defer test_state.deinit(); - try processParticipationFlagUpdates(allocator, test_state.cached_state); + try processParticipationFlagUpdates(test_state.cached_state); } defer state_transition.deinitStateTransition(); } diff --git a/test/int/epoch/process_pending_consolidations.zig b/test/int/epoch/process_pending_consolidations.zig index 60b60a623..45ca2feb4 100644 --- a/test/int/epoch/process_pending_consolidations.zig +++ b/test/int/epoch/process_pending_consolidations.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; @@ -8,7 +8,7 @@ const TestRunner = @import("./test_runner.zig").TestRunner; test "processPendingConsolidations - sanity" { try TestRunner(state_transition.processPendingConsolidations, .{ - .alloc = true, + .alloc = false, .err_return = true, .void_return = true, }).testProcessEpochFn(); diff --git a/test/int/epoch/process_pending_deposits.zig b/test/int/epoch/process_pending_deposits.zig index 0494c83de..20eb5896b 100644 --- a/test/int/epoch/process_pending_deposits.zig +++ b/test/int/epoch/process_pending_deposits.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_randao_mixes_reset.zig b/test/int/epoch/process_randao_mixes_reset.zig index ae986161a..45a1c156d 100644 --- a/test/int/epoch/process_randao_mixes_reset.zig +++ b/test/int/epoch/process_randao_mixes_reset.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; @@ -11,7 +11,7 @@ test "processRandaoMixesReset - sanity" { state_transition.processRandaoMixesReset, .{ .alloc = false, - .err_return = false, + .err_return = true, .void_return = true, }, ).testProcessEpochFn(); diff --git a/test/int/epoch/process_registry_updates.zig b/test/int/epoch/process_registry_updates.zig index 565273e78..84971bab7 100644 --- a/test/int/epoch/process_registry_updates.zig +++ b/test/int/epoch/process_registry_updates.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_rewards_and_penalties.zig b/test/int/epoch/process_rewards_and_penalties.zig index e35fe02f5..d54878771 100644 --- a/test/int/epoch/process_rewards_and_penalties.zig +++ b/test/int/epoch/process_rewards_and_penalties.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_slashings.zig b/test/int/epoch/process_slashings.zig index 58dcaaa46..85c778e18 100644 --- a/test/int/epoch/process_slashings.zig +++ b/test/int/epoch/process_slashings.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; diff --git a/test/int/epoch/process_slashings_reset.zig b/test/int/epoch/process_slashings_reset.zig index 2ce2a239f..8b6cae591 100644 --- a/test/int/epoch/process_slashings_reset.zig +++ b/test/int/epoch/process_slashings_reset.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const ReusedEpochTransitionCache = state_transition.ReusedEpochTransitionCache; const EpochTransitionCache = state_transition.EpochTransitionCache; @@ -9,7 +9,7 @@ const TestRunner = @import("./test_runner.zig").TestRunner; test "processSlashingsReset - sanity" { try TestRunner(state_transition.processSlashingsReset, .{ .alloc = false, - .err_return = false, + .err_return = true, .void_return = true, }).testProcessEpochFn(); defer state_transition.deinitStateTransition(); diff --git a/test/int/epoch/process_sync_committee_updates.zig b/test/int/epoch/process_sync_committee_updates.zig index 9f02a7c1d..09bbc5cee 100644 --- a/test/int/epoch/process_sync_committee_updates.zig +++ b/test/int/epoch/process_sync_committee_updates.zig @@ -1,17 +1,21 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const EpochTransitionCache = state_transition.EpochTransitionCache; const processSyncCommitteeUpdates = state_transition.processSyncCommitteeUpdates; +const Node = @import("persistent_merkle_tree").Node; // this function runs without EpochTransionCache so cannot use getTestProcessFn test "processSyncCommitteeUpdates - sanity" { const allocator = std.testing.allocator; const validator_count_arr = &.{ 256, 10_000 }; + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + inline for (validator_count_arr) |validator_count| { - var test_state = try TestCachedBeaconStateAllForks.init(allocator, validator_count); + var test_state = try TestCachedBeaconState.init(allocator, &pool, validator_count); defer test_state.deinit(); try processSyncCommitteeUpdates(allocator, test_state.cached_state); } diff --git a/test/int/epoch/test_runner.zig b/test/int/epoch/test_runner.zig index 9c412b244..524456b27 100644 --- a/test/int/epoch/test_runner.zig +++ b/test/int/epoch/test_runner.zig @@ -1,8 +1,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const state_transition = @import("state_transition"); const EpochTransitionCache = state_transition.EpochTransitionCache; +const Node = @import("persistent_merkle_tree").Node; pub const TestOpt = struct { alloc: bool = false, @@ -17,8 +18,11 @@ pub fn TestRunner(process_epoch_fn: anytype, opt: TestOpt) type { const allocator = std.testing.allocator; const validator_count_arr = &.{ 256, 10_000 }; + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + inline for (validator_count_arr) |validator_count| { - var test_state = try TestCachedBeaconStateAllForks.init(allocator, validator_count); + var test_state = try TestCachedBeaconState.init(allocator, &pool, validator_count); defer test_state.deinit(); if (opt.fulu) { diff --git a/test/int/process_block_header.zig b/test/int/process_block_header.zig index 3afaaa298..24917a353 100644 --- a/test/int/process_block_header.zig +++ b/test/int/process_block_header.zig @@ -1,7 +1,10 @@ test "process block header - sanity" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); const slot = config.mainnet.chain_config.ELECTRA_FORK_EPOCH * preset.SLOTS_PER_EPOCH + 2025 * preset.SLOTS_PER_EPOCH - 1; defer test_state.deinit(); @@ -10,12 +13,12 @@ test "process block header - sanity" { var message: types.electra.BeaconBlock.Type = types.electra.BeaconBlock.default_value; const proposer_index = proposers[slot % preset.SLOTS_PER_EPOCH]; - var header_parent_root: [32]u8 = undefined; - try types.phase0.BeaconBlockHeader.hashTreeRoot(test_state.cached_state.state.latestBlockHeader(), &header_parent_root); + var latest_header_view = try test_state.cached_state.state.latestBlockHeader(); + const header_parent_root = try latest_header_view.hashTreeRoot(); message.slot = slot; message.proposer_index = proposer_index; - message.parent_root = header_parent_root; + message.parent_root = header_parent_root.*; const beacon_block = BeaconBlock{ .electra = &message }; @@ -27,8 +30,9 @@ const std = @import("std"); const types = @import("consensus_types"); const config = @import("config"); const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const preset = @import("preset").preset; const processBlockHeader = state_transition.processBlockHeader; const Block = state_transition.Block; const BeaconBlock = state_transition.BeaconBlock; +const Node = @import("persistent_merkle_tree").Node; diff --git a/test/int/process_eth1_data.zig b/test/int/process_eth1_data.zig index dc60eff14..e89236b01 100644 --- a/test/int/process_eth1_data.zig +++ b/test/int/process_eth1_data.zig @@ -1,7 +1,12 @@ +const Node = @import("persistent_merkle_tree").Node; + test "process eth1 data - sanity" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); const block = types.electra.BeaconBlock.default_value; @@ -12,7 +17,7 @@ const std = @import("std"); const types = @import("consensus_types"); const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const processEth1Data = state_transition.processEth1Data; const SignedBlock = state_transition.SignedBlock; const SignedBeaconBlock = state_transition.SignedBeaconBlock; diff --git a/test/int/process_execution_payload.zig b/test/int/process_execution_payload.zig index 9dbdb795e..d41e08768 100644 --- a/test/int/process_execution_payload.zig +++ b/test/int/process_execution_payload.zig @@ -1,11 +1,16 @@ +const Node = @import("persistent_merkle_tree").Node; + test "process execution payload - sanity" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); var execution_payload: types.electra.ExecutionPayload.Type = types.electra.ExecutionPayload.default_value; - execution_payload.timestamp = test_state.cached_state.state.genesisTime() + test_state.cached_state.state.slot() * config.mainnet.chain_config.SECONDS_PER_SLOT; + execution_payload.timestamp = (try test_state.cached_state.state.genesisTime()) + (try test_state.cached_state.state.slot()) * config.mainnet.chain_config.SECONDS_PER_SLOT; var body: types.electra.BeaconBlockBody.Type = types.electra.BeaconBlockBody.default_value; body.execution_payload = execution_payload; @@ -28,7 +33,7 @@ const types = @import("consensus_types"); const config = @import("config"); const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const processExecutionPayload = state_transition.processExecutionPayload; const SignedBlock = state_transition.SignedBlock; const Block = state_transition.Block; diff --git a/test/int/process_operations.zig b/test/int/process_operations.zig index db072bd73..906e0c742 100644 --- a/test/int/process_operations.zig +++ b/test/int/process_operations.zig @@ -1,7 +1,12 @@ +const Node = @import("persistent_merkle_tree").Node; + test "process operations" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); const electra_block = types.electra.BeaconBlock.default_value; @@ -15,7 +20,7 @@ const std = @import("std"); const types = @import("consensus_types"); const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const processOperations = state_transition.processOperations; const Block = state_transition.Block; const BeaconBlock = state_transition.BeaconBlock; diff --git a/test/int/process_randao.zig b/test/int/process_randao.zig index 476dcd06f..549666dae 100644 --- a/test/int/process_randao.zig +++ b/test/int/process_randao.zig @@ -1,7 +1,12 @@ +const Node = @import("persistent_merkle_tree").Node; + test "process randao - sanity" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); const slot = config.mainnet.chain_config.ELECTRA_FORK_EPOCH * preset.SLOTS_PER_EPOCH + 2025 * preset.SLOTS_PER_EPOCH - 1; defer test_state.deinit(); @@ -10,12 +15,12 @@ test "process randao - sanity" { var message: types.electra.BeaconBlock.Type = types.electra.BeaconBlock.default_value; const proposer_index = proposers[slot % preset.SLOTS_PER_EPOCH]; - var header_parent_root: [32]u8 = undefined; - try types.phase0.BeaconBlockHeader.hashTreeRoot(test_state.cached_state.state.latestBlockHeader(), &header_parent_root); + var latest_header_view = try test_state.cached_state.state.latestBlockHeader(); + const header_parent_root = try latest_header_view.hashTreeRoot(); message.slot = slot; message.proposer_index = proposer_index; - message.parent_root = header_parent_root; + message.parent_root = header_parent_root.*; const beacon_block = BeaconBlock{ .electra = &message }; const block = Block{ .regular = beacon_block }; @@ -28,7 +33,7 @@ const config = @import("config"); const Allocator = std.mem.Allocator; const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const preset = @import("preset").preset; diff --git a/test/int/process_sync_aggregate.zig b/test/int/process_sync_aggregate.zig index 042e8beef..a5ce74737 100644 --- a/test/int/process_sync_aggregate.zig +++ b/test/int/process_sync_aggregate.zig @@ -1,16 +1,19 @@ test "process sync aggregate - sanity" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); const state = test_state.cached_state.state; const config = test_state.cached_state.config; - const previous_slot = state.slot() - 1; + const previous_slot = (try state.slot()) - 1; const root_signed = try state_transition.getBlockRootAtSlot(state, previous_slot); - const domain = try config.getDomain(state.slot(), c.DOMAIN_SYNC_COMMITTEE, previous_slot); + const domain = try config.getDomain(try state.slot(), c.DOMAIN_SYNC_COMMITTEE, previous_slot); var signing_root: Root = undefined; - try computeSigningRoot(types.primitive.Root, &root_signed, domain, &signing_root); + try computeSigningRoot(types.primitive.Root, root_signed, domain, &signing_root); const committee_indices = @as(*const [preset.SYNC_COMMITTEE_SIZE]ValidatorIndex, @ptrCast(test_state.cached_state.getEpochCache().current_sync_committee_indexed.get().getValidatorIndices())); // validator 0 signs @@ -39,7 +42,8 @@ const preset = @import("preset").preset; const c = @import("constants"); const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = @import("state_transition").test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = @import("state_transition").test_utils.TestCachedBeaconState; +const Node = @import("persistent_merkle_tree").Node; const state_transition = @import("state_transition"); const processSyncAggregate = state_transition.processSyncAggregate; diff --git a/test/int/process_withdrawals.zig b/test/int/process_withdrawals.zig index 31161466f..681323a5b 100644 --- a/test/int/process_withdrawals.zig +++ b/test/int/process_withdrawals.zig @@ -1,7 +1,12 @@ +const Node = @import("persistent_merkle_tree").Node; + test "process withdrawals - sanity" { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); var withdrawals_result = WithdrawalsResult{ .withdrawals = try Withdrawals.initCapacity( @@ -23,7 +28,7 @@ test "process withdrawals - sanity" { const std = @import("std"); const state_transition = @import("state_transition"); const preset = @import("preset").preset; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const processWithdrawals = state_transition.processWithdrawals; const getExpectedWithdrawals = state_transition.getExpectedWithdrawals; const WithdrawalsResult = state_transition.WithdrawalsResult; diff --git a/test/int/state_transition.zig b/test/int/state_transition.zig index b745f7b15..af4b54045 100644 --- a/test/int/state_transition.zig +++ b/test/int/state_transition.zig @@ -1,17 +1,18 @@ const std = @import("std"); const testing = std.testing; const Allocator = std.mem.Allocator; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const generateElectraBlock = state_transition.test_utils.generateElectraBlock; const types = @import("consensus_types"); const Root = types.primitive.Root.Type; const ZERO_HASH = @import("constants").ZERO_HASH; const state_transition = @import("state_transition"); +const Node = @import("persistent_merkle_tree").Node; const stateTransition = state_transition.state_transition.stateTransition; const TransitionOpt = state_transition.state_transition.TransitionOpt; const SignedBeaconBlock = state_transition.state_transition.SignedBeaconBlock; -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; +const CachedBeaconState = state_transition.CachedBeaconState; const SignedBlock = state_transition.SignedBlock; const TestCase = struct { @@ -30,7 +31,9 @@ test "state transition - electra block" { inline for (test_cases) |tc| { const allocator = std.testing.allocator; - var test_state = try TestCachedBeaconStateAllForks.init(allocator, 256); + var pool = try Node.Pool.init(allocator, 1024); + defer pool.deinit(); + var test_state = try TestCachedBeaconState.init(allocator, &pool, 256); defer test_state.deinit(); const electra_block_ptr = try allocator.create(types.electra.SignedBeaconBlock.Type); try generateElectraBlock(allocator, test_state.cached_state, electra_block_ptr); diff --git a/test/int_slow/era/root.zig b/test/int_slow/era/root.zig index 30c096857..e4f061181 100644 --- a/test/int_slow/era/root.zig +++ b/test/int_slow/era/root.zig @@ -63,7 +63,7 @@ test "write an era file from an existing era file" { try writer.writeBlock(allocator, block); } var state = try reader.readState(allocator, null); - defer state.deinit(allocator); + defer state.deinit(); try writer.writeState(allocator, state); @@ -104,7 +104,7 @@ test "write an era file from an existing era file" { } // Compare state var out_state = try out_reader.readState(allocator, null); - defer out_state.deinit(allocator); + defer out_state.deinit(); const serialized = try state.serialize(allocator); defer allocator.free(serialized); diff --git a/test/spec/runner/epoch_processing.zig b/test/spec/runner/epoch_processing.zig index 73bfc7c4b..859704953 100644 --- a/test/spec/runner/epoch_processing.zig +++ b/test/spec/runner/epoch_processing.zig @@ -1,13 +1,15 @@ const ssz = @import("consensus_types"); +const Node = @import("persistent_merkle_tree").Node; const Allocator = std.mem.Allocator; const Root = ssz.primitive.Root.Type; const ForkSeq = @import("config").ForkSeq; const Preset = @import("preset").Preset; const preset = @import("preset").preset; +const active_preset = @import("preset").active_preset; const std = @import("std"); const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; +const BeaconState = state_transition.BeaconState; const EpochTransitionCache = state_transition.EpochTransitionCache; const Withdrawals = ssz.capella.Withdrawals.Type; const WithdrawalsResult = state_transition.WithdrawalsResult; @@ -46,39 +48,44 @@ pub fn TestCase(comptime fork: ForkSeq, comptime epoch_process_fn: EpochProcessi const tc_utils = TestCaseUtils(fork); return struct { - pre: TestCachedBeaconStateAllForks, + pre: TestCachedBeaconState, // a null post state means the test is expected to fail - post: ?BeaconStateAllForks, + post: ?*BeaconState, const Self = @This(); pub fn execute(allocator: std.mem.Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); + const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; + var pool = try Node.Pool.init(allocator, pool_size); + defer pool.deinit(); + + var tc = try Self.init(allocator, &pool, dir); defer tc.deinit(); try tc.runTest(); } - pub fn init(allocator: std.mem.Allocator, dir: std.fs.Dir) !Self { + pub fn init(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { var tc = Self{ .pre = undefined, .post = undefined, }; // load pre state - tc.pre = try tc_utils.loadPreState(allocator, dir); + tc.pre = try tc_utils.loadPreState(allocator, pool, dir); errdefer tc.pre.deinit(); // load pre state - tc.post = try tc_utils.loadPostState(allocator, dir); + tc.post = try tc_utils.loadPostState(allocator, pool, dir); return tc; } pub fn deinit(self: *Self) void { self.pre.deinit(); - if (self.post) |*post| { - post.deinit(self.pre.allocator); + if (self.post) |post| { + post.deinit(); + self.pre.allocator.destroy(post); } state_transition.deinitStateTransition(); } @@ -86,7 +93,7 @@ pub fn TestCase(comptime fork: ForkSeq, comptime epoch_process_fn: EpochProcessi fn runTest(self: *Self) !void { if (self.post) |post| { try self.process(); - try expectEqualBeaconStates(post, self.pre.cached_state.state.*); + try expectEqualBeaconStates(post, self.pre.cached_state.state); } else { self.process() catch |err| { if (err == error.SkipZigTest) { @@ -108,22 +115,22 @@ pub fn TestCase(comptime fork: ForkSeq, comptime epoch_process_fn: EpochProcessi } switch (epoch_process_fn) { - .effective_balance_updates => _ = try state_transition.processEffectiveBalanceUpdates(pre, epoch_transition_cache), - .eth1_data_reset => state_transition.processEth1DataReset(allocator, pre, epoch_transition_cache), - .historical_roots_update => try state_transition.processHistoricalRootsUpdate(allocator, pre, epoch_transition_cache), + .effective_balance_updates => _ = try state_transition.processEffectiveBalanceUpdates(allocator, pre, epoch_transition_cache), + .eth1_data_reset => try state_transition.processEth1DataReset(pre, epoch_transition_cache), + .historical_roots_update => try state_transition.processHistoricalRootsUpdate(pre, epoch_transition_cache), .inactivity_updates => try state_transition.processInactivityUpdates(pre, epoch_transition_cache), .justification_and_finalization => try state_transition.processJustificationAndFinalization(pre, epoch_transition_cache), - .participation_flag_updates => try state_transition.processParticipationFlagUpdates(allocator, pre), - .participation_record_updates => state_transition.processParticipationRecordUpdates(allocator, pre), - .randao_mixes_reset => state_transition.processRandaoMixesReset(pre, epoch_transition_cache), + .participation_flag_updates => try state_transition.processParticipationFlagUpdates(pre), + .participation_record_updates => try state_transition.processParticipationRecordUpdates(pre), + .randao_mixes_reset => try state_transition.processRandaoMixesReset(pre, epoch_transition_cache), .registry_updates => try state_transition.processRegistryUpdates(pre, epoch_transition_cache), .rewards_and_penalties => try state_transition.processRewardsAndPenalties(allocator, pre, epoch_transition_cache), .slashings => try state_transition.processSlashings(allocator, pre, epoch_transition_cache), - .slashings_reset => state_transition.processSlashingsReset(pre, epoch_transition_cache), + .slashings_reset => try state_transition.processSlashingsReset(pre, epoch_transition_cache), .sync_committee_updates => try state_transition.processSyncCommitteeUpdates(allocator, pre), - .historical_summaries_update => try state_transition.processHistoricalSummariesUpdate(allocator, pre, epoch_transition_cache), + .historical_summaries_update => try state_transition.processHistoricalSummariesUpdate(pre, epoch_transition_cache), .pending_deposits => try state_transition.processPendingDeposits(allocator, pre, epoch_transition_cache), - .pending_consolidations => try state_transition.processPendingConsolidations(allocator, pre, epoch_transition_cache), + .pending_consolidations => try state_transition.processPendingConsolidations(pre, epoch_transition_cache), .proposer_lookahead => { try state_transition.processProposerLookahead(allocator, pre, epoch_transition_cache); }, diff --git a/test/spec/runner/fork.zig b/test/spec/runner/fork.zig index d0b179c1e..41233cc32 100644 --- a/test/spec/runner/fork.zig +++ b/test/spec/runner/fork.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Node = @import("persistent_merkle_tree").Node; const ForkSeq = @import("config").ForkSeq; const state_transition = @import("state_transition"); const upgradeStateToAltair = state_transition.upgradeStateToAltair; @@ -7,11 +8,12 @@ const upgradeStateToCapella = state_transition.upgradeStateToCapella; const upgradeStateToDeneb = state_transition.upgradeStateToDeneb; const upgradeStateToElectra = state_transition.upgradeStateToElectra; const upgradeStateToFulu = state_transition.upgradeStateToFulu; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; +const BeaconState = state_transition.BeaconState; const test_case = @import("../test_case.zig"); const TestCaseUtils = test_case.TestCaseUtils; const expectEqualBeaconStates = test_case.expectEqualBeaconStates; +const active_preset = @import("preset").active_preset; pub const Handler = enum { fork, @@ -36,13 +38,17 @@ pub fn TestCase(comptime target_fork: ForkSeq) type { const post_tc_utils = TestCaseUtils(target_fork); return struct { - pre: TestCachedBeaconStateAllForks, - post: ?BeaconStateAllForks, + pre: TestCachedBeaconState, + post: ?*BeaconState, const Self = @This(); pub fn execute(allocator: Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); + const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; + var pool = try Node.Pool.init(allocator, pool_size); + defer pool.deinit(); + + var tc = try Self.init(allocator, &pool, dir); defer { tc.deinit(); state_transition.deinitStateTransition(); @@ -51,14 +57,14 @@ pub fn TestCase(comptime target_fork: ForkSeq) type { try tc.runTest(); } - fn init(allocator: Allocator, dir: std.fs.Dir) !Self { + fn init(allocator: Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { const meta_fork = try loadTargetFork(allocator, dir); if (meta_fork != target_fork) return error.InvalidMetaFile; - var pre_state = try pre_tc_utils.loadPreState(allocator, dir); + var pre_state = try pre_tc_utils.loadPreState(allocator, pool, dir); errdefer pre_state.deinit(); - const post_state = try post_tc_utils.loadPostState(allocator, dir); + const post_state = try post_tc_utils.loadPostState(allocator, pool, dir); return .{ .pre = pre_state, @@ -68,15 +74,16 @@ pub fn TestCase(comptime target_fork: ForkSeq) type { fn deinit(self: *Self) void { self.pre.deinit(); - if (self.post) |*post_state| { - post_state.deinit(self.pre.allocator); + if (self.post) |post| { + post.deinit(); + self.pre.allocator.destroy(post); } } fn runTest(self: *Self) !void { if (self.post) |expected| { try self.upgrade(); - try expectEqualBeaconStates(expected, self.pre.cached_state.state.*); + try expectEqualBeaconStates(expected, self.pre.cached_state.state); } else { self.upgrade() catch |err| { if (err == error.SkipZigTest) { diff --git a/test/spec/runner/operations.zig b/test/spec/runner/operations.zig index 0fb10a4a6..216443f28 100644 --- a/test/spec/runner/operations.zig +++ b/test/spec/runner/operations.zig @@ -1,12 +1,14 @@ +const std = @import("std"); +const Node = @import("persistent_merkle_tree").Node; const ssz = @import("consensus_types"); const Root = ssz.primitive.Root.Type; const ForkSeq = @import("config").ForkSeq; const Preset = @import("preset").Preset; const preset = @import("preset").preset; -const std = @import("std"); +const active_preset = @import("preset").active_preset; const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; +const BeaconState = state_transition.BeaconState; const Withdrawals = ssz.capella.Withdrawals.Type; const WithdrawalsResult = state_transition.WithdrawalsResult; const test_case = @import("../test_case.zig"); @@ -73,22 +75,29 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type { const OpType = @field(ForkTypes, operation.operationObject()); return struct { - pre: TestCachedBeaconStateAllForks, + pre: TestCachedBeaconState, // a null post state means the test is expected to fail - post: ?BeaconStateAllForks, + post: ?*BeaconState, op: OpType.Type, bls_setting: BlsSetting, const Self = @This(); pub fn execute(allocator: std.mem.Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); - defer tc.deinit(); + const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; + var pool = try Node.Pool.init(allocator, pool_size); + defer pool.deinit(); + + var tc = try Self.init(allocator, &pool, dir); + defer { + tc.deinit(); + state_transition.deinitStateTransition(); + } try tc.runTest(); } - pub fn init(allocator: std.mem.Allocator, dir: std.fs.Dir) !Self { + pub fn init(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { var tc = Self{ .pre = undefined, .post = undefined, @@ -97,11 +106,11 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type { }; // load pre state - tc.pre = try tc_utils.loadPreState(allocator, dir); + tc.pre = try tc_utils.loadPreState(allocator, pool, dir); errdefer tc.pre.deinit(); // load pre state - tc.post = try tc_utils.loadPostState(allocator, dir); + tc.post = try tc_utils.loadPostState(allocator, pool, dir); // load the op try loadSszValue(OpType, allocator, dir, comptime operation.inputName() ++ ".ssz_snappy", &tc.op); @@ -119,8 +128,9 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type { OpType.deinit(self.pre.allocator, &self.op); } self.pre.deinit(); - if (self.post) |*post| { - post.deinit(self.pre.allocator); + if (self.post) |post| { + post.deinit(); + self.pre.allocator.destroy(post); } } @@ -154,13 +164,13 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type { try state_transition.processBlsToExecutionChange(self.pre.cached_state, &self.op); }, .consolidation_request => { - try state_transition.processConsolidationRequest(allocator, self.pre.cached_state, &self.op); + try state_transition.processConsolidationRequest(self.pre.cached_state, &self.op); }, .deposit => { try state_transition.processDeposit(allocator, self.pre.cached_state, &self.op); }, .deposit_request => { - try state_transition.processDepositRequest(allocator, self.pre.cached_state, &self.op); + try state_transition.processDepositRequest(self.pre.cached_state, &self.op); }, .execution_payload => { try state_transition.processExecutionPayload( @@ -180,7 +190,7 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type { try state_transition.processVoluntaryExit(self.pre.cached_state, &self.op, verify); }, .withdrawal_request => { - try state_transition.processWithdrawalRequest(allocator, self.pre.cached_state, &self.op); + try state_transition.processWithdrawalRequest(self.pre.cached_state, &self.op); }, .withdrawals => { var withdrawals_result = WithdrawalsResult{ @@ -208,7 +218,7 @@ pub fn TestCase(comptime fork: ForkSeq, comptime operation: Operation) type { pub fn runTest(self: *Self) !void { if (self.post) |post| { try self.process(); - try expectEqualBeaconStates(post, self.pre.cached_state.state.*); + try expectEqualBeaconStates(post, self.pre.cached_state.state); } else { self.process() catch |err| { if (err == error.SkipZigTest) { diff --git a/test/spec/runner/rewards.zig b/test/spec/runner/rewards.zig index fe144380d..8a2b50af7 100644 --- a/test/spec/runner/rewards.zig +++ b/test/spec/runner/rewards.zig @@ -1,9 +1,10 @@ const std = @import("std"); +const Node = @import("persistent_merkle_tree").Node; const ct = @import("consensus_types"); const ssz = @import("ssz"); const ForkSeq = @import("config").ForkSeq; const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const TestCaseUtils = @import("../test_case.zig").TestCaseUtils; const loadSszValue = @import("../test_case.zig").loadSszSnappyValue; @@ -11,6 +12,7 @@ const EpochTransitionCache = state_transition.EpochTransitionCache; const getRewardsAndPenaltiesFn = state_transition.getRewardsAndPenalties; const preset = @import("preset").preset; +const active_preset = @import("preset").active_preset; pub const Handler = enum { basic, @@ -28,7 +30,7 @@ pub fn TestCase(comptime fork: ForkSeq) type { const tc_utils = TestCaseUtils(fork); return struct { - pre: TestCachedBeaconStateAllForks, + pre: TestCachedBeaconState, expected_rewards: []u64, expected_penalties: []u64, actual_rewards: []u64, @@ -37,7 +39,11 @@ pub fn TestCase(comptime fork: ForkSeq) type { const Self = @This(); pub fn execute(allocator: std.mem.Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); + const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; + var pool = try Node.Pool.init(allocator, pool_size); + defer pool.deinit(); + + var tc = try Self.init(allocator, &pool, dir); defer { tc.deinit(); state_transition.deinitStateTransition(); @@ -46,12 +52,12 @@ pub fn TestCase(comptime fork: ForkSeq) type { try tc.runTest(); } - fn init(allocator: std.mem.Allocator, dir: std.fs.Dir) !Self { - var pre_state = try tc_utils.loadPreState(allocator, dir); + fn init(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { + var pre_state = try tc_utils.loadPreState(allocator, pool, dir); errdefer pre_state.deinit(); const cache_allocator = pre_state.allocator; - const validator_count = pre_state.cached_state.state.validators().items.len; + const validator_count = try pre_state.cached_state.state.validatorsCount(); const expected = try Self.buildExpectedRewardsPenalties(cache_allocator, dir, validator_count); return .{ @@ -159,7 +165,7 @@ pub fn TestCase(comptime fork: ForkSeq) type { fn process(self: *Self) !void { const allocator = self.pre.allocator; - const cloned_state = try self.pre.cached_state.clone(allocator); + const cloned_state = try self.pre.cached_state.clone(allocator, .{ .transfer_cache = false }); defer { cloned_state.deinit(); allocator.destroy(cloned_state); diff --git a/test/spec/runner/sanity.zig b/test/spec/runner/sanity.zig index cc9abb373..becbeb302 100644 --- a/test/spec/runner/sanity.zig +++ b/test/spec/runner/sanity.zig @@ -1,11 +1,12 @@ const std = @import("std"); +const Node = @import("persistent_merkle_tree").Node; const ssz = @import("consensus_types"); const ForkSeq = @import("config").ForkSeq; const Preset = @import("preset").Preset; const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; +const BeaconState = state_transition.BeaconState; +const CachedBeaconState = state_transition.CachedBeaconState; const test_case = @import("../test_case.zig"); const loadSszValue = test_case.loadSszSnappyValue; const expectEqualBeaconStates = test_case.expectEqualBeaconStates; @@ -29,14 +30,14 @@ pub fn SlotsTestCase(comptime fork: ForkSeq) type { const tc_utils = TestCaseUtils(fork); return struct { - pre: TestCachedBeaconStateAllForks, - post: BeaconStateAllForks, + pre: TestCachedBeaconState, + post: *BeaconState, slots: u64, const Self = @This(); - pub fn execute(allocator: std.mem.Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); + pub fn execute(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !void { + var tc = try Self.init(allocator, pool, dir); defer { tc.deinit(); state_transition.deinitStateTransition(); @@ -45,7 +46,7 @@ pub fn SlotsTestCase(comptime fork: ForkSeq) type { try tc.runTest(); } - pub fn init(allocator: std.mem.Allocator, dir: std.fs.Dir) !Self { + pub fn init(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { var tc = Self{ .pre = undefined, .post = undefined, @@ -53,11 +54,11 @@ pub fn SlotsTestCase(comptime fork: ForkSeq) type { }; // load pre state - tc.pre = try tc_utils.loadPreState(allocator, dir); + tc.pre = try tc_utils.loadPreState(allocator, pool, dir); errdefer tc.pre.deinit(); // load post state - tc.post = try tc_utils.loadPostState(allocator, dir) orelse + tc.post = try tc_utils.loadPostState(allocator, pool, dir) orelse return error.PostStateNotFound; // load slots @@ -73,21 +74,22 @@ pub fn SlotsTestCase(comptime fork: ForkSeq) type { pub fn deinit(self: *Self) void { self.pre.deinit(); - self.post.deinit(self.pre.allocator); + self.post.deinit(); + self.pre.allocator.destroy(self.post); } pub fn process(self: *Self) !void { - try state_transition.state_transition.processSlotsWithTransientCache( + try state_transition.state_transition.processSlots( self.pre.allocator, self.pre.cached_state, - self.pre.cached_state.state.slot() + self.slots, + try self.pre.cached_state.state.slot() + self.slots, undefined, ); } pub fn runTest(self: *Self) !void { try self.process(); - try expectEqualBeaconStates(self.post, self.pre.cached_state.state.*); + try expectEqualBeaconStates(self.post, self.pre.cached_state.state); } }; } @@ -98,16 +100,16 @@ pub fn BlocksTestCase(comptime fork: ForkSeq) type { const SignedBeaconBlock = @field(ForkTypes, "SignedBeaconBlock"); return struct { - pre: TestCachedBeaconStateAllForks, + pre: TestCachedBeaconState, // a null post state means the test is expected to fail - post: ?BeaconStateAllForks, + post: ?*BeaconState, blocks: []SignedBeaconBlock.Type, bls_setting: BlsSetting, const Self = @This(); - pub fn execute(allocator: std.mem.Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); + pub fn execute(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !void { + var tc = try Self.init(allocator, pool, dir); defer { tc.deinit(); state_transition.deinitStateTransition(); @@ -116,7 +118,7 @@ pub fn BlocksTestCase(comptime fork: ForkSeq) type { try tc.runTest(); } - pub fn init(allocator: std.mem.Allocator, dir: std.fs.Dir) !Self { + pub fn init(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { var tc = Self{ .pre = undefined, .post = undefined, @@ -125,11 +127,11 @@ pub fn BlocksTestCase(comptime fork: ForkSeq) type { }; // load pre state - tc.pre = try tc_utils.loadPreState(allocator, dir); + tc.pre = try tc_utils.loadPreState(allocator, pool, dir); errdefer tc.pre.deinit(); // load post state - tc.post = try tc_utils.loadPostState(allocator, dir); + tc.post = try tc_utils.loadPostState(allocator, pool, dir); // Load meta.yaml for blocks_count var meta_file = try dir.openFile("meta.yaml", .{}); @@ -171,14 +173,15 @@ pub fn BlocksTestCase(comptime fork: ForkSeq) type { } self.pre.allocator.free(self.blocks); self.pre.deinit(); - if (self.post) |*post| { - post.deinit(self.pre.allocator); + if (self.post) |post| { + post.deinit(); + self.pre.allocator.destroy(post); } } - pub fn process(self: *Self) !*CachedBeaconStateAllForks { + pub fn process(self: *Self) !*CachedBeaconState { const verify = self.bls_setting.verify(); - var post_state: *CachedBeaconStateAllForks = self.pre.cached_state; + var post_state: *CachedBeaconState = self.pre.cached_state; for (self.blocks, 0..) |*block, i| { const signed_block = @unionInit(state_transition.SignedBeaconBlock, @tagName(fork), block); { @@ -218,7 +221,7 @@ pub fn BlocksTestCase(comptime fork: ForkSeq) type { actual.deinit(); self.pre.allocator.destroy(actual); } - try expectEqualBeaconStates(post, actual.state.*); + try expectEqualBeaconStates(post, actual.state); } else { _ = self.process() catch |err| { if (err == error.SkipZigTest) { diff --git a/test/spec/runner/transition.zig b/test/spec/runner/transition.zig index 51fb29ebc..dd33b200d 100644 --- a/test/spec/runner/transition.zig +++ b/test/spec/runner/transition.zig @@ -1,30 +1,36 @@ const std = @import("std"); +const Node = @import("persistent_merkle_tree").Node; const ssz = @import("consensus_types"); const ForkSeq = @import("config").ForkSeq; const Preset = @import("preset").Preset; const state_transition = @import("state_transition"); -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const SignedBeaconBlock = state_transition.SignedBeaconBlock; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; -const CachedBeaconStateAllForks = state_transition.CachedBeaconStateAllForks; +const BeaconState = state_transition.BeaconState; +const CachedBeaconState = state_transition.CachedBeaconState; const test_case = @import("../test_case.zig"); const loadSszValue = test_case.loadSszSnappyValue; const expectEqualBeaconStates = test_case.expectEqualBeaconStates; const TestCaseUtils = test_case.TestCaseUtils; const loadSignedBeaconBlock = test_case.loadSignedBeaconBlock; +const active_preset = @import("preset").active_preset; pub fn Transition(comptime fork: ForkSeq) type { const tc_utils = TestCaseUtils(fork); return struct { - pre: TestCachedBeaconStateAllForks, - post: ?BeaconStateAllForks, + pre: TestCachedBeaconState, + post: ?*BeaconState, blocks: []SignedBeaconBlock, const Self = @This(); pub fn execute(allocator: std.mem.Allocator, dir: std.fs.Dir) !void { - var tc = try Self.init(allocator, dir); + const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; + var pool = try Node.Pool.init(allocator, pool_size); + defer pool.deinit(); + + var tc = try Self.init(allocator, &pool, dir); defer { tc.deinit(); state_transition.deinitStateTransition(); @@ -32,7 +38,7 @@ pub fn Transition(comptime fork: ForkSeq) type { try tc.runTest(); } - pub fn init(allocator: std.mem.Allocator, dir: std.fs.Dir) !Self { + pub fn init(allocator: std.mem.Allocator, pool: *Node.Pool, dir: std.fs.Dir) !Self { var tc = Self{ .pre = undefined, .post = undefined, @@ -91,11 +97,11 @@ pub fn Transition(comptime fork: ForkSeq) type { } // load pre state - tc.pre = try tc_utils.loadPreStatePreFork(allocator, dir, fork_epoch); + tc.pre = try tc_utils.loadPreStatePreFork(allocator, pool, dir, fork_epoch); errdefer tc.pre.deinit(); // load post state - tc.post = try tc_utils.loadPostState(allocator, dir); + tc.post = try tc_utils.loadPostState(allocator, pool, dir); return tc; } @@ -106,13 +112,14 @@ pub fn Transition(comptime fork: ForkSeq) type { } self.pre.allocator.free(self.blocks); self.pre.deinit(); - if (self.post) |*post| { - post.deinit(self.pre.allocator); + if (self.post) |post| { + post.deinit(); + self.pre.allocator.destroy(post); } } - pub fn process(self: *Self) !*CachedBeaconStateAllForks { - var post_state: *CachedBeaconStateAllForks = self.pre.cached_state; + pub fn process(self: *Self) !*CachedBeaconState { + var post_state: *CachedBeaconState = self.pre.cached_state; for (self.blocks, 0..) |beacon_block, i| { // if error, clean pre_state of stateTransition() function errdefer { @@ -155,7 +162,7 @@ pub fn Transition(comptime fork: ForkSeq) type { actual.deinit(); self.pre.allocator.destroy(actual); } - try expectEqualBeaconStates(post, actual.state.*); + try expectEqualBeaconStates(post, actual.state); } else { _ = self.process() catch |err| { if (err == error.SkipZigTest) { diff --git a/test/spec/test_case.zig b/test/spec/test_case.zig index 5fb2ff968..75ec313c4 100644 --- a/test/spec/test_case.zig +++ b/test/spec/test_case.zig @@ -4,9 +4,10 @@ const snappy = @import("snappy").raw; const ForkSeq = @import("config").ForkSeq; const isFixedType = @import("ssz").isFixedType; const state_transition = @import("state_transition"); +const Node = @import("persistent_merkle_tree").Node; const SignedBeaconBlock = state_transition.SignedBeaconBlock; -const BeaconStateAllForks = state_transition.BeaconStateAllForks; -const TestCachedBeaconStateAllForks = state_transition.test_utils.TestCachedBeaconStateAllForks; +const BeaconState = state_transition.BeaconState; +const TestCachedBeaconState = state_transition.test_utils.TestCachedBeaconState; const types = @import("consensus_types"); const Epoch = types.primitive.Epoch.Type; @@ -46,53 +47,62 @@ pub fn TestCaseUtils(comptime fork: ForkSeq) type { }; } - pub fn loadPreStatePreFork(allocator: Allocator, dir: std.fs.Dir, fork_epoch: Epoch) !TestCachedBeaconStateAllForks { + pub fn loadPreStatePreFork(allocator: Allocator, pool: *Node.Pool, dir: std.fs.Dir, fork_epoch: Epoch) !TestCachedBeaconState { const fork_pre = comptime getForkPre(); const ForkPreTypes = @field(types, fork_pre.name()); - const pre_state = try allocator.create(ForkPreTypes.BeaconState.Type); - var transfered_pre_state: bool = false; - errdefer { - if (!transfered_pre_state) { - ForkPreTypes.BeaconState.deinit(allocator, pre_state); - allocator.destroy(pre_state); - } - } - pre_state.* = ForkPreTypes.BeaconState.default_value; - try loadSszSnappyValue(ForkPreTypes.BeaconState, allocator, dir, "pre.ssz_snappy", pre_state); - transfered_pre_state = true; + var pre_state = ForkPreTypes.BeaconState.default_value; + try loadSszSnappyValue(ForkPreTypes.BeaconState, allocator, dir, "pre.ssz_snappy", &pre_state); + defer ForkPreTypes.BeaconState.deinit(allocator, &pre_state); + + const pre_state_all_forks = try allocator.create(BeaconState); + errdefer allocator.destroy(pre_state_all_forks); + + pre_state_all_forks.* = @unionInit( + BeaconState, + fork_pre.name(), + try ForkPreTypes.BeaconState.TreeView.fromValue(allocator, pool, &pre_state), + ); + errdefer pre_state_all_forks.deinit(); - var pre_state_all_forks = try BeaconStateAllForks.init(fork_pre, pre_state); - return try TestCachedBeaconStateAllForks.initFromState(allocator, &pre_state_all_forks, fork, fork_epoch); + return try TestCachedBeaconState.initFromState(allocator, pre_state_all_forks, fork, fork_epoch); } - pub fn loadPreState(allocator: Allocator, dir: std.fs.Dir) !TestCachedBeaconStateAllForks { - const pre_state = try allocator.create(ForkTypes.BeaconState.Type); - var transfered_pre_state: bool = false; - errdefer { - if (!transfered_pre_state) { - ForkTypes.BeaconState.deinit(allocator, pre_state); - allocator.destroy(pre_state); - } - } - pre_state.* = ForkTypes.BeaconState.default_value; - try loadSszSnappyValue(ForkTypes.BeaconState, allocator, dir, "pre.ssz_snappy", pre_state); - transfered_pre_state = true; + pub fn loadPreState(allocator: Allocator, pool: *Node.Pool, dir: std.fs.Dir) !TestCachedBeaconState { + var pre_state = ForkTypes.BeaconState.default_value; + try loadSszSnappyValue(ForkTypes.BeaconState, allocator, dir, "pre.ssz_snappy", &pre_state); + defer ForkTypes.BeaconState.deinit(allocator, &pre_state); + + const pre_state_all_forks = try allocator.create(BeaconState); + errdefer allocator.destroy(pre_state_all_forks); - var pre_state_all_forks = try BeaconStateAllForks.init(fork, pre_state); - return try TestCachedBeaconStateAllForks.initFromState(allocator, &pre_state_all_forks, fork, pre_state_all_forks.fork().epoch); + pre_state_all_forks.* = @unionInit( + BeaconState, + fork.name(), + try ForkTypes.BeaconState.TreeView.fromValue(allocator, pool, &pre_state), + ); + errdefer pre_state_all_forks.deinit(); + + var f = try pre_state_all_forks.fork(); + const fork_epoch = try f.get("epoch"); + return try TestCachedBeaconState.initFromState(allocator, pre_state_all_forks, fork, fork_epoch); } /// consumer should deinit the returned state and destroy the pointer - pub fn loadPostState(allocator: Allocator, dir: std.fs.Dir) !?BeaconStateAllForks { + pub fn loadPostState(allocator: Allocator, pool: *Node.Pool, dir: std.fs.Dir) !?*BeaconState { if (dir.statFile("post.ssz_snappy")) |_| { - const post_state = try allocator.create(ForkTypes.BeaconState.Type); - errdefer { - ForkTypes.BeaconState.deinit(allocator, post_state); - allocator.destroy(post_state); - } - post_state.* = ForkTypes.BeaconState.default_value; - try loadSszSnappyValue(ForkTypes.BeaconState, allocator, dir, "post.ssz_snappy", post_state); - return try BeaconStateAllForks.init(fork, post_state); + var post_state = ForkTypes.BeaconState.default_value; + try loadSszSnappyValue(ForkTypes.BeaconState, allocator, dir, "post.ssz_snappy", &post_state); + defer ForkTypes.BeaconState.deinit(allocator, &post_state); + + const post_state_all_forks = try allocator.create(BeaconState); + errdefer allocator.destroy(post_state_all_forks); + + post_state_all_forks.* = @unionInit( + BeaconState, + fork.name(), + try ForkTypes.BeaconState.TreeView.fromValue(allocator, pool, &post_state), + ); + return post_state_all_forks; } else |err| { if (err == error.FileNotFound) { return null; @@ -238,34 +248,85 @@ pub fn loadSszSnappyValue(comptime ST: type, allocator: std.mem.Allocator, dir: } } -pub fn expectEqualBeaconStates(expected: BeaconStateAllForks, actual: BeaconStateAllForks) !void { +pub fn expectEqualBeaconStates(expected: *BeaconState, actual: *BeaconState) !void { if (expected.forkSeq() != actual.forkSeq()) return error.ForkMismatch; - switch (expected.forkSeq()) { - .phase0 => { - if (!phase0.BeaconState.equals(expected.phase0, actual.phase0)) return error.NotEqual; - }, - .altair => { - if (!altair.BeaconState.equals(expected.altair, actual.altair)) return error.NotEqual; - }, - .bellatrix => { - if (!bellatrix.BeaconState.equals(expected.bellatrix, actual.bellatrix)) return error.NotEqual; - }, - .capella => { - if (!capella.BeaconState.equals(expected.capella, actual.capella)) return error.NotEqual; - }, - .deneb => { - if (!deneb.BeaconState.equals(expected.deneb, actual.deneb)) return error.NotEqual; - }, - .electra => { - if (!electra.BeaconState.equals(expected.electra, actual.electra)) { - // more debug - if (!phase0.BeaconBlockHeader.equals(&expected.electra.latest_block_header, &actual.electra.latest_block_header)) return error.LatestBlockHeaderNotEqual; - return error.NotEqual; + if (!std.mem.eql( + u8, + try expected.hashTreeRoot(), + try actual.hashTreeRoot(), + )) { + const Debug = struct { + fn printDiff(comptime StateST: type, expected_state: *BeaconState, actual_state: *BeaconState) !void { + var expected_view = StateST.TreeView{ .base_view = expected_state.baseView() }; + var actual_view = StateST.TreeView{ .base_view = actual_state.baseView() }; + + inline for (StateST.fields) |field| { + const expected_field_root = try expected_view.getRoot(field.name); + const actual_field_root = try actual_view.getRoot(field.name); + if (!std.mem.eql(u8, expected_field_root, actual_field_root)) { + std.debug.print( + "field: {s}\n expected_root: {s}\n actual_root: {s}\n", + .{ + field.name, + std.fmt.fmtSliceHexLower(expected_field_root), + std.fmt.fmtSliceHexLower(actual_field_root), + }, + ); + + @setEvalBranchQuota(100000); + const FieldST = StateST.getFieldType(field.name); + const allocator = std.testing.allocator; + { + var expected_field_view = try expected_view.get(field.name); + if (comptime @hasDecl(FieldST, "TreeView") and @hasDecl(FieldST.TreeView, "length") and @typeInfo(@TypeOf(FieldST.TreeView.length)) == .@"fn") { + std.debug.print( + " expected_value_length: {any}\n", + .{try expected_field_view.length()}, + ); + } + var expected_field_value: FieldST.Type = undefined; + try expected_view.getValue(allocator, field.name, &expected_field_value); + defer if (@hasDecl(FieldST, "deinit")) + FieldST.deinit(allocator, &expected_field_value); + + std.debug.print( + " expected_value: {any}\n", + .{expected_field_value}, + ); + } + { + var actual_field_view = try actual_view.get(field.name); + if (comptime @hasDecl(FieldST, "TreeView") and @hasDecl(FieldST.TreeView, "length") and @typeInfo(@TypeOf(FieldST.TreeView.length)) == .@"fn") { + std.debug.print( + " actual_value_length: {any}\n", + .{try actual_field_view.length()}, + ); + } + var actual_field_value: FieldST.Type = undefined; + try actual_view.getValue(allocator, field.name, &actual_field_value); + defer if (@hasDecl(FieldST, "deinit")) + FieldST.deinit(allocator, &actual_field_value); + + std.debug.print( + " actual_value: {any}\n", + .{actual_field_value}, + ); + } + } + } } - }, - .fulu => { - if (!fulu.BeaconState.equals(expected.fulu, actual.fulu)) return error.NotEqual; - }, + }; + + switch (expected.forkSeq()) { + .phase0 => try Debug.printDiff(types.phase0.BeaconState, expected, actual), + .altair => try Debug.printDiff(types.altair.BeaconState, expected, actual), + .bellatrix => try Debug.printDiff(types.bellatrix.BeaconState, expected, actual), + .capella => try Debug.printDiff(types.capella.BeaconState, expected, actual), + .deneb => try Debug.printDiff(types.deneb.BeaconState, expected, actual), + .electra => try Debug.printDiff(types.electra.BeaconState, expected, actual), + .fulu => try Debug.printDiff(types.fulu.BeaconState, expected, actual), + } + return error.NotEqual; } } diff --git a/test/spec/writer/finality.zig b/test/spec/writer/finality.zig index 1121de0dd..cf8f1fa74 100644 --- a/test/spec/writer/finality.zig +++ b/test/spec/writer/finality.zig @@ -18,18 +18,22 @@ pub const header = \\// Do not commit changes by hand. \\ \\const std = @import("std"); + \\const Node = @import("persistent_merkle_tree").Node; \\const ForkSeq = @import("config").ForkSeq; \\const active_preset = @import("preset").active_preset; \\const spec_test_options = @import("spec_test_options"); \\const Sanity = @import("../runner/sanity.zig"); \\ \\const allocator = std.testing.allocator; + \\const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; \\ \\ ; const test_template = \\test "{s} finality {s} {s}" {{ + \\ var pool = try Node.Pool.init(allocator, pool_size); + \\ defer pool.deinit(); \\ const test_dir_name = try std.fs.path.join(allocator, &[_][]const u8{{ \\ spec_test_options.spec_test_out_dir, \\ spec_test_options.spec_test_version, @@ -38,7 +42,7 @@ const test_template = \\ defer allocator.free(test_dir_name); \\ const test_dir = std.fs.cwd().openDir(test_dir_name, .{{}}) catch return error.SkipZigTest; \\ - \\ try Sanity.BlocksTestCase(.{s}).execute(allocator, test_dir); + \\ try Sanity.BlocksTestCase(.{s}).execute(allocator, &pool, test_dir); \\}} \\ \\ diff --git a/test/spec/writer/random.zig b/test/spec/writer/random.zig index 15b071333..8cc9aa760 100644 --- a/test/spec/writer/random.zig +++ b/test/spec/writer/random.zig @@ -20,18 +20,22 @@ pub const header = \\// Do not commit changes by hand. \\ \\const std = @import("std"); + \\const Node = @import("persistent_merkle_tree").Node; \\const ForkSeq = @import("config").ForkSeq; \\const active_preset = @import("preset").active_preset; \\const spec_test_options = @import("spec_test_options"); \\const Sanity = @import("../runner/sanity.zig"); \\ \\const allocator = std.testing.allocator; + \\const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; \\ \\ ; const test_template = \\test "{s} random {s} {s}" {{ + \\ var pool = try Node.Pool.init(allocator, pool_size); + \\ defer pool.deinit(); \\ const test_dir_name = try std.fs.path.join(allocator, &[_][]const u8{{ \\ spec_test_options.spec_test_out_dir, \\ spec_test_options.spec_test_version, @@ -40,7 +44,7 @@ const test_template = \\ defer allocator.free(test_dir_name); \\ const test_dir = std.fs.cwd().openDir(test_dir_name, .{{}}) catch return error.SkipZigTest; \\ - \\ try Sanity.BlocksTestCase(.{s}).execute(allocator, test_dir); + \\ try Sanity.BlocksTestCase(.{s}).execute(allocator, &pool, test_dir); \\}} \\ \\ diff --git a/test/spec/writer/sanity.zig b/test/spec/writer/sanity.zig index c16394132..ed9df0dce 100644 --- a/test/spec/writer/sanity.zig +++ b/test/spec/writer/sanity.zig @@ -11,18 +11,21 @@ pub const header = \\// Do not commit changes by hand. \\ \\const std = @import("std"); + \\const Node = @import("persistent_merkle_tree").Node; \\const ForkSeq = @import("config").ForkSeq; \\const active_preset = @import("preset").active_preset; \\const spec_test_options = @import("spec_test_options"); \\const Sanity = @import("../runner/sanity.zig"); \\ \\const allocator = std.testing.allocator; - \\ + \\const pool_size = if (active_preset == .mainnet) 10_000_000 else 1_000_000; \\ ; const test_template = \\test "{s} sanity {s} {s}" {{ + \\ var pool = try Node.Pool.init(allocator, pool_size); + \\ defer pool.deinit(); \\ const test_dir_name = try std.fs.path.join(allocator, &[_][]const u8{{ \\ spec_test_options.spec_test_out_dir, \\ spec_test_options.spec_test_version, @@ -48,8 +51,8 @@ pub fn writeTest( test_case_name: []const u8, ) !void { const execute_call = switch (handler) { - .slots => std.fmt.allocPrint(std.heap.page_allocator, "try Sanity.SlotsTestCase(.{s}).execute(allocator, test_dir);", .{@tagName(fork)}) catch unreachable, - .blocks => std.fmt.allocPrint(std.heap.page_allocator, "try Sanity.BlocksTestCase(.{s}).execute(allocator, test_dir);", .{@tagName(fork)}) catch unreachable, + .slots => std.fmt.allocPrint(std.heap.page_allocator, "try Sanity.SlotsTestCase(.{s}).execute(allocator, &pool, test_dir);", .{@tagName(fork)}) catch unreachable, + .blocks => std.fmt.allocPrint(std.heap.page_allocator, "try Sanity.BlocksTestCase(.{s}).execute(allocator, &pool, test_dir);", .{@tagName(fork)}) catch unreachable, }; defer std.heap.page_allocator.free(execute_call); try writer.print(test_template, .{ diff --git a/zbuild.zon b/zbuild.zon index d742251d7..36d7e4af9 100644 --- a/zbuild.zon +++ b/zbuild.zon @@ -81,7 +81,7 @@ }, .era = .{ .root_source_file = "src/era/root.zig", - .imports = .{ .consensus_types, .config, .preset, .state_transition, .snappy }, + .imports = .{ .consensus_types, .config, .preset, .state_transition, .snappy, .persistent_merkle_tree }, }, .hashing = .{ .root_source_file = "src/hashing/root.zig", @@ -113,6 +113,7 @@ .preset, .constants, .hex, + .persistent_merkle_tree, }, }, }, @@ -186,13 +187,15 @@ .bench_process_block = .{ .root_module = .{ .root_source_file = "bench/state_transition/process_block.zig", - .imports = .{ .state_transition, .consensus_types, .config, .zbench }, + .imports = .{ .state_transition, .consensus_types, .config, .zbench, .persistent_merkle_tree, .download_era_options, .era }, + .strip = false, + .omit_frame_pointer = false, }, }, .bench_process_epoch = .{ .root_module = .{ .root_source_file = "bench/state_transition/process_epoch.zig", - .imports = .{ .state_transition, .consensus_types, .config, .zbench }, + .imports = .{ .state_transition, .consensus_types, .config, .zbench, .persistent_merkle_tree, .download_era_options, .era }, }, }, }, @@ -208,6 +211,7 @@ .preset, .constants, .blst, + .persistent_merkle_tree, }, }, .filters = .{},