Skip to content

Commit

Permalink
feat:add more helper
Browse files Browse the repository at this point in the history
Signed-off-by: Chen Kai <[email protected]>
  • Loading branch information
GrapeBaBa committed Oct 15, 2024
1 parent ef01db4 commit 13c8acf
Show file tree
Hide file tree
Showing 2 changed files with 327 additions and 0 deletions.
202 changes: 202 additions & 0 deletions src/consensus/helpers/epoch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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 electra = @import("../../consensus/electra/types.zig");
const validator_helper = @import("../../consensus/helpers/validator.zig");

/// getCurrentEpoch returns the current epoch for the given state.
/// @return The current epoch.
Expand Down Expand Up @@ -78,6 +80,206 @@ pub fn computeActivationExitEpoch(epoch: primitives.Epoch) primitives.Epoch {
return @as(primitives.Epoch, epoch + 1 + preset.ActivePreset.get().MAX_SEED_LOOKAHEAD);
}

/// computeExitEpochAndUpdateChurn computes the exit epoch and updates the churn for the given state.
/// @param state - The state.
/// @param exit_balance - The exit balance.
/// @param allocator - The allocator.
/// @return The exit epoch and the updated churn.
/// Spec pseudocode definition:
/// def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch:
/// earliest_exit_epoch = max(state.earliest_exit_epoch, compute_activation_exit_epoch(get_current_epoch(state)))
/// per_epoch_churn = get_activation_exit_churn_limit(state)
/// # New epoch for exits.
/// if state.earliest_exit_epoch < earliest_exit_epoch:
/// exit_balance_to_consume = per_epoch_churn
/// else:
/// exit_balance_to_consume = state.exit_balance_to_consume
///
/// # Exit doesn't fit in the current earliest epoch.
/// if exit_balance > exit_balance_to_consume:
/// balance_to_process = exit_balance - exit_balance_to_consume
/// additional_epochs = (balance_to_process - 1) // per_epoch_churn + 1
/// earliest_exit_epoch += additional_epochs
/// exit_balance_to_consume += additional_epochs * per_epoch_churn
///
/// # Consume the balance and update state variables.
/// state.exit_balance_to_consume = exit_balance_to_consume - exit_balance
/// state.earliest_exit_epoch = earliest_exit_epoch
///
/// return state.earliest_exit_epoch
pub fn computeExitEpochAndUpdateChurn(state: *consensus.BeaconState, exit_balance: primitives.Gwei, allocator: std.mem.Allocator) !primitives.Epoch {
var earliest_exit_epoch = @max(state.electra.earliest_exit_epoch, computeActivationExitEpoch(getCurrentEpoch(state)));
const per_epoch_churn = try getActivationExitChurnLimit(state, allocator);
var exit_balance_to_consume: primitives.Gwei = undefined;

// New epoch for exits.
if (state.electra.earliest_exit_epoch < earliest_exit_epoch) {
exit_balance_to_consume = per_epoch_churn;
} else {
exit_balance_to_consume = state.electra.exit_balance_to_consume;
}

// Exit doesn't fit in the current earliest epoch.
if (exit_balance > exit_balance_to_consume) {
const balance_to_process = exit_balance - exit_balance_to_consume;
const additional_epochs = @divFloor((balance_to_process - 1), per_epoch_churn) + 1;
earliest_exit_epoch += additional_epochs;
exit_balance_to_consume += additional_epochs * per_epoch_churn;
}

// Consume the balance and update state variables.
state.electra.exit_balance_to_consume = exit_balance_to_consume - exit_balance;
state.electra.earliest_exit_epoch = earliest_exit_epoch;

return state.electra.earliest_exit_epoch;
}

