nimbus-eth2/tests/fork_choice/scenarios/votes.nim

703 lines
18 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.push raises: [].}
# import ../interpreter # included to be able to use "suite"
func setup_votes(): tuple[fork_choice: ForkChoiceBackend, ops: seq[Operation]] =
var balances = @[Gwei(1), Gwei(1)]
let GenesisRoot = fakeHash(0)
# Initialize the fork choice context
# We start with epoch 0 fully finalized to avoid epoch 0 special cases.
result.fork_choice = ForkChoiceBackend.init(
FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# ----------------------------------
# Head should be genesis
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: GenesisRoot)
# Add block 2
#
# 0
# /
# 2
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(1).start_slot + 2,
root: fakeHash(2)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# Head should be 2
#
# 0
# /
# 2 <- head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(2))
# Add block 1 as a fork
#
# 0
# / \
# 2 1
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(1).start_slot + 1,
root: fakeHash(1)),
parent_root: GenesisRoot,
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# Head is still 2 due to tiebreaker as fakeHash(2) (0xD8...) > fakeHash(1) (0x7C...)
#
# 0
# / \
# head-> 2 1
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(2))
# Add a vote to block 1
#
# 0
# / \
# 2 1 <- +vote
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(0),
block_root: fakeHash(1),
target_epoch: Epoch(2))
# Head is now 1 as 1 has an extra vote
#
# 0
# / \
# 2 1 <- head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(1))
# Add a vote to block 2
#
# 0
# / \
# +vote-> 2 1
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(1),
block_root: fakeHash(2),
target_epoch: Epoch(2))
# Head is back to 2 due to tiebreaker as fakeHash(2) (0xD8...) > fakeHash(1) (0x7C...)
#
# 0
# / \
# head-> 2 1
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(2))
# Add block 3 as on chain 1
#
# 0
# / \
# 2 1
# |
# 3
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(1).start_slot + 3,
root: fakeHash(3)),
parent_root: fakeHash(1),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# Head is still 2
#
# 0
# / \
# head-> 2 1
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(2)
)
# Move validator #0 vote from 1 to 3
#
# 0
# / \
# 2 1 <- -vote
# |
# 3 <- +vote
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(0),
block_root: fakeHash(3),
target_epoch: Epoch(3))
# Head is still 2
#
# 0
# / \
# head-> 2 1
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(2))
# Move validator #1 vote from 2 to 1 (this is an equivocation, but fork choice doesn't
# care)
#
# 0
# / \
# -vote-> 2 1 <- +vote
# |
# 3
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(1),
block_root: fakeHash(1),
target_epoch: Epoch(3))
# Head is now 3
#
# 0
# / \
# 2 1
# |
# 3 <- head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(3))
# Add block 4 on chain 1-3
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(1).start_slot + 4,
root: fakeHash(4)),
parent_root: fakeHash(3),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# Head is now 4
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4 <- head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(4))
# Add block 5, which has a justified epoch of 2.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# /
# 5 <- justified epoch = 2
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(2).start_slot,
root: fakeHash(5)),
parent_root: fakeHash(4),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))))
# Ensure that 5 is filtered out and the head stays at 4.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4 <- head
# /
# 5
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(4))
# Add block 6, which has a justified epoch of 0.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6 <- justified epoch = 0
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(2).start_slot + 1,
root: fakeHash(6)),
parent_root: fakeHash(4),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# Move both votes to 5.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# +2 vote-> 5 6
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(0),
block_root: fakeHash(5),
target_epoch: Epoch(4))
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(1),
block_root: fakeHash(5),
target_epoch: Epoch(4))
# Add blocks 7, 8 and 9. Adding these blocks helps test the `best_descendant`
# functionality.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6
# |
# 7
# |
# 8
# /
# 9
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(2).start_slot + 2,
root: fakeHash(7)),
parent_root: fakeHash(5),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(2).start_slot + 3,
root: fakeHash(8)),
parent_root: fakeHash(7),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))))
# Finalizes 5
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(2).start_slot + 4,
root: fakeHash(9)),
parent_root: fakeHash(8),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))))
# Ensure that 6 is the head, even though 5 has all the votes. This is testing to ensure
# that 5 is filtered out due to a differing justified epoch.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6 <- head
# |
# 7
# |
# 8
# /
# 9
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: GenesisRoot, epoch: Epoch(1)),
finalized: Checkpoint(root: GenesisRoot, epoch: Epoch(1))),
justified_state_balances: balances,
expected_head: fakeHash(6))
# Change fork-choice justified epoch to 1, and the start block to 5 and ensure that 9 is
# the head.
#
# << Change justified epoch to 1 >>
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6
# |
# 7
# |
# 8
# /
# head-> 9
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(9))
# Update votes to block 9
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6
# |
# 7
# |
# 8
# /
# 9 <- +2 votes
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(0),
block_root: fakeHash(9),
target_epoch: Epoch(5))
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(1),
block_root: fakeHash(9),
target_epoch: Epoch(5))
# Head should still be 9
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(9))
# Add block 10 (also finalizes 5)
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6
# |
# 7
# |
# 8
# / \
# 9 10
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(3).start_slot,
root: fakeHash(10)),
parent_root: fakeHash(8),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))))
# Head should still be 9
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(9))
# Introduce 2 new validators
balances = @[Gwei(1), Gwei(1), Gwei(1), Gwei(1)]
# Have them vote for block 10
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6
# |
# 7
# |
# 8
# / \
# 9 10 <- +2 votes
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(2),
block_root: fakeHash(10),
target_epoch: Epoch(5))
result.ops.add Operation(
kind: ProcessAttestation,
validator_index: ValidatorIndex(3),
block_root: fakeHash(10),
target_epoch: Epoch(5))
# Check that the head is now 10.
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# / \
# 5 6
# |
# 7
# |
# 8
# / \
# 9 10 <- head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(10))
# Set the last 2 validators balances to 0
balances = @[Gwei(1), Gwei(1), Gwei(0), Gwei(0)]
# head should be 9 again
# .
# |
# 8
# / \
# head -> 9 10
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(9))
# Set the last 2 validators balances back to 1
balances = @[Gwei(1), Gwei(1), Gwei(1), Gwei(1)]
# head should be 10 again
# .
# |
# 8
# / \
# 9 10 <- head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(10))
# Remove the validators
balances = @[Gwei(1), Gwei(1)]
# head should be 9 again
# .
# |
# 8
# / \
# head -> 9 10
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(9))
# Ensure that pruning does prune.
#
#
# 0
# / \
# 2 1
# |
# 3
# |
# 4
# -------pruned here ------
# 5 6
# |
# 7
# |
# 8
# / \
# 9 10
# Note: 5 and 6 become orphans
# - 5 is the new root
# - 6 is a discarded chain
result.ops.add Operation(
kind: Prune,
prune_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
expected_len: 6)
# Prune shouldn't have changed the head
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(9))
# Add block 11
#
# 5 6
# |
# 7
# |
# 8
# / \
# 9 10
# |
# 11
result.ops.add Operation(
kind: ProcessBlock,
bid: BlockId(
slot: Epoch(3).start_slot + 1,
root: fakeHash(11)),
parent_root: fakeHash(9),
blk_checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))))
# Head is now 11
result.ops.add Operation(
kind: FindHead,
checkpoints: FinalityCheckpoints(
justified: Checkpoint(root: fakeHash(5), epoch: Epoch(2)),
finalized: Checkpoint(root: fakeHash(5), epoch: Epoch(2))),
justified_state_balances: balances,
expected_head: fakeHash(11))
proc test_votes() =
test "fork_choice - testing with votes":
# for i in 0 ..< 12:
# echo " block (", i, ") hash: ", fakeHash(i)
# echo " ------------------------------------------------------"
var (ctx, ops) = setup_votes()
ctx.run(ops)
test_votes()