Boundary Attestation and Balances Helper functions (#1115)

This commit is contained in:
terence tsao
2018-12-18 20:55:36 -08:00
committed by GitHub
parent c513ccb4e2
commit 74532a80d1
7 changed files with 406 additions and 11 deletions

View File

@@ -13,8 +13,8 @@ import (
// 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]
// [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
@@ -29,7 +29,7 @@ func Attestations(state *pb.BeaconState) []*pb.PendingAttestationRecord {
earliestSlot = state.Slot - epochLength
}
if earliestSlot <= attestation.GetData().Slot && attestation.GetData().Slot < state.Slot {
if earliestSlot <= attestation.GetData().GetSlot() && attestation.GetData().GetSlot() < state.Slot {
thisEpochAttestations = append(thisEpochAttestations, attestation)
}
}
@@ -37,7 +37,7 @@ func Attestations(state *pb.BeaconState) []*pb.PendingAttestationRecord {
}
// BoundaryAttestations returns the pending attestations from
// the epoch boundary block.
// the epoch's boundary block.
//
// Spec pseudocode definition:
// [a for a in this_epoch_attestations if a.data.epoch_boundary_root ==
@@ -66,3 +66,79 @@ func BoundaryAttestations(
}
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 := types.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

@@ -8,7 +8,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/params"
)
func TestEpochAttestations_ok(t *testing.T) {
func TestEpochAttestations(t *testing.T) {
if params.BeaconConfig().EpochLength != 64 {
t.Errorf("EpochLength should be 64 for these tests to pass")
}
@@ -51,11 +51,11 @@ func TestEpochAttestations_ok(t *testing.T) {
for _, tt := range tests {
state.Slot = tt.stateSlot
if Attestations(state)[0].Data.Slot != tt.firstAttestationSlot {
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].Data.Slot,
Attestations(state)[0].GetData().GetSlot(),
)
}
}
@@ -95,12 +95,149 @@ func TestEpochBoundaryAttestations(t *testing.T) {
t.Fatalf("EpochBoundaryAttestations failed: %v", err)
}
if epochBoundaryAttestation[0].GetData().JustifiedSlot != 0 {
t.Errorf("Wanted justified slot 0 for epoch boundary attestation, got: %d", epochBoundaryAttestation[0].Data.JustifiedSlot)
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().JustifiedBlockRootHash32, []byte{0}) {
if !bytes.Equal(epochBoundaryAttestation[0].GetData().GetJustifiedBlockRootHash32(), []byte{0}) {
t.Errorf("Wanted justified block hash [0] for epoch boundary attestation, got: %v",
epochBoundaryAttestation[0].Data.JustifiedBlockRootHash32)
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

@@ -16,6 +16,7 @@ go_library(
"//shared/hashutil:go_default_library",
"//shared/mathutil:go_default_library",
"//shared/params:go_default_library",
"//shared/slices:go_default_library",
],
)

View File

@@ -14,6 +14,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/bitutil"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/slices"
)
const bitsInByte = 8
@@ -421,6 +422,65 @@ func EffectiveBalance(validator *pb.ValidatorRecord) uint64 {
return validator.Balance
}
// Attesters returns the validator records using validator indices.
//
// Spec pseudocode definition:
// Let this_epoch_boundary_attesters = [state.validator_registry[i]
// for indices in this_epoch_boundary_attester_indices for i in indices].
func Attesters(state *pb.BeaconState, attesterIndices []uint32) []*pb.ValidatorRecord {
var boundaryAttesters []*pb.ValidatorRecord
for _, attesterIndex := range attesterIndices {
boundaryAttesters = append(boundaryAttesters, state.ValidatorRegistry[attesterIndex])
}
return boundaryAttesters
}
// ValidatorIndices returns all the validator indices from the input attestations
// and state.
//
// Spec pseudocode definition:
// Let this_epoch_boundary_attester_indices be the union of the validator
// index sets given by [get_attestation_participants(state, a.data, a.participation_bitfield)
// for a in this_epoch_boundary_attestations]
func ValidatorIndices(
state *pb.BeaconState,
boundaryAttestations []*pb.PendingAttestationRecord,
) ([]uint32, error) {
var attesterIndicesIntersection []uint32
for _, boundaryAttestation := range boundaryAttestations {
attesterIndices, err := AttestationParticipants(
state,
boundaryAttestation.Data,
boundaryAttestation.ParticipationBitfield)
if err != nil {
return nil, err
}
attesterIndicesIntersection = slices.Union(attesterIndicesIntersection, attesterIndices)
}
return attesterIndicesIntersection, nil
}
// AttestingBalance returns the combined balances from the input validator
// records.
//
// Spec pseudocode definition:
// Let this_epoch_boundary_attesting_balance =
// sum([get_effective_balance(v) for v in this_epoch_boundary_attesters])
func AttestingBalance(boundaryAttesters []*pb.ValidatorRecord) uint64 {
var boundaryAttestingBalance uint64
for _, boundaryAttester := range boundaryAttesters {
boundaryAttestingBalance += EffectiveBalance(boundaryAttester)
}
return boundaryAttestingBalance
}
// minEmptyValidator returns the lowest validator index which the status is withdrawn.
func minEmptyValidator(validators []*pb.ValidatorRecord) int {
for i := 0; i < len(validators); i++ {

View File

@@ -646,3 +646,82 @@ func TestGetActiveValidatorRecord(t *testing.T) {
t.Errorf("Active validators don't match. Wanted: %v, Got: %v", outputValidators, validators)
}
}
func TestBoundaryAttestingBalance(t *testing.T) {
attesters := []*pb.ValidatorRecord{
{Balance: 25 * 1e9},
{Balance: 26 * 1e9},
{Balance: 32 * 1e9},
{Balance: 33 * 1e9},
{Balance: 100 * 1e9},
}
attestedBalances := AttestingBalance(attesters)
// 25 + 26 + 32 + 32 + 32 = 147
if attestedBalances != 147*1e9 {
t.Errorf("Incorrect attested balances. Wanted: %f, got: %d", 147*1e9, attestedBalances)
}
}
func TestBoundaryAttesters(t *testing.T) {
var validators []*pb.ValidatorRecord
for i := 0; i < 100; i++ {
validators = append(validators, &pb.ValidatorRecord{Pubkey: []byte{byte(i)}})
}
state := &pb.BeaconState{ValidatorRegistry: validators}
boundaryAttesters := Attesters(state, []uint32{5, 2, 87, 42, 99, 0})
expectedBoundaryAttesters := []*pb.ValidatorRecord{
{Pubkey: []byte{byte(5)}},
{Pubkey: []byte{byte(2)}},
{Pubkey: []byte{byte(87)}},
{Pubkey: []byte{byte(42)}},
{Pubkey: []byte{byte(99)}},
{Pubkey: []byte{byte(0)}},
}
if !reflect.DeepEqual(expectedBoundaryAttesters, boundaryAttesters) {
t.Errorf("Incorrect boundary attesters. Wanted: %v, got: %v", expectedBoundaryAttesters, boundaryAttesters)
}
}
func TestBoundaryAttesterIndices(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: 100, Committee: committeeIndices},
},
})
}
state := &pb.BeaconState{
ShardAndCommitteesAtSlots: shardAndCommittees,
Slot: 5,
}
boundaryAttestations := []*pb.PendingAttestationRecord{
{Data: &pb.AttestationData{Slot: 2, Shard: 100}, ParticipationBitfield: []byte{'F'}}, // returns indices 1,5,6
{Data: &pb.AttestationData{Slot: 2, Shard: 100}, ParticipationBitfield: []byte{3}}, // returns indices 6,7
{Data: &pb.AttestationData{Slot: 2, Shard: 100}, ParticipationBitfield: []byte{'A'}}, // returns indices 1,7
}
attesterIndices, err := ValidatorIndices(state, boundaryAttestations)
if err != nil {
t.Fatalf("Failed to run BoundaryAttesterIndices: %v", err)
}
if !reflect.DeepEqual(attesterIndices, []uint32{1, 5, 6, 7}) {
t.Errorf("Incorrect boundary attester indices. Wanted: %v, got: %v", []uint32{1, 5, 6, 7}, attesterIndices)
}
}

