diff --git a/beacon-chain/core/epoch/precompute/BUILD.bazel b/beacon-chain/core/epoch/precompute/BUILD.bazel index 01b50f9e92..e38faff534 100644 --- a/beacon-chain/core/epoch/precompute/BUILD.bazel +++ b/beacon-chain/core/epoch/precompute/BUILD.bazel @@ -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", + ], ) diff --git a/beacon-chain/core/epoch/precompute/attestation.go b/beacon-chain/core/epoch/precompute/attestation.go new file mode 100644 index 0000000000..016b721764 --- /dev/null +++ b/beacon-chain/core/epoch/precompute/attestation.go @@ -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 +} diff --git a/beacon-chain/core/epoch/precompute/attestation_test.go b/beacon-chain/core/epoch/precompute/attestation_test.go new file mode 100644 index 0000000000..8d4f03119d --- /dev/null +++ b/beacon-chain/core/epoch/precompute/attestation_test.go @@ -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), ðpb.Eth1Data{}) + if err != nil { + t.Fatal(err) + } + beaconState.Slot = 1 + att := ðpb.Attestation{Data: ðpb.AttestationData{ + Target: ðpb.Checkpoint{Epoch: 0}, + Crosslink: ðpb.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), ðpb.Eth1Data{}) + if err != nil { + t.Fatal(err) + } + beaconState.Slot = 1 + att := ðpb.Attestation{Data: ðpb.AttestationData{ + Target: ðpb.Checkpoint{Epoch: 0}, + Crosslink: ðpb.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), ðpb.Eth1Data{}) + if err != nil { + t.Fatal(err) + } + beaconState.Slot = params.BeaconConfig().SlotsPerEpoch + att := ðpb.Attestation{Data: ðpb.AttestationData{ + Target: ðpb.Checkpoint{Epoch: 0}, + Crosslink: ðpb.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), ðpb.Eth1Data{}) + if err != nil { + t.Fatal(err) + } + beaconState.Slot = params.BeaconConfig().SlotsPerEpoch + 1 + att := ðpb.Attestation{Data: ðpb.AttestationData{ + Target: ðpb.Checkpoint{Epoch: 1}, + Crosslink: ðpb.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), ðpb.Eth1Data{}) + if err != nil { + t.Fatal(err) + } + beaconState.Slot = params.BeaconConfig().SlotsPerEpoch + + bf := []byte{0xff} + att1 := ðpb.Attestation{Data: ðpb.AttestationData{ + Target: ðpb.Checkpoint{Epoch: 0}, + Crosslink: ðpb.Crosslink{Shard: 960}}, AggregationBits: bf} + att2 := ðpb.Attestation{Data: ðpb.AttestationData{ + Target: ðpb.Checkpoint{Epoch: 0}, + Crosslink: ðpb.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") + } + } +} diff --git a/beacon-chain/core/epoch/precompute/new.go b/beacon-chain/core/epoch/precompute/new.go new file mode 100644 index 0000000000..74e036c850 --- /dev/null +++ b/beacon-chain/core/epoch/precompute/new.go @@ -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 +} diff --git a/beacon-chain/core/epoch/precompute/new_test.go b/beacon-chain/core/epoch/precompute/new_test.go new file mode 100644 index 0000000000..bc77445fb7 --- /dev/null +++ b/beacon-chain/core/epoch/precompute/new_test.go @@ -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") + } +} diff --git a/shared/params/config.go b/shared/params/config.go index 0c3a1c4008..58b0df7cec 100644 --- a/shared/params/config.go +++ b/shared/params/config.go @@ -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