diff --git a/src/consensus/altair/types.zig b/src/consensus/altair/types.zig index 28016ce..421bf4c 100644 --- a/src/consensus/altair/types.zig +++ b/src/consensus/altair/types.zig @@ -98,11 +98,11 @@ pub const BeaconBlockBody = @Type( }, ); -pub const BeaconStateSSZ = @Type( +pub const BeaconState = @Type( .{ .@"struct" = .{ .layout = .auto, - .fields = @typeInfo(phase0.BeaconStateSSZ).@"struct".fields ++ &[_]std.builtin.Type.StructField{ + .fields = @typeInfo(phase0.BeaconState).@"struct".fields ++ &[_]std.builtin.Type.StructField{ .{ .name = "inactivity_scores", .type = []u64, @@ -131,53 +131,8 @@ pub const BeaconStateSSZ = @Type( }, ); -pub const BeaconState = struct { - beacon_state_ssz: BeaconStateSSZ, - allocator: std.mem.Allocator, - - pub fn init(allocator: std.mem.Allocator, beacon_state_ssz: BeaconStateSSZ) !BeaconState { - return BeaconState{ - .beacon_state_ssz = beacon_state_ssz, - .allocator = allocator, - }; - } - - pub fn deinit(self: *BeaconState) void { - self.allocator.free(self.beacon_state_ssz.validators); - self.allocator.free(self.beacon_state_ssz.balances); - self.allocator.free(self.beacon_state_ssz.randao_mixes); - self.allocator.free(self.beacon_state_ssz.slashings); - self.allocator.free(self.beacon_state_ssz.previous_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.current_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.justification_bits); - self.allocator.destroy(self.beacon_state_ssz.previous_justified_checkpoint); - self.allocator.destroy(self.beacon_state_ssz.current_justified_checkpoint); - if (self.beacon_state_ssz.finalized_checkpoint) |checkpoint| { - self.allocator.destroy(checkpoint); - } - self.allocator.destroy(self.beacon_state_ssz.fork); - if (self.beacon_state_ssz.latest_block_header) |latest_block_header| { - self.allocator.destroy(latest_block_header); - } - self.allocator.free(self.beacon_state_ssz.block_roots); - self.allocator.free(self.beacon_state_ssz.state_roots); - self.allocator.free(self.beacon_state_ssz.historical_roots); - if (self.beacon_state_ssz.eth1_data) |eth1_data| { - self.allocator.destroy(eth1_data); - } - self.allocator.free(self.beacon_state_ssz.eth1_data_votes); - self.allocator.free(self.beacon_state_ssz.inactivity_scores); - if (self.beacon_state_ssz.current_sync_committee) |current_sync_committee| { - self.allocator.destroy(current_sync_committee); - } - if (self.beacon_state_ssz.next_sync_committee) |next_sync_committee| { - self.allocator.destroy(next_sync_committee); - } - } -}; - test "test BeaconState" { - const state = BeaconStateSSZ{ + const state = BeaconState{ .genesis_time = 0, .genesis_validators_root = undefined, .slot = 0, diff --git a/src/consensus/bellatrix/types.zig b/src/consensus/bellatrix/types.zig index 02feab8..f06ff3b 100644 --- a/src/consensus/bellatrix/types.zig +++ b/src/consensus/bellatrix/types.zig @@ -66,11 +66,11 @@ pub const BeaconBlockBody = @Type( }, ); -pub const BeaconStateSSZ = @Type( +pub const BeaconState = @Type( .{ .@"struct" = .{ .layout = .auto, - .fields = @typeInfo(altair.BeaconStateSSZ).@"struct".fields ++ &[_]std.builtin.Type.StructField{ + .fields = @typeInfo(altair.BeaconState).@"struct".fields ++ &[_]std.builtin.Type.StructField{ .{ .name = "latest_execution_payload_header", .type = ?*consensus.ExecutionPayloadHeader, @@ -85,54 +85,6 @@ pub const BeaconStateSSZ = @Type( }, ); -pub const BeaconState = struct { - beacon_state_ssz: BeaconStateSSZ, - allocator: std.mem.Allocator, - - pub fn init(allocator: std.mem.Allocator, beacon_state_ssz: BeaconStateSSZ) !BeaconState { - return BeaconState{ - .beacon_state_ssz = beacon_state_ssz, - .allocator = allocator, - }; - } - - pub fn deinit(self: *BeaconState) void { - self.allocator.free(self.beacon_state_ssz.validators); - self.allocator.free(self.beacon_state_ssz.balances); - self.allocator.free(self.beacon_state_ssz.randao_mixes); - self.allocator.free(self.beacon_state_ssz.slashings); - self.allocator.free(self.beacon_state_ssz.previous_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.current_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.justification_bits); - self.allocator.destroy(self.beacon_state_ssz.previous_justified_checkpoint); - self.allocator.destroy(self.beacon_state_ssz.current_justified_checkpoint); - if (self.beacon_state_ssz.finalized_checkpoint) |checkpoint| { - self.allocator.destroy(checkpoint); - } - self.allocator.destroy(self.beacon_state_ssz.fork); - if (self.beacon_state_ssz.latest_block_header) |latest_block_header| { - self.allocator.destroy(latest_block_header); - } - self.allocator.free(self.beacon_state_ssz.block_roots); - self.allocator.free(self.beacon_state_ssz.state_roots); - self.allocator.free(self.beacon_state_ssz.historical_roots); - if (self.beacon_state_ssz.eth1_data) |eth1_data| { - self.allocator.destroy(eth1_data); - } - self.allocator.free(self.beacon_state_ssz.eth1_data_votes); - self.allocator.free(self.beacon_state_ssz.inactivity_scores); - if (self.beacon_state_ssz.current_sync_committee) |current_sync_committee| { - self.allocator.destroy(current_sync_committee); - } - if (self.beacon_state_ssz.next_sync_committee) |next_sync_committee| { - self.allocator.destroy(next_sync_committee); - } - if (self.beacon_state_ssz.latest_execution_payload_header) |latest_execution_payload_header| { - self.allocator.destroy(latest_execution_payload_header); - } - } -}; - test "test ExecutionPayloadHeader" { const header = ExecutionPayloadHeader{ .parent_hash = undefined, @@ -194,7 +146,7 @@ test "test BeaconBlockBody" { } test "test BeaconState" { - const state = BeaconStateSSZ{ + const state = BeaconState{ .genesis_time = 0, .genesis_validators_root = undefined, .slot = 0, diff --git a/src/consensus/capella/types.zig b/src/consensus/capella/types.zig index ca0ef0c..5b7b054 100644 --- a/src/consensus/capella/types.zig +++ b/src/consensus/capella/types.zig @@ -109,11 +109,11 @@ pub const BeaconBlockBody = @Type( }, ); -pub const BeaconStateSSZ = @Type( +pub const BeaconState = @Type( .{ .@"struct" = .{ .layout = .auto, - .fields = @typeInfo(bellatrix.BeaconStateSSZ).@"struct".fields ++ &[_]std.builtin.Type.StructField{ + .fields = @typeInfo(bellatrix.BeaconState).@"struct".fields ++ &[_]std.builtin.Type.StructField{ .{ .name = "next_withdrawal_index", .type = primitives.WithdrawalIndex, @@ -142,55 +142,6 @@ pub const BeaconStateSSZ = @Type( }, ); -pub const BeaconState = struct { - beacon_state_ssz: BeaconStateSSZ, - allocator: std.mem.Allocator, - - pub fn init(allocator: std.mem.Allocator, beacon_state_ssz: BeaconStateSSZ) !BeaconState { - return BeaconState{ - .beacon_state_ssz = beacon_state_ssz, - .allocator = allocator, - }; - } - - pub fn deinit(self: *BeaconState) void { - self.allocator.free(self.beacon_state_ssz.validators); - self.allocator.free(self.beacon_state_ssz.balances); - self.allocator.free(self.beacon_state_ssz.randao_mixes); - self.allocator.free(self.beacon_state_ssz.slashings); - self.allocator.free(self.beacon_state_ssz.previous_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.current_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.justification_bits); - self.allocator.destroy(self.beacon_state_ssz.previous_justified_checkpoint); - self.allocator.destroy(self.beacon_state_ssz.current_justified_checkpoint); - if (self.beacon_state_ssz.finalized_checkpoint) |checkpoint| { - self.allocator.destroy(checkpoint); - } - self.allocator.destroy(self.beacon_state_ssz.fork); - if (self.beacon_state_ssz.latest_block_header) |latest_block_header| { - self.allocator.destroy(latest_block_header); - } - self.allocator.free(self.beacon_state_ssz.block_roots); - self.allocator.free(self.beacon_state_ssz.state_roots); - self.allocator.free(self.beacon_state_ssz.historical_roots); - if (self.beacon_state_ssz.eth1_data) |eth1_data| { - self.allocator.destroy(eth1_data); - } - self.allocator.free(self.beacon_state_ssz.eth1_data_votes); - self.allocator.free(self.beacon_state_ssz.inactivity_scores); - if (self.beacon_state_ssz.current_sync_committee) |current_sync_committee| { - self.allocator.destroy(current_sync_committee); - } - if (self.beacon_state_ssz.next_sync_committee) |next_sync_committee| { - self.allocator.destroy(next_sync_committee); - } - if (self.beacon_state_ssz.latest_execution_payload_header) |latest_execution_payload_header| { - self.allocator.destroy(latest_execution_payload_header); - } - self.allocator.free(self.beacon_state_ssz.historical_summaries); - } -}; - test "test ExecutionPayloadHeader" { const header = ExecutionPayloadHeader{ .parent_hash = undefined, @@ -256,7 +207,7 @@ test "test BeaconBlockBody" { } test "test BeaconState" { - const state = BeaconStateSSZ{ + const state = BeaconState{ .genesis_time = 0, .genesis_validators_root = undefined, .slot = 0, diff --git a/src/consensus/electra/types.zig b/src/consensus/electra/types.zig index 352a4ee..795982d 100644 --- a/src/consensus/electra/types.zig +++ b/src/consensus/electra/types.zig @@ -155,11 +155,11 @@ test "test ExecutionPayloadHeader" { try std.testing.expectEqual(header.block_number, 21); } -pub const BeaconStateSSZ = @Type( +pub const BeaconState = @Type( .{ .@"struct" = .{ .layout = .auto, - .fields = @typeInfo(capella.BeaconStateSSZ).@"struct".fields ++ &[_]std.builtin.Type.StructField{ + .fields = @typeInfo(capella.BeaconState).@"struct".fields ++ &[_]std.builtin.Type.StructField{ .{ .name = "deposit_requests_start_index", // # [New in Electra:EIP6110] .type = u64, @@ -230,57 +230,49 @@ pub const BeaconStateSSZ = @Type( }, ); -pub const BeaconState = struct { - beacon_state_ssz: BeaconStateSSZ, - allocator: std.mem.Allocator, - - pub fn init(allocator: std.mem.Allocator, beacon_state_ssz: BeaconStateSSZ) !BeaconState { - return BeaconState{ - .beacon_state_ssz = beacon_state_ssz, - .allocator = allocator, - }; - } +test "test BeaconState" { + const state = BeaconState{ + .genesis_time = 0, + .genesis_validators_root = undefined, + .slot = 0, + .fork = undefined, + .latest_block_header = 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 = undefined, + .inactivity_scores = undefined, + .current_sync_committee = undefined, + .next_sync_committee = undefined, + .latest_execution_payload_header = undefined, + .next_withdrawal_index = 0, + .next_withdrawal_validator_index = 0, + .historical_summaries = undefined, + .deposit_requests_start_index = 0, + .deposit_balance_to_consume = 0, + .exit_balance_to_consume = 0, + .earliest_exit_epoch = 0, + .consolidation_balance_to_consume = 0, + .earliest_consolidation_epoch = 0, + .pending_balance_deposits = undefined, + .pending_partial_withdrawals = undefined, + .pending_consolidations = undefined, + }; - pub fn deinit(self: *BeaconState) void { - self.allocator.free(self.beacon_state_ssz.validators); - self.allocator.free(self.beacon_state_ssz.balances); - self.allocator.free(self.beacon_state_ssz.randao_mixes); - self.allocator.free(self.beacon_state_ssz.slashings); - self.allocator.free(self.beacon_state_ssz.previous_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.current_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.justification_bits); - self.allocator.destroy(self.beacon_state_ssz.previous_justified_checkpoint); - self.allocator.destroy(self.beacon_state_ssz.current_justified_checkpoint); - if (self.beacon_state_ssz.finalized_checkpoint) |checkpoint| { - self.allocator.destroy(checkpoint); - } - self.allocator.destroy(self.beacon_state_ssz.fork); - if (self.beacon_state_ssz.latest_block_header) |latest_block_header| { - self.allocator.destroy(latest_block_header); - } - self.allocator.free(self.beacon_state_ssz.block_roots); - self.allocator.free(self.beacon_state_ssz.state_roots); - self.allocator.free(self.beacon_state_ssz.historical_roots); - if (self.beacon_state_ssz.eth1_data) |eth1_data| { - self.allocator.destroy(eth1_data); - } - self.allocator.free(self.beacon_state_ssz.eth1_data_votes); - self.allocator.free(self.beacon_state_ssz.inactivity_scores); - if (self.beacon_state_ssz.current_sync_committee) |current_sync_committee| { - self.allocator.destroy(current_sync_committee); - } - if (self.beacon_state_ssz.next_sync_committee) |next_sync_committee| { - self.allocator.destroy(next_sync_committee); - } - if (self.beacon_state_ssz.latest_execution_payload_header) |latest_execution_payload_header| { - self.allocator.destroy(latest_execution_payload_header); - } - self.allocator.free(self.beacon_state_ssz.historical_summaries); - self.allocator.free(self.beacon_state_ssz.pending_balance_deposits); - self.allocator.free(self.beacon_state_ssz.pending_partial_withdrawals); - self.allocator.free(self.beacon_state_ssz.pending_consolidations); - } -}; + try std.testing.expectEqual(state.genesis_time, 0); +} test "test Attestation" { const attestation = Attestation{ diff --git a/src/consensus/helpers/attestation.zig b/src/consensus/helpers/attestation.zig new file mode 100644 index 0000000..4b0a5f1 --- /dev/null +++ b/src/consensus/helpers/attestation.zig @@ -0,0 +1,96 @@ +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"); +const altair = @import("../../consensus/altair/types.zig"); +const epoch_helper = @import("../../consensus/helpers/epoch.zig"); + +/// isSlashableAttestationData checks if two attestations are slashable according to Casper FFG rules. +/// @param data_1 The first attestation data. +/// @param data_2 The second attestation data. +/// @return True if the attestations are slashable, false otherwise. +/// Spec pseudocode definition: +/// def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: +/// """ +/// Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. +/// """ +/// return ( +/// # Double vote +/// (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or +/// # Surround vote +/// (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) +/// ) +pub fn isSlashableAttestationData(data1: consensus.AttestationData, data2: consensus.AttestationData) bool { + // Check if `data_1` and `data_2` are slashable according to Casper FFG rules. + return ( + // Double vote + (!std.meta.eql(data1, data2) and data1.target.epoch == data2.target.epoch) or + // Surround vote + (data1.source.epoch < data2.source.epoch and data2.target.epoch < data1.target.epoch)); +} + +test "test isSlashableAttestationData" { + const data1 = consensus.AttestationData{ + .slot = 0, + .index = 0, + .beacon_block_root = undefined, + .source = consensus.Checkpoint{ + .epoch = 0, + .root = undefined, + }, + .target = consensus.Checkpoint{ + .epoch = 0, + .root = undefined, + }, + }; + + const data2 = consensus.AttestationData{ + .slot = 0, + .index = 0, + .beacon_block_root = undefined, + .source = consensus.Checkpoint{ + .epoch = 0, + .root = undefined, + }, + .target = consensus.Checkpoint{ + .epoch = 0, + .root = undefined, + }, + }; + + try std.testing.expectEqual(isSlashableAttestationData(data1, data2), false); + + const data3 = consensus.AttestationData{ + .slot = 0, + .index = 0, + .beacon_block_root = undefined, + .source = consensus.Checkpoint{ + .epoch = 0, + .root = undefined, + }, + .target = consensus.Checkpoint{ + .epoch = 1, + .root = undefined, + }, + }; + + const data4 = consensus.AttestationData{ + .slot = 0, + .index = 0, + .beacon_block_root = undefined, + .source = consensus.Checkpoint{ + .epoch = 1, + .root = undefined, + }, + .target = consensus.Checkpoint{ + .epoch = 1, + .root = undefined, + }, + }; + + try std.testing.expectEqual(isSlashableAttestationData(data3, data4), true); + try std.testing.expectEqual(isSlashableAttestationData(data1, data4), false); +} diff --git a/src/consensus/helpers/epoch.zig b/src/consensus/helpers/epoch.zig new file mode 100644 index 0000000..b2ea351 --- /dev/null +++ b/src/consensus/helpers/epoch.zig @@ -0,0 +1,52 @@ +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"); +const altair = @import("../../consensus/altair/types.zig"); + +/// getCurrentEpoch returns the current epoch for the given state. +/// @return The current epoch. +/// Spec pseudocode definition: +/// def get_current_epoch(state: BeaconState) -> Epoch: +/// """ +/// Return the current epoch. +/// """ +/// return compute_epoch_at_slot(state.slot) +pub fn getCurrentEpoch(state: *const consensus.BeaconState) primitives.Epoch { + return computeEpochAtSlot(state.slot()); +} + +/// Return the epoch number at `slot`. +/// @param slot - The slot number. +/// @return The epoch number. +/// @note This function is equivalent to `slot // SLOTS_PER_EPOCH`. +/// Spec pseudocode definition: +/// +/// def compute_epoch_at_slot(slot: Slot) -> Epoch: +/// """ +/// Return the epoch number at ``slot``. +/// """ +/// return Epoch(slot // SLOTS_PER_EPOCH) +pub fn computeEpochAtSlot(slot: primitives.Slot) primitives.Epoch { + // Return the epoch number at `slot`. + return @divFloor(slot, preset.ActivePreset.get().SLOTS_PER_EPOCH); +} + +test "test compute_epoch_at_slot" { + preset.ActivePreset.set(preset.Presets.mainnet); + defer preset.ActivePreset.reset(); + const epoch = computeEpochAtSlot(0); + try std.testing.expectEqual(0, epoch); + + const epoch2 = computeEpochAtSlot(1); + try std.testing.expectEqual(0, epoch2); + + const epoch3 = computeEpochAtSlot(10); + try std.testing.expectEqual(0, epoch3); + + const epoch4 = computeEpochAtSlot(100); + try std.testing.expectEqual(3, epoch4); +} diff --git a/src/consensus/helpers/validator.zig b/src/consensus/helpers/validator.zig new file mode 100644 index 0000000..5c0b009 --- /dev/null +++ b/src/consensus/helpers/validator.zig @@ -0,0 +1,427 @@ +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"); +const altair = @import("../../consensus/altair/types.zig"); +const epoch_helper = @import("../../consensus/helpers/epoch.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 +/// @param validator The validator to check. +/// 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 validator The validator to check. +/// @param state The beacon state. +/// @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(validator: *const consensus.Validator, state: *const consensus.BeaconState) 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; +} + +/// isSlashableValidator checks if a validator is slashable. +/// A validator is slashable if it is not yet slashed and is within the range of epochs where it can be withdrawn. +/// @param validator The validator to check. +/// @param epoch The epoch to check. +/// @return True if the validator is slashable, false otherwise. +/// Spec pseudocode definition: +/// +/// def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: +/// """ +/// Check if ``validator`` is slashable. +/// """ +/// return not validator.slashed and validator.activation_epoch <= epoch < validator.withdrawable_epoch +pub fn isSlashableValidator(validator: *const consensus.Validator, epoch: primitives.Epoch) bool { + return (!validator.slashed) and (validator.activation_epoch <= epoch and epoch < validator.withdrawable_epoch); +} + +/// getActiveValidatorIndices returns the indices of active validators for the given epoch. +/// @param state The beacon state. +/// @param epoch The epoch for which to get the active validator indices. +/// @return The indices of active validators for the given epoch. +/// Spec pseudocode definition: +/// def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: +/// """ +/// Return the sequence of active validator indices at ``epoch``. +/// """ +/// return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] +pub fn getActiveValidatorIndices(state: *const consensus.BeaconState, epoch: primitives.Epoch, allocator: std.mem.Allocator) ![]const primitives.ValidatorIndex { + var active_validators = std.ArrayList(primitives.ValidatorIndex).init(allocator); + errdefer active_validators.deinit(); + + for (state.validators(), 0..) |v, i| { + if (isActiveValidator(&v, epoch)) { + try active_validators.append(@as(primitives.Epoch, i)); + } + } + + return active_validators.toOwnedSlice(); +} + +/// getValidatorChurnLimit returns the validator churn limit for the given state. +/// The churn limit is the maximum number of validators who can leave the validator set in one epoch. +/// @param state The beacon state. +/// @return The validator churn limit. +/// Spec pseudocode definition: +/// def get_validator_churn_limit(state: BeaconState) -> uint64: +/// """ +/// Return the validator churn limit for the current epoch. +/// """ +/// active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) +/// return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) +pub fn getValidatorChurnLimit(state: *const consensus.BeaconState, allocator: std.mem.Allocator) !u64 { + const active_validator_indices = try getActiveValidatorIndices(state, epoch_helper.getCurrentEpoch(state), allocator); + defer allocator.free(active_validator_indices); + const conf = configs.ActiveConfig.get(); + return @max(conf.MIN_PER_EPOCH_CHURN_LIMIT, @divFloor(@as(u64, active_validator_indices.len), conf.CHURN_LIMIT_QUOTIENT)); +} + +test "test getValidatorChurnLimit" { + preset.ActivePreset.set(preset.Presets.minimal); + defer preset.ActivePreset.reset(); + configs.ActiveConfig.set(preset.Presets.minimal); + defer configs.ActiveConfig.reset(); + var finalized_checkpoint = consensus.Checkpoint{ + .epoch = 5, + .root = .{0} ** 32, + }; + + var validators = std.ArrayList(consensus.Validator).init(std.testing.allocator); + defer validators.deinit(); + + const validator1 = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 10, + .withdrawable_epoch = 10, + }; + + const validator2 = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 20, + .withdrawable_epoch = 20, + }; + + // add 800 validators + for (0..400) |_| { + try validators.append(validator1); + try validators.append(validator2); + } + + const state = consensus.BeaconState{ + .altair = altair.BeaconState{ + .genesis_time = 0, + .genesis_validators_root = .{0} ** 32, + .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 = validators.items, + .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 = &finalized_checkpoint, + .latest_block_header = undefined, + .inactivity_scores = undefined, + .current_sync_committee = undefined, + .next_sync_committee = undefined, + }, + }; + + const churn_limit = try getValidatorChurnLimit(&state, std.testing.allocator); + try std.testing.expectEqual(churn_limit, 25); + + var validators1 = std.ArrayList(consensus.Validator).init(std.testing.allocator); + defer validators1.deinit(); + + try validators1.append(validator1); + try validators1.append(validator2); + + const state1 = consensus.BeaconState{ + .altair = altair.BeaconState{ + .genesis_time = 0, + .genesis_validators_root = .{0} ** 32, + .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 = validators1.items, + .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 = &finalized_checkpoint, + .latest_block_header = undefined, + .inactivity_scores = undefined, + .current_sync_committee = undefined, + .next_sync_committee = undefined, + }, + }; + + const churn_limit1 = try getValidatorChurnLimit(&state1, std.testing.allocator); + try std.testing.expectEqual(churn_limit1, 2); +} + +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); + defer preset.ActivePreset.reset(); + 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" { + var 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 = &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(&validator, &state); + 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(&validator2, &state); + try std.testing.expectEqual(result2, false); +} + +test "test isSlashableValidator" { + preset.ActivePreset.set(preset.Presets.mainnet); + defer preset.ActivePreset.reset(); + 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 = 10, + }; + const epoch: primitives.Epoch = 5; + const result = isSlashableValidator(&validator, epoch); + try std.testing.expectEqual(result, true); + + const validator2 = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 10, + .withdrawable_epoch = 5, + }; + const epoch2: primitives.Epoch = 5; + const result2 = isSlashableValidator(&validator2, epoch2); + try std.testing.expectEqual(result2, false); +} + +test "test_getActiveValidatorIndices_withTwoActiveValidators" { + var finalized_checkpoint = consensus.Checkpoint{ + .epoch = 5, + .root = .{0} ** 32, + }; + + var validators = std.ArrayList(consensus.Validator).init(std.testing.allocator); + defer validators.deinit(); + + const validator1 = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 10, + .withdrawable_epoch = 10, + }; + + const validator2 = consensus.Validator{ + .pubkey = undefined, + .withdrawal_credentials = undefined, + .effective_balance = 0, + .slashed = false, + .activation_eligibility_epoch = 0, + .activation_epoch = 0, + .exit_epoch = 20, + .withdrawable_epoch = 20, + }; + try validators.append(validator1); + try validators.append(validator2); + + const state = consensus.BeaconState{ + .altair = altair.BeaconState{ + .genesis_time = 0, + .genesis_validators_root = .{0} ** 32, + .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 = validators.items, + .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 = &finalized_checkpoint, + .latest_block_header = undefined, + .inactivity_scores = undefined, + .current_sync_committee = undefined, + .next_sync_committee = undefined, + }, + }; + + const indices = try getActiveValidatorIndices(&state, @as(primitives.Epoch, 5), std.testing.allocator); + defer std.testing.allocator.free(indices); + try std.testing.expectEqual(indices.len, 2); +} diff --git a/src/consensus/helpers/weak_subjectivity.zig b/src/consensus/helpers/weak_subjectivity.zig new file mode 100644 index 0000000..665f195 --- /dev/null +++ b/src/consensus/helpers/weak_subjectivity.zig @@ -0,0 +1,9 @@ +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"); +const altair = @import("../../consensus/altair/types.zig"); +const epoch_helper = @import("../../consensus/helpers/epoch.zig"); diff --git a/src/consensus/phase0/types.zig b/src/consensus/phase0/types.zig index a1356b4..9ea126e 100644 --- a/src/consensus/phase0/types.zig +++ b/src/consensus/phase0/types.zig @@ -21,7 +21,7 @@ pub const BeaconBlockBody = struct { voluntary_exits: []consensus.SignedVoluntaryExit, }; -pub const BeaconStateSSZ = struct { +pub const BeaconState = struct { genesis_time: u64, genesis_validators_root: primitives.Root, slot: primitives.Slot, @@ -42,49 +42,11 @@ pub const BeaconStateSSZ = struct { justification_bits: []bool, previous_justified_checkpoint: *consensus.Checkpoint, current_justified_checkpoint: *consensus.Checkpoint, - finalized_checkpoint: ?*consensus.Checkpoint, -}; - -pub const BeaconState = struct { - beacon_state_ssz: BeaconStateSSZ, - allocator: std.mem.Allocator, - - pub fn init(allocator: std.mem.Allocator, beacon_state_ssz: BeaconStateSSZ) !BeaconState { - return BeaconState{ - .beacon_state_ssz = beacon_state_ssz, - .allocator = allocator, - }; - } - - pub fn deinit(self: *BeaconState) void { - self.allocator.free(self.beacon_state_ssz.validators); - self.allocator.free(self.beacon_state_ssz.balances); - self.allocator.free(self.beacon_state_ssz.randao_mixes); - self.allocator.free(self.beacon_state_ssz.slashings); - self.allocator.free(self.beacon_state_ssz.previous_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.current_epoch_attestations); - self.allocator.free(self.beacon_state_ssz.justification_bits); - self.allocator.destroy(self.beacon_state_ssz.previous_justified_checkpoint); - self.allocator.destroy(self.beacon_state_ssz.current_justified_checkpoint); - if (self.beacon_state_ssz.finalized_checkpoint) |checkpoint| { - self.allocator.destroy(checkpoint); - } - self.allocator.destroy(self.beacon_state_ssz.fork); - if (self.beacon_state_ssz.latest_block_header) |latest_block_header| { - self.allocator.destroy(latest_block_header); - } - self.allocator.free(self.beacon_state_ssz.block_roots); - self.allocator.free(self.beacon_state_ssz.state_roots); - self.allocator.free(self.beacon_state_ssz.historical_roots); - if (self.beacon_state_ssz.eth1_data) |eth1_data| { - self.allocator.destroy(eth1_data); - } - self.allocator.free(self.beacon_state_ssz.eth1_data_votes); - } + finalized_checkpoint: *consensus.Checkpoint, }; test "test BeaconState" { - const state = BeaconStateSSZ{ + const state = BeaconState{ .genesis_time = 0, .genesis_validators_root = undefined, .slot = 0, diff --git a/src/consensus/types.zig b/src/consensus/types.zig index 45b2861..8462929 100644 --- a/src/consensus/types.zig +++ b/src/consensus/types.zig @@ -38,74 +38,6 @@ pub const Validator = struct { activation_epoch: primitives.Epoch, exit_epoch: primitives.Epoch, withdrawable_epoch: primitives.Epoch, - - /// 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 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(self: *const Validator, epoch: primitives.Epoch) bool { - return self.activation_epoch <= epoch and epoch < self.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(self: *const Validator) bool { - return self.activation_eligibility_epoch == constants.FAR_FUTURE_EPOCH and - self.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. - /// @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(self: *const Validator, state: *const BeaconState) bool { - return - // Placement in queue is finalized - self.activation_eligibility_epoch <= state.finalizedCheckpointEpoch() and - // Has not yet been activated - self.activation_epoch == constants.FAR_FUTURE_EPOCH; - } - - /// isSlashableValidator checks if a validator is slashable. - /// A validator is slashable if it is not yet slashed and is within the range of epochs where it can be withdrawn. - /// @param epoch The epoch to check. - /// @return True if the validator is slashable, false otherwise. - /// Spec pseudocode definition: - /// - /// def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: - /// """ - /// Check if ``validator`` is slashable. - /// """ - /// return not validator.slashed and validator.activation_epoch <= epoch < validator.withdrawable_epoch - pub fn isSlashableValidator(self: *const Validator, epoch: primitives.Epoch) bool { - return (!self.slashed) and (self.activation_epoch <= epoch and epoch < self.withdrawable_epoch); - } }; pub const AttestationData = struct { @@ -507,7 +439,7 @@ pub const BeaconState = union(primitives.ForkType) { /// @return The slot of the state. pub fn slot(self: *const BeaconState) primitives.Slot { return switch (self.*) { - inline else => |state| state.beacon_state_ssz.slot, + inline else => |state| state.slot, }; } @@ -515,108 +447,17 @@ pub const BeaconState = union(primitives.ForkType) { /// @return The validators of the state. pub fn validators(self: *const BeaconState) []const Validator { return switch (self.*) { - inline else => |state| state.beacon_state_ssz.validators, + inline else => |state| state.validators, }; } pub fn finalizedCheckpointEpoch(self: *const BeaconState) primitives.Epoch { return switch (self.*) { - inline else => |state| getCheckpointEpoch(state.beacon_state_ssz.finalized_checkpoint), - }; - } - - /// getActiveValidatorIndices returns the indices of active validators for the given epoch. - /// @param epoch The epoch for which to get the active validator indices. - /// @return The indices of active validators for the given epoch. - /// Spec pseudocode definition: - /// def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: - /// """ - /// Return the sequence of active validator indices at ``epoch``. - /// """ - /// return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] - pub fn getActiveValidatorIndices(self: *const BeaconState, epoch: primitives.Epoch) ![]const primitives.ValidatorIndex { - var active_validators = std.ArrayList(primitives.ValidatorIndex).init(self.allocator()); - errdefer active_validators.deinit(); - - for (self.validators(), 0..) |v, i| { - if (v.isActiveValidator(epoch)) { - try active_validators.append(@as(primitives.Epoch, i)); - } - } - - return active_validators.toOwnedSlice(); - } - - /// getCurrentEpoch returns the current epoch for the given state. - /// @return The current epoch. - /// Spec pseudocode definition: - /// def get_current_epoch(state: BeaconState) -> Epoch: - /// """ - /// Return the current epoch. - /// """ - /// return compute_epoch_at_slot(state.slot) - pub fn getCurrentEpoch(self: *const BeaconState) primitives.Epoch { - return primitives.computeEpochAtSlot(self.slot()); - } - - /// getValidatorChurnLimit returns the validator churn limit for the given state. - /// The churn limit is the maximum number of validators who can leave the validator set in one epoch. - /// @return The validator churn limit. - /// Spec pseudocode definition: - /// def get_validator_churn_limit(state: BeaconState) -> uint64: - /// """ - /// Return the validator churn limit for the current epoch. - /// """ - /// active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - /// return max(config.MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // config.CHURN_LIMIT_QUOTIENT) - pub fn getValidatorChurnLimit(self: *const BeaconState) !u64 { - const active_validator_indices = try self.getActiveValidatorIndices(self.getCurrentEpoch()); - defer self.allocator().free(active_validator_indices); - const conf = configs.ActiveConfig.get(); - return @max(conf.MIN_PER_EPOCH_CHURN_LIMIT, @divFloor(@as(u64, active_validator_indices.len), conf.CHURN_LIMIT_QUOTIENT)); - } - - pub fn allocator(self: *const BeaconState) std.mem.Allocator { - return switch (self.*) { - inline else => |state| state.allocator, + inline else => |state| state.finalized_checkpoint.epoch, }; } - - pub fn deinit(self: *BeaconState) void { - switch (self.*) { - inline else => |*state| state.deinit(), - } - } }; -pub fn getCheckpointEpoch(checkpoint: ?*Checkpoint) primitives.Epoch { - return if (checkpoint) |c| c.epoch else @as(primitives.Epoch, 0); -} - -/// isSlashableAttestationData checks if two attestations are slashable according to Casper FFG rules. -/// @param data_1 The first attestation data. -/// @param data_2 The second attestation data. -/// @return True if the attestations are slashable, false otherwise. -/// Spec pseudocode definition: -/// def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationData) -> bool: -/// """ -/// Check if ``data_1`` and ``data_2`` are slashable according to Casper FFG rules. -/// """ -/// return ( -/// # Double vote -/// (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or -/// # Surround vote -/// (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) -/// ) -pub fn isSlashableAttestationData(data1: AttestationData, data2: AttestationData) bool { - // Check if `data_1` and `data_2` are slashable according to Casper FFG rules. - return ( - // Double vote - (!std.meta.eql(data1, data2) and data1.target.epoch == data2.target.epoch) or - // Surround vote - (data1.source.epoch < data2.source.epoch and data2.target.epoch < data1.target.epoch)); -} - // pub fn compute_fork_data_root(current_version: primitives.Version, genesis_validators_root: primitives.Root) primitives.Root { // const fork_data = ForkData{ // .current_version = current_version, @@ -626,69 +467,6 @@ pub fn isSlashableAttestationData(data1: AttestationData, data2: AttestationData // return ssz.serialize_root(&fork_data); // } -test "test isSlashableAttestationData" { - const data1 = AttestationData{ - .slot = 0, - .index = 0, - .beacon_block_root = undefined, - .source = Checkpoint{ - .epoch = 0, - .root = undefined, - }, - .target = Checkpoint{ - .epoch = 0, - .root = undefined, - }, - }; - - const data2 = AttestationData{ - .slot = 0, - .index = 0, - .beacon_block_root = undefined, - .source = Checkpoint{ - .epoch = 0, - .root = undefined, - }, - .target = Checkpoint{ - .epoch = 0, - .root = undefined, - }, - }; - - try std.testing.expectEqual(isSlashableAttestationData(data1, data2), false); - - const data3 = AttestationData{ - .slot = 0, - .index = 0, - .beacon_block_root = undefined, - .source = Checkpoint{ - .epoch = 0, - .root = undefined, - }, - .target = Checkpoint{ - .epoch = 1, - .root = undefined, - }, - }; - - const data4 = AttestationData{ - .slot = 0, - .index = 0, - .beacon_block_root = undefined, - .source = Checkpoint{ - .epoch = 1, - .root = undefined, - }, - .target = Checkpoint{ - .epoch = 1, - .root = undefined, - }, - }; - - try std.testing.expectEqual(isSlashableAttestationData(data3, data4), true); - try std.testing.expectEqual(isSlashableAttestationData(data1, data4), false); -} - test "test Attestation" { const attestation = Attestation{ .phase0 = phase0.Attestation{ @@ -1086,7 +864,7 @@ test "test PendingConsolidation" { } test "test BeaconState" { - const state_ssz = phase0.BeaconStateSSZ{ + const p0 = phase0.BeaconState{ .genesis_time = 0, .genesis_validators_root = undefined, .slot = 0, @@ -1111,13 +889,10 @@ test "test BeaconState" { }; const state = BeaconState{ - .phase0 = phase0.BeaconState{ - .allocator = std.testing.allocator, - .beacon_state_ssz = state_ssz, - }, + .phase0 = p0, }; - try std.testing.expectEqual(state.phase0.beacon_state_ssz.genesis_time, 0); + try std.testing.expectEqual(state.phase0.genesis_time, 0); } test "test Validator" { @@ -1134,328 +909,3 @@ test "test Validator" { try std.testing.expectEqual(validator.effective_balance, 0); } - -test "test isActiveValidator" { - const validator = 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 = validator.isActiveValidator(epoch); - try std.testing.expectEqual(result, true); -} - -test "test isEligibleForActivationQueue" { - preset.ActivePreset.set(preset.Presets.mainnet); - defer preset.ActivePreset.reset(); - const validator = 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 = validator.isEligibleForActivationQueue(); - try std.testing.expectEqual(result, true); -} - -test "test isEligibleForActivation" { - var finalized_checkpoint = Checkpoint{ - .epoch = 5, - .root = .{0} ** 32, - }; - - const state_ssz = phase0.BeaconStateSSZ{ - .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 = &finalized_checkpoint, - .latest_block_header = undefined, - }; - - const state = BeaconState{ - .phase0 = phase0.BeaconState{ - .allocator = std.testing.allocator, - .beacon_state_ssz = state_ssz, - }, - }; - - const validator = 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 = validator.isEligibleForActivation(&state); - try std.testing.expectEqual(result, true); - - const validator2 = 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 = validator2.isEligibleForActivation(&state); - try std.testing.expectEqual(result2, false); -} - -test "test isSlashableValidator" { - preset.ActivePreset.set(preset.Presets.mainnet); - defer preset.ActivePreset.reset(); - const validator = Validator{ - .pubkey = undefined, - .withdrawal_credentials = undefined, - .effective_balance = 0, - .slashed = false, - .activation_eligibility_epoch = 0, - .activation_epoch = 0, - .exit_epoch = 10, - .withdrawable_epoch = 10, - }; - const epoch: primitives.Epoch = 5; - const result = validator.isSlashableValidator(epoch); - try std.testing.expectEqual(result, true); - - const validator2 = Validator{ - .pubkey = undefined, - .withdrawal_credentials = undefined, - .effective_balance = 0, - .slashed = false, - .activation_eligibility_epoch = 0, - .activation_epoch = 0, - .exit_epoch = 10, - .withdrawable_epoch = 5, - }; - const epoch2: primitives.Epoch = 5; - const result2 = validator2.isSlashableValidator(epoch2); - try std.testing.expectEqual(result2, false); -} - -test "test_getActiveValidatorIndices_withTwoActiveValidators" { - var finalized_checkpoint = Checkpoint{ - .epoch = 5, - .root = .{0} ** 32, - }; - - var validators = std.ArrayList(Validator).init(std.testing.allocator); - defer validators.deinit(); - - const validator1 = Validator{ - .pubkey = undefined, - .withdrawal_credentials = undefined, - .effective_balance = 0, - .slashed = false, - .activation_eligibility_epoch = 0, - .activation_epoch = 0, - .exit_epoch = 10, - .withdrawable_epoch = 10, - }; - - const validator2 = Validator{ - .pubkey = undefined, - .withdrawal_credentials = undefined, - .effective_balance = 0, - .slashed = false, - .activation_eligibility_epoch = 0, - .activation_epoch = 0, - .exit_epoch = 20, - .withdrawable_epoch = 20, - }; - try validators.append(validator1); - try validators.append(validator2); - - const state_ssz = altair.BeaconStateSSZ{ - .genesis_time = 0, - .genesis_validators_root = .{0} ** 32, - .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 = validators.items, - .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 = &finalized_checkpoint, - .latest_block_header = undefined, - .inactivity_scores = undefined, - .current_sync_committee = undefined, - .next_sync_committee = undefined, - }; - - const state = BeaconState{ - .altair = altair.BeaconState{ - .allocator = std.testing.allocator, - .beacon_state_ssz = state_ssz, - }, - }; - - const indices = try state.getActiveValidatorIndices(@as(primitives.Epoch, 5)); - defer std.testing.allocator.free(indices); - try std.testing.expectEqual(indices.len, 2); -} - -test "test getValidatorChurnLimit" { - preset.ActivePreset.set(preset.Presets.minimal); - defer preset.ActivePreset.reset(); - configs.ActiveConfig.set(preset.Presets.minimal); - defer configs.ActiveConfig.reset(); - var finalized_checkpoint = Checkpoint{ - .epoch = 5, - .root = .{0} ** 32, - }; - - var validators = std.ArrayList(Validator).init(std.testing.allocator); - defer validators.deinit(); - - const validator1 = Validator{ - .pubkey = undefined, - .withdrawal_credentials = undefined, - .effective_balance = 0, - .slashed = false, - .activation_eligibility_epoch = 0, - .activation_epoch = 0, - .exit_epoch = 10, - .withdrawable_epoch = 10, - }; - - const validator2 = Validator{ - .pubkey = undefined, - .withdrawal_credentials = undefined, - .effective_balance = 0, - .slashed = false, - .activation_eligibility_epoch = 0, - .activation_epoch = 0, - .exit_epoch = 20, - .withdrawable_epoch = 20, - }; - - // add 800 validators - for (0..400) |_| { - try validators.append(validator1); - try validators.append(validator2); - } - - const state_ssz = altair.BeaconStateSSZ{ - .genesis_time = 0, - .genesis_validators_root = .{0} ** 32, - .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 = validators.items, - .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 = &finalized_checkpoint, - .latest_block_header = undefined, - .inactivity_scores = undefined, - .current_sync_committee = undefined, - .next_sync_committee = undefined, - }; - - const state = BeaconState{ - .altair = altair.BeaconState{ - .allocator = std.testing.allocator, - .beacon_state_ssz = state_ssz, - }, - }; - - const churn_limit = try state.getValidatorChurnLimit(); - try std.testing.expectEqual(churn_limit, 25); - - var validators1 = std.ArrayList(Validator).init(std.testing.allocator); - defer validators1.deinit(); - - try validators1.append(validator1); - try validators1.append(validator2); - - const state_ssz1 = altair.BeaconStateSSZ{ - .genesis_time = 0, - .genesis_validators_root = .{0} ** 32, - .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 = validators1.items, - .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 = &finalized_checkpoint, - .latest_block_header = undefined, - .inactivity_scores = undefined, - .current_sync_committee = undefined, - .next_sync_committee = undefined, - }; - - const state1 = BeaconState{ - .altair = altair.BeaconState{ - .allocator = std.testing.allocator, - .beacon_state_ssz = state_ssz1, - }, - }; - - const churn_limit1 = try state1.getValidatorChurnLimit(); - try std.testing.expectEqual(churn_limit1, 2); -} diff --git a/src/primitives/types.zig b/src/primitives/types.zig index d555ef0..99d97ba 100644 --- a/src/primitives/types.zig +++ b/src/primitives/types.zig @@ -87,22 +87,6 @@ pub const ForkType = enum { electra, }; -/// Return the epoch number at `slot`. -/// @param slot - The slot number. -/// @return The epoch number. -/// @note This function is equivalent to `slot // SLOTS_PER_EPOCH`. -/// Spec pseudocode definition: -/// -/// def compute_epoch_at_slot(slot: Slot) -> Epoch: -/// """ -/// Return the epoch number at ``slot``. -/// """ -/// return Epoch(slot // SLOTS_PER_EPOCH) -pub fn computeEpochAtSlot(slot: Slot) Epoch { - // Return the epoch number at `slot`. - return @divFloor(slot, preset.ActivePreset.get().SLOTS_PER_EPOCH); -} - /// computeActivationExitEpoch computes the activation exit epoch for a given epoch. /// @param epoch The epoch to compute the activation exit epoch for. /// @return The activation exit epoch. @@ -150,22 +134,6 @@ test "test ForkType length" { try std.testing.expectEqual(6, ForkTypeLength); } -test "test compute_epoch_at_slot" { - preset.ActivePreset.set(preset.Presets.mainnet); - defer preset.ActivePreset.reset(); - const epoch = computeEpochAtSlot(0); - try std.testing.expectEqual(0, epoch); - - const epoch2 = computeEpochAtSlot(1); - try std.testing.expectEqual(0, epoch2); - - const epoch3 = computeEpochAtSlot(10); - try std.testing.expectEqual(0, epoch3); - - const epoch4 = computeEpochAtSlot(100); - try std.testing.expectEqual(3, epoch4); -} - test "test compute_activation_exit_epochs" { preset.ActivePreset.set(preset.Presets.mainnet); defer preset.ActivePreset.reset(); diff --git a/src/root.zig b/src/root.zig index e520969..587ab48 100644 --- a/src/root.zig +++ b/src/root.zig @@ -10,6 +10,10 @@ 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 epoch_helper = @import("consensus/helpers/epoch.zig"); +pub const attestation_helper = @import("consensus/helpers/attestation.zig"); +pub const weak_subjectivity_helper = @import("consensus/helpers/weak_subjectivity.zig"); +pub const validator_helper = @import("consensus/helpers/validator.zig"); test { @import("std").testing.refAllDeclsRecursive(@This());