View File

@@ -18,3 +18,23 @@ func Intersection(a []uint32, b []uint32) []uint32 {
}
return set
}
// Union of two uint32 slices with time
// complexity of approximately O(n) leveraging a map to
// check for element existence off by a constant factor
// of underlying map efficiency.
func Union(a []uint32, b []uint32) []uint32 {
set := make([]uint32, 0)
m := make(map[uint32]bool)
for i := 0; i < len(a); i++ {
m[a[i]] = true
set = append(set, a[i])
}
for i := 0; i < len(b); i++ {
if _, found := m[b[i]]; !found {
set = append(set, b[i])
}
}
return set
}

View File

@@ -27,3 +27,25 @@ func TestIntersection(t *testing.T) {
}
}
}
func TestUnion(t *testing.T) {
testCases := []struct {
setA []uint32
setB []uint32
out []uint32
}{
{[]uint32{2, 3, 5}, []uint32{4, 6}, []uint32{2, 3, 5, 4, 6}},
{[]uint32{2, 3, 5}, []uint32{3, 5}, []uint32{2, 3, 5}},
{[]uint32{2, 3, 5}, []uint32{2, 3, 5}, []uint32{2, 3, 5}},
{[]uint32{2, 3, 5}, []uint32{}, []uint32{2, 3, 5}},
{[]uint32{}, []uint32{2, 3, 5}, []uint32{2, 3, 5}},
{[]uint32{}, []uint32{}, []uint32{}},
{[]uint32{1}, []uint32{1}, []uint32{1}},
}
for _, tt := range testCases {
result := Union(tt.setA, tt.setB)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
}
}