nimbus-eth2/tests/test_sync_committee_pool.nim

371 lines
12 KiB
Nim

# beacon_chain
# Copyright (c) 2021-2024 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: [].}
{.used.}
import
unittest2,
../beacon_chain/spec/[beaconstate, helpers, signatures],
../beacon_chain/consensus_object_pools/sync_committee_msg_pool,
./testblockutil
func aggregate(sigs: openArray[CookedSig]): CookedSig =
var agg {.noinit.}: AggregateSignature
agg.init sigs[0]
for i in 1 ..< sigs.len:
agg.aggregate sigs[i]
agg.finish
suite "Sync committee pool":
setup:
let
rng = HmacDrbgContext.new()
cfg = block:
var res = defaultRuntimeConfig
res.ALTAIR_FORK_EPOCH = 0.Epoch
res.BELLATRIX_FORK_EPOCH = 20.Epoch
res
var pool = SyncCommitteeMsgPool.init(rng, cfg)
test "An empty pool is safe to use":
let headBid =
BlockId(slot: Slot(1), root: eth2digest(@[1.byte, 2, 3]))
var outContribution: SyncCommitteeContribution
let success = pool.produceContribution(
Slot(1),
headBid,
SyncSubcommitteeIndex(0),
outContribution)
check(success == false)
let aggregate = pool.produceSyncAggregate(headBid, headBid.slot + 1)
check:
aggregate.sync_committee_bits.isZeros
aggregate.sync_committee_signature == ValidatorSig.infinity
test "An empty pool is safe to prune":
pool.pruneData(Slot(0))
test "An empty pool is safe to prune 2":
pool.pruneData(Slot(10000))
test "Missed slots across sync committee period boundary":
let
genesis_validators_root = eth2digest(@[5.byte, 6, 7])
privkey1 = MockPrivKeys[1.ValidatorIndex]
privkey2 = MockPrivKeys[2.ValidatorIndex]
nextPeriod = cfg.BELLATRIX_FORK_EPOCH.sync_committee_period + 1
bid1 = BlockId(
slot: Slot(nextPeriod.start_slot - 2), # Committee based on `slot + 1`
root: eth2digest(@[1.byte]))
sig1 = get_sync_committee_message_signature(
bellatrixFork(cfg), genesis_validators_root,
bid1.slot, bid1.root, privkey1)
sig2 = get_sync_committee_message_signature(
bellatrixFork(cfg), genesis_validators_root,
bid1.slot + 1, bid1.root, privkey2)
pool.addSyncCommitteeMessage(
bid1.slot, bid1, 1, sig1, SyncSubcommitteeIndex(0), @[1'u64])
pool.addSyncCommitteeMessage(
# Same participant index in next period represents different validator
bid1.slot + 1, bid1, 2, sig2, SyncSubcommitteeIndex(0), @[1'u64])
var contribution: SyncCommitteeContribution
let success = pool.produceContribution(
bid1.slot + 1, bid1, SyncSubcommitteeIndex(0), contribution)
check:
success
contribution.slot == bid1.slot + 1
contribution.beacon_block_root == bid1.root
contribution.subcommittee_index == SyncSubcommitteeIndex(0).uint64
contribution.aggregation_bits.countOnes == 1
contribution.aggregation_bits[1] == true
contribution.signature == sig2.toValidatorSig
test "Missed slots across fork transition":
let
genesis_validators_root = eth2digest(@[5.byte, 6, 7])
privkey1 = MockPrivKeys[1.ValidatorIndex]
privkey2 = MockPrivKeys[1.ValidatorIndex]
bid1 = BlockId(
slot: cfg.BELLATRIX_FORK_EPOCH.start_slot - 1,
root: eth2digest(@[1.byte]))
sig1 = get_sync_committee_message_signature(
altairFork(cfg), genesis_validators_root,
bid1.slot, bid1.root, privkey1)
sig2 = get_sync_committee_message_signature(
bellatrixFork(cfg), genesis_validators_root,
bid1.slot + 1, bid1.root, privkey2)
pool.addSyncCommitteeMessage(
bid1.slot, bid1, 1, sig1, SyncSubcommitteeIndex(0), @[1'u64])
pool.addSyncCommitteeMessage(
bid1.slot + 1, bid1, 2, sig2, SyncSubcommitteeIndex(0), @[2'u64])
var contribution: SyncCommitteeContribution
let success = pool.produceContribution(
bid1.slot + 1, bid1, SyncSubcommitteeIndex(0), contribution)
check:
success
contribution.slot == bid1.slot + 1
contribution.beacon_block_root == bid1.root
contribution.subcommittee_index == SyncSubcommitteeIndex(0).uint64
contribution.aggregation_bits.countOnes == 1
contribution.aggregation_bits[2] == true
contribution.signature == sig2.toValidatorSig
test "isSeen":
let
fork = altairFork(cfg)
genesis_validators_root = eth2digest(@[5.byte, 6, 7])
privkey1 = MockPrivKeys[1.ValidatorIndex]
bid1 = BlockId(slot: Slot(100), root: eth2digest(@[1.byte]))
bid2 = BlockId(slot: Slot(101), root: eth2digest(@[1.byte, 2]))
sig1 = get_sync_committee_message_signature(
fork, genesis_validators_root, bid2.slot, bid1.root, privkey1)
sig2 = get_sync_committee_message_signature(
fork, genesis_validators_root, bid2.slot, bid2.root, privkey1)
msg1 = SyncCommitteeMessage(
slot: bid2.slot,
beacon_block_root: bid1.root,
validator_index: 1,
signature: sig1.toValidatorSig)
msg2 = SyncCommitteeMessage(
slot: bid2.slot,
beacon_block_root: bid2.root,
validator_index: 1,
signature: sig2.toValidatorSig)
check:
not pool.isSeen(msg1, SyncSubcommitteeIndex(0), bid2)
not pool.isSeen(msg2, SyncSubcommitteeIndex(0), bid2)
pool.addSyncCommitteeMessage(
bid2.slot, bid1, 1, sig1, SyncSubcommitteeIndex(0), @[1'u64])
check:
pool.isSeen(msg1, SyncSubcommitteeIndex(0), bid2)
not pool.isSeen(msg2, SyncSubcommitteeIndex(0), bid2)
pool.addSyncCommitteeMessage(
bid2.slot, bid2, 1, sig1, SyncSubcommitteeIndex(0), @[1'u64])
check:
pool.isSeen(msg1, SyncSubcommitteeIndex(0), bid2)
pool.isSeen(msg2, SyncSubcommitteeIndex(0), bid2)
test "Aggregating votes":
let
fork = altairFork(cfg)
genesis_validators_root = eth2digest(@[5.byte, 6, 7])
privkey1 = MockPrivKeys[1.ValidatorIndex]
privkey2 = MockPrivKeys[2.ValidatorIndex]
privkey3 = MockPrivKeys[3.ValidatorIndex]
privkey4 = MockPrivKeys[4.ValidatorIndex]
bid1 = BlockId(slot: Slot(100), root: eth2digest(@[1.byte]))
bid2 = BlockId(slot: Slot(101), root: eth2digest(@[1.byte, 2]))
bid3 = BlockId(slot: Slot(101), root: eth2digest(@[1.byte, 2, 3]))
subcommittee1 = SyncSubcommitteeIndex(0)
subcommittee2 = SyncSubcommitteeIndex(1)
sig1 = get_sync_committee_message_signature(
fork, genesis_validators_root, bid1.slot, bid1.root, privkey1)
sig2 = get_sync_committee_message_signature(
fork, genesis_validators_root, bid2.slot, bid2.root, privkey1)
sig3 = get_sync_committee_message_signature(
fork, genesis_validators_root, bid3.slot, bid3.root, privkey1)
sig4 = get_sync_committee_message_signature(
fork, genesis_validators_root, bid3.slot, bid2.root, privkey1)
# Inserting sync committee messages
#
pool.addSyncCommitteeMessage(
bid1.slot, bid1, 1, sig1, subcommittee1, @[1'u64])
pool.addSyncCommitteeMessage(
bid1.slot, bid1, 2, sig2, subcommittee1, @[10'u64])
pool.addSyncCommitteeMessage(
bid2.slot, bid1, 3, sig3, subcommittee2, @[7'u64])
pool.addSyncCommitteeMessage(
bid2.slot, bid2, 4, sig4, subcommittee2, @[3'u64])
# Insert a duplicate message (this should be handled gracefully)
pool.addSyncCommitteeMessage(
bid1.slot, bid1, 1, sig1, subcommittee1, @[1'u64])
# Producing contributions
#
block:
# Checking a committee where there was no activity:
var outContribution: SyncCommitteeContribution
let success = pool.produceContribution(
bid2.slot,
bid2,
subcommittee1,
outContribution)
check:
not success
block:
# Checking a committee where 2 signatures should have been aggregated:
var outContribution: SignedContributionAndProof
template contribution: untyped = outContribution.message.contribution
let success = pool.produceContribution(
bid1.slot,
bid1,
subcommittee1,
contribution)
let sig = aggregate [sig1, sig2]
check:
success
contribution.slot == bid1.slot
contribution.beacon_block_root == bid1.root
contribution.subcommittee_index == subcommittee1.uint64
contribution.aggregation_bits.countOnes == 2
contribution.aggregation_bits[1] == true
contribution.aggregation_bits[8] == false
contribution.aggregation_bits[10] == true
contribution.signature == sig.toValidatorSig
check:
not pool.covers(contribution, bid1)
pool.addContribution(outContribution, bid1, sig)
check:
pool.isSeen(outContribution.message)
pool.covers(contribution, bid1)
block:
# Checking a committee with a signle participant:
var outContribution: SignedContributionAndProof
template contribution: untyped = outContribution.message.contribution
let success = pool.produceContribution(
bid1.slot,
bid1,
subcommittee2,
contribution)
check:
success
contribution.slot == bid1.slot
contribution.beacon_block_root == bid1.root
contribution.subcommittee_index == subcommittee2.uint64
contribution.aggregation_bits.countOnes == 1
contribution.aggregation_bits[7] == true
contribution.signature == sig3.toValidatorSig
check:
not pool.covers(contribution, bid1)
pool.addContribution(outContribution, bid1, sig3)
check:
pool.isSeen(outContribution.message)
pool.covers(contribution, bid1)
block:
# Checking another committee with a signle participant
# voting for a different block:
var outContribution: SignedContributionAndProof
template contribution: untyped = outContribution.message.contribution
let success = pool.produceContribution(
bid2.slot,
bid2,
subcommittee2,
contribution)
check:
success
contribution.slot == bid2.slot
contribution.beacon_block_root == bid2.root
contribution.subcommittee_index == subcommittee2.uint64
contribution.aggregation_bits.countOnes == 1
contribution.aggregation_bits[3] == true
contribution.signature == sig4.toValidatorSig
check:
not pool.covers(contribution, bid2)
pool.addContribution(outContribution, bid2, sig4)
check:
pool.isSeen(outContribution.message)
pool.covers(contribution, bid2)
block:
# Checking a block root nobody voted for
var outContribution: SignedContributionAndProof
template contribution: untyped = outContribution.message.contribution
let success = pool.produceContribution(
bid3.slot,
bid3,
subcommittee2,
contribution)
check:
not success
# Obtaining a SyncAggregate
#
block:
# Checking for a block that got no votes
let aggregate = pool.produceSyncAggregate(bid3, bid3.slot + 1)
check:
aggregate.sync_committee_bits.isZeros
aggregate.sync_committee_signature == ValidatorSig.infinity
block:
# Checking for a block that got votes from 1 committee
let aggregate = pool.produceSyncAggregate(bid2, bid2.slot + 1)
check:
aggregate.sync_committee_bits.countOnes == 1
aggregate.sync_committee_signature == sig4.toValidatorSig
block:
# Checking for a block that got votes from 2 committees
let aggregate = pool.produceSyncAggregate(bid1, bid1.slot + 1)
let sig = aggregate [sig1, sig2, sig3]
check:
aggregate.sync_committee_bits.countOnes == 3
aggregate.sync_committee_signature == sig.toValidatorSig
# Pruning the data
#
pool.pruneData(Slot(200), force = true)
block:
# After pruning, all votes are gone
var outContribution: SyncCommitteeContribution
let success = pool.produceContribution(
bid1.slot,
bid1,
subcommittee1,
outContribution)
check:
not success
let aggregate = pool.produceSyncAggregate(bid2, bid2.slot + 1)
check:
aggregate.sync_committee_bits.isZeros
aggregate.sync_committee_signature == ValidatorSig.infinity