Methods to precompute process_epoch records (#3788)

This commit is contained in:
terence tsao
2019-10-16 17:48:26 -07:00
committed by GitHub
parent a62ac97a35
commit 2d863a1e63
6 changed files with 540 additions and 2 deletions

View File

@@ -1,8 +1,36 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["type.go"],
srcs = [
"attestation.go",
"new.go",
"type.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/epoch/precompute",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//proto/beacon/p2p/v1:go_default_library",
"//shared/traceutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"attestation_test.go",
"new_test.go",
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/state:go_default_library",
"//proto/beacon/p2p/v1:go_default_library",
"//proto/eth/v1alpha1:go_default_library",
"//shared/params:go_default_library",
"//shared/testutil:go_default_library",
],
)

View File

@@ -0,0 +1,169 @@
package precompute
import (
"bytes"
"context"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"github.com/prysmaticlabs/prysm/shared/traceutil"
"go.opencensus.io/trace"
)
// ProcessAttestations process the attestations in state and update individual validator's pre computes,
// it also tracks and updates epoch attesting balances.
func ProcessAttestations(
ctx context.Context,
state *pb.BeaconState,
vp []*Validator,
bp *Balance) ([]*Validator, *Balance, error) {
ctx, span := trace.StartSpan(ctx, "precomputeEpoch.ProcessAttestations")
defer span.End()
v := &Validator{}
var err error
for _, a := range append(state.PreviousEpochAttestations, state.CurrentEpochAttestations...) {
v.IsCurrentEpochAttester, v.IsCurrentEpochTargetAttester, err = attestedCurrentEpoch(state, a)
if err != nil {
traceutil.AnnotateError(span, err)
return nil, nil, errors.Wrap(err, "could not check validator attested current epoch")
}
v.IsPrevEpochAttester, v.IsPrevEpochTargetAttester, v.IsPrevEpochHeadAttester, err = attestedPrevEpoch(state, a)
if err != nil {
traceutil.AnnotateError(span, err)
return nil, nil, errors.Wrap(err, "could not check validator attested previous epoch")
}
// Get attested indices and update the pre computed fields for each attested validators.
indices, err := helpers.AttestingIndices(state, a.Data, a.AggregationBits)
if err != nil {
return nil, nil, err
}
vp = updateValidator(vp, v, indices, a)
}
bp = updateBalance(vp, bp)
return vp, bp, nil
}
// Has attestation `a` attested once in current epoch and epoch boundary block.
func attestedCurrentEpoch(s *pb.BeaconState, a *pb.PendingAttestation) (bool, bool, error) {
currentEpoch := helpers.CurrentEpoch(s)
var votedCurrentEpoch, votedTarget bool
// Did validator vote current epoch.
if a.Data.Target.Epoch == currentEpoch {
votedCurrentEpoch = true
same, err := sameTarget(s, a, currentEpoch)
if err != nil {
return false, false, err
}
if same {
votedTarget = true
}
}
return votedCurrentEpoch, votedTarget, nil
}
// Has attestation `a` attested once in previous epoch and epoch boundary block and the same head.
func attestedPrevEpoch(s *pb.BeaconState, a *pb.PendingAttestation) (bool, bool, bool, error) {
prevEpoch := helpers.PrevEpoch(s)
var votedPrevEpoch, votedTarget, votedHead bool
// Did validator vote previous epoch.
if a.Data.Target.Epoch == prevEpoch {
votedPrevEpoch = true
same, err := sameTarget(s, a, prevEpoch)
if err != nil {
return false, false, false, errors.Wrap(err, "could not check same target")
}
if same {
votedTarget = true
}
same, err = sameHead(s, a)
if err != nil {
return false, false, false, errors.Wrap(err, "could not check same head")
}
if same {
votedHead = true
}
}
return votedPrevEpoch, votedTarget, votedHead, nil
}
// Has attestation `a` attested to the same target block in state.
func sameTarget(state *pb.BeaconState, a *pb.PendingAttestation, e uint64) (bool, error) {
r, err := helpers.BlockRoot(state, e)
if err != nil {
return false, err
}
if bytes.Equal(a.Data.Target.Root, r) {
return true, nil
}
return false, nil
}
// Has attestation `a` attested to the same block by attestation slot in state.
func sameHead(state *pb.BeaconState, a *pb.PendingAttestation) (bool, error) {
aSlot, err := helpers.AttestationDataSlot(state, a.Data)
if err != nil {
return false, err
}
r, err := helpers.BlockRootAtSlot(state, aSlot)
if err != nil {
return false, err
}
if bytes.Equal(a.Data.BeaconBlockRoot, r) {
return true, nil
}
return false, nil
}
// This updates pre computed validator store.
func updateValidator(vp []*Validator, record *Validator, indices []uint64, a *pb.PendingAttestation) []*Validator {
for _, i := range indices {
if record.IsCurrentEpochAttester {
vp[i].IsCurrentEpochAttester = true
}
if record.IsCurrentEpochTargetAttester {
vp[i].IsCurrentEpochTargetAttester = true
}
if record.IsPrevEpochAttester {
vp[i].IsPrevEpochAttester = true
}
if record.IsPrevEpochTargetAttester {
vp[i].IsPrevEpochTargetAttester = true
}
if record.IsPrevEpochHeadAttester {
vp[i].IsPrevEpochHeadAttester = true
}
vp[i].InclusionDistance = a.InclusionDelay
vp[i].ProposerIndex = a.ProposerIndex
}
return vp
}
// This updates pre computed balance store.
func updateBalance(vp []*Validator, bp *Balance) *Balance {
for _, v := range vp {
if !v.IsSlashed {
if v.IsCurrentEpochAttester {
bp.CurrentEpochAttesters += v.CurrentEpochEffectiveBalance
}
if v.IsCurrentEpochTargetAttester {
bp.CurrentEpochTargetAttesters += v.CurrentEpochEffectiveBalance
}
if v.IsPrevEpochAttester {
bp.PrevEpochAttesters += v.CurrentEpochEffectiveBalance
}
if v.IsPrevEpochTargetAttester {
bp.PrevEpochTargetAttesters += v.CurrentEpochEffectiveBalance
}
if v.IsPrevEpochHeadAttester {
bp.PrevEpochHeadAttesters += v.CurrentEpochEffectiveBalance
}
}
}
return bp
}

