Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 304 additions & 1 deletion src/state_transition/block/process_deposit.zig
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub fn addValidatorToRegistry(
try balances.push(amount);
}

/// refer to https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/electra/beacon-chain.md#new-is_valid_deposit_signature
/// Refer to https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/electra/beacon-chain.md#new-is_valid_deposit_signature
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the style guide, comments that are sentences should end with a period. While you've correctly capitalized 'Refer', please also add a period at the end of the line for full compliance.

/// Refer to https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/electra/beacon-chain.md#new-is_valid_deposit_signature.
References
  1. Comments are sentences, with a space after the slash, with a capital letter and a full stop, or a colon if they relate to something that follows. Comments after the end of a line can be phrases, with no punctuation. (link)

pub fn validateDepositSignature(
config: *const BeaconConfig,
pubkey: *const BLSPubkey,
Expand Down Expand Up @@ -226,3 +226,306 @@ pub fn validateDepositSignature(
try signature.validate(true);
try verify(&signing_root, &public_key, &signature, null, null);
}

// Tests
const testing = std.testing;
const Node = @import("persistent_merkle_tree").Node;
const TestCachedBeaconState = @import("../test_utils/root.zig").TestCachedBeaconState;
const interopPubkeysCached = @import("../test_utils/interop_pubkeys.zig").interopPubkeysCached;
const interopSign = @import("../test_utils/interop_pubkeys.zig").interopSign;

test "deposit data - phase0 accessors return correct values" {
const pubkey_bytes = [_]u8{1} ** 48;
const withdrawal_creds = [_]u8{2} ** 32;
const sig = [_]u8{3} ** 96;
const deposit = DepositData{ .phase0 = .{
.pubkey = pubkey_bytes,
.withdrawal_credentials = withdrawal_creds,
.amount = 32_000_000_000,
.signature = sig,
} };

try testing.expectEqual(pubkey_bytes, deposit.pubkey().*);
try testing.expectEqual(withdrawal_creds, deposit.withdrawalCredentials().*);
try testing.expectEqual(@as(u64, 32_000_000_000), deposit.amount());
try testing.expectEqual(sig, deposit.signature());
}

test "deposit data - electra accessors return correct values" {
const pubkey_bytes = [_]u8{4} ** 48;
const withdrawal_creds = [_]u8{5} ** 32;
const sig = [_]u8{6} ** 96;
const deposit = DepositData{ .electra = .{
.pubkey = pubkey_bytes,
.withdrawal_credentials = withdrawal_creds,
.amount = 64_000_000_000,
.signature = sig,
.index = 42,
} };

try testing.expectEqual(pubkey_bytes, deposit.pubkey().*);
try testing.expectEqual(withdrawal_creds, deposit.withdrawalCredentials().*);
try testing.expectEqual(@as(u64, 64_000_000_000), deposit.amount());
try testing.expectEqual(sig, deposit.signature());
}

test "addValidatorToRegistry - new validator has correct fields and entries" {
const allocator = testing.allocator;
const pool_size = 256 * 5;
var pool = try Node.Pool.init(allocator, pool_size);
defer pool.deinit();

var test_state = try TestCachedBeaconState.init(allocator, &pool, 256);
defer test_state.deinit();

var state = test_state.cached_state.state.castToFork(.electra);

// Use interop key 256 — a valid pubkey not already in the state.
var new_pubkeys: [257]BLSPubkey = undefined;
try interopPubkeysCached(257, &new_pubkeys);
const new_pubkey = new_pubkeys[256];
Comment on lines +284 to +286
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve clarity and avoid magic numbers, it's better to define a constant for the new validator's index. This also improves consistency with other tests in this file. You can define the array size based on this constant.

    const new_validator_index: usize = 256;
    var new_pubkeys: [new_validator_index + 1]BLSPubkey = undefined;
    try interopPubkeysCached(new_pubkeys.len, &new_pubkeys);
    const new_pubkey = new_pubkeys[new_validator_index];


const withdrawal_creds = [_]u8{0x01} ++ [_]u8{0} ** 31;
const amount: u64 = 32_000_000_000;

var validators_before = try state.validators();
const count_before = try validators_before.length();
var balances_before = try state.balances();
const balances_count_before = try balances_before.length();

try addValidatorToRegistry(
.electra,
allocator,
test_state.cached_state.epoch_cache,
state,
&new_pubkey,
&withdrawal_creds,
amount,
);

// Validator count increased by one.
var validators_after = try state.validators();
const count_after = try validators_after.length();
try testing.expectEqual(count_before + 1, count_after);

// Balance entry was added with the deposit amount.
var balances_after = try state.balances();
const balances_count_after = try balances_after.length();
try testing.expectEqual(balances_count_before + 1, balances_count_after);
const new_balance = try balances_after.get(count_before);
try testing.expectEqual(amount, new_balance);

// Effective balance is correctly computed: min(amount rounded down, max_effective).
const expected_effective = @min(
amount - (amount % preset.EFFECTIVE_BALANCE_INCREMENT),
preset.MIN_ACTIVATION_BALANCE,
);
var new_validator = try validators_after.get(count_before);
const effective_bal = try new_validator.get("effective_balance");
try testing.expectEqual(expected_effective, effective_bal);

// Activation/exit epochs are set to FAR_FUTURE_EPOCH.
const activation_epoch = try new_validator.get("activation_eligibility_epoch");
try testing.expectEqual(c.FAR_FUTURE_EPOCH, activation_epoch);
const exit_epoch = try new_validator.get("exit_epoch");
try testing.expectEqual(c.FAR_FUTURE_EPOCH, exit_epoch);

// Inactivity score, participation entries were added (electra >= altair).
var inactivity_scores = try state.inactivityScores();
const inactivity_count = try inactivity_scores.length();
try testing.expectEqual(count_after, inactivity_count);

var prev_participation = try state.previousEpochParticipation();
const prev_count = try prev_participation.length();
try testing.expectEqual(count_after, prev_count);

var curr_participation = try state.currentEpochParticipation();
const curr_count = try curr_participation.length();
try testing.expectEqual(count_after, curr_count);
}

test "applyDeposit electra - existing validator creates pending deposit" {
const allocator = testing.allocator;
const pool_size = 256 * 5;
var pool = try Node.Pool.init(allocator, pool_size);
defer pool.deinit();

var test_state = try TestCachedBeaconState.init(allocator, &pool, 256);
defer test_state.deinit();

var state = test_state.cached_state.state.castToFork(.electra);

// Get the pubkey for validator 0 (already in the state).
var pubkeys: [1]BLSPubkey = undefined;
try interopPubkeysCached(1, &pubkeys);
const existing_pubkey = pubkeys[0];

const withdrawal_creds = [_]u8{0x01} ++ [_]u8{0} ** 31;
const deposit_amount: u64 = 1_000_000_000;

var validators_before = try state.validators();
const count_before = try validators_before.length();
var balances_before = try state.balances();
const balance_before = try balances_before.get(0);

var pending_before = try state.pendingDeposits();
const pending_count_before = try pending_before.length();

const deposit = DepositData{ .phase0 = .{
.pubkey = existing_pubkey,
.withdrawal_credentials = withdrawal_creds,
.amount = deposit_amount,
.signature = [_]u8{0} ** 96,
} };

try applyDeposit(
.electra,
allocator,
test_state.config,
test_state.cached_state.epoch_cache,
state,
&deposit,
);

// Validator count unchanged — existing validator, no new registration.
var validators_after = try state.validators();
const count_after = try validators_after.length();
try testing.expectEqual(count_before, count_after);

// Balance unchanged — electra defers deposit to pending.
var balances_after = try state.balances();
const balance_after = try balances_after.get(0);
try testing.expectEqual(balance_before, balance_after);

// A pending deposit was created.
var pending_after = try state.pendingDeposits();
const pending_count_after = try pending_after.length();
try testing.expectEqual(pending_count_before + 1, pending_count_after);
}

test "applyDeposit electra - invalid signature skips new validator" {
const allocator = testing.allocator;
const pool_size = 256 * 5;
var pool = try Node.Pool.init(allocator, pool_size);
defer pool.deinit();

var test_state = try TestCachedBeaconState.init(allocator, &pool, 256);
defer test_state.deinit();

var state = test_state.cached_state.state.castToFork(.electra);

// Use a new pubkey not in the state. Generate interop key 256.
var new_pubkeys: [257]BLSPubkey = undefined;
try interopPubkeysCached(257, &new_pubkeys);
const new_pubkey = new_pubkeys[256];
Comment on lines +418 to +420
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve clarity and avoid magic numbers, it's better to define a constant for the new validator's index. This also improves consistency with other tests in this file. You can define the array size based on this constant.

    const new_validator_index: usize = 256;
    var new_pubkeys: [new_validator_index + 1]BLSPubkey = undefined;
    try interopPubkeysCached(new_pubkeys.len, &new_pubkeys);
    const new_pubkey = new_pubkeys[new_validator_index];


const withdrawal_creds = [_]u8{0x01} ++ [_]u8{0} ** 31;
const deposit_amount: u64 = 32_000_000_000;

// Invalid signature — all zeros is not a valid BLS signature.
const invalid_sig = [_]u8{0} ** 96;

var validators_before = try state.validators();
const count_before = try validators_before.length();

var pending_before = try state.pendingDeposits();
const pending_count_before = try pending_before.length();

const deposit = DepositData{ .phase0 = .{
.pubkey = new_pubkey,
.withdrawal_credentials = withdrawal_creds,
.amount = deposit_amount,
.signature = invalid_sig,
} };

try applyDeposit(
.electra,
allocator,
test_state.config,
test_state.cached_state.epoch_cache,
state,
&deposit,
);

// No new validator added — invalid signature was rejected.
var validators_after = try state.validators();
const count_after = try validators_after.length();
try testing.expectEqual(count_before, count_after);

// No pending deposit created either — the deposit was entirely skipped.
var pending_after = try state.pendingDeposits();
const pending_count_after = try pending_after.length();
try testing.expectEqual(pending_count_before, pending_count_after);
}

test "applyDeposit electra - valid signature adds validator and pending deposit" {
const allocator = testing.allocator;
const pool_size = 256 * 5;
var pool = try Node.Pool.init(allocator, pool_size);
defer pool.deinit();

var test_state = try TestCachedBeaconState.init(allocator, &pool, 256);
defer test_state.deinit();

var state = test_state.cached_state.state.castToFork(.electra);

// Use interop key 256 — a new validator not already in the state.
const new_validator_index: usize = 256;
var all_pubkeys: [257]BLSPubkey = undefined;
try interopPubkeysCached(257, &all_pubkeys);
Comment on lines +474 to +475
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid magic numbers and improve clarity, it's better to define the array size and the number of pubkeys to generate based on the new_validator_index constant.

    var all_pubkeys: [new_validator_index + 1]BLSPubkey = undefined;
    try interopPubkeysCached(all_pubkeys.len, &all_pubkeys);

const new_pubkey = all_pubkeys[new_validator_index];

const withdrawal_creds = [_]u8{0x01} ++ [_]u8{0} ** 31;
const deposit_amount: u64 = 32_000_000_000;

// Construct a valid deposit signature using the interop secret key.
const deposit_message = DepositMessage{
.pubkey = new_pubkey,
.withdrawal_credentials = withdrawal_creds,
.amount = deposit_amount,
};
const GENESIS_FORK_VERSION = test_state.config.chain.GENESIS_FORK_VERSION;
var domain: Domain = undefined;
try computeDomain(DOMAIN_DEPOSIT, GENESIS_FORK_VERSION, ZERO_HASH, &domain);
var signing_root: Root = undefined;
try computeSigningRoot(types.phase0.DepositMessage, &deposit_message, &domain, &signing_root);
const sig = try interopSign(new_validator_index, &signing_root);
const compressed_sig = sig.compress();

var validators_before = try state.validators();
const count_before = try validators_before.length();

var pending_before = try state.pendingDeposits();
const pending_count_before = try pending_before.length();

const deposit = DepositData{ .phase0 = .{
.pubkey = new_pubkey,
.withdrawal_credentials = withdrawal_creds,
.amount = deposit_amount,
.signature = compressed_sig,
} };

try applyDeposit(
.electra,
allocator,
test_state.config,
test_state.cached_state.epoch_cache,
state,
&deposit,
);

// New validator was added to the registry.
var validators_after = try state.validators();
const count_after = try validators_after.length();
try testing.expectEqual(count_before + 1, count_after);

// In electra, new validators start with balance = 0 in the registry.
var balances_after = try state.balances();
const new_balance = try balances_after.get(count_before);
try testing.expectEqual(@as(u64, 0), new_balance);

// A pending deposit was created for the actual amount.
var pending_after = try state.pendingDeposits();
const pending_count_after = try pending_after.length();
try testing.expectEqual(pending_count_before + 1, pending_count_after);
}
Loading