/// getActivationExitChurnLimit returns the churn limit for the current epoch dedicated to activations and exits.
/// @param state - The state.
/// @param allocator - The allocator.
/// @return The churn limit for the current epoch dedicated to activations and exits.
/// Spec pseudocode definition:
/// def get_activation_exit_churn_limit(state: BeaconState) -> Gwei:
/// """
/// Return the churn limit for the current epoch dedicated to activations and exits.
/// """
/// return min(config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_balance_churn_limit(state))
pub fn getActivationExitChurnLimit(state: *const consensus.BeaconState, allocator: std.mem.Allocator) !primitives.Gwei {
const balance_churn_limit = try validator_helper.getBalanceChurnLimit(state, allocator);
// Return the churn limit for the current epoch dedicated to activations and exits.
return @min(configs.ActiveConfig.get().MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, balance_churn_limit);
}

test "test compute_exit_epoch_and_update_churn" {
preset.ActivePreset.set(preset.Presets.minimal);
defer preset.ActivePreset.reset();
configs.ActiveConfig.set(preset.Presets.minimal);
defer configs.ActiveConfig.reset();
var validators = std.ArrayList(consensus.Validator).init(std.testing.allocator);
defer validators.deinit();
const validator1 = consensus.Validator{
.pubkey = undefined,
.withdrawal_credentials = undefined,
.effective_balance = 10000000000000,
.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 = 10000000000000,
.slashed = false,
.activation_eligibility_epoch = 0,
.activation_epoch = 0,
.exit_epoch = 20,
.withdrawable_epoch = 20,
};
try validators.append(validator1);
try validators.append(validator2);
var state = consensus.BeaconState{
.electra = electra.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 = undefined,
.latest_block_header = undefined,
.inactivity_scores = undefined,
.current_sync_committee = undefined,
.next_sync_committee = undefined,
.earliest_exit_epoch = 5,
.exit_balance_to_consume = 10000000000000,
.latest_execution_payload_header = undefined,
.historical_summaries = undefined,
.pending_balance_deposits = undefined,
.pending_partial_withdrawals = undefined,
.pending_consolidations = undefined,
},
};

const exit_epoch = try computeExitEpochAndUpdateChurn(&state, 10000000000000, std.testing.allocator);
try std.testing.expectEqual(5, exit_epoch);
}

test "test get_activation_exit_churn_limit" {
preset.ActivePreset.set(preset.Presets.minimal);
defer preset.ActivePreset.reset();
configs.ActiveConfig.set(preset.Presets.minimal);
defer configs.ActiveConfig.reset();
var validators = std.ArrayList(consensus.Validator).init(std.testing.allocator);
defer validators.deinit();
const validator1 = consensus.Validator{
.pubkey = undefined,
.withdrawal_credentials = undefined,
.effective_balance = 10000000000000,
.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 = 10000000000000,
.slashed = false,
.activation_eligibility_epoch = 0,
.activation_epoch = 0,
.exit_epoch = 20,
.withdrawable_epoch = 20,
};
try validators.append(validator1);
try validators.append(validator2);
var 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 = undefined,
.latest_block_header = undefined,
.inactivity_scores = undefined,
.current_sync_committee = undefined,
.next_sync_committee = undefined,
},
};
const churn_limit = try getActivationExitChurnLimit(&state, std.testing.allocator);
try std.testing.expectEqual(128000000000, churn_limit);
}