View File

@@ -0,0 +1,242 @@
package precompute
import (
"context"
"reflect"
"testing"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/shared/testutil"
)
func TestUpdateValidator(t *testing.T) {
vp := []*Validator{{}, {}, {}, {}, {}, {}}
record := &Validator{IsCurrentEpochAttester: true, IsCurrentEpochTargetAttester: true,
IsPrevEpochAttester: true, IsPrevEpochTargetAttester: true, IsPrevEpochHeadAttester: true}
a := &pb.PendingAttestation{InclusionDelay: 1, ProposerIndex: 2}
// Indices 1 3 and 5 attested
vp = updateValidator(vp, record, []uint64{1, 3, 5}, a)
wanted := &Validator{IsCurrentEpochAttester: true, IsCurrentEpochTargetAttester: true,
IsPrevEpochAttester: true, IsPrevEpochTargetAttester: true, IsPrevEpochHeadAttester: true, ProposerIndex: 2, InclusionDistance: 1}
wantedVp := []*Validator{{}, wanted, {}, wanted, {}, wanted}
if !reflect.DeepEqual(vp, wantedVp) {
t.Error("Incorrect attesting validator calculations")
}
}
func TestUpdateBalance(t *testing.T) {
vp := []*Validator{
{IsCurrentEpochAttester: true, CurrentEpochEffectiveBalance: 100},
{IsCurrentEpochTargetAttester: true, IsCurrentEpochAttester: true, CurrentEpochEffectiveBalance: 100},
{IsCurrentEpochTargetAttester: true, CurrentEpochEffectiveBalance: 100},
{IsPrevEpochAttester: true, CurrentEpochEffectiveBalance: 100},
{IsPrevEpochAttester: true, IsPrevEpochTargetAttester: true, CurrentEpochEffectiveBalance: 100},
{IsPrevEpochHeadAttester: true, CurrentEpochEffectiveBalance: 100},
{IsPrevEpochAttester: true, IsPrevEpochHeadAttester: true, CurrentEpochEffectiveBalance: 100},
{IsSlashed: true, IsCurrentEpochAttester: true, CurrentEpochEffectiveBalance: 100},
}
wantedBp := &Balance{
CurrentEpochAttesters: 200,
CurrentEpochTargetAttesters: 200,
PrevEpochAttesters: 300,
PrevEpochTargetAttesters: 100,
PrevEpochHeadAttesters: 200,
}
bp := updateBalance(vp, &Balance{})
if !reflect.DeepEqual(bp, wantedBp) {
t.Error("Incorrect balance calculations")
}
}
func TestSameHead(t *testing.T) {
deposits, _, _ := testutil.SetupInitialDeposits(t, 100)
beaconState, err := state.GenesisBeaconState(deposits, uint64(0), &ethpb.Eth1Data{})
if err != nil {
t.Fatal(err)
}
beaconState.Slot = 1
att := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0},
Crosslink: &ethpb.Crosslink{Shard: 0}}}
attSlot, err := helpers.AttestationDataSlot(beaconState, att.Data)
if err != nil {
t.Fatal(err)
}
r := []byte{'A'}
beaconState.BlockRoots[attSlot] = r
att.Data.BeaconBlockRoot = r
same, err := sameHead(beaconState, &pb.PendingAttestation{Data: att.Data})
if err != nil {
t.Fatal(err)
}
if !same {
t.Error("head in state does not match head in attestation")
}
att.Data.BeaconBlockRoot = []byte{'B'}
same, err = sameHead(beaconState, &pb.PendingAttestation{Data: att.Data})
if err != nil {
t.Fatal(err)
}
if same {
t.Error("head in state matches head in attestation")
}
}
func TestSameTarget(t *testing.T) {
deposits, _, _ := testutil.SetupInitialDeposits(t, 100)
beaconState, err := state.GenesisBeaconState(deposits, uint64(0), &ethpb.Eth1Data{})
if err != nil {
t.Fatal(err)
}
beaconState.Slot = 1
att := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0},
Crosslink: &ethpb.Crosslink{Shard: 0}}}
attSlot, err := helpers.AttestationDataSlot(beaconState, att.Data)
if err != nil {
t.Fatal(err)
}
r := []byte{'A'}
beaconState.BlockRoots[attSlot] = r
att.Data.Target.Root = r
same, err := sameTarget(beaconState, &pb.PendingAttestation{Data: att.Data}, 0)
if err != nil {
t.Fatal(err)
}
if !same {
t.Error("head in state does not match head in attestation")
}
att.Data.Target.Root = []byte{'B'}
same, err = sameTarget(beaconState, &pb.PendingAttestation{Data: att.Data}, 0)
if err != nil {
t.Fatal(err)
}
if same {
t.Error("head in state matches head in attestation")
}
}
func TestAttestedPrevEpoch(t *testing.T) {
deposits, _, _ := testutil.SetupInitialDeposits(t, 100)
beaconState, err := state.GenesisBeaconState(deposits, uint64(0), &ethpb.Eth1Data{})
if err != nil {
t.Fatal(err)
}
beaconState.Slot = params.BeaconConfig().SlotsPerEpoch
att := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0},
Crosslink: &ethpb.Crosslink{Shard: 960}}}
attSlot, err := helpers.AttestationDataSlot(beaconState, att.Data)
if err != nil {
t.Fatal(err)
}
r := []byte{'A'}
beaconState.BlockRoots[attSlot] = r
att.Data.Target.Root = r
att.Data.BeaconBlockRoot = r
votedEpoch, votedTarget, votedHead, err := attestedPrevEpoch(beaconState, &pb.PendingAttestation{Data: att.Data})
if err != nil {
t.Fatal(err)
}
if !votedEpoch {
t.Error("did not vote epoch")
}
if !votedTarget {
t.Error("did not vote target")
}
if !votedHead {
t.Error("did not vote head")
}
}
func TestAttestedCurrentEpoch(t *testing.T) {
deposits, _, _ := testutil.SetupInitialDeposits(t, 100)
beaconState, err := state.GenesisBeaconState(deposits, uint64(0), &ethpb.Eth1Data{})
if err != nil {
t.Fatal(err)
}
beaconState.Slot = params.BeaconConfig().SlotsPerEpoch + 1
att := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 1},
Crosslink: &ethpb.Crosslink{}}}
attSlot, err := helpers.AttestationDataSlot(beaconState, att.Data)
if err != nil {
t.Fatal(err)
}
r := []byte{'A'}
beaconState.BlockRoots[attSlot] = r
att.Data.Target.Root = r
att.Data.BeaconBlockRoot = r
votedEpoch, votedTarget, err := attestedCurrentEpoch(beaconState, &pb.PendingAttestation{Data: att.Data})
if err != nil {
t.Fatal(err)
}
if !votedEpoch {
t.Error("did not vote epoch")
}
if !votedTarget {
t.Error("did not vote target")
}
}
func TestProcessAttestations(t *testing.T) {
helpers.ClearAllCaches()
params.UseMinimalConfig()
defer params.UseMainnetConfig()
validators := uint64(64)
deposits, _, _ := testutil.SetupInitialDeposits(t, validators)
beaconState, err := state.GenesisBeaconState(deposits, uint64(0), &ethpb.Eth1Data{})
if err != nil {
t.Fatal(err)
}
beaconState.Slot = params.BeaconConfig().SlotsPerEpoch
bf := []byte{0xff}
att1 := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0},
Crosslink: &ethpb.Crosslink{Shard: 960}}, AggregationBits: bf}
att2 := &ethpb.Attestation{Data: &ethpb.AttestationData{
Target: &ethpb.Checkpoint{Epoch: 0},
Crosslink: &ethpb.Crosslink{Shard: 961}}, AggregationBits: bf}
beaconState.BlockRoots[0] = []byte{'A'}
att1.Data.Target.Root = []byte{'A'}
att1.Data.BeaconBlockRoot = []byte{'A'}
beaconState.BlockRoots[0] = []byte{'B'}
att2.Data.Target.Root = []byte{'A'}
att2.Data.BeaconBlockRoot = []byte{'B'}
beaconState.PreviousEpochAttestations = []*pb.PendingAttestation{{Data: att1.Data, AggregationBits: bf}}
beaconState.CurrentEpochAttestations = []*pb.PendingAttestation{{Data: att2.Data, AggregationBits: bf}}
vp := make([]*Validator, validators)
for i := 0; i < len(vp); i++ {
vp[i] = &Validator{CurrentEpochEffectiveBalance: 100}
}
bp := &Balance{}
vp, bp, err = ProcessAttestations(context.Background(), beaconState, vp, bp)
if err != nil {
t.Fatal(err)
}
indices, _ := helpers.AttestingIndices(beaconState, att1.Data, att1.AggregationBits)
for _, i := range indices {
if !vp[i].IsPrevEpochAttester {
t.Error("Not a prev epoch attester")
}
}
indices, _ = helpers.AttestingIndices(beaconState, att2.Data, att2.AggregationBits)
for _, i := range indices {
if !vp[i].IsPrevEpochAttester {
t.Error("Not a prev epoch attester")
}
if !vp[i].IsPrevEpochHeadAttester {
t.Error("Not a prev epoch head attester")
}
}
}

