Implemented Rest of Epoch Helpers (#1145)

* beginning epoch processing helper funcs

* renamed helpers to under epoch_operations

* implemented AttestingValidatorIndices

* deleted old epoch_processing.go file

* implemented WinningRoot & LowerThan helper funcs

* rest of the epoch helpers

* gazelle

* added tests

* merged

* mentioned LowerThan is for big endian format

* fixed spelling
This commit is contained in:
terence tsao 2018-12-22 12:38:09 -08:00 committed by Preston Van Loon
parent 1065617087
commit b9a233da7d
9 changed files with 1140 additions and 397 deletions

View File

@ -2,19 +2,21 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["epoch_processing.go"],
srcs = ["epoch_operations.go"],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/core/validators:go_default_library",
"//proto/beacon/p2p/v1:go_default_library",
"//shared/bytes:go_default_library",
"//shared/params:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["epoch_processing_test.go"],
srcs = ["epoch_operations_test.go"],
embed = [":go_default_library"],
deps = [
"//proto/beacon/p2p/v1:go_default_library",

View File

@ -0,0 +1,338 @@
package epoch
import (
"bytes"
"fmt"
block "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/beacon-chain/core/validators"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
b "github.com/prysmaticlabs/prysm/shared/bytes"
"github.com/prysmaticlabs/prysm/shared/params"
)
// Attestations returns the pending attestations of slots in the epoch
// (state.slot-EPOCH_LENGTH...state.slot-1), not attestations that got
// included in the chain during the epoch.
//
// Spec pseudocode definition:
// [a for a in state.latest_attestations
// if state.slot - EPOCH_LENGTH <= a.data.slot < state.slot]
func Attestations(state *pb.BeaconState) []*pb.PendingAttestationRecord {
epochLength := params.BeaconConfig().EpochLength
var thisEpochAttestations []*pb.PendingAttestationRecord
var earliestSlot uint64
for _, attestation := range state.LatestAttestations {
// If the state slot is less than epochLength, then the earliestSlot would
// result in a negative number. Therefore we should default to
// earliestSlot = 0 in this case.
if state.Slot > epochLength {
earliestSlot = state.Slot - epochLength
}
if earliestSlot <= attestation.GetData().GetSlot() && attestation.GetData().GetSlot() < state.Slot {
thisEpochAttestations = append(thisEpochAttestations, attestation)
}
}
return thisEpochAttestations
}
// BoundaryAttestations returns the pending attestations from
// the epoch's boundary block.
//
// Spec pseudocode definition:
// [a for a in this_epoch_attestations if a.data.epoch_boundary_root ==
// get_block_root(state, state.slot-EPOCH_LENGTH) and a.justified_slot ==
// state.justified_slot]
func BoundaryAttestations(
state *pb.BeaconState,
thisEpochAttestations []*pb.PendingAttestationRecord,
) ([]*pb.PendingAttestationRecord, error) {
epochLength := params.BeaconConfig().EpochLength
var boundaryAttestations []*pb.PendingAttestationRecord
for _, attestation := range thisEpochAttestations {
boundaryBlockRoot, err := block.BlockRoot(state, state.Slot-epochLength)
if err != nil {
return nil, err
}
attestationData := attestation.GetData()
sameRoot := bytes.Equal(attestationData.JustifiedBlockRootHash32, boundaryBlockRoot)
sameSlotNum := attestationData.JustifiedSlot == state.JustifiedSlot
if sameRoot && sameSlotNum {
boundaryAttestations = append(boundaryAttestations, attestation)
}
}
return boundaryAttestations, nil
}
// PrevAttestations returns the attestations of the previous epoch
// (state.slot - 2 * EPOCH_LENGTH...state.slot - EPOCH_LENGTH).
//
// Spec pseudocode definition:
// [a for a in state.latest_attestations
// if state.slot - 2 * EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]
func PrevAttestations(state *pb.BeaconState) []*pb.PendingAttestationRecord {
epochLength := params.BeaconConfig().EpochLength
var prevEpochAttestations []*pb.PendingAttestationRecord
var earliestSlot uint64
for _, attestation := range state.LatestAttestations {
// If the state slot is less than 2 * epochLength, then the earliestSlot would
// result in a negative number. Therefore we should default to
// earliestSlot = 0 in this case.
if state.Slot > 2*epochLength {
earliestSlot = state.Slot - 2*epochLength
}
if earliestSlot <= attestation.GetData().GetSlot() &&
attestation.GetData().GetSlot() < state.Slot-epochLength {
prevEpochAttestations = append(prevEpochAttestations, attestation)
}
}
return prevEpochAttestations
}
// PrevJustifiedAttestations returns the justified attestations
// of the previous 2 epochs.
//
// Spec pseudocode definition:
// [a for a in this_epoch_attestations + previous_epoch_attestations
// if a.justified_slot == state.previous_justified_slot]
func PrevJustifiedAttestations(
state *pb.BeaconState,
thisEpochAttestations []*pb.PendingAttestationRecord,
prevEpochAttestations []*pb.PendingAttestationRecord,
) []*pb.PendingAttestationRecord {
var prevJustifiedAttestations []*pb.PendingAttestationRecord
epochAttestations := append(thisEpochAttestations, prevEpochAttestations...)
for _, attestation := range epochAttestations {
if attestation.GetData().GetJustifiedSlot() == state.PreviousJustifiedSlot {
prevJustifiedAttestations = append(prevJustifiedAttestations, attestation)
}
}
return prevJustifiedAttestations
}
// PrevHeadAttestations returns the pending attestations from
// the canonical beacon chain.
//
// Spec pseudocode definition:
// [a for a in previous_epoch_attestations
// if a.beacon_block_root == get_block_root(state, a.slot)]
func PrevHeadAttestations(
state *pb.BeaconState,
prevEpochAttestations []*pb.PendingAttestationRecord,
) ([]*pb.PendingAttestationRecord, error) {
var headAttestations []*pb.PendingAttestationRecord
for _, attestation := range prevEpochAttestations {
canonicalBlockRoot, err := block.BlockRoot(state, attestation.GetData().GetSlot())
if err != nil {
return nil, err
}
attestationData := attestation.GetData()
if bytes.Equal(attestationData.BeaconBlockRootHash32, canonicalBlockRoot) {
headAttestations = append(headAttestations, attestation)
}
}
return headAttestations, nil
}
// WinningRoot returns the shard block root with the most combined validator
// effective balance. The ties broken by favoring lower shard block root values.
//
// Spec pseudocode definition:
// Let winning_root(shard_committee) be equal to the value of shard_block_root
// such that sum([get_effective_balance(state, i)
// for i in attesting_validator_indices(shard_committee, shard_block_root)])
// is maximized (ties broken by favoring lower shard_block_root values)
func WinningRoot(
state *pb.BeaconState,
shardCommittee *pb.ShardAndCommittee,
thisEpochAttestations []*pb.PendingAttestationRecord,
prevEpochAttestations []*pb.PendingAttestationRecord) ([]byte, error) {
var winnerBalance uint64
var winnerRoot []byte
var candidateRoots [][]byte
attestations := append(thisEpochAttestations, prevEpochAttestations...)
for _, attestation := range attestations {
if attestation.Data.Shard == shardCommittee.Shard {
candidateRoots = append(candidateRoots, attestation.Data.ShardBlockRootHash32)
}
}
for _, candidateRoot := range candidateRoots {
indices, err := validators.AttestingValidatorIndices(
state,
shardCommittee,
candidateRoot,
thisEpochAttestations,
prevEpochAttestations)
if err != nil {
return nil, fmt.Errorf("could not get attesting validator indices: %v", err)
}
var rootBalance uint64
for _, index := range indices {
rootBalance += validators.EffectiveBalance(state, index)
}
if rootBalance > winnerBalance ||
(rootBalance == winnerBalance && b.LowerThan(candidateRoot, winnerRoot)) {
winnerBalance = rootBalance
winnerRoot = candidateRoot
}
}
return winnerRoot, nil
}
// AttestingValidators returns the validators of the winning root.
//
// Spec pseudocode definition:
// Let `attesting_validators(shard_committee)` be equal to
// `attesting_validator_indices(shard_committee, winning_root(shard_committee))` for convenience
func AttestingValidators(
state *pb.BeaconState,
shardCommittee *pb.ShardAndCommittee,
thisEpochAttestations []*pb.PendingAttestationRecord,
prevEpochAttestations []*pb.PendingAttestationRecord) ([]uint32, error) {
root, err := WinningRoot(
state,
shardCommittee,
thisEpochAttestations,
prevEpochAttestations)
if err != nil {
return nil, fmt.Errorf("could not get winning root: %v", err)
}
indices, err := validators.AttestingValidatorIndices(
state,
shardCommittee,
root,
thisEpochAttestations,
prevEpochAttestations)
if err != nil {
return nil, fmt.Errorf("could not get attesting validator indices: %v", err)
}
return indices, nil
}
// TotalAttestingBalance returns the total balance at stake of the validators
// attested to the winning root.
//
// Spec pseudocode definition:
// Let total_balance(shard_committee) =
// sum([get_effective_balance(state, i) for i in shard_committee.committee])
func TotalAttestingBalance(
state *pb.BeaconState,
shardCommittee *pb.ShardAndCommittee,
thisEpochAttestations []*pb.PendingAttestationRecord,
prevEpochAttestations []*pb.PendingAttestationRecord) (uint64, error) {
var totalBalance uint64
attestedValidatorIndices, err := AttestingValidators(state, shardCommittee, thisEpochAttestations, prevEpochAttestations)
if err != nil {
return 0, fmt.Errorf("could not get attesting validator indices: %v", err)
}
for _, index := range attestedValidatorIndices {
totalBalance += validators.EffectiveBalance(state, index)
}
return totalBalance, nil
}
// TotalBalance returns the total balance at stake of the validators
// from the shard committee regardless of validators attested or not.
//
// Spec pseudocode definition:
// Let total_balance(shard_committee) =
// sum([get_effective_balance(state, i) for i in shard_committee.committee])
func TotalBalance(
state *pb.BeaconState,
shardCommittee *pb.ShardAndCommittee) uint64 {
var totalBalance uint64
for _, index := range shardCommittee.Committee {
totalBalance += validators.EffectiveBalance(state, index)
}
return totalBalance
}
// InclusionSlot returns the slot number of when the validator's
// attestation gets included in the beacon chain.
//
// Spec pseudocode definition:
// Let inclusion_slot(state, index) =
// a.slot_included for the attestation a where index is in
// get_attestation_participants(state, a.data, a.participation_bitfield)
func InclusionSlot(state *pb.BeaconState, validatorIndex uint32) (uint64, error) {
for _, attestation := range state.LatestAttestations {
participatedValidators, err := validators.AttestationParticipants(state, attestation.Data, attestation.ParticipationBitfield)
if err != nil {
return 0, fmt.Errorf("could not get attestation participants: %v", err)
}
for _, index := range participatedValidators {
if index == validatorIndex {
return attestation.SlotIncluded, nil
}
}
}
return 0, fmt.Errorf("could not find inclusion slot for validator index %d", validatorIndex)
}
// InclusionDistance returns the difference in slot number of when attestation
// gets submitted and when it gets included.
//
// Spec pseudocode definition:
// Let inclusion_distance(state, index) =
// a.slot_included - a.data.slot where a is the above attestation same as
// inclusion_slot
func InclusionDistance(state *pb.BeaconState, validatorIndex uint32) (uint64, error) {
for _, attestation := range state.LatestAttestations {
participatedValidators, err := validators.AttestationParticipants(state, attestation.Data, attestation.ParticipationBitfield)
if err != nil {
return 0, fmt.Errorf("could not get attestation participants: %v", err)
}
for _, index := range participatedValidators {
if index == validatorIndex {
return attestation.SlotIncluded - attestation.Data.Slot, nil
}
}
}
return 0, fmt.Errorf("could not find inclusion distance for validator index %d", validatorIndex)
}
// AdjustForInclusionDistance returns the calculated reward based on
// how long it took for attestation to get included. The longer, the lower
// the the reward.
//
// Spec pseudocode definition:
// def adjust_for_inclusion_distance(magnitude: int, distance: int) -> int:
// """
// Adjusts the reward of an attestation based on how long it took to get included
// (the longer, the lower the reward). Returns a value between ``0`` and ``magnitude``.
// ""
// return magnitude // 2 + (magnitude // 2) * MIN_ATTESTATION_INCLUSION_DELAY // distance
func AdjustForInclusionDistance(magniture uint64, distance uint64) uint64 {
return magniture/2 + (magniture/2)*
params.BeaconConfig().MinAttestationInclusionDelay/distance
}

View File

@ -0,0 +1,660 @@
package epoch
import (
"bytes"
"reflect"
"testing"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/params"
)
func TestEpochAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
var pendingAttestations []*pb.PendingAttestationRecord
for i := uint64(0); i < params.BeaconConfig().EpochLength*2; i++ {
pendingAttestations = append(pendingAttestations, &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: i,
},
})
}
state := &pb.BeaconState{LatestAttestations: pendingAttestations}
tests := []struct {
stateSlot uint64
firstAttestationSlot uint64
}{
{
stateSlot: 10,
firstAttestationSlot: 0,
},
{
stateSlot: 63,
firstAttestationSlot: 0,
},
{
stateSlot: 64,
firstAttestationSlot: 64 - params.BeaconConfig().EpochLength,
}, {
stateSlot: 127,
firstAttestationSlot: 127 - params.BeaconConfig().EpochLength,
}, {
stateSlot: 128,
firstAttestationSlot: 128 - params.BeaconConfig().EpochLength,
},
}
for _, tt := range tests {
state.Slot = tt.stateSlot
if Attestations(state)[0].GetData().GetSlot() != tt.firstAttestationSlot {
t.Errorf(
"Result slot was an unexpected value. Wanted %d, got %d",
tt.firstAttestationSlot,
Attestations(state)[0].GetData().GetSlot(),
)
}
}
}
func TestEpochBoundaryAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
epochAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{0}, JustifiedSlot: 0}},
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{1}, JustifiedSlot: 1}},
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{2}, JustifiedSlot: 2}},
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{3}, JustifiedSlot: 3}},
}
var latestBlockRootHash [][]byte
for i := uint64(0); i < params.BeaconConfig().EpochLength; i++ {
latestBlockRootHash = append(latestBlockRootHash, []byte{byte(i)})
}
state := &pb.BeaconState{
LatestAttestations: epochAttestations,
Slot: params.BeaconConfig().EpochLength,
LatestBlockRootHash32S: [][]byte{},
}
epochBoundaryAttestation, err := BoundaryAttestations(state, epochAttestations)
if err == nil {
t.Fatalf("EpochBoundaryAttestations should have failed with empty block root hash")
}
state.LatestBlockRootHash32S = latestBlockRootHash
epochBoundaryAttestation, err = BoundaryAttestations(state, epochAttestations)
if err != nil {
t.Fatalf("EpochBoundaryAttestations failed: %v", err)
}
if epochBoundaryAttestation[0].GetData().GetJustifiedSlot() != 0 {
t.Errorf("Wanted justified slot 0 for epoch boundary attestation, got: %d", epochBoundaryAttestation[0].GetData().GetJustifiedSlot())
}
if !bytes.Equal(epochBoundaryAttestation[0].GetData().GetJustifiedBlockRootHash32(), []byte{0}) {
t.Errorf("Wanted justified block hash [0] for epoch boundary attestation, got: %v",
epochBoundaryAttestation[0].GetData().GetJustifiedBlockRootHash32())
}
}
func TestPrevEpochAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
var pendingAttestations []*pb.PendingAttestationRecord
for i := uint64(0); i < params.BeaconConfig().EpochLength*4; i++ {
pendingAttestations = append(pendingAttestations, &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: i,
},
})
}
state := &pb.BeaconState{LatestAttestations: pendingAttestations}
tests := []struct {
stateSlot uint64
firstAttestationSlot uint64
}{
{
stateSlot: 10,
firstAttestationSlot: 0,
},
{
stateSlot: 127,
firstAttestationSlot: 0,
},
{
stateSlot: 383,
firstAttestationSlot: 383 - 2*params.BeaconConfig().EpochLength,
},
{
stateSlot: 129,
firstAttestationSlot: 129 - 2*params.BeaconConfig().EpochLength,
},
{
stateSlot: 256,
firstAttestationSlot: 256 - 2*params.BeaconConfig().EpochLength,
},
}
for _, tt := range tests {
state.Slot = tt.stateSlot
if PrevAttestations(state)[0].GetData().GetSlot() != tt.firstAttestationSlot {
t.Errorf(
"Result slot was an unexpected value. Wanted %d, got %d",
tt.firstAttestationSlot,
Attestations(state)[0].GetData().GetSlot(),
)
}
}
}
func TestPrevJustifiedAttestations(t *testing.T) {
prevEpochAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{JustifiedSlot: 0}},
{Data: &pb.AttestationData{JustifiedSlot: 2}},
{Data: &pb.AttestationData{JustifiedSlot: 5}},
{Data: &pb.AttestationData{Shard: 2, JustifiedSlot: 100}},
{Data: &pb.AttestationData{Shard: 3, JustifiedSlot: 100}},
{Data: &pb.AttestationData{JustifiedSlot: 999}},
}
thisEpochAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{JustifiedSlot: 0}},
{Data: &pb.AttestationData{JustifiedSlot: 10}},
{Data: &pb.AttestationData{JustifiedSlot: 15}},
{Data: &pb.AttestationData{Shard: 0, JustifiedSlot: 100}},
{Data: &pb.AttestationData{Shard: 1, JustifiedSlot: 100}},
{Data: &pb.AttestationData{JustifiedSlot: 888}},
}
state := &pb.BeaconState{PreviousJustifiedSlot: 100}
prevJustifiedAttestations := PrevJustifiedAttestations(state, thisEpochAttestations, prevEpochAttestations)
for i, attestation := range prevJustifiedAttestations {
if attestation.GetData().Shard != uint64(i) {
t.Errorf("Wanted shard %d, got %d", i, attestation.GetData().Shard)
}
if attestation.GetData().GetJustifiedSlot() != 100 {
t.Errorf("Wanted justified slot 100, got %d", attestation.GetData().GetJustifiedSlot())
}
}
}
func TestHeadAttestations_Ok(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
prevAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Slot: 1, BeaconBlockRootHash32: []byte{'A'}}},
{Data: &pb.AttestationData{Slot: 2, BeaconBlockRootHash32: []byte{'B'}}},
{Data: &pb.AttestationData{Slot: 3, BeaconBlockRootHash32: []byte{'C'}}},
{Data: &pb.AttestationData{Slot: 4, BeaconBlockRootHash32: []byte{'D'}}},
}
state := &pb.BeaconState{Slot: 5, LatestBlockRootHash32S: [][]byte{{'A'}, {'X'}, {'C'}, {'Y'}}}
headAttestations, err := PrevHeadAttestations(state, prevAttestations)
if err != nil {
t.Fatalf("PrevHeadAttestations failed with %v", err)
}
if headAttestations[0].GetData().GetSlot() != 1 {
t.Errorf("headAttestations[0] wanted slot 1, got slot %d", headAttestations[0].GetData().GetSlot())
}
if headAttestations[1].GetData().GetSlot() != 3 {
t.Errorf("headAttestations[1] wanted slot 3, got slot %d", headAttestations[1].GetData().GetSlot())
}
if !bytes.Equal([]byte{'A'}, headAttestations[0].GetData().GetBeaconBlockRootHash32()) {
t.Errorf("headAttestations[0] wanted hash [A], got slot %v",
headAttestations[0].GetData().GetBeaconBlockRootHash32())
}
if !bytes.Equal([]byte{'C'}, headAttestations[1].GetData().GetBeaconBlockRootHash32()) {
t.Errorf("headAttestations[1] wanted hash [C], got slot %v",
headAttestations[1].GetData().GetBeaconBlockRootHash32())
}
}
func TestHeadAttestations_NotOk(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
prevAttestations := []*pb.PendingAttestationRecord{{Data: &pb.AttestationData{Slot: 1}}}
state := &pb.BeaconState{Slot: 0}
if _, err := PrevHeadAttestations(state, prevAttestations); err == nil {
t.Fatal("PrevHeadAttestations should have failed with invalid range")
}
}
func TestWinningRoot_Ok(t *testing.T) {
defaultBalance := params.BeaconConfig().MaxDeposit
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{0, 1, 2, 3, 4, 5, 6, 7}},
}}}
// Assign 32 ETH balance to every validator in shardAndCommittees.
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
ValidatorBalances: []uint64{
defaultBalance, defaultBalance, defaultBalance, defaultBalance,
defaultBalance, defaultBalance, defaultBalance, defaultBalance,
},
}
// Generate 10 roots ([]byte{100}...[]byte{110})
var attestations []*pb.PendingAttestationRecord
for i := 0; i < 10; i++ {
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: 0,
Shard: 1,
ShardBlockRootHash32: []byte{byte(i + 100)},
},
// Validator 1 and 7 attested to all 10 roots.
ParticipationBitfield: []byte{'A'},
}
attestations = append(attestations, attestation)
}
// Since all 10 roots have the balance of 64 ETHs
// WinningRoot chooses the lowest hash: []byte{100}
winnerRoot, err := WinningRoot(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
attestations,
nil)
if err != nil {
t.Fatalf("Could not execute WinningRoot: %v", err)
}
if !bytes.Equal(winnerRoot, []byte{100}) {
t.Errorf("Incorrect winner root, wanted:[100], got: %v", winnerRoot)
}
// Give root [105] one more attester
attestations[5].ParticipationBitfield = []byte{'C'}
winnerRoot, err = WinningRoot(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
attestations,
nil)
if err != nil {
t.Fatalf("Could not execute WinningRoot: %v", err)
}
if !bytes.Equal(winnerRoot, []byte{105}) {
t.Errorf("Incorrect winner root, wanted:[105], got: %v", winnerRoot)
}
}
func TestWinningRoot_OutOfBound(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
}
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Shard: 1,
ShardBlockRootHash32: []byte{},
},
ParticipationBitfield: []byte{'A'},
}
_, err := WinningRoot(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
[]*pb.PendingAttestationRecord{attestation},
nil)
if err == nil {
t.Fatal("WinningRoot should have failed")
}
}
func TestAttestingValidators_Ok(t *testing.T) {
defaultBalance := params.BeaconConfig().MaxDeposit
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{0, 1, 2, 3, 4, 5, 6, 7}},
}}}
// Assign 32 ETH balance to every validator in shardAndCommittees.
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
ValidatorBalances: []uint64{
defaultBalance, defaultBalance, defaultBalance, defaultBalance,
defaultBalance, defaultBalance, defaultBalance, defaultBalance,
},
}
// Generate 10 roots ([]byte{100}...[]byte{110})
var attestations []*pb.PendingAttestationRecord
for i := 0; i < 10; i++ {
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: 0,
Shard: 1,
ShardBlockRootHash32: []byte{byte(i + 100)},
},
// Validator 1 and 7 attested to the above roots.
ParticipationBitfield: []byte{'A'},
}
attestations = append(attestations, attestation)
}
attestedValidators, err := AttestingValidators(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
attestations,
nil)
if err != nil {
t.Fatalf("Could not execute WinningRoot: %v", err)
}
// Verify the winner root is attested by validator 1 and 7.
if !reflect.DeepEqual(attestedValidators, []uint32{1, 7}) {
t.Errorf("Active validators don't match. Wanted:[1,7], Got: %v", attestedValidators)
}
}
func TestAttestingValidators_CantGetWinningRoot(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
}
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Shard: 1,
ShardBlockRootHash32: []byte{},
},
ParticipationBitfield: []byte{'A'},
}
_, err := AttestingValidators(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
[]*pb.PendingAttestationRecord{attestation},
nil)
if err == nil {
t.Fatal("AttestingValidators should have failed")
}
}
func TestTotalAttestingBalance_Ok(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{0, 1, 2, 3, 4, 5, 6, 7}},
}}}
// Assign validators to different balances.
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
ValidatorBalances: []uint64{16 * 1e9, 18 * 1e9, 20 * 1e9, 31 * 1e9,
32 * 1e9, 34 * 1e9, 50 * 1e9, 50 * 1e9},
}
// Generate 10 roots ([]byte{100}...[]byte{110})
var attestations []*pb.PendingAttestationRecord
for i := 0; i < 10; i++ {
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: 0,
Shard: 1,
ShardBlockRootHash32: []byte{byte(i + 100)},
},
// All validators attested to the above roots.
ParticipationBitfield: []byte{0xff},
}
attestations = append(attestations, attestation)
}
attestedBalance, err := TotalAttestingBalance(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
attestations,
nil)
if err != nil {
t.Fatalf("Could not execute TotalAttestingBalance: %v", err)
}
// Verify the Attested balances are 16+18+20+31+(32*4)=213.
if attestedBalance != 213*1e9 {
t.Errorf("Incorrect attested balance. Wanted:231*1e9, Got: %d", attestedBalance)
}
}
func TestTotalAttestingBalance_NotOfBound(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
}
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Shard: 1,
ShardBlockRootHash32: []byte{},
},
ParticipationBitfield: []byte{'A'},
}
_, err := TotalAttestingBalance(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
[]*pb.PendingAttestationRecord{attestation},
nil)
if err == nil {
t.Fatal("TotalAttestingBalance should have failed")
}
}
func TestTotalBalance(t *testing.T) {
shardAndCommittees := &pb.ShardAndCommittee{Shard: 1, Committee: []uint32{0, 1, 2, 3, 4, 5, 6, 7}}
// Assign validators to different balances.
state := &pb.BeaconState{
Slot: 5,
ValidatorBalances: []uint64{20 * 1e9, 25 * 1e9, 30 * 1e9, 30 * 1e9,
32 * 1e9, 34 * 1e9, 50 * 1e9, 50 * 1e9},
}
// 20 + 25 + 30 + 30 + 32 + 32 + 32 + 32 = 233
totalBalance := TotalBalance(state, shardAndCommittees)
if totalBalance != 233*1e9 {
t.Errorf("Incorrect total balance. Wanted: 233*1e9, got: %d", totalBalance)
}
}
func TestInclusionSlot_Ok(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{0, 1, 2, 3, 4, 5, 6, 7}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
LatestAttestations: []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Shard: 1, Slot: 0},
// Validator 1 and 7 participated.
ParticipationBitfield: []byte{'A'},
SlotIncluded: 100},
},
}
slot, err := InclusionSlot(state, 1)
if err != nil {
t.Fatalf("Could not execute InclusionSlot: %v", err)
}
// validator 7's attestation got included in slot 100.
if slot != 100 {
t.Errorf("Incorrect slot. Wanted: 100, got: %d", slot)
}
}
func TestInclusionSlot_BadBitfield(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{1}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
LatestAttestations: []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Shard: 1, Slot: 0},
ParticipationBitfield: []byte{},
SlotIncluded: 9},
},
}
_, err := InclusionSlot(state, 1)
if err == nil {
t.Fatal("InclusionSlot should have failed")
}
}
func TestInclusionSlot_NotFound(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{1}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
}
_, err := InclusionSlot(state, 1)
if err == nil {
t.Fatal("InclusionSlot should have failed")
}
}
func TestInclusionDistance_Ok(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{0, 1, 2, 3, 4, 5, 6, 7}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
LatestAttestations: []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Shard: 1, Slot: 0},
// Validator 1 and 7 participated.
ParticipationBitfield: []byte{'A'},
SlotIncluded: 9},
},
}
distance, err := InclusionDistance(state, 7)
if err != nil {
t.Fatalf("Could not execute InclusionDistance: %v", err)
}
// Inclusion distance is 9 because input validator index is 7,
// validator 7's attested slot 0 and got included slot 9.
if distance != 9 {
t.Errorf("Incorrect distance. Wanted: 9, got: %d", distance)
}
}
func TestInclusionDistance_BadBitfield(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{1}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
LatestAttestations: []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Shard: 1, Slot: 0},
ParticipationBitfield: []byte{},
SlotIncluded: 9},
},
}
_, err := InclusionDistance(state, 1)
if err == nil {
t.Fatal("InclusionDistance should have failed")
}
}
func TestInclusionDistance_NotFound(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1, Committee: []uint32{1}},
}}}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
}
_, err := InclusionDistance(state, 1)
if err == nil {
t.Fatal("InclusionDistance should have failed")
}
}
func TestAdjustForInclusionDistance(t *testing.T) {
tests := []struct {
a uint64
b uint64
c uint64
}{
{a: 10, b: 1, c: 25},
{a: 10, b: 2, c: 15},
{a: 10, b: 16, c: 6},
{a: 50, b: 1, c: 125},
{a: 50, b: 16, c: 31},
}
for _, tt := range tests {
if AdjustForInclusionDistance(tt.a, tt.b) != tt.c {
t.Errorf(
"AdjustForInclusionDistance(%d, %d) = %d, want = %d",
tt.a, tt.b, AdjustForInclusionDistance(tt.a, tt.b), tt.c)
}
}
}

