diff --git a/pkgs/metrics/src/lib.zig b/pkgs/metrics/src/lib.zig index 1ed9d9687..74d386ad5 100644 --- a/pkgs/metrics/src/lib.zig +++ b/pkgs/metrics/src/lib.zig @@ -46,6 +46,30 @@ const Metrics = struct { lean_attestation_validation_time_seconds: ForkChoiceAttestationValidationTimeHistogram, lean_pq_signature_attestation_signing_time_seconds: PQSignatureSigningHistogram, lean_pq_signature_attestation_verification_time_seconds: PQSignatureVerificationHistogram, + // Granular metrics for block processing breakdown + lean_fork_choice_updatehead_time_seconds: ForkChoiceUpdateHeadHistogram, + lean_chain_database_write_time_seconds: ChainDatabaseWriteHistogram, + lean_chain_attestation_loop_time_seconds: ChainAttestationLoopHistogram, + lean_chain_state_clone_time_seconds: ChainStateCloneHistogram, + lean_chain_onblockfollowup_time_seconds: ChainOnBlockFollowupHistogram, + lean_fork_choice_computedeltas_time_seconds: ForkChoiceComputeDeltasHistogram, + lean_fork_choice_applydeltas_time_seconds: ForkChoiceApplyDeltasHistogram, + lean_chain_signature_verification_time_seconds: ChainSignatureVerificationHistogram, + lean_chain_proposer_attestation_time_seconds: ChainProposerAttestationHistogram, + // State transition internal metrics + lean_state_transition_state_root_validation_time_seconds: StateRootValidationHistogram, + lean_state_transition_state_root_in_slot_time_seconds: StateRootInSlotHistogram, + lean_state_transition_block_header_hash_time_seconds: BlockHeaderHashHistogram, + lean_state_transition_get_justification_time_seconds: GetJustificationHistogram, + lean_state_transition_with_justifications_time_seconds: WithJustificationsHistogram, + // Justifications cache metrics + lean_state_transition_justifications_cache_hits_total: JustificationsCacheHitsCounter, + lean_state_transition_justifications_cache_misses_total: JustificationsCacheMissesCounter, + lean_state_transition_justifications_cache_hit_time_seconds: JustificationsCacheHitTimeHistogram, + lean_state_transition_justifications_cache_miss_time_seconds: JustificationsCacheMissTimeHistogram, + // Block processing path counters + lean_chain_blocks_with_cached_state_total: BlocksWithCachedStateCounter, + lean_chain_blocks_with_computed_state_total: BlocksWithComputedStateCounter, // Aggregated attestation signature metrics lean_pq_sig_aggregated_signatures_total: PQSigAggregatedSignaturesTotalCounter, lean_pq_sig_attestations_in_aggregated_signatures_total: PQSigAttestationsInAggregatedTotalCounter, @@ -70,12 +94,33 @@ const Metrics = struct { const ChainHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 }); const BlockProcessingHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 }); - const StateTransitionHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 4 }); - const SlotsProcessingHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); - const BlockProcessingTimeHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); - const AttestationsProcessingHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const StateTransitionHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.03, 0.04, 0.05, 0.075, 0.1, 0.125, 0.15, 0.2, 0.25, 0.3, 0.4, 0.6, 0.8, 1, 1.5, 2 }); + const SlotsProcessingHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const BlockProcessingTimeHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const AttestationsProcessingHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); const PQSignatureSigningHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); const PQSignatureVerificationHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + // Granular histogram types + const ForkChoiceUpdateHeadHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1 }); + const ChainDatabaseWriteHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1 }); + const ChainAttestationLoopHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const ChainStateCloneHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const ChainOnBlockFollowupHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1 }); + const ForkChoiceComputeDeltasHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const ForkChoiceApplyDeltasHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const ChainSignatureVerificationHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5 }); + const ChainProposerAttestationHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + // State transition internal histogram types + const StateRootValidationHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5 }); + const StateRootInSlotHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const BlockHeaderHashHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const GetJustificationHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0000005, 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, 0.0001, 0.0002, 0.0005, 0.001 }); + const WithJustificationsHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0000005, 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, 0.0001, 0.0002, 0.0005, 0.001 }); + // Justifications cache metric types + const JustificationsCacheHitsCounter = metrics_lib.Counter(u64); + const JustificationsCacheMissesCounter = metrics_lib.Counter(u64); + const JustificationsCacheHitTimeHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0000005, 0.000001, 0.000002, 0.000005, 0.00001, 0.00002, 0.00005, 0.0001, 0.0002, 0.0005, 0.001 }); + const JustificationsCacheMissTimeHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.0000005, 0.000001, 0.000002, 0.000005, 0.00001, 0.00005, 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1 }); const LeanHeadSlotGauge = metrics_lib.Gauge(u64); const LeanLatestJustifiedSlotGauge = metrics_lib.Gauge(u64); const LeanLatestFinalizedSlotGauge = metrics_lib.Gauge(u64); @@ -86,6 +131,8 @@ const Metrics = struct { const ForkChoiceAttestationsValidLabeledCounter = metrics_lib.CounterVec(u64, struct { source: []const u8 }); const ForkChoiceAttestationsInvalidLabeledCounter = metrics_lib.CounterVec(u64, struct { source: []const u8 }); const ForkChoiceAttestationValidationTimeHistogram = metrics_lib.Histogram(f32, &[_]f32{ 0.005, 0.01, 0.025, 0.05, 0.1, 1 }); + const BlocksWithCachedStateCounter = metrics_lib.Counter(u64); + const BlocksWithComputedStateCounter = metrics_lib.Counter(u64); // Aggregated attestation signature metric types const PQSigAggregatedSignaturesTotalCounter = metrics_lib.Counter(u64); const PQSigAttestationsInAggregatedTotalCounter = metrics_lib.Counter(u64); @@ -203,6 +250,102 @@ fn observePQSignatureAttestationVerification(ctx: ?*anyopaque, value: f32) void histogram.observe(value); } +fn observeForkChoiceUpdateHead(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ForkChoiceUpdateHeadHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeChainDatabaseWrite(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ChainDatabaseWriteHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeChainAttestationLoop(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ChainAttestationLoopHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeChainStateClone(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ChainStateCloneHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeChainOnBlockFollowup(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ChainOnBlockFollowupHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeForkChoiceComputeDeltas(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ForkChoiceComputeDeltasHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeForkChoiceApplyDeltas(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ForkChoiceApplyDeltasHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeChainSignatureVerification(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ChainSignatureVerificationHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeChainProposerAttestation(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.ChainProposerAttestationHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeStateRootValidation(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.StateRootValidationHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeStateRootInSlot(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.StateRootInSlotHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeBlockHeaderHash(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.BlockHeaderHashHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeGetJustification(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.GetJustificationHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeWithJustifications(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.WithJustificationsHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeJustificationsCacheHitTime(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.JustificationsCacheHitTimeHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + +fn observeJustificationsCacheMissTime(ctx: ?*anyopaque, value: f32) void { + const histogram_ptr = ctx orelse return; + const histogram: *Metrics.JustificationsCacheMissTimeHistogram = @ptrCast(@alignCast(histogram_ptr)); + histogram.observe(value); +} + fn observePQSigBuildingTime(ctx: ?*anyopaque, value: f32) void { const histogram_ptr = ctx orelse return; // No-op if not initialized const histogram: *Metrics.PQSigBuildingTimeHistogram = @ptrCast(@alignCast(histogram_ptr)); @@ -267,6 +410,71 @@ pub var lean_pq_sig_aggregated_signatures_verification_time_seconds: Histogram = .observe = &observePQSigAggregatedVerification, }; +pub var lean_fork_choice_updatehead_time_seconds: Histogram = .{ + .context = null, + .observe = &observeForkChoiceUpdateHead, +}; +pub var lean_chain_database_write_time_seconds: Histogram = .{ + .context = null, + .observe = &observeChainDatabaseWrite, +}; +pub var lean_chain_attestation_loop_time_seconds: Histogram = .{ + .context = null, + .observe = &observeChainAttestationLoop, +}; +pub var lean_chain_state_clone_time_seconds: Histogram = .{ + .context = null, + .observe = &observeChainStateClone, +}; +pub var lean_chain_onblockfollowup_time_seconds: Histogram = .{ + .context = null, + .observe = &observeChainOnBlockFollowup, +}; +pub var lean_fork_choice_computedeltas_time_seconds: Histogram = .{ + .context = null, + .observe = &observeForkChoiceComputeDeltas, +}; +pub var lean_fork_choice_applydeltas_time_seconds: Histogram = .{ + .context = null, + .observe = &observeForkChoiceApplyDeltas, +}; +pub var lean_chain_signature_verification_time_seconds: Histogram = .{ + .context = null, + .observe = &observeChainSignatureVerification, +}; +pub var lean_chain_proposer_attestation_time_seconds: Histogram = .{ + .context = null, + .observe = &observeChainProposerAttestation, +}; +pub var lean_state_transition_state_root_validation_time_seconds: Histogram = .{ + .context = null, + .observe = &observeStateRootValidation, +}; +pub var lean_state_transition_state_root_in_slot_time_seconds: Histogram = .{ + .context = null, + .observe = &observeStateRootInSlot, +}; +pub var lean_state_transition_block_header_hash_time_seconds: Histogram = .{ + .context = null, + .observe = &observeBlockHeaderHash, +}; +pub var lean_state_transition_get_justification_time_seconds: Histogram = .{ + .context = null, + .observe = &observeGetJustification, +}; +pub var lean_state_transition_with_justifications_time_seconds: Histogram = .{ + .context = null, + .observe = &observeWithJustifications, +}; +pub var lean_state_transition_justifications_cache_hit_time_seconds: Histogram = .{ + .context = null, + .observe = &observeJustificationsCacheHitTime, +}; +pub var lean_state_transition_justifications_cache_miss_time_seconds: Histogram = .{ + .context = null, + .observe = &observeJustificationsCacheMissTime, +}; + /// Initializes the metrics system. Must be called once at startup. pub fn init(allocator: std.mem.Allocator) !void { if (g_initialized) return; @@ -297,6 +505,27 @@ pub fn init(allocator: std.mem.Allocator) !void { .lean_attestation_validation_time_seconds = Metrics.ForkChoiceAttestationValidationTimeHistogram.init("lean_attestation_validation_time_seconds", .{ .help = "Time taken to validate attestation." }, .{}), .lean_pq_signature_attestation_signing_time_seconds = Metrics.PQSignatureSigningHistogram.init("lean_pq_signature_attestation_signing_time_seconds", .{ .help = "Time taken to sign an attestation." }, .{}), .lean_pq_signature_attestation_verification_time_seconds = Metrics.PQSignatureVerificationHistogram.init("lean_pq_signature_attestation_verification_time_seconds", .{ .help = "Time taken to verify an attestation signature." }, .{}), + .lean_fork_choice_updatehead_time_seconds = Metrics.ForkChoiceUpdateHeadHistogram.init("lean_fork_choice_updatehead_time_seconds", .{ .help = "Fork choice head computation." }, .{}), + .lean_chain_database_write_time_seconds = Metrics.ChainDatabaseWriteHistogram.init("lean_chain_database_write_time_seconds", .{ .help = "Block and state database writes." }, .{}), + .lean_chain_attestation_loop_time_seconds = Metrics.ChainAttestationLoopHistogram.init("lean_chain_attestation_loop_time_seconds", .{ .help = "Attestation validation in block processing." }, .{}), + .lean_chain_state_clone_time_seconds = Metrics.ChainStateCloneHistogram.init("lean_chain_state_clone_time_seconds", .{ .help = "SSZ state cloning." }, .{}), + .lean_chain_onblockfollowup_time_seconds = Metrics.ChainOnBlockFollowupHistogram.init("lean_chain_onblockfollowup_time_seconds", .{ .help = "Event emission and finalization checks." }, .{}), + .lean_fork_choice_computedeltas_time_seconds = Metrics.ForkChoiceComputeDeltasHistogram.init("lean_fork_choice_computedeltas_time_seconds", .{ .help = "Validator weight delta computation." }, .{}), + .lean_fork_choice_applydeltas_time_seconds = Metrics.ForkChoiceApplyDeltasHistogram.init("lean_fork_choice_applydeltas_time_seconds", .{ .help = "Weight delta propagation and best descendant updates." }, .{}), + .lean_chain_signature_verification_time_seconds = Metrics.ChainSignatureVerificationHistogram.init("lean_chain_signature_verification_time_seconds", .{ .help = "XMSS signature verification for block attestations." }, .{}), + .lean_chain_proposer_attestation_time_seconds = Metrics.ChainProposerAttestationHistogram.init("lean_chain_proposer_attestation_time_seconds", .{ .help = "Proposer attestation processing." }, .{}), + .lean_state_transition_state_root_validation_time_seconds = Metrics.StateRootValidationHistogram.init("lean_state_transition_state_root_validation_time_seconds", .{ .help = "State root validation in apply_transition." }, .{}), + .lean_state_transition_state_root_in_slot_time_seconds = Metrics.StateRootInSlotHistogram.init("lean_state_transition_state_root_in_slot_time_seconds", .{ .help = "State root computation in process_slot." }, .{}), + .lean_state_transition_block_header_hash_time_seconds = Metrics.BlockHeaderHashHistogram.init("lean_state_transition_block_header_hash_time_seconds", .{ .help = "Block header hash in process_block_header." }, .{}), + .lean_state_transition_get_justification_time_seconds = Metrics.GetJustificationHistogram.init("lean_state_transition_get_justification_time_seconds", .{ .help = "Justifications HashMap creation from state." }, .{}), + .lean_state_transition_with_justifications_time_seconds = Metrics.WithJustificationsHistogram.init("lean_state_transition_with_justifications_time_seconds", .{ .help = "State update with justifications HashMap." }, .{}), + // Justifications cache metrics + .lean_state_transition_justifications_cache_hits_total = Metrics.JustificationsCacheHitsCounter.init("lean_state_transition_justifications_cache_hits_total", .{ .help = "Total justifications cache hits (clone from cache)." }, .{}), + .lean_state_transition_justifications_cache_misses_total = Metrics.JustificationsCacheMissesCounter.init("lean_state_transition_justifications_cache_misses_total", .{ .help = "Total justifications cache misses (build from state)." }, .{}), + .lean_state_transition_justifications_cache_hit_time_seconds = Metrics.JustificationsCacheHitTimeHistogram.init("lean_state_transition_justifications_cache_hit_time_seconds", .{ .help = "Time to clone justifications from cache." }, .{}), + .lean_state_transition_justifications_cache_miss_time_seconds = Metrics.JustificationsCacheMissTimeHistogram.init("lean_state_transition_justifications_cache_miss_time_seconds", .{ .help = "Time to build justifications from state (cache miss)." }, .{}), + .lean_chain_blocks_with_cached_state_total = Metrics.BlocksWithCachedStateCounter.init("lean_chain_blocks_with_cached_state_total", .{ .help = "Blocks processed with precomputed state (skip apply_transition)." }, .{}), + .lean_chain_blocks_with_computed_state_total = Metrics.BlocksWithComputedStateCounter.init("lean_chain_blocks_with_computed_state_total", .{ .help = "Blocks processed with computed state (call apply_transition with cache)." }, .{}), // Aggregated attestation signature metrics .lean_pq_sig_aggregated_signatures_total = Metrics.PQSigAggregatedSignaturesTotalCounter.init("lean_pq_sig_aggregated_signatures_total", .{ .help = "Total number of aggregated signatures." }, .{}), .lean_pq_sig_attestations_in_aggregated_signatures_total = Metrics.PQSigAttestationsInAggregatedTotalCounter.init("lean_pq_sig_attestations_in_aggregated_signatures_total", .{ .help = "Total number of attestations included into aggregated signatures." }, .{}), @@ -334,6 +563,22 @@ pub fn init(allocator: std.mem.Allocator) !void { lean_attestation_validation_time_seconds.context = @ptrCast(&metrics.lean_attestation_validation_time_seconds); lean_pq_signature_attestation_signing_time_seconds.context = @ptrCast(&metrics.lean_pq_signature_attestation_signing_time_seconds); lean_pq_signature_attestation_verification_time_seconds.context = @ptrCast(&metrics.lean_pq_signature_attestation_verification_time_seconds); + lean_fork_choice_updatehead_time_seconds.context = @ptrCast(&metrics.lean_fork_choice_updatehead_time_seconds); + lean_chain_database_write_time_seconds.context = @ptrCast(&metrics.lean_chain_database_write_time_seconds); + lean_chain_attestation_loop_time_seconds.context = @ptrCast(&metrics.lean_chain_attestation_loop_time_seconds); + lean_chain_state_clone_time_seconds.context = @ptrCast(&metrics.lean_chain_state_clone_time_seconds); + lean_chain_onblockfollowup_time_seconds.context = @ptrCast(&metrics.lean_chain_onblockfollowup_time_seconds); + lean_fork_choice_computedeltas_time_seconds.context = @ptrCast(&metrics.lean_fork_choice_computedeltas_time_seconds); + lean_fork_choice_applydeltas_time_seconds.context = @ptrCast(&metrics.lean_fork_choice_applydeltas_time_seconds); + lean_chain_signature_verification_time_seconds.context = @ptrCast(&metrics.lean_chain_signature_verification_time_seconds); + lean_chain_proposer_attestation_time_seconds.context = @ptrCast(&metrics.lean_chain_proposer_attestation_time_seconds); + lean_state_transition_state_root_validation_time_seconds.context = @ptrCast(&metrics.lean_state_transition_state_root_validation_time_seconds); + lean_state_transition_state_root_in_slot_time_seconds.context = @ptrCast(&metrics.lean_state_transition_state_root_in_slot_time_seconds); + lean_state_transition_block_header_hash_time_seconds.context = @ptrCast(&metrics.lean_state_transition_block_header_hash_time_seconds); + lean_state_transition_get_justification_time_seconds.context = @ptrCast(&metrics.lean_state_transition_get_justification_time_seconds); + lean_state_transition_with_justifications_time_seconds.context = @ptrCast(&metrics.lean_state_transition_with_justifications_time_seconds); + lean_state_transition_justifications_cache_hit_time_seconds.context = @ptrCast(&metrics.lean_state_transition_justifications_cache_hit_time_seconds); + lean_state_transition_justifications_cache_miss_time_seconds.context = @ptrCast(&metrics.lean_state_transition_justifications_cache_miss_time_seconds); lean_pq_sig_attestation_signatures_building_time_seconds.context = @ptrCast(&metrics.lean_pq_sig_attestation_signatures_building_time_seconds); lean_pq_sig_aggregated_signatures_verification_time_seconds.context = @ptrCast(&metrics.lean_pq_sig_aggregated_signatures_verification_time_seconds); diff --git a/pkgs/node/src/chain.zig b/pkgs/node/src/chain.zig index 7fd5eaf51..081b06643 100644 --- a/pkgs/node/src/chain.zig +++ b/pkgs/node/src/chain.zig @@ -101,6 +101,7 @@ pub const BeamChain = struct { last_emitted_finalized: types.Checkpoint, connected_peers: *const std.StringHashMap(PeerInfo), node_registry: *const NodeNameRegistry, + justifications_cache: std.AutoHashMap(types.Root, std.AutoHashMapUnmanaged(types.Root, []u8)), force_block_production: bool, is_aggregator_enabled: bool, // Cached finalized state loaded from database (separate from states map to avoid affecting pruning) @@ -158,6 +159,7 @@ pub const BeamChain = struct { .last_emitted_finalized = fork_choice.fcStore.latest_finalized, .connected_peers = connected_peers, .node_registry = opts.node_registry, + .justifications_cache = std.AutoHashMap(types.Root, std.AutoHashMapUnmanaged(types.Root, []u8)).init(allocator), .force_block_production = opts.force_block_production, .is_aggregator_enabled = opts.is_aggregator, .public_key_cache = xmss.PublicKeyCache.init(allocator), @@ -186,6 +188,17 @@ pub const BeamChain = struct { } self.states.deinit(); + // Clean up justifications cache + var cache_it = self.justifications_cache.iterator(); + while (cache_it.next()) |entry| { + var just_it = entry.value_ptr.iterator(); + while (just_it.next()) |just_entry| { + self.allocator.free(just_entry.value_ptr.*); + } + entry.value_ptr.deinit(self.allocator); + } + self.justifications_cache.deinit(); + // Clean up cached finalized state if present if (self.cached_finalized_state) |cached_state| { cached_state.deinit(); @@ -795,22 +808,80 @@ pub const BeamChain = struct { break :computedroot cblock_root; }; - const post_state = if (blockInfo.postState) |post_state_ptr| post_state_ptr else computedstate: { + const post_state = if (blockInfo.postState) |post_state_ptr| cachedstate: { + // PATH 1: Block with precomputed state (proposer or DB replay) + // These blocks skip apply_transition() so cache metrics aren't recorded + // Track this path for visibility + if (comptime !zeam_metrics.isZKVM()) { + zeam_metrics.metrics.lean_chain_blocks_with_cached_state_total.incr(); + } + break :cachedstate post_state_ptr; + } else computedstate: { + // PATH 2: Block needs fresh validation - cache is used here + if (comptime !zeam_metrics.isZKVM()) { + zeam_metrics.metrics.lean_chain_blocks_with_computed_state_total.incr(); + } + // 1. get parent state const pre_state = self.states.get(block.parent_root) orelse return BlockProcessingError.MissingPreState; const cpost_state = try self.allocator.create(types.BeamState); + errdefer self.allocator.destroy(cpost_state); + const clone_timer = zeam_metrics.lean_chain_state_clone_time_seconds.start(); try types.sszClone(self.allocator, types.BeamState, pre_state.*, cpost_state); + _ = clone_timer.observe(); + errdefer cpost_state.deinit(); // 2. verify XMSS signatures (independent step; placed before STF for now, parallelizable later) // Use public key cache to avoid repeated SSZ deserialization of validator public keys + const sig_verify_timer = zeam_metrics.lean_chain_signature_verification_time_seconds.start(); try stf.verifySignatures(self.allocator, pre_state, &signedBlock, &self.public_key_cache); + _ = sig_verify_timer.observe(); + + // 3. prepare justifications (from cache or state) + var justifications: std.AutoHashMapUnmanaged(types.Root, []u8) = .empty; + var owns_justifications = true; + + const get_just_timer = zeam_metrics.lean_state_transition_get_justification_time_seconds.start(); + if (self.justifications_cache.fetchRemove(block.parent_root)) |kv| { + justifications = kv.value; + zeam_metrics.metrics.lean_state_transition_justifications_cache_hits_total.incr(); + } else { + try pre_state.getJustification(self.allocator, &justifications); + zeam_metrics.metrics.lean_state_transition_justifications_cache_misses_total.incr(); + } + _ = get_just_timer.observe(); - // 3. apply state transition assuming signatures are valid (STF does not re-verify) + defer if (owns_justifications) { + var it = justifications.iterator(); + while (it.next()) |entry| { + self.allocator.free(entry.value_ptr.*); + } + justifications.deinit(self.allocator); + }; + + // 4. apply state transition try stf.apply_transition(self.allocator, cpost_state, block, .{ .logger = self.stf_logger, .validSignatures = true, + .justifications = &justifications, .rootToSlotCache = &self.root_to_slot_cache, }); + + // 5. store justifications to cache (clear old entries first) + var clear_it = self.justifications_cache.iterator(); + while (clear_it.next()) |entry| { + var map = entry.value_ptr.*; + var map_it = map.iterator(); + while (map_it.next()) |e| { + self.allocator.free(e.value_ptr.*); + } + map.deinit(self.allocator); + } + self.justifications_cache.clearRetainingCapacity(); + + try self.justifications_cache.put(block_root, justifications); + owns_justifications = false; + break :computedstate cpost_state; }; @@ -836,6 +907,7 @@ pub const BeamChain = struct { block.slot, }); + const attestation_loop_timer = zeam_metrics.lean_chain_attestation_loop_time_seconds.start(); const aggregated_attestations = block.body.attestations.constSlice(); const signature_groups = signedBlock.signature.attestation_signatures.constSlice(); @@ -908,15 +980,19 @@ pub const BeamChain = struct { self.logger.warn("failed to store aggregated payload for attestation index={d}: {any}", .{ index, e }); }; } + _ = attestation_loop_timer.observe(); // 5. fc update head + const updatehead_timer = zeam_metrics.lean_fork_choice_updatehead_time_seconds.start(); _ = try self.forkChoice.updateHead(); + _ = updatehead_timer.observe(); break :fcprocessing freshFcBlock; }; try self.states.put(fcBlock.blockRoot, post_state); // 6. proposer attestation + const proposer_attest_timer = zeam_metrics.lean_chain_proposer_attestation_time_seconds.start(); const proposer_signature = signedBlock.signature.proposer_signature; const signed_proposer_attestation = types.SignedAttestation{ .validator_id = signedBlock.message.proposer_attestation.validator_id, @@ -927,16 +1003,19 @@ pub const BeamChain = struct { self.forkChoice.onSignedAttestation(signed_proposer_attestation) catch |e| { self.logger.err("error processing proposer attestation={f} error={any}", .{ signed_proposer_attestation, e }); }; + _ = proposer_attest_timer.observe(); const processing_time = onblock_timer.observe(); // 7. Save block and state to database and confirm the block in forkchoice + const db_write_timer = zeam_metrics.lean_chain_database_write_time_seconds.start(); self.updateBlockDb(signedBlock, fcBlock.blockRoot, post_state.*, block.slot) catch |err| { self.logger.err("failed to update block database for block root=0x{x}: {any}", .{ &fcBlock.blockRoot, err, }); }; + _ = db_write_timer.observe(); try self.forkChoice.confirmBlock(block_root); self.logger.info("processed block with root=0x{x} slot={d} processing time={d} (computed root={any} computed state={any})", .{ @@ -951,6 +1030,8 @@ pub const BeamChain = struct { pub fn onBlockFollowup(self: *Self, pruneForkchoice: bool, signedBlock: ?*const types.SignedBlockWithAttestation) void { _ = signedBlock; + const followup_timer = zeam_metrics.lean_chain_onblockfollowup_time_seconds.start(); + defer _ = followup_timer.observe(); // 8. Asap emit new events via SSE (use forkchoice ProtoBlock directly) const new_head = self.forkChoice.getHead(); if (api.events.NewHeadEvent.fromProtoBlock(self.allocator, new_head)) |head_event| { diff --git a/pkgs/node/src/forkchoice.zig b/pkgs/node/src/forkchoice.zig index 7b06bc196..56a7a8560 100644 --- a/pkgs/node/src/forkchoice.zig +++ b/pkgs/node/src/forkchoice.zig @@ -142,6 +142,8 @@ pub const ProtoArray = struct { // Internal unlocked version - assumes caller holds lock fn applyDeltasUnlocked(self: *Self, deltas: []isize, cutoff_weight: u64) !void { + const applydeltas_timer = zeam_metrics.lean_fork_choice_applydeltas_time_seconds.start(); + defer _ = applydeltas_timer.observe(); if (deltas.len != self.nodes.items.len) { return ForkChoiceError.InvalidDeltas; } @@ -973,6 +975,9 @@ pub const ForkChoice = struct { // Internal unlocked version - assumes caller holds lock // Always reads from the per-validator attestation tracker (sole source of truth for fork choice). fn computeDeltasUnlocked(self: *Self, from_known: bool) ![]isize { + const computedeltas_timer = zeam_metrics.lean_fork_choice_computedeltas_time_seconds.start(); + defer _ = computedeltas_timer.observe(); + // prep the deltas data structure while (self.deltas.items.len < self.protoArray.nodes.items.len) { try self.deltas.append(self.allocator, 0); diff --git a/pkgs/state-transition/src/transition.zig b/pkgs/state-transition/src/transition.zig index 5fdd9694c..718ac165b 100644 --- a/pkgs/state-transition/src/transition.zig +++ b/pkgs/state-transition/src/transition.zig @@ -19,6 +19,8 @@ pub const StateTransitionOpts = struct { validSignatures: bool = true, validateResult: bool = true, logger: zeam_utils.ModuleLogger, + // Optional pre-populated justifications map; if null, built from state internally + justifications: ?*std.AutoHashMapUnmanaged(types.Root, []u8) = null, rootToSlotCache: ?*types.RootToSlotCache = null, }; @@ -45,7 +47,7 @@ pub fn apply_raw_block(allocator: Allocator, state: *types.BeamState, block: *ty try state.process_slots(allocator, block.slot, logger); // process block and modify the pre state to post state - try state.process_block(allocator, block.*, logger, cache); + try state.process_block(allocator, block.*, .{ .logger = logger, .justifications = null, .rootToSlotCache = cache }); logger.debug("extracting state root\n", .{}); // extract the post state root @@ -205,13 +207,15 @@ pub fn apply_transition(allocator: Allocator, state: *types.BeamState, block: ty try state.process_slots(allocator, block.slot, opts.logger); // process the block - try state.process_block(allocator, block, opts.logger, opts.rootToSlotCache); + try state.process_block(allocator, block, opts); const validateResult = opts.validateResult; if (validateResult) { // verify the post state root + const validation_timer = zeam_metrics.lean_state_transition_state_root_validation_time_seconds.start(); var state_root: [32]u8 = undefined; try zeam_utils.hashTreeRoot(*types.BeamState, state, &state_root, allocator); + _ = validation_timer.observe(); if (!std.mem.eql(u8, &state_root, &block.state_root)) { opts.logger.debug("state root={x} block root={x}\n", .{ &state_root, &block.state_root }); return StateTransitionError.InvalidPostState; diff --git a/pkgs/types/src/state.zig b/pkgs/types/src/state.zig index 62dbf23f8..ad766e624 100644 --- a/pkgs/types/src/state.zig +++ b/pkgs/types/src/state.zig @@ -246,8 +246,10 @@ pub const BeamState = struct { // this completes latest block header for parentRoot checks of new block if (std.mem.eql(u8, &self.latest_block_header.state_root, &utils.ZERO_HASH)) { + const slot_timer = zeam_metrics.lean_state_transition_state_root_in_slot_time_seconds.start(); var prev_state_root: [32]u8 = undefined; try zeam_utils.hashTreeRoot(*BeamState, self, &prev_state_root, allocator); + _ = slot_timer.observe(); self.latest_block_header.state_root = prev_state_root; } } @@ -297,8 +299,10 @@ pub const BeamState = struct { } // 4. verify latest block header is the parent + const header_timer = zeam_metrics.lean_state_transition_block_header_hash_time_seconds.start(); var head_root: [32]u8 = undefined; try zeam_utils.hashTreeRoot(block.BeamBlockHeader, self.latest_block_header, &head_root, allocator); + _ = header_timer.observe(); if (!std.mem.eql(u8, &head_root, &staged_block.parent_root)) { logger.err("state root={x} block root={x}\n", .{ &head_root, &staged_block.parent_root }); return StateTransitionError.InvalidParentRoot; @@ -327,27 +331,33 @@ pub const BeamState = struct { try staged_block.blockToLatestBlockHeader(allocator, &self.latest_block_header); } - pub fn process_block(self: *Self, allocator: Allocator, staged_block: BeamBlock, logger: zeam_utils.ModuleLogger, cache: ?*utils.RootToSlotCache) !void { + pub fn process_block(self: *Self, allocator: Allocator, staged_block: BeamBlock, opts: anytype) !void { const block_timer = zeam_metrics.lean_state_transition_block_processing_time_seconds.start(); defer _ = block_timer.observe(); + const logger = if (@hasField(@TypeOf(opts), "logger")) opts.logger else opts; + // start block processing try self.process_block_header(allocator, staged_block, logger); // PQ devner-0 has no execution // try process_execution_payload_header(state, block); - try self.process_operations(allocator, staged_block, logger, cache); + try self.process_operations(allocator, staged_block, opts); } - fn process_operations(self: *Self, allocator: Allocator, staged_block: BeamBlock, logger: zeam_utils.ModuleLogger, cache: ?*utils.RootToSlotCache) !void { + fn process_operations(self: *Self, allocator: Allocator, staged_block: BeamBlock, opts: anytype) !void { // 1. process attestations - try self.process_attestations(allocator, staged_block.body.attestations, logger, cache); + try self.process_attestations(allocator, staged_block.body.attestations, opts); } - fn process_attestations(self: *Self, allocator: Allocator, attestations: AggregatedAttestations, logger: zeam_utils.ModuleLogger, cache: ?*utils.RootToSlotCache) !void { + fn process_attestations(self: *Self, allocator: Allocator, attestations: AggregatedAttestations, opts: anytype) !void { const attestations_timer = zeam_metrics.lean_state_transition_attestations_processing_time_seconds.start(); defer _ = attestations_timer.observe(); + const logger = if (@hasField(@TypeOf(opts), "logger")) opts.logger else opts; + const justifications_ptr = if (@hasField(@TypeOf(opts), "justifications")) opts.justifications else null; + const cache: ?*utils.RootToSlotCache = if (@hasField(@TypeOf(opts), "rootToSlotCache")) opts.rootToSlotCache else null; + if (comptime !zeam_metrics.isZKVM()) { const attestation_count: u64 = @intCast(attestations.constSlice().len); zeam_metrics.metrics.lean_state_transition_attestations_processed_total.incrBy(attestation_count); @@ -364,15 +374,23 @@ pub const BeamState = struct { // work directly with SSZ types // historical_block_hashes and justified_slots are already SSZ types in state - var justifications: std.AutoHashMapUnmanaged(Root, []u8) = .empty; - defer { - var iterator = justifications.iterator(); + // Use provided justifications map or build from state + var local_justifications: std.AutoHashMapUnmanaged(Root, []u8) = .empty; + const owns_local = justifications_ptr == null; + defer if (owns_local) { + var iterator = local_justifications.iterator(); while (iterator.next()) |entry| { allocator.free(entry.value_ptr.*); } - justifications.deinit(allocator); - } - try self.getJustification(allocator, &justifications); + local_justifications.deinit(allocator); + }; + + const justifications: *std.AutoHashMapUnmanaged(Root, []u8) = if (justifications_ptr) |ptr| ptr else blk: { + const get_just_timer = zeam_metrics.lean_state_transition_get_justification_time_seconds.start(); + try self.getJustification(allocator, &local_justifications); + _ = get_just_timer.observe(); + break :blk &local_justifications; + }; var finalized_slot: Slot = self.latest_finalized.slot; @@ -402,7 +420,10 @@ pub const BeamState = struct { const attestation_str = try attestation_data.toJsonString(allocator); defer allocator.free(attestation_str); - logger.debug("processing attestation={s} validators_count={d}\n", .{ attestation_str, validator_indices.items.len }); + logger.debug( + "processing attestation={s} validators_count={d}\n", + .{ attestation_str, validator_indices.items.len }, + ); const historical_len: Slot = @intCast(self.historical_block_hashes.len()); if (source_slot >= historical_len) { @@ -534,7 +555,9 @@ pub const BeamState = struct { } } - try self.withJustifications(allocator, &justifications); + const with_just_timer = zeam_metrics.lean_state_transition_with_justifications_time_seconds.start(); + try self.withJustifications(allocator, justifications); + _ = with_just_timer.observe(); logger.debug("poststate:historical hashes={d} justified slots={d}\n justifications_roots:{d}\n justifications_validators={d}\n", .{ self.historical_block_hashes.len(), self.justified_slots.len(), self.justifications_roots.len(), self.justifications_validators.len() }); const justified_str_final = try self.latest_justified.toJsonString(allocator); @@ -811,7 +834,7 @@ test "process_attestations invalid justifiable slot returns error without panic" try state.process_slots(std.testing.allocator, 1, logger); var block_1 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); defer block_1.deinit(); - try state.process_block(std.testing.allocator, block_1, logger, null); + try state.process_block(std.testing.allocator, block_1, .{ .logger = logger }); try state.process_slots(std.testing.allocator, 2, logger); var block_2 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); @@ -861,7 +884,7 @@ test "process_attestations invalid justifiable slot returns error without panic" try std.testing.expectError( StateTransitionError.InvalidJustifiableSlot, - state.process_attestations(std.testing.allocator, attestations_list, logger, null), + state.process_attestations(std.testing.allocator, attestations_list, .{ .logger = logger }), ); } @@ -896,7 +919,7 @@ test "justified_slots rebases when finalization advances" { try state.process_slots(std.testing.allocator, 1, logger); var block_1 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); defer block_1.deinit(); - try state.process_block(std.testing.allocator, block_1, logger, null); + try state.process_block(std.testing.allocator, block_1, .{ .logger = logger }); try state.process_slots(std.testing.allocator, 2, logger); var block_2_parent_root: Root = undefined; @@ -915,7 +938,7 @@ test "justified_slots rebases when finalization advances" { var block_2 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{att_0_to_1}); att_0_to_1_transferred = true; defer block_2.deinit(); - try state.process_block(std.testing.allocator, block_2, logger, null); + try state.process_block(std.testing.allocator, block_2, .{ .logger = logger }); try state.process_slots(std.testing.allocator, 3, logger); var block_3_parent_root: Root = undefined; @@ -934,7 +957,7 @@ test "justified_slots rebases when finalization advances" { var block_3 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{att_1_to_2}); att_1_to_2_transferred = true; defer block_3.deinit(); - try state.process_block(std.testing.allocator, block_3, logger, null); + try state.process_block(std.testing.allocator, block_3, .{ .logger = logger }); try std.testing.expectEqual(@as(Slot, 1), state.latest_finalized.slot); try std.testing.expectEqual(@as(usize, 1), state.justified_slots.len()); @@ -964,7 +987,7 @@ test "pruning keeps pending justifications" { try state.process_slots(std.testing.allocator, 1, logger); var block_1 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); defer block_1.deinit(); - try state.process_block(std.testing.allocator, block_1, logger, null); + try state.process_block(std.testing.allocator, block_1, .{ .logger = logger }); try state.process_slots(std.testing.allocator, 2, logger); var block_2_parent_root: Root = undefined; @@ -983,7 +1006,7 @@ test "pruning keeps pending justifications" { var block_2 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{att_0_to_1}); att_0_to_1_transferred = true; defer block_2.deinit(); - try state.process_block(std.testing.allocator, block_2, logger, null); + try state.process_block(std.testing.allocator, block_2, .{ .logger = logger }); try std.testing.expectEqual(@as(Slot, 0), state.latest_finalized.slot); try std.testing.expectEqual(@as(Slot, 1), state.latest_justified.slot); @@ -992,12 +1015,12 @@ test "pruning keeps pending justifications" { try state.process_slots(std.testing.allocator, 3, logger); var block_3 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); defer block_3.deinit(); - try state.process_block(std.testing.allocator, block_3, logger, null); + try state.process_block(std.testing.allocator, block_3, .{ .logger = logger }); try state.process_slots(std.testing.allocator, 4, logger); var block_4 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); defer block_4.deinit(); - try state.process_block(std.testing.allocator, block_4, logger, null); + try state.process_block(std.testing.allocator, block_4, .{ .logger = logger }); try state.process_slots(std.testing.allocator, 5, logger); var block_5 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); @@ -1045,7 +1068,7 @@ test "pruning keeps pending justifications" { try attestations_list.append(att_1_to_2); att_1_to_2_transferred = true; - try state.process_attestations(std.testing.allocator, attestations_list, logger, null); + try state.process_attestations(std.testing.allocator, attestations_list, .{ .logger = logger }); try std.testing.expectEqual(@as(Slot, 1), state.latest_finalized.slot); try std.testing.expectEqual(@as(Slot, 2), state.latest_justified.slot); @@ -1079,7 +1102,7 @@ test "root_to_slot_cache lifecycle" { // Simulate chain.zig adding parent to cache before STF. try cache.put(block_1.parent_root, 0); - try state.process_block(std.testing.allocator, block_1, logger, &cache); + try state.process_block(std.testing.allocator, block_1, .{ .logger = logger, .rootToSlotCache = &cache }); // After block 1: cache should have the genesis root at slot 0. try std.testing.expectEqual(@as(usize, 1), cache.count()); @@ -1105,7 +1128,7 @@ test "root_to_slot_cache lifecycle" { // Simulate chain.zig adding parent to cache before STF. try cache.put(block_2_parent_root, 1); - try state.process_block(std.testing.allocator, block_2, logger, &cache); + try state.process_block(std.testing.allocator, block_2, .{ .logger = logger, .rootToSlotCache = &cache }); // After block 2: cache should have grown (genesis root + block 1 root). try std.testing.expect(cache.count() >= 2); @@ -1120,7 +1143,7 @@ test "root_to_slot_cache lifecycle" { var block_3_parent_root: Root = undefined; try zeam_utils.hashTreeRoot(block.BeamBlockHeader, state.latest_block_header, &block_3_parent_root, std.testing.allocator); try cache.put(block_3_parent_root, 2); - try state.process_block(std.testing.allocator, block_3, logger, &cache); + try state.process_block(std.testing.allocator, block_3, .{ .logger = logger, .rootToSlotCache = &cache }); try state.process_slots(std.testing.allocator, 4, logger); var block_4 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); @@ -1130,7 +1153,7 @@ test "root_to_slot_cache lifecycle" { var block_4_parent_root: Root = undefined; try zeam_utils.hashTreeRoot(block.BeamBlockHeader, state.latest_block_header, &block_4_parent_root, std.testing.allocator); try cache.put(block_4_parent_root, 3); - try state.process_block(std.testing.allocator, block_4, logger, &cache); + try state.process_block(std.testing.allocator, block_4, .{ .logger = logger, .rootToSlotCache = &cache }); try state.process_slots(std.testing.allocator, 5, logger); var block_5 = try makeBlock(std.testing.allocator, &state, state.slot, &[_]attestation.AggregatedAttestation{}); @@ -1178,7 +1201,7 @@ test "root_to_slot_cache lifecycle" { try attestations_list.append(att_1_to_2); att_1_to_2_transferred = true; - try state.process_attestations(std.testing.allocator, attestations_list, logger, &cache); + try state.process_attestations(std.testing.allocator, attestations_list, .{ .logger = logger, .rootToSlotCache = &cache }); // Verify finalization advanced and justification cleanup used the cache. try std.testing.expectEqual(@as(Slot, 1), state.latest_finalized.slot);