View File

@@ -0,0 +1,45 @@
package precompute
import (
"context"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
"go.opencensus.io/trace"
)
// New gets called at the beginning of process epoch cycle to return
// pre computed instances of validators attesting records and total
// balances attested in an epoch.
func New(ctx context.Context, state *pb.BeaconState) ([]*Validator, *Balance) {
ctx, span := trace.StartSpan(ctx, "precomputeEpoch.New")
defer span.End()
vp := make([]*Validator, len(state.Validators))
bp := &Balance{}
currentEpoch := helpers.CurrentEpoch(state)
prevEpoch := helpers.PrevEpoch(state)
for i, v := range state.Validators {
// Was validator withdrawable or slashed
withdrawable := currentEpoch >= v.WithdrawableEpoch
p := &Validator{
IsSlashed: v.Slashed,
IsWithdrawableCurrentEpoch: withdrawable,
CurrentEpochEffectiveBalance: v.EffectiveBalance,
}
// Was validator active current epoch
if helpers.IsActiveValidator(v, currentEpoch) {
p.IsActiveCurrentEpoch = true
bp.CurrentEpoch += v.EffectiveBalance
}
// Was validator active previous epoch
if helpers.IsActiveValidator(v, prevEpoch) {
p.IsActivePrevEpoch = true
bp.PrevEpoch += v.EffectiveBalance
}
vp[i] = p
}
return vp, bp
}