View File

@ -1,144 +0,0 @@
package epoch
import (
"bytes"
b "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/params"
)
// Attestations returns the pending attestations of slots in the epoch
// (state.slot-EPOCH_LENGTH...state.slot-1), not attestations that got
// included in the chain during the epoch.
//
// Spec pseudocode definition:
// [a for a in state.latest_attestations
// if state.slot - EPOCH_LENGTH <= a.data.slot < state.slot]
func Attestations(state *pb.BeaconState) []*pb.PendingAttestationRecord {
epochLength := params.BeaconConfig().EpochLength
var thisEpochAttestations []*pb.PendingAttestationRecord
var earliestSlot uint64
for _, attestation := range state.LatestAttestations {
// If the state slot is less than epochLength, then the earliestSlot would
// result in a negative number. Therefore we should default to
// earliestSlot = 0 in this case.
if state.Slot > epochLength {
earliestSlot = state.Slot - epochLength
}
if earliestSlot <= attestation.GetData().GetSlot() && attestation.GetData().GetSlot() < state.Slot {
thisEpochAttestations = append(thisEpochAttestations, attestation)
}
}
return thisEpochAttestations
}
// BoundaryAttestations returns the pending attestations from
// the epoch's boundary block.
//
// Spec pseudocode definition:
// [a for a in this_epoch_attestations if a.data.epoch_boundary_root ==
// get_block_root(state, state.slot-EPOCH_LENGTH) and a.justified_slot ==
// state.justified_slot]
func BoundaryAttestations(
state *pb.BeaconState,
thisEpochAttestations []*pb.PendingAttestationRecord,
) ([]*pb.PendingAttestationRecord, error) {
epochLength := params.BeaconConfig().EpochLength
var boundaryAttestations []*pb.PendingAttestationRecord
for _, attestation := range thisEpochAttestations {
boundaryBlockRoot, err := b.BlockRoot(state, state.Slot-epochLength)
if err != nil {
return nil, err
}
attestationData := attestation.GetData()
sameRoot := bytes.Equal(attestationData.JustifiedBlockRootHash32, boundaryBlockRoot)
sameSlotNum := attestationData.JustifiedSlot == state.JustifiedSlot
if sameRoot && sameSlotNum {
boundaryAttestations = append(boundaryAttestations, attestation)
}
}
return boundaryAttestations, nil
}
// PrevAttestations returns the attestations of the previous epoch
// (state.slot - 2 * EPOCH_LENGTH...state.slot - EPOCH_LENGTH).
//
// Spec pseudocode definition:
// [a for a in state.latest_attestations
// if state.slot - 2 * EPOCH_LENGTH <= a.slot < state.slot - EPOCH_LENGTH]
func PrevAttestations(state *pb.BeaconState) []*pb.PendingAttestationRecord {
epochLength := params.BeaconConfig().EpochLength
var prevEpochAttestations []*pb.PendingAttestationRecord
var earliestSlot uint64
for _, attestation := range state.LatestAttestations {
// If the state slot is less than 2 * epochLength, then the earliestSlot would
// result in a negative number. Therefore we should default to
// earliestSlot = 0 in this case.
if state.Slot > 2*epochLength {
earliestSlot = state.Slot - 2*epochLength
}
if earliestSlot <= attestation.GetData().GetSlot() &&
attestation.GetData().GetSlot() < state.Slot-epochLength {
prevEpochAttestations = append(prevEpochAttestations, attestation)
}
}
return prevEpochAttestations
}
// PrevJustifiedAttestations returns the justified attestations
// of the previous 2 epochs.
//
// Spec pseudocode definition:
// [a for a in this_epoch_attestations + previous_epoch_attestations
// if a.justified_slot == state.previous_justified_slot]
func PrevJustifiedAttestations(
state *pb.BeaconState,
thisEpochAttestations []*pb.PendingAttestationRecord,
prevEpochAttestations []*pb.PendingAttestationRecord,
) []*pb.PendingAttestationRecord {
var prevJustifiedAttestations []*pb.PendingAttestationRecord
epochAttestations := append(thisEpochAttestations, prevEpochAttestations...)
for _, attestation := range epochAttestations {
if attestation.GetData().GetJustifiedSlot() == state.PreviousJustifiedSlot {
prevJustifiedAttestations = append(prevJustifiedAttestations, attestation)
}
}
return prevJustifiedAttestations
}
// PrevHeadAttestations returns the pending attestations from
// the canonical beacon chain.
//
// Spec pseudocode definition:
// [a for a in previous_epoch_attestations
// if a.beacon_block_root == get_block_root(state, a.slot)]
func PrevHeadAttestations(
state *pb.BeaconState,
prevEpochAttestations []*pb.PendingAttestationRecord,
) ([]*pb.PendingAttestationRecord, error) {
var headAttestations []*pb.PendingAttestationRecord
for _, attestation := range prevEpochAttestations {
canonicalBlockRoot, err := b.BlockRoot(state, attestation.GetData().GetSlot())
if err != nil {
return nil, err
}
attestationData := attestation.GetData()
if bytes.Equal(attestationData.BeaconBlockRootHash32, canonicalBlockRoot) {
headAttestations = append(headAttestations, attestation)
}
}
return headAttestations, nil
}

