Skip to content

Commit fa07e45

Browse files
committed
feat:add voluntary exit
Signed-off-by: Chen Kai <281165273grape@gmail.com>
1 parent 8a62f1d commit fa07e45

File tree

5 files changed

+135
-39
lines changed

5 files changed

+135
-39
lines changed

src/consensus/altair/types.zig

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,6 @@ pub const LightClientBootstrap = struct {
3838
current_sync_committee_branch: primitives.CurrentSyncCommitteeBranch,
3939
};
4040

41-
pub const SignedVoluntaryExit = struct {
42-
message: consensus.VoluntaryExit,
43-
signature: primitives.BLSSignature,
44-
};
45-
4641
pub const SyncAggregate = struct {
4742
sync_committee_bits: []bool,
4843
sync_committee_signature: primitives.BLSSignature,
@@ -198,15 +193,6 @@ test "test BeaconBlockBody" {
198193
try std.testing.expectEqual(body.randao_reveal.len, 96);
199194
}
200195

201-
test "test SignedVoluntaryExit" {
202-
const exit = SignedVoluntaryExit{
203-
.message = undefined,
204-
.signature = undefined,
205-
};
206-
207-
try std.testing.expectEqual(exit.message, undefined);
208-
}
209-
210196
test "test SyncAggregate" {
211197
const aggregate = SyncAggregate{
212198
.sync_committee_bits = &[_]bool{},

src/consensus/helpers/validator.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,16 @@ pub fn switchToCompoundingValidator(state: *consensus.BeaconState, index: primit
487487
}
488488
}
489489

490+
pub fn getPendingBalanceToWithdraw(state: *const consensus.BeaconState, validator_index: primitives.ValidatorIndex) primitives.Gwei {
491+
var total: primitives.Gwei = 0;
492+
for (state.pendingPartialWithdrawals()) |withdrawal| {
493+
if (withdrawal.index() == validator_index) {
494+
total += withdrawal.amount();
495+
}
496+
}
497+
return total;
498+
}
499+
490500
test "test getBalanceChurnLimit" {
491501
preset.ActivePreset.set(preset.Presets.minimal);
492502
defer preset.ActivePreset.reset();
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const std = @import("std");
2+
const primitives = @import("../../primitives/types.zig");
3+
const consensus = @import("../../consensus/types.zig");
4+
const configs = @import("../../configs/config.zig");
5+
const constants = @import("../../primitives/constants.zig");
6+
const preset = @import("../../presets/preset.zig");
7+
const phase0 = @import("../../consensus/phase0/types.zig");
8+
const altair = @import("../../consensus/altair/types.zig");
9+
const electra = @import("../../consensus/electra/types.zig");
10+
const validator_helper = @import("../../consensus/helpers/validator.zig");
11+
const epoch_helper = @import("../../consensus/helpers/epoch.zig");
12+
const domain_helper = @import("../../consensus/helpers/domain.zig");
13+
const signing_root_helper = @import("../../consensus/helpers/signing_root.zig");
14+
const bls_helper = @import("../../consensus/helpers/bls.zig");
15+
16+
/// processVoluntaryExit processes a voluntary exit.
17+
///
18+
/// Spec pseudocode definition:
19+
/// def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None:
20+
/// voluntary_exit = signed_voluntary_exit.message
21+
/// validator = state.validators[voluntary_exit.validator_index]
22+
/// # Verify the validator is active
23+
/// assert is_active_validator(validator, get_current_epoch(state))
24+
/// # Verify exit has not been initiated
25+
/// assert validator.exit_epoch == FAR_FUTURE_EPOCH
26+
/// # Exits must specify an epoch when they become valid; they are not valid before then
27+
/// assert get_current_epoch(state) >= voluntary_exit.epoch
28+
/// # Verify the validator has been active long enough
29+
/// assert get_current_epoch(state) >= validator.activation_epoch + config.SHARD_COMMITTEE_PERIOD
30+
/// # Verify signature
31+
/// # [Modified in Deneb:EIP7044]
32+
/// domain = compute_domain(DOMAIN_VOLUNTARY_EXIT, config.CAPELLA_FORK_VERSION, state.genesis_validators_root)
33+
/// signing_root = compute_signing_root(voluntary_exit, domain)
34+
/// assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
35+
/// # Initiate exit
36+
/// initiate_validator_exit(state, voluntary_exit.validator_index)
37+
pub fn processVoluntaryExit(state: *consensus.BeaconState, signed_voluntary_exit: *const consensus.SignedVoluntaryExit, allocator: std.mem.Allocator) !void {
38+
const voluntary_exit = signed_voluntary_exit.message;
39+
const validator = state.validators()[voluntary_exit.validator_index];
40+
41+
const current_epoch = epoch_helper.getCurrentEpoch(state);
42+
43+
// Verify the validator is active
44+
if (!validator_helper.isActiveValidator(&validator, current_epoch)) {
45+
return error.ValidatorNotActive;
46+
}
47+
48+
// Verify exit has not been initiated
49+
if (validator.exit_epoch != constants.FAR_FUTURE_EPOCH) {
50+
return error.ValidatorExitInitiated;
51+
}
52+
53+
// Exits must specify an epoch when they become valid; they are not valid before then
54+
if (current_epoch < voluntary_exit.epoch) {
55+
return error.ExitTooEarly;
56+
}
57+
58+
// Verify the validator has been active long enough
59+
if (current_epoch < validator.activation_epoch + configs.ActiveConfig.get().SHARD_COMMITTEE_PERIOD) {
60+
return error.ValidatorTooYoung;
61+
}
62+
63+
const state_enum = @intFromEnum(state.*);
64+
const is_electra_or_later = state_enum >= @intFromEnum(primitives.ForkType.electra);
65+
const is_deneb_or_later = state_enum >= @intFromEnum(primitives.ForkType.deneb);
66+
67+
if (is_electra_or_later) {
68+
// Only exit validator if it has no pending withdrawals in the queue
69+
const pending_balance = validator_helper.getPendingBalanceToWithdraw(state, voluntary_exit.validator_index);
70+
if (pending_balance != 0) {
71+
return error.ValidatorHasBalance;
72+
}
73+
}
74+
75+
const fork_version = if (is_deneb_or_later)
76+
configs.ActiveConfig.get().CAPELLA_FORK_VERSION
77+
else
78+
null;
79+
80+
// Verify signature
81+
const domain = try domain_helper.computeDomain(constants.DOMAIN_VOLUNTARY_EXIT, fork_version, state.genesisValidatorsRoot(), allocator);
82+
const signing_root = try signing_root_helper.computeSigningRoot(&voluntary_exit, &domain, allocator);
83+
if (!bls_helper.verify(&validator.pubkey, &signing_root, &signed_voluntary_exit.signature)) {
84+
return error.InvalidSignature;
85+
}
86+
87+
// Initiate exit
88+
try validator_helper.initiateValidatorExit(state, voluntary_exit.validator_index, allocator);
89+
}

src/consensus/types.zig

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,9 @@ pub const VoluntaryExit = struct {
179179
validator_index: primitives.ValidatorIndex,
180180
};
181181

182-
pub const SignedVoluntaryExit = union(primitives.ForkType) {
183-
phase0: NonExistType,
184-
altair: altair.SignedVoluntaryExit,
185-
bellatrix: altair.SignedVoluntaryExit,
186-
capella: altair.SignedVoluntaryExit,
187-
deneb: altair.SignedVoluntaryExit,
188-
electra: altair.SignedVoluntaryExit,
182+
pub const SignedVoluntaryExit = struct {
183+
message: VoluntaryExit,
184+
signature: primitives.BLSSignature,
189185
};
190186

191187
pub const SignedBeaconBlockHeader = struct {
@@ -530,6 +526,20 @@ pub const PendingPartialWithdrawal = union(primitives.ForkType) {
530526
capella: NonExistType,
531527
deneb: NonExistType,
532528
electra: electra.PendingPartialWithdrawal,
529+
530+
pub fn index(self: *const PendingPartialWithdrawal) primitives.ValidatorIndex {
531+
return switch (self.*) {
532+
.phase0, .altair, .bellatrix, .capella, .deneb => @panic("index is not supported for phase0, altair, bellatrix, capella, deneb"),
533+
inline else => |withdrawal| withdrawal.index,
534+
};
535+
}
536+
537+
pub fn amount(self: *const PendingPartialWithdrawal) primitives.Gwei {
538+
return switch (self.*) {
539+
.phase0, .altair, .bellatrix, .capella, .deneb => @panic("amount is not supported for phase0, altair, bellatrix, capella, deneb"),
540+
inline else => |withdrawal| withdrawal.amount,
541+
};
542+
}
533543
};
534544

535545
pub const WithdrawalRequest = union(primitives.ForkType) {
@@ -748,11 +758,19 @@ pub const BeaconState = union(primitives.ForkType) {
748758
/// pendingBalanceDeposits returns the pending balance deposits of the given state.
749759
pub fn pendingBalanceDeposit(self: *const BeaconState) []PendingBalanceDeposit {
750760
return switch (self.*) {
751-
.phase0, .altair, .bellatrix, .capella, .deneb => @panic("pending_balance_deposits is not supported for phase0"),
761+
.phase0, .altair, .bellatrix, .capella, .deneb => @panic("pending_balance_deposits is not supported for phase0, altair, bellatrix, capella, deneb"),
752762
inline else => |state| state.pending_balance_deposits,
753763
};
754764
}
755765

766+
/// pendingPartialWithdrawals returns the pending partial withdrawals of the given state.
767+
pub fn pendingPartialWithdrawals(self: *const BeaconState) []PendingPartialWithdrawal {
768+
return switch (self.*) {
769+
.phase0, .altair, .bellatrix, .capella, .deneb => @panic("pending_partial_withdrawals is not supported for phase0, altair, bellatrix, capella, deneb"),
770+
inline else => |state| state.pending_partial_withdrawals,
771+
};
772+
}
773+
756774
/// eth1DepositIndex returns the eth1 deposit index of the given state.
757775
pub fn eth1DepositIndex(self: *const BeaconState) u64 {
758776
return switch (self.*) {
@@ -783,23 +801,6 @@ test "test Attestation" {
783801
try std.testing.expectEqual(attestation1.altair.aggregation_bits.len, 0);
784802
}
785803

786-
test "test SignedVoluntaryExit" {
787-
const exit = SignedVoluntaryExit{
788-
.phase0 = NonExistType{},
789-
};
790-
791-
try std.testing.expectEqual(exit.phase0, NonExistType{});
792-
793-
const exit1 = SignedVoluntaryExit{
794-
.altair = altair.SignedVoluntaryExit{
795-
.message = undefined,
796-
.signature = undefined,
797-
},
798-
};
799-
800-
try std.testing.expectEqual(exit1.altair.message, undefined);
801-
}
802-
803804
test "test SyncAggregate" {
804805
const aggregate = SyncAggregate{
805806
.phase0 = NonExistType{},
@@ -1203,3 +1204,12 @@ test "test Validator" {
12031204

12041205
try std.testing.expectEqual(validator.effective_balance, 0);
12051206
}
1207+
1208+
test "test SignedVoluntaryExit" {
1209+
const exit = SignedVoluntaryExit{
1210+
.message = undefined,
1211+
.signature = undefined,
1212+
};
1213+
1214+
try std.testing.expectEqual(exit.message, undefined);
1215+
}

src/root.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub const genesis_helper = @import("consensus/helpers/genesis.zig");
2828
pub const bls_helper = @import("consensus/helpers/bls.zig");
2929
pub const bls = @import("./bls/bls.zig");
3030
pub const deposit_helper = @import("consensus/helpers/deposit.zig");
31+
pub const voluntary_exit_helper = @import("consensus/helpers/voluntary_exit.zig");
3132

3233
test {
3334
@import("std").testing.refAllDeclsRecursive(@This());

0 commit comments

Comments
 (0)