View File

@@ -0,0 +1,49 @@
package precompute
import (
"context"
"reflect"
"testing"
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/params"
)
func TestNew(t *testing.T) {
ffe := params.BeaconConfig().FarFutureEpoch
s := &pb.BeaconState{
Slot: params.BeaconConfig().SlotsPerEpoch,
// Validator 0 is slashed
// Validator 1 is withdrawable
// Validator 2 is active prev epoch and current epoch
// Validator 3 is active prev epoch
Validators: []*ethpb.Validator{
{Slashed: true, WithdrawableEpoch: ffe, EffectiveBalance: 100},
{EffectiveBalance: 100},
{WithdrawableEpoch: ffe, ExitEpoch: ffe, EffectiveBalance: 100},
{WithdrawableEpoch: ffe, ExitEpoch: 1, EffectiveBalance: 100},
},
}
v, b := New(context.Background(), s)
if !reflect.DeepEqual(v[0], &Validator{IsSlashed: true, CurrentEpochEffectiveBalance: 100}) {
t.Error("Incorrect validator 0 status")
}
if !reflect.DeepEqual(v[1], &Validator{IsWithdrawableCurrentEpoch: true, CurrentEpochEffectiveBalance: 100}) {
t.Error("Incorrect validator 1 status")
}
if !reflect.DeepEqual(v[2], &Validator{IsActiveCurrentEpoch: true, IsActivePrevEpoch: true, CurrentEpochEffectiveBalance: 100}) {
t.Error("Incorrect validator 2 status")
}
if !reflect.DeepEqual(v[3], &Validator{IsActivePrevEpoch: true, CurrentEpochEffectiveBalance: 100}) {
t.Error("Incorrect validator 3 status")
}
wantedBalances := &Balance{
CurrentEpoch: 100,
PrevEpoch: 200,
}
if !reflect.DeepEqual(b, wantedBalances) {
t.Error("Incorrect wanted balance")
}
}

View File

@@ -323,6 +323,11 @@ func UseMinimalConfig() {
beaconConfig = MinimalSpecConfig()
}
// UseMainnetConfig for beacon chain services.
func UseMainnetConfig() {
beaconConfig = defaultBeaconConfig
}
// OverrideBeaconConfig by replacing the config. The preferred pattern is to
// call BeaconConfig(), change the specific parameters, and then call
// OverrideBeaconConfig(c). Any subsequent calls to params.BeaconConfig() will