View File

@ -1,243 +0,0 @@
package epoch
import (
"bytes"
"testing"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/params"
)
func TestEpochAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
var pendingAttestations []*pb.PendingAttestationRecord
for i := uint64(0); i < params.BeaconConfig().EpochLength*2; i++ {
pendingAttestations = append(pendingAttestations, &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: i,
},
})
}
state := &pb.BeaconState{LatestAttestations: pendingAttestations}
tests := []struct {
stateSlot uint64
firstAttestationSlot uint64
}{
{
stateSlot: 10,
firstAttestationSlot: 0,
},
{
stateSlot: 63,
firstAttestationSlot: 0,
},
{
stateSlot: 64,
firstAttestationSlot: 64 - params.BeaconConfig().EpochLength,
}, {
stateSlot: 127,
firstAttestationSlot: 127 - params.BeaconConfig().EpochLength,
}, {
stateSlot: 128,
firstAttestationSlot: 128 - params.BeaconConfig().EpochLength,
},
}
for _, tt := range tests {
state.Slot = tt.stateSlot
if Attestations(state)[0].GetData().GetSlot() != tt.firstAttestationSlot {
t.Errorf(
"Result slot was an unexpected value. Wanted %d, got %d",
tt.firstAttestationSlot,
Attestations(state)[0].GetData().GetSlot(),
)
}
}
}
func TestEpochBoundaryAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
epochAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{0}, JustifiedSlot: 0}},
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{1}, JustifiedSlot: 1}},
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{2}, JustifiedSlot: 2}},
{Data: &pb.AttestationData{JustifiedBlockRootHash32: []byte{3}, JustifiedSlot: 3}},
}
var latestBlockRootHash [][]byte
for i := uint64(0); i < params.BeaconConfig().EpochLength; i++ {
latestBlockRootHash = append(latestBlockRootHash, []byte{byte(i)})
}
state := &pb.BeaconState{
LatestAttestations: epochAttestations,
Slot: params.BeaconConfig().EpochLength,
LatestBlockRootHash32S: [][]byte{},
}
epochBoundaryAttestation, err := BoundaryAttestations(state, epochAttestations)
if err == nil {
t.Fatalf("EpochBoundaryAttestations should have failed with empty block root hash")
}
state.LatestBlockRootHash32S = latestBlockRootHash
epochBoundaryAttestation, err = BoundaryAttestations(state, epochAttestations)
if err != nil {
t.Fatalf("EpochBoundaryAttestations failed: %v", err)
}
if epochBoundaryAttestation[0].GetData().GetJustifiedSlot() != 0 {
t.Errorf("Wanted justified slot 0 for epoch boundary attestation, got: %d", epochBoundaryAttestation[0].GetData().GetJustifiedSlot())
}
if !bytes.Equal(epochBoundaryAttestation[0].GetData().GetJustifiedBlockRootHash32(), []byte{0}) {
t.Errorf("Wanted justified block hash [0] for epoch boundary attestation, got: %v",
epochBoundaryAttestation[0].GetData().GetJustifiedBlockRootHash32())
}
}
func TestPrevEpochAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
var pendingAttestations []*pb.PendingAttestationRecord
for i := uint64(0); i < params.BeaconConfig().EpochLength*4; i++ {
pendingAttestations = append(pendingAttestations, &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: i,
},
})
}
state := &pb.BeaconState{LatestAttestations: pendingAttestations}
tests := []struct {
stateSlot uint64
firstAttestationSlot uint64
}{
{
stateSlot: 10,
firstAttestationSlot: 0,
},
{
stateSlot: 127,
firstAttestationSlot: 0,
},
{
stateSlot: 383,
firstAttestationSlot: 383 - 2*params.BeaconConfig().EpochLength,
},
{
stateSlot: 129,
firstAttestationSlot: 129 - 2*params.BeaconConfig().EpochLength,
},
{
stateSlot: 256,
firstAttestationSlot: 256 - 2*params.BeaconConfig().EpochLength,
},
}
for _, tt := range tests {
state.Slot = tt.stateSlot
if PrevAttestations(state)[0].GetData().GetSlot() != tt.firstAttestationSlot {
t.Errorf(
"Result slot was an unexpected value. Wanted %d, got %d",
tt.firstAttestationSlot,
Attestations(state)[0].GetData().GetSlot(),
)
}
}
}
func TestPrevJustifiedAttestations(t *testing.T) {
prevEpochAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{JustifiedSlot: 0}},
{Data: &pb.AttestationData{JustifiedSlot: 2}},
{Data: &pb.AttestationData{JustifiedSlot: 5}},
{Data: &pb.AttestationData{Shard: 2, JustifiedSlot: 100}},
{Data: &pb.AttestationData{Shard: 3, JustifiedSlot: 100}},
{Data: &pb.AttestationData{JustifiedSlot: 999}},
}
thisEpochAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{JustifiedSlot: 0}},
{Data: &pb.AttestationData{JustifiedSlot: 10}},
{Data: &pb.AttestationData{JustifiedSlot: 15}},
{Data: &pb.AttestationData{Shard: 0, JustifiedSlot: 100}},
{Data: &pb.AttestationData{Shard: 1, JustifiedSlot: 100}},
{Data: &pb.AttestationData{JustifiedSlot: 888}},
}
state := &pb.BeaconState{PreviousJustifiedSlot: 100}
prevJustifiedAttestations := PrevJustifiedAttestations(state, thisEpochAttestations, prevEpochAttestations)
for i, attestation := range prevJustifiedAttestations {
if attestation.GetData().Shard != uint64(i) {
t.Errorf("Wanted shard %d, got %d", i, attestation.GetData().Shard)
}
if attestation.GetData().GetJustifiedSlot() != 100 {
t.Errorf("Wanted justified slot 100, got %d", attestation.GetData().GetJustifiedSlot())
}
}
}
func TestHeadAttestations_Ok(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
prevAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Slot: 1, BeaconBlockRootHash32: []byte{'A'}}},
{Data: &pb.AttestationData{Slot: 2, BeaconBlockRootHash32: []byte{'B'}}},
{Data: &pb.AttestationData{Slot: 3, BeaconBlockRootHash32: []byte{'C'}}},
{Data: &pb.AttestationData{Slot: 4, BeaconBlockRootHash32: []byte{'D'}}},
}
state := &pb.BeaconState{Slot: 5, LatestBlockRootHash32S: [][]byte{{'A'}, {'X'}, {'C'}, {'Y'}}}
headAttestations, err := PrevHeadAttestations(state, prevAttestations)
if err != nil {
t.Fatalf("PrevHeadAttestations failed with %v", err)
}
if headAttestations[0].GetData().GetSlot() != 1 {
t.Errorf("headAttestations[0] wanted slot 1, got slot %d", headAttestations[0].GetData().GetSlot())
}
if headAttestations[1].GetData().GetSlot() != 3 {
t.Errorf("headAttestations[1] wanted slot 3, got slot %d", headAttestations[1].GetData().GetSlot())
}
if !bytes.Equal([]byte{'A'}, headAttestations[0].GetData().GetBeaconBlockRootHash32()) {
t.Errorf("headAttestations[0] wanted hash [A], got slot %v",
headAttestations[0].GetData().GetBeaconBlockRootHash32())
}
if !bytes.Equal([]byte{'C'}, headAttestations[1].GetData().GetBeaconBlockRootHash32()) {
t.Errorf("headAttestations[1] wanted hash [C], got slot %v",
headAttestations[1].GetData().GetBeaconBlockRootHash32())
}
}
func TestHeadAttestations_NotOk(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
prevAttestations := []*pb.PendingAttestationRecord{{Data: &pb.AttestationData{Slot: 1}}}
state := &pb.BeaconState{Slot: 0}
if _, err := PrevHeadAttestations(state, prevAttestations); err == nil {
t.Fatal("PrevHeadAttestations should have failed with invalid range")
}
}

