diff --git a/src/configs/config.zig b/src/configs/config.zig index 9201909..1e1aca2 100644 --- a/src/configs/config.zig +++ b/src/configs/config.zig @@ -1,5 +1,33 @@ const std = @import("std"); const primitives = @import("../primitives/types.zig"); +const preset = @import("../presets/preset.zig"); + +pub const ActiveConfig = struct { + var config: Config = undefined; + var mutex = std.Thread.Mutex{}; + var is_initialized = std.atomic.Value(bool).init(false); + + pub fn set(presets: preset.Presets) void { + if (is_initialized.swap(true, .acquire)) { + return; + } + + mutex.lock(); + defer mutex.unlock(); + + config = switch (presets) { + .mainnet => mainnet_config, + .minimal => minimal_config, + }; + } + + pub fn get() Config { + if (!is_initialized.load(.acquire)) { + @panic("ActiveConfig not initialized"); + } + return config; + } +}; pub const Config = struct { PRESET_BASE: []const u8, @@ -238,3 +266,9 @@ test "minimal config has correct MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT" { test "minimal config has correct MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA" { try std.testing.expectEqual(minimal_config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA, 64000000000); } + +test "test ActiveConfig" { + ActiveConfig.set(preset.Presets.mainnet); + const active_config = ActiveConfig.get(); + try std.testing.expectEqual(active_config.PRESET_BASE, "mainnet"); +} diff --git a/src/consensus/electra/types.zig b/src/consensus/electra/types.zig index e1eb3a7..ece117d 100644 --- a/src/consensus/electra/types.zig +++ b/src/consensus/electra/types.zig @@ -1,5 +1,5 @@ const std = @import("std"); -pub const primitives = @import("../../primitives/types.zig"); +const primitives = @import("../../primitives/types.zig"); const preset = @import("../../presets/preset.zig"); const consensus = @import("../../consensus/types.zig"); const phase0 = @import("../../consensus/phase0/types.zig"); diff --git a/src/consensus/helpers/validator.zig b/src/consensus/helpers/validator.zig new file mode 100644 index 0000000..1a168ed --- /dev/null +++ b/src/consensus/helpers/validator.zig @@ -0,0 +1,154 @@ +const std = @import("std"); +const primitives = @import("../../primitives/types.zig"); +const consensus = @import("../../consensus/types.zig"); +const configs = @import("../../configs/config.zig"); +const constants = @import("../../primitives/constants.zig"); +const preset = @import("../../presets/preset.zig"); +const phase0 = @import("../../consensus/phase0/types.zig"); + +/// Check if a validator is active at a given epoch. +/// A validator is active if the current epoch is greater than or equal to the validator's activation epoch and less than the validator's exit epoch. +/// @param validator The validator to check. +/// @param epoch The epoch to check. +/// @return True if the validator is active, false otherwise. +/// Spec pseudocode definition: +/// +/// def is_active_validator(validator: Validator, epoch: Epoch) -> bool: +/// """ +/// Check if ``validator`` is active. +/// """ +/// return validator.activation_epoch <= epoch < validator.exit_epoch +pub fn isActiveValidator(validator: *const consensus.Validator, epoch: primitives.Epoch) bool { + return validator.activation_epoch <= epoch and epoch < validator.exit_epoch; +} + +/// isEligibleForActivationQueue carries out the logic for IsEligibleForActivationQueue +/// Spec pseudocode definition: +/// +/// def is_eligible_for_activation_queue(validator: Validator) -> bool: +/// """ +/// Check if ``validator`` is eligible to be placed into the activation queue. +/// """ +/// return ( +/// validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH +/// and validator.effective_balance == MAX_EFFECTIVE_BALANCE +/// ) +pub fn isEligibleForActivationQueue(validator: *const consensus.Validator) bool { + return validator.activation_eligibility_epoch == constants.FAR_FUTURE_EPOCH and + validator.effective_balance == preset.ActivePreset.get().MIN_ACTIVATION_BALANCE; +} + +/// isEligibleForActivation checks if a validator is eligible for activation. +/// A validator is eligible for activation if it is not yet activated and its activation eligibility epoch is less than or equal to the finalized epoch. +/// @param state The beacon state. +/// @param validator The validator to check. +/// @return True if the validator is eligible for activation, false otherwise. +/// Spec pseudocode definition: +/// +/// def is_eligible_for_activation(state: BeaconState, validator: Validator) -> bool: +/// """ +/// Check if ``validator`` is eligible for activation. +/// """ +/// return ( +/// validator.activation_eligibility_epoch <= state.finalized_checkpoint.epoch +/// and validator.activation_epoch == FAR_FUTURE_EPOCH +/// ) +pub fn isEligibleForActivation(state: *const consensus.BeaconState, validator: *const consensus.Validator) bool { + return + // Placement in queue is finalized + validator.activation_eligibility_epoch <= state.finalizedCheckpointEpoch() and + // Has not yet been activated + validator.activation_epoch == constants.FAR_FUTURE_EPOCH; +} + +test "test isActiveValidator" { + const validator = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 10, + .withdrawable_epoch = 0, + }; + const epoch: primitives.Epoch = 5; + const result = isActiveValidator(&validator, epoch); + try std.testing.expectEqual(result, true); +} + +test "test isEligibleForActivationQueue" { + preset.ActivePreset.set(preset.Presets.mainnet); + const validator = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = preset.ActivePreset.get().MIN_ACTIVATION_BALANCE, + .slashed = false, + .activation_eligibility_epoch = constants.FAR_FUTURE_EPOCH, + .activation_epoch = 0, + .exit_epoch = 0, + .withdrawable_epoch = 0, + }; + const result = isEligibleForActivationQueue(&validator); + try std.testing.expectEqual(result, true); +} + +test "test isEligibleForActivation" { + const finalized_checkpoint = consensus.Checkpoint{ + .epoch = 5, + .root = .{0} ** 32, + }; + const state = consensus.BeaconState{ + .phase0 = phase0.BeaconState{ + .genesis_time = 0, + .genesis_validators_root = undefined, + .slot = 0, + .fork = undefined, + .block_roots = undefined, + .state_roots = undefined, + .historical_roots = undefined, + .eth1_data = undefined, + .eth1_data_votes = undefined, + .eth1_deposit_index = 0, + .validators = undefined, + .balances = undefined, + .randao_mixes = undefined, + .slashings = undefined, + .previous_epoch_attestations = undefined, + .current_epoch_attestations = undefined, + .justification_bits = undefined, + .previous_justified_checkpoint = undefined, + .current_justified_checkpoint = undefined, + .finalized_checkpoint = @constCast(&finalized_checkpoint), + .latest_block_header = undefined, + }, + }; + + const validator = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = constants.FAR_FUTURE_EPOCH, + .exit_epoch = 0, + .withdrawable_epoch = 0, + }; + + const result = isEligibleForActivation(&state, &validator); + try std.testing.expectEqual(result, true); + + const validator2 = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 10, + .activation_epoch = constants.FAR_FUTURE_EPOCH, + .exit_epoch = 0, + .withdrawable_epoch = 0, + }; + + const result2 = isEligibleForActivation(&state, &validator2); + try std.testing.expectEqual(result2, false); +} diff --git a/src/consensus/phase0/types.zig b/src/consensus/phase0/types.zig index 9ea126e..3344d0d 100644 --- a/src/consensus/phase0/types.zig +++ b/src/consensus/phase0/types.zig @@ -42,7 +42,7 @@ pub const BeaconState = struct { justification_bits: []bool, previous_justified_checkpoint: *consensus.Checkpoint, current_justified_checkpoint: *consensus.Checkpoint, - finalized_checkpoint: *consensus.Checkpoint, + finalized_checkpoint: ?*consensus.Checkpoint, }; test "test BeaconState" { diff --git a/src/consensus/types.zig b/src/consensus/types.zig index 2702742..1d344fe 100644 --- a/src/consensus/types.zig +++ b/src/consensus/types.zig @@ -430,6 +430,16 @@ pub const BeaconState = union(primitives.ForkType) { capella: capella.BeaconState, deneb: capella.BeaconState, electra: electra.BeaconState, + + pub fn finalizedCheckpointEpoch(self: *const BeaconState) primitives.Epoch { + return switch (self.*) { + inline else => |state| getCheckpointEpoch(state.finalized_checkpoint), + }; + } + + fn getCheckpointEpoch(checkpoint: ?*Checkpoint) primitives.Epoch { + return if (checkpoint) |c| c.epoch else @as(primitives.Epoch, 0); + } }; test "test Attestation" { @@ -857,3 +867,18 @@ test "test BeaconState" { try std.testing.expectEqual(state.phase0.genesis_time, 0); } + +test "test Validator" { + const validator = Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 0, + .withdrawable_epoch = 0, + }; + + try std.testing.expectEqual(validator.effective_balance, 0); +} diff --git a/src/presets/preset.zig b/src/presets/preset.zig index cd00249..540c99a 100644 --- a/src/presets/preset.zig +++ b/src/presets/preset.zig @@ -1,6 +1,33 @@ const std = @import("std"); const primitives = @import("../primitives/types.zig"); +pub const ActivePreset = struct { + var preset: BeaconPreset = undefined; + var mutex = std.Thread.Mutex{}; + var is_initialized = std.atomic.Value(bool).init(false); + + pub fn set(presets: Presets) void { + if (is_initialized.swap(true, .acquire)) { + return; + } + + mutex.lock(); + defer mutex.unlock(); + + preset = switch (presets) { + .mainnet => mainnet_preset, + .minimal => minimal_preset, + }; + } + + pub fn get() BeaconPreset { + if (!is_initialized.load(.acquire)) { + @panic("ActivePreset not initialized"); + } + return preset; + } +}; + pub const Presets = enum { mainnet, minimal, @@ -328,3 +355,9 @@ test "minimal preset" { try std.testing.expectEqual(minimal_preset.MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA, 4096); try std.testing.expectEqual(minimal_preset.MIN_ACTIVATION_BALANCE, 32000000000); } + +test "test ActivePreset" { + ActivePreset.set(Presets.mainnet); + const active_preset = ActivePreset.get(); + try std.testing.expectEqual(active_preset.MAX_BYTES_PER_TRANSACTION, 1073741824); +} diff --git a/src/root.zig b/src/root.zig index e520969..250d58a 100644 --- a/src/root.zig +++ b/src/root.zig @@ -10,6 +10,7 @@ pub const bellatrix = @import("consensus/bellatrix/types.zig"); pub const capella = @import("consensus/capella/types.zig"); pub const deneb = @import("consensus/deneb/types.zig"); pub const electra = @import("consensus/electra/types.zig"); +pub const validator_helpers = @import("consensus/helpers/validator.zig"); test { @import("std").testing.refAllDeclsRecursive(@This());