test "test compute_epoch_at_slot" {
preset.ActivePreset.set(preset.Presets.mainnet);
defer preset.ActivePreset.reset();
Expand Down
125 changes: 125 additions & 0 deletions src/consensus/helpers/validator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const phase0 = @import("../../consensus/phase0/types.zig");
const altair = @import("../../consensus/altair/types.zig");
const epoch_helper = @import("../../consensus/helpers/epoch.zig");
const shuffle_helper = @import("../../consensus/helpers/shuffle.zig");
const balance = @import("../../consensus/helpers/balance.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.
Expand Down Expand Up @@ -174,6 +175,130 @@ pub fn computeProposerIndex(state: *const consensus.BeaconState, indices: []cons
}
}

/// getBalanceChurnLimit returns the balance churn limit for the current epoch.
/// The churn limit is the maximum number of validators who can leave the validator set in one epoch.
/// @param state The beacon state.
/// @param allocator The allocator.
/// @return The balance churn limit.
/// Spec pseudocode definition:
/// def get_balance_churn_limit(state: BeaconState) -> Gwei:
/// """
/// Return the churn limit for the current epoch.
/// """
/// churn = max(
/// config.MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA,
/// get_total_active_balance(state) // config.CHURN_LIMIT_QUOTIENT
/// )
/// return churn - churn % EFFECTIVE_BALANCE_INCREMENT
pub fn getBalanceChurnLimit(state: *const consensus.BeaconState, allocator: std.mem.Allocator) !primitives.Gwei {
// Return the churn limit for the current epoch.
const total_active_balance = try balance.getTotalActiveBalance(state, allocator);
const churn = @max(configs.ActiveConfig.get().MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA, @divFloor(total_active_balance, configs.ActiveConfig.get().CHURN_LIMIT_QUOTIENT));
return churn - @mod(churn, preset.ActivePreset.get().EFFECTIVE_BALANCE_INCREMENT);
}

pub fn initiateValidatorExit(state: *const consensus.BeaconState, index: primitives.ValidatorIndex, allocator: std.mem.Allocator) !void {
// Return if validator already initiated exit
var validator = state.validators()[index];
if (validator.exit_epoch != constants.FAR_FUTURE_EPOCH) {
return;
}

// Compute exit queue epoch
var exit_epochs = std.ArrayList(primitives.Epoch).init(allocator);
defer exit_epochs.deinit();

for (state.validators()) |v| {
if (v.exit_epoch != constants.FAR_FUTURE_EPOCH) {
try exit_epochs.append(v.exit_epoch);
}
}

var exit_queue_epoch = @max(std.mem.max(primitives.Epoch, exit_epochs.items), epoch_helper.computeActivationExitEpoch(epoch_helper.getCurrentEpoch(state)));

var exit_queue_churn: usize = 0;
for (state.validators()) |v| {
if (v.exit_epoch == exit_queue_epoch) {
exit_queue_churn += 1;
}
}

if (exit_queue_churn >= try getValidatorChurnLimit(state, allocator)) {
exit_queue_epoch += 1;
}

// Set validator exit epoch and withdrawable epoch
validator.exit_epoch = exit_queue_epoch;
validator.withdrawable_epoch = exit_queue_epoch + configs.ActiveConfig.get().MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
}

test "test getBalanceChurnLimit" {
preset.ActivePreset.set(preset.Presets.minimal);
defer preset.ActivePreset.reset();
configs.ActiveConfig.set(preset.Presets.minimal);
defer configs.ActiveConfig.reset();
const 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 = 12312312312,
.slashed = false,
.activation_eligibility_epoch = 0,
.activation_epoch = 0,
.exit_epoch = 0,
.withdrawable_epoch = 0,
};
try validators.append(validator1);
const validator2 = consensus.Validator{
.pubkey = undefined,
.withdrawal_credentials = undefined,
.effective_balance = 232323232332,
.slashed = false,
.activation_eligibility_epoch = 0,
.activation_epoch = 0,
.exit_epoch = 0,
.withdrawable_epoch = 0,
};
try validators.append(validator2);

const state = consensus.BeaconState{
.altair = altair.BeaconState{
.genesis_time = 0,
.genesis_validators_root = .{0} ** 32,
.slot = 100,
.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 result = try getBalanceChurnLimit(&state, std.testing.allocator);
try std.testing.expectEqual(@as(primitives.Gwei, 64000000000), result);
}

test "test getValidatorChurnLimit" {
preset.ActivePreset.set(preset.Presets.minimal);
defer preset.ActivePreset.reset();
Expand Down

0 comments on commit 13c8acf

Please sign in to comment.