View File

@ -268,16 +268,8 @@ func TestAttestationParticipants_ok(t *testing.T) {
})
}
var validators []*pb.ValidatorRecord
for i := uint64(0); i < params.BeaconConfig().BootstrappedValidatorsCount; i++ {
validators = append(validators, &pb.ValidatorRecord{
Pubkey: []byte{byte(i)},
})
}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
ValidatorRegistry: validators,
}
attestationData := &pb.AttestationData{}

View File

@ -500,6 +500,39 @@ func ValidatorIndices(
return attesterIndicesIntersection, nil
}
// AttestingValidatorIndices returns the shard committee validator indices
// if the validator shard committee matches the input attestations.
//
// Spec pseudocode definition:
// Let attesting_validator_indices(shard_committee, shard_block_root)
// be the union of the validator index sets given by
// [get_attestation_participants(state, a.data, a.participation_bitfield)
// for a in this_epoch_attestations + previous_epoch_attestations
// if a.shard == shard_committee.shard and a.shard_block_root == shard_block_root]
func AttestingValidatorIndices(
state *pb.BeaconState,
shardCommittee *pb.ShardAndCommittee,
shardBlockRoot []byte,
thisEpochAttestations []*pb.PendingAttestationRecord,
prevEpochAttestations []*pb.PendingAttestationRecord) ([]uint32, error) {
var validatorIndicesCommittees []uint32
attestations := append(thisEpochAttestations, prevEpochAttestations...)
for _, attestation := range attestations {
if attestation.Data.Shard == shardCommittee.Shard &&
bytes.Equal(attestation.Data.ShardBlockRootHash32, shardBlockRoot) {
validatorIndicesCommittee, err := AttestationParticipants(state, attestation.Data, attestation.ParticipationBitfield)
if err != nil {
return nil, fmt.Errorf("could not get attester indices: %v", err)
}
validatorIndicesCommittees = slices.Union(validatorIndicesCommittees, validatorIndicesCommittee)
}
}
return validatorIndicesCommittees, nil
}
// AttestingBalance returns the combined balances from the input validator
// records.
//

View File

@ -794,3 +794,96 @@ func TestBeaconProposerIndex(t *testing.T) {
}
}
}
func TestAttestingValidatorIndices_Ok(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
var committeeIndices []uint32
for i := uint32(0); i < 8; i++ {
committeeIndices = append(committeeIndices, i)
}
var shardAndCommittees []*pb.ShardAndCommitteeArray
for i := uint64(0); i < params.BeaconConfig().EpochLength*2; i++ {
shardAndCommittees = append(shardAndCommittees, &pb.ShardAndCommitteeArray{
ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: i, Committee: committeeIndices},
},
})
}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
}
prevAttestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: 3,
Shard: 3,
ShardBlockRootHash32: []byte{'B'},
},
ParticipationBitfield: []byte{'A'}, // 01000001 = 1,7
}
thisAttestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: 3,
Shard: 3,
ShardBlockRootHash32: []byte{'B'},
},
ParticipationBitfield: []byte{'F'}, // 01000110 = 1,5,6
}
indices, err := AttestingValidatorIndices(
state,
shardAndCommittees[3].ArrayShardAndCommittee[0],
[]byte{'B'},
[]*pb.PendingAttestationRecord{thisAttestation},
[]*pb.PendingAttestationRecord{prevAttestation})
if err != nil {
t.Fatalf("could not execute AttestingValidatorIndices: %v", err)
}
// Union(1,7,1,5,6) = 1,5,6,7
if !reflect.DeepEqual(indices, []uint32{1, 5, 6, 7}) {
t.Errorf("could not get incorrect validator indices. Wanted: %v, got: %v",
[]uint32{1, 5, 6, 7}, indices)
}
}
func TestAttestingValidatorIndices_OutOfBound(t *testing.T) {
shardAndCommittees := []*pb.ShardAndCommitteeArray{
{ArrayShardAndCommittee: []*pb.ShardAndCommittee{
{Shard: 1},
}},
}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
}
attestation := &pb.PendingAttestationRecord{
Data: &pb.AttestationData{
Slot: 0,
Shard: 1,
ShardBlockRootHash32: []byte{'B'},
},
ParticipationBitfield: []byte{'A'}, // 01000001 = 1,7
}
_, err := AttestingValidatorIndices(
state,
shardAndCommittees[0].ArrayShardAndCommittee[0],
[]byte{'B'},
[]*pb.PendingAttestationRecord{attestation},
nil)
// This will fail because participation bitfield is length:1, committee bitfield is length 0.
if err == nil {
t.Fatal("AttestingValidatorIndices should have failed with incorrect bitfield")
}
}

View File

@ -45,3 +45,15 @@ func Bytes8(x uint64) []byte {
func FromBytes8(x []byte) uint64 {
return binary.BigEndian.Uint64(x)
}
// LowerThan returns true if byte slice x is lower than byte slice y. (big endian format)
// This is used in spec to compare winning block root hash.
// Mentioned in spec as "ties broken by favoring lower `shard_block_root` values".
func LowerThan(x []byte, y []byte) bool {
for i, b := range x {
if b > y[i] {
return false
}
}
return true
}