Implement fork choice spec tests (#10159)

* starting

* Reimplement store

* begin on proposer boost

* implement fork choice proposer boost algorithm

* boosting

* gaz

* add mutexes and previous root

* comment on compute proposer boost

* safe

* rem todo

* reset and add tests

* unit test for proposer boost score

* boost works

* Can process block

* Basic test case passing

* ex ante

* test

* propoer test

* More progresses

* More fixes

* rm unused pieces

* Refactor, add phase 0

* locks

* vanilla ex-ante attack

* test similar to spec test

* works works works

* boost test working for num votes > proposer boost weight

* commentary fixes

* rem unused

* comments

* Proposer boost use store time

* Reset proposer root

* debugging

* passing

* Rm unused visibility imports

* Move update head to better place

* Fix deepsrc complains

* Fix more complains

* Raul's feedback

* Use correct byte lengths

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
This commit is contained in:
terence tsao
2022-01-31 11:03:48 -08:00
committed by GitHub
parent aed6e13498
commit 8a01d412f5
30 changed files with 534 additions and 169 deletions

View File

@@ -28,6 +28,7 @@ go_library(
"//beacon-chain:__subpackages__",
"//cmd/beacon-chain:__subpackages__",
"//testing/slasher/simulator:__pkg__",
"//testing/spectest:__subpackages__",
],
deps = [
"//async:go_default_library",

View File

@@ -107,6 +107,17 @@ func (s *Service) PreviousJustifiedCheckpt() *ethpb.Checkpoint {
return ethpb.CopyCheckpoint(cp)
}
// BestJustifiedCheckpt returns the best justified checkpoint from store.
func (s *Service) BestJustifiedCheckpt() *ethpb.Checkpoint {
cp := s.store.BestJustifiedCheckpt()
// If there is no best justified checkpoint, return the checkpoint with root as zeros to be used for genesis cases.
if cp == nil {
return &ethpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
}
return ethpb.CopyCheckpoint(cp)
}
// HeadSlot returns the slot of the head of the chain.
func (s *Service) HeadSlot() types.Slot {
s.headLock.RLock()
@@ -316,3 +327,8 @@ func (s *Service) HeadValidatorIndexToPublicKey(_ context.Context, index types.V
}
return v.PublicKey(), nil
}
// SetGenesisTime sets the genesis time of beacon chain.
func (s *Service) SetGenesisTime(t time.Time) {
s.genesisTime = t
}

View File

@@ -23,6 +23,21 @@ import (
"go.opencensus.io/trace"
)
// UpdateHeadWithBalances updates the beacon state head after getting justified balanced from cache.
func (s *Service) UpdateHeadWithBalances(ctx context.Context) error {
cp := s.store.JustifiedCheckpt()
if cp == nil {
return errors.New("no justified checkpoint")
}
balances, err := s.justifiedBalances.get(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
msg := fmt.Sprintf("could not read balances for state w/ justified checkpoint %#x", cp.Root)
return errors.Wrap(err, msg)
}
return s.updateHead(ctx, balances)
}
// This defines the current chain service's view of head.
type head struct {
slot types.Slot // current head slot.
@@ -37,21 +52,6 @@ func (s *Service) updateHead(ctx context.Context, balances []uint64) error {
ctx, span := trace.StartSpan(ctx, "blockChain.updateHead")
defer span.End()
// To get the proper head update, a node first checks its best justified
// can become justified. This is designed to prevent bounce attack and
// ensure head gets its best justified info.
bestJustified := s.store.BestJustifiedCheckpt()
if bestJustified == nil {
return errNilBestJustifiedInStore
}
justified := s.store.JustifiedCheckpt()
if justified == nil {
return errNilJustifiedInStore
}
if bestJustified.Epoch > justified.Epoch {
s.store.SetJustifiedCheckpt(bestJustified)
}
// Get head from the fork choice service.
f := s.store.FinalizedCheckpt()
if f == nil {

View File

@@ -29,7 +29,7 @@ import (
// ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot)
// if ancestor_at_finalized_slot == store.finalized_checkpoint.root:
// store.justified_checkpoint = store.best_justified_checkpoint
func (s *Service) newSlot(ctx context.Context, slot types.Slot) error {
func (s *Service) NewSlot(ctx context.Context, slot types.Slot) error {
// Reset proposer boost root in fork choice.
if err := s.cfg.ForkChoiceStore.ResetBoostedProposerRoot(ctx); err != nil {

View File

@@ -90,7 +90,7 @@ func TestService_newSlot(t *testing.T) {
store.SetBestJustifiedCheckpt(test.args.bestJustified)
service.store = store
require.NoError(t, service.newSlot(ctx, test.args.slot))
require.NoError(t, service.NewSlot(ctx, test.args.slot))
if test.args.shouldEqual {
require.DeepSSZEqual(t, service.store.BestJustifiedCheckpt(), service.store.JustifiedCheckpt())
} else {

View File

@@ -2,6 +2,7 @@ package blockchain
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
@@ -9,12 +10,11 @@ import (
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/time"
"github.com/prysmaticlabs/prysm/time/slots"
"go.opencensus.io/trace"
)
// onAttestation is called whenever an attestation is received, verifies the attestation is valid and saves
// OnAttestation is called whenever an attestation is received, verifies the attestation is valid and saves
// it to the DB. As a stateless function, this does not hold nor delay attestation based on the spec descriptions.
// The delay is handled by the caller in `processAttestations`.
//
@@ -36,7 +36,7 @@ import (
//
// # Update latest messages for attesting indices
// update_latest_messages(store, indexed_attestation.attesting_indices, attestation)
func (s *Service) onAttestation(ctx context.Context, a *ethpb.Attestation) error {
func (s *Service) OnAttestation(ctx context.Context, a *ethpb.Attestation) error {
ctx, span := trace.StartSpan(ctx, "blockChain.onAttestation")
defer span.End()
@@ -59,7 +59,7 @@ func (s *Service) onAttestation(ctx context.Context, a *ethpb.Attestation) error
return err
}
genesisTime := baseState.GenesisTime()
genesisTime := uint64(s.genesisTime.Unix())
// Verify attestation target is from current epoch or previous epoch.
if err := verifyAttTargetEpoch(ctx, genesisTime, uint64(time.Now().Unix()), tgt); err != nil {

View File

@@ -3,6 +3,7 @@ package blockchain
import (
"context"
"testing"
"time"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
@@ -17,7 +18,6 @@ import (
"github.com/prysmaticlabs/prysm/testing/assert"
"github.com/prysmaticlabs/prysm/testing/require"
"github.com/prysmaticlabs/prysm/testing/util"
"github.com/prysmaticlabs/prysm/time"
"github.com/prysmaticlabs/prysm/time/slots"
)
@@ -117,7 +117,7 @@ func TestStore_OnAttestation_ErrorConditions(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := service.onAttestation(ctx, tt.a)
err := service.OnAttestation(ctx, tt.a)
if tt.wantedErr != "" {
assert.ErrorContains(t, tt.wantedErr, err)
} else {
@@ -140,7 +140,7 @@ func TestStore_OnAttestation_Ok(t *testing.T) {
service, err := NewService(ctx, opts...)
require.NoError(t, err)
genesisState, pks := util.DeterministicGenesisState(t, 64)
require.NoError(t, genesisState.SetGenesisTime(uint64(time.Now().Unix())-params.BeaconConfig().SecondsPerSlot))
service.SetGenesisTime(time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0))
require.NoError(t, service.saveGenesisData(ctx, genesisState))
att, err := util.GenerateAttestations(genesisState, pks, 1, 0, false)
require.NoError(t, err)
@@ -150,7 +150,7 @@ func TestStore_OnAttestation_Ok(t *testing.T) {
require.NoError(t, err)
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, copied, tRoot))
require.NoError(t, service.cfg.ForkChoiceStore.ProcessBlock(ctx, 0, tRoot, tRoot, tRoot, 1, 1))
require.NoError(t, service.onAttestation(ctx, att[0]))
require.NoError(t, service.OnAttestation(ctx, att[0]))
}
func TestStore_SaveCheckpointState(t *testing.T) {

View File

@@ -160,11 +160,10 @@ func (s *Service) onBlock(ctx context.Context, signed block.SignedBeaconBlock, b
}
newFinalized := postState.FinalizedCheckpointEpoch() > finalized.Epoch
if newFinalized {
if err := s.finalizedImpliesNewJustified(ctx, postState); err != nil {
return errors.Wrap(err, "could not save new justified")
}
s.store.SetPrevFinalizedCheckpt(finalized)
s.store.SetFinalizedCheckpt(postState.FinalizedCheckpoint())
s.store.SetPrevJustifiedCheckpt(justified)
s.store.SetJustifiedCheckpt(postState.CurrentJustifiedCheckpoint())
}
balances, err := s.justifiedBalances.get(ctx, bytesutil.ToBytes32(justified.Root))

View File

@@ -13,7 +13,6 @@ import (
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/monitoring/tracing"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
"github.com/prysmaticlabs/prysm/time/slots"
"go.opencensus.io/trace"
@@ -45,7 +44,7 @@ func (s *Service) getBlockPreState(ctx context.Context, b block.BeaconBlock) (st
}
// Verify block slot time is not from the future.
if err := slots.VerifyTime(preState.GenesisTime(), b.Slot(), params.BeaconNetworkConfig().MaximumGossipClockDisparity); err != nil {
if err := slots.VerifyTime(uint64(s.genesisTime.Unix()), b.Slot(), params.BeaconNetworkConfig().MaximumGossipClockDisparity); err != nil {
return nil, err
}
@@ -328,56 +327,6 @@ func (s *Service) ancestorByDB(ctx context.Context, r [32]byte, slot types.Slot)
return s.ancestorByDB(ctx, bytesutil.ToBytes32(b.ParentRoot()), slot)
}
// This updates justified check point in store, if the new justified is later than stored justified or
// the store's justified is not in chain with finalized check point.
//
// Spec definition:
// # Potentially update justified if different from store
// if store.justified_checkpoint != state.current_justified_checkpoint:
// # Update justified if new justified is later than store justified
// if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
// store.justified_checkpoint = state.current_justified_checkpoint
// return
// # Update justified if store justified is not in chain with finalized checkpoint
// finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
// ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot)
// if ancestor_at_finalized_slot != store.finalized_checkpoint.root:
// store.justified_checkpoint = state.current_justified_checkpoint
func (s *Service) finalizedImpliesNewJustified(ctx context.Context, state state.BeaconState) error {
// Update justified if it's different than the one cached in the store.
justified := s.store.JustifiedCheckpt()
if justified == nil {
return errNilJustifiedInStore
}
if !attestation.CheckPointIsEqual(justified, state.CurrentJustifiedCheckpoint()) {
if state.CurrentJustifiedCheckpoint().Epoch > justified.Epoch {
s.store.SetJustifiedCheckpt(state.CurrentJustifiedCheckpoint())
// we don't need to check if the previous justified checkpoint was an ancestor since the new
// finalized checkpoint is overriding it.
return nil
}
finalized := s.store.FinalizedCheckpt()
if finalized == nil {
return errNilFinalizedInStore
}
// Update justified if store justified is not in chain with finalized check point.
finalizedSlot, err := slots.EpochStart(finalized.Epoch)
if err != nil {
return err
}
justifiedRoot := s.ensureRootNotZeros(bytesutil.ToBytes32(justified.Root))
anc, err := s.ancestor(ctx, justifiedRoot[:], finalizedSlot)
if err != nil {
return err
}
if !bytes.Equal(anc, finalized.Root) {
s.store.SetJustifiedCheckpt(state.CurrentJustifiedCheckpoint())
}
}
return nil
}
// This retrieves missing blocks from DB (ie. the blocks that couldn't be received over sync) and inserts them to fork choice store.
// This is useful for block tree visualizer and additional vote accounting.
func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, blk block.BeaconBlock,

View File

@@ -25,7 +25,6 @@ import (
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
"github.com/prysmaticlabs/prysm/testing/assert"
@@ -737,89 +736,6 @@ func TestEnsureRootNotZeroHashes(t *testing.T) {
assert.Equal(t, root, r, "Did not get wanted justified root")
}
func TestFinalizedImpliesNewJustified(t *testing.T) {
beaconDB := testDB.SetupDB(t)
fcs := protoarray.New(0, 0, [32]byte{'a'})
opts := []Option{
WithDatabase(beaconDB),
WithStateGen(stategen.New(beaconDB)),
WithForkChoiceStore(fcs),
}
ctx := context.Background()
type args struct {
cachedCheckPoint *ethpb.Checkpoint
stateCheckPoint *ethpb.Checkpoint
diffFinalizedCheckPoint bool
}
tests := []struct {
name string
args args
want *ethpb.Checkpoint
}{
{
name: "Same justified, do nothing",
args: args{
cachedCheckPoint: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'a'}},
stateCheckPoint: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'a'}},
},
want: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'a'}},
},
{
name: "Different justified, higher epoch, cache new justified",
args: args{
cachedCheckPoint: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'a'}},
stateCheckPoint: &ethpb.Checkpoint{Epoch: 2, Root: []byte{'b'}},
},
want: &ethpb.Checkpoint{Epoch: 2, Root: []byte{'b'}},
},
{
name: "finalized has different justified, cache new justified",
args: args{
cachedCheckPoint: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'a'}},
stateCheckPoint: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'b'}},
diffFinalizedCheckPoint: true,
},
want: &ethpb.Checkpoint{Epoch: 1, Root: []byte{'b'}},
},
}
for _, test := range tests {
beaconState, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, beaconState.SetCurrentJustifiedCheckpoint(test.args.stateCheckPoint))
service, err := NewService(ctx, opts...)
require.NoError(t, err)
service.store.SetJustifiedCheckpt(test.args.cachedCheckPoint)
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Root: bytesutil.PadTo(test.want.Root, 32)}))
genesisState, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, genesisState, bytesutil.ToBytes32(test.want.Root)))
if test.args.diffFinalizedCheckPoint {
b1 := util.NewBeaconBlock()
b1.Block.Slot = 1
b1.Block.ParentRoot = bytesutil.PadTo([]byte{'a'}, 32)
r1, err := b1.Block.HashTreeRoot()
require.NoError(t, err)
b100 := util.NewBeaconBlock()
b100.Block.Slot = 100
b100.Block.ParentRoot = r1[:]
r100, err := b100.Block.HashTreeRoot()
require.NoError(t, err)
for _, b := range []*ethpb.SignedBeaconBlock{b1, b100} {
beaconBlock := util.NewBeaconBlock()
beaconBlock.Block.Slot = b.Block.Slot
beaconBlock.Block.ParentRoot = bytesutil.PadTo(b.Block.ParentRoot, 32)
require.NoError(t, service.cfg.BeaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(beaconBlock)))
}
service.store.SetFinalizedCheckpt(&ethpb.Checkpoint{Root: []byte{'c'}, Epoch: 1})
service.store.SetJustifiedCheckpt(&ethpb.Checkpoint{Root: r100[:], Epoch: service.store.JustifiedCheckpt().Epoch})
}
require.NoError(t, service.finalizedImpliesNewJustified(ctx, beaconState))
assert.Equal(t, true, attestation.CheckPointIsEqual(test.want, service.store.JustifiedCheckpt()), "Did not get wanted check point")
}
}
func TestVerifyBlkDescendant(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()

View File

@@ -42,7 +42,7 @@ func (s *Service) ReceiveAttestationNoPubsub(ctx context.Context, att *ethpb.Att
ctx, span := trace.StartSpan(ctx, "beacon-chain.blockchain.ReceiveAttestationNoPubsub")
defer span.End()
if err := s.onAttestation(ctx, att); err != nil {
if err := s.OnAttestation(ctx, att); err != nil {
return errors.Wrap(err, "could not process attestation")
}
@@ -139,7 +139,7 @@ func (s *Service) spawnProcessAttestationsRoutine(stateFeed *event.Feed) {
case <-s.ctx.Done():
return
case <-st.C():
if err := s.newSlot(s.ctx, s.CurrentSlot()); err != nil {
if err := s.NewSlot(s.ctx, s.CurrentSlot()); err != nil {
log.WithError(err).Error("Could not process new slot")
return
}

View File

@@ -8,7 +8,10 @@ go_library(
"pending_deposits.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache",
visibility = ["//beacon-chain:__subpackages__"],
visibility = [
"//beacon-chain:__subpackages__",
"//testing/spectest:__subpackages__",
],
deps = [
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",

View File

@@ -7,7 +7,10 @@ go_library(
"interfaces.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice",
visibility = ["//beacon-chain:__subpackages__"],
visibility = [
"//beacon-chain:__subpackages__",
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/forkchoice/protoarray:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",

View File

@@ -16,6 +16,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray",
visibility = [
"//beacon-chain:__subpackages__",
"//testing/spectest:__subpackages__",
],
deps = [
"//config/fieldparams:go_default_library",

View File

@@ -7,6 +7,7 @@ import (
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
"github.com/prysmaticlabs/prysm/config/params"
"go.opencensus.io/trace"
)
@@ -229,6 +230,13 @@ func (s *Store) FinalizedEpoch() types.Epoch {
return s.finalizedEpoch
}
// ProposerBoost of fork choice store.
func (s *Store) ProposerBoost() [fieldparams.RootLength]byte {
s.proposerBoostLock.RLock()
defer s.proposerBoostLock.RUnlock()
return s.proposerBoostRoot
}
// Nodes of fork choice store.
func (s *Store) Nodes() []*Node {
s.nodesLock.RLock()

View File

@@ -43,7 +43,7 @@ type Node struct {
weight uint64 // weight of this node.
bestChild uint64 // bestChild index of this node.
bestDescendant uint64 // bestDescendant of this node.
graffiti [32]byte // graffiti of the block node.
graffiti [fieldparams.RootLength]byte // graffiti of the block node.
}
// optimisticStore defines a structure that tracks the tips of the fully

View File

@@ -13,6 +13,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/operations/attestations",
visibility = [
"//beacon-chain:__subpackages__",
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/operations/attestations/kv:go_default_library",

View File

@@ -19,6 +19,7 @@ go_library(
"//beacon-chain:__subpackages__",
"//testing/endtoend:__subpackages__",
"//testing/slasher/simulator:__pkg__",
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/core/altair:go_default_library",

View File

@@ -0,0 +1,14 @@
load("@prysm//tools/go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = ["forkchoice_test.go"],
data = glob(["*.yaml"]) + [
"@consensus_spec_tests_mainnet//:test_data",
],
tags = ["spectest"],
deps = [
"//runtime/version:go_default_library",
"//testing/spectest/shared/common/forkchoice:go_default_library",
],
)

View File

@@ -0,0 +1,12 @@
package forkchoice
import (
"testing"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/prysmaticlabs/prysm/testing/spectest/shared/common/forkchoice"
)
func TestMainnet_Altair_Forkchoice(t *testing.T) {
forkchoice.Run(t, "mainnet", version.Altair)
}

View File

@@ -0,0 +1,14 @@
load("@prysm//tools/go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = ["forkchoice_test.go"],
data = glob(["*.yaml"]) + [
"@consensus_spec_tests_mainnet//:test_data",
],
tags = ["spectest"],
deps = [
"//runtime/version:go_default_library",
"//testing/spectest/shared/common/forkchoice:go_default_library",
],
)

View File

@@ -0,0 +1,12 @@
package forkchoice
import (
"testing"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/prysmaticlabs/prysm/testing/spectest/shared/common/forkchoice"
)
func TestMainnet_Altair_Forkchoice(t *testing.T) {
forkchoice.Run(t, "mainnet", version.Phase0)
}

View File

@@ -0,0 +1,18 @@
load("@prysm//tools/go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = ["forkchoice_test.go"],
data = glob(["*.yaml"]) + [
"@consensus_spec_tests_minimal//:test_data",
],
eth_network = "minimal",
tags = [
"minimal",
"spectest",
],
deps = [
"//runtime/version:go_default_library",
"//testing/spectest/shared/common/forkchoice:go_default_library",
],
)

View File

@@ -0,0 +1,12 @@
package forkchoice
import (
"testing"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/prysmaticlabs/prysm/testing/spectest/shared/common/forkchoice"
)
func TestMinimal_Altair_Forkchoice(t *testing.T) {
forkchoice.Run(t, "minimal", version.Altair)
}

View File

@@ -0,0 +1,18 @@
load("@prysm//tools/go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = ["forkchoice_test.go"],
data = glob(["*.yaml"]) + [
"@consensus_spec_tests_minimal//:test_data",
],
eth_network = "minimal",
tags = [
"minimal",
"spectest",
],
deps = [
"//runtime/version:go_default_library",
"//testing/spectest/shared/common/forkchoice:go_default_library",
],
)

View File

@@ -0,0 +1,12 @@
package forkchoice
import (
"testing"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/prysmaticlabs/prysm/testing/spectest/shared/common/forkchoice"
)
func TestMinimal_Altair_Forkchoice(t *testing.T) {
forkchoice.Run(t, "minimal", version.Phase0)
}

View File

@@ -0,0 +1,40 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
testonly = True,
srcs = [
"runner.go",
"service.go",
"type.go",
],
importpath = "github.com/prysmaticlabs/prysm/testing/spectest/shared/common/forkchoice",
visibility = ["//testing/spectest:__subpackages__"],
deps = [
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/forkchoice/protoarray:go_default_library",
"//beacon-chain/operations/attestations:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/state/v1:go_default_library",
"//beacon-chain/state/v2:go_default_library",
"//beacon-chain/state/v3:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/block:go_default_library",
"//proto/prysm/v1alpha1/wrapper:go_default_library",
"//runtime/version:go_default_library",
"//testing/require:go_default_library",
"//testing/spectest/utils:go_default_library",
"//testing/util:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_golang_snappy//:go_default_library",
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
],
)

View File

@@ -0,0 +1,228 @@
package forkchoice
import (
"context"
"fmt"
"path"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/golang/snappy"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1"
v2 "github.com/prysmaticlabs/prysm/beacon-chain/state/v2"
v3 "github.com/prysmaticlabs/prysm/beacon-chain/state/v3"
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/prysmaticlabs/prysm/testing/require"
"github.com/prysmaticlabs/prysm/testing/spectest/utils"
"github.com/prysmaticlabs/prysm/testing/util"
)
// Run executes "forkchoice" test.
func Run(t *testing.T, config string, fork int) {
require.NoError(t, utils.SetConfig(t, config))
testFolders, _ := utils.TestFolders(t, config, version.String(fork), "fork_choice")
for _, folder := range testFolders {
folderPath := path.Join("fork_choice", folder.Name(), "pyspec_tests")
testFolders, testsFolderPath := utils.TestFolders(t, config, version.String(fork), folderPath)
for _, folder := range testFolders {
t.Run(folder.Name(), func(t *testing.T) {
ctx := context.Background()
preStepsFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), "steps.yaml")
require.NoError(t, err)
var steps []Step
require.NoError(t, utils.UnmarshalYaml(preStepsFile, &steps))
preBeaconStateFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), "anchor_state.ssz_snappy")
require.NoError(t, err)
preBeaconStateSSZ, err := snappy.Decode(nil /* dst */, preBeaconStateFile)
require.NoError(t, err)
blockFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), "anchor_block.ssz_snappy")
require.NoError(t, err)
blockSSZ, err := snappy.Decode(nil /* dst */, blockFile)
require.NoError(t, err)
var beaconState state.BeaconState
var beaconBlock block.SignedBeaconBlock
switch fork {
case version.Phase0:
beaconState = unmarshalPhase0State(t, preBeaconStateSSZ)
beaconBlock = unmarshalPhase0Block(t, blockSSZ)
case version.Altair:
beaconState = unmarshalAltairState(t, preBeaconStateSSZ)
beaconBlock = unmarshalAltairBlock(t, blockSSZ)
case version.Bellatrix:
beaconState = unmarshalBellatrixState(t, preBeaconStateSSZ)
beaconBlock = unmarshalBellatrixBlock(t, blockSSZ)
default:
t.Fatalf("unknown fork version: %v", fork)
}
service := startChainService(t, beaconState, beaconBlock)
var lastTick int64
for _, step := range steps {
if step.Tick != nil {
newTick := int64(*step.Tick)
service.SetGenesisTime(time.Unix(time.Now().Unix()-newTick, 0))
if newTick > lastTick {
slot := uint64(newTick) / params.BeaconConfig().SecondsPerSlot
require.NoError(t, service.NewSlot(ctx, types.Slot(slot)))
lastTick = newTick
}
}
if step.Block != nil {
blockFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), fmt.Sprint(*step.Block, ".ssz_snappy"))
require.NoError(t, err)
blockSSZ, err := snappy.Decode(nil /* dst */, blockFile)
require.NoError(t, err)
var beaconBlock block.SignedBeaconBlock
switch fork {
case version.Phase0:
beaconBlock = unmarshalSignedPhase0Block(t, blockSSZ)
case version.Altair:
beaconBlock = unmarshalSignedAltairBlock(t, blockSSZ)
case version.Bellatrix:
beaconBlock = unmarshalSignedBellatrixBlock(t, blockSSZ)
default:
t.Fatalf("unknown fork version: %v", fork)
}
r, err := beaconBlock.Block().HashTreeRoot()
require.NoError(t, err)
if step.Valid != nil && !*step.Valid {
require.Equal(t, true, service.ReceiveBlock(ctx, beaconBlock, r) != nil)
} else {
require.NoError(t, service.ReceiveBlock(ctx, beaconBlock, r))
}
}
if step.Attestation != nil {
attFile, err := util.BazelFileBytes(testsFolderPath, folder.Name(), fmt.Sprint(*step.Attestation, ".ssz_snappy"))
require.NoError(t, err)
attSSZ, err := snappy.Decode(nil /* dst */, attFile)
require.NoError(t, err)
att := &ethpb.Attestation{}
require.NoError(t, att.UnmarshalSSZ(attSSZ), "Failed to unmarshal")
require.NoError(t, service.OnAttestation(ctx, att))
}
if step.Check != nil {
require.NoError(t, service.UpdateHeadWithBalances(ctx))
c := step.Check
if c.Head != nil {
r, err := service.HeadRoot(ctx)
require.NoError(t, err)
require.DeepEqual(t, common.FromHex(c.Head.Root), r)
require.Equal(t, types.Slot(c.Head.Slot), service.HeadSlot())
}
if c.JustifiedCheckPoint != nil {
cp := &ethpb.Checkpoint{
Epoch: types.Epoch(c.JustifiedCheckPoint.Epoch),
Root: common.FromHex(c.JustifiedCheckPoint.Root),
}
require.DeepEqual(t, cp, service.CurrentJustifiedCheckpt())
}
if c.BestJustifiedCheckPoint != nil {
cp := &ethpb.Checkpoint{
Epoch: types.Epoch(c.BestJustifiedCheckPoint.Epoch),
Root: common.FromHex(c.BestJustifiedCheckPoint.Root),
}
require.DeepEqual(t, cp, service.BestJustifiedCheckpt())
}
if c.FinalizedCheckPoint != nil {
cp := &ethpb.Checkpoint{
Epoch: types.Epoch(c.FinalizedCheckPoint.Epoch),
Root: common.FromHex(c.FinalizedCheckPoint.Root),
}
require.DeepSSZEqual(t, cp, service.FinalizedCheckpt())
}
if c.ProposerBoostRoot != nil {
want := common.FromHex(*c.ProposerBoostRoot)
require.DeepEqual(t, bytesutil.ToBytes32(want), service.ProtoArrayStore().ProposerBoost())
}
}
}
})
}
}
}
func unmarshalPhase0State(t *testing.T, raw []byte) state.BeaconState {
base := &ethpb.BeaconState{}
require.NoError(t, base.UnmarshalSSZ(raw))
st, err := v1.InitializeFromProto(base)
require.NoError(t, err)
return st
}
func unmarshalPhase0Block(t *testing.T, raw []byte) block.SignedBeaconBlock {
base := &ethpb.BeaconBlock{}
require.NoError(t, base.UnmarshalSSZ(raw))
blk, err := wrapper.WrappedSignedBeaconBlock(&ethpb.SignedBeaconBlock{Block: base, Signature: make([]byte, fieldparams.BLSSignatureLength)})
require.NoError(t, err)
return blk
}
func unmarshalSignedPhase0Block(t *testing.T, raw []byte) block.SignedBeaconBlock {
base := &ethpb.SignedBeaconBlock{}
require.NoError(t, base.UnmarshalSSZ(raw))
blk, err := wrapper.WrappedSignedBeaconBlock(base)
require.NoError(t, err)
return blk
}
func unmarshalAltairState(t *testing.T, raw []byte) state.BeaconState {
base := &ethpb.BeaconStateAltair{}
require.NoError(t, base.UnmarshalSSZ(raw))
st, err := v2.InitializeFromProto(base)
require.NoError(t, err)
return st
}
func unmarshalAltairBlock(t *testing.T, raw []byte) block.SignedBeaconBlock {
base := &ethpb.BeaconBlockAltair{}
require.NoError(t, base.UnmarshalSSZ(raw))
blk, err := wrapper.WrappedSignedBeaconBlock(&ethpb.SignedBeaconBlockAltair{Block: base, Signature: make([]byte, fieldparams.BLSSignatureLength)})
require.NoError(t, err)
return blk
}
func unmarshalSignedAltairBlock(t *testing.T, raw []byte) block.SignedBeaconBlock {
base := &ethpb.SignedBeaconBlockAltair{}
require.NoError(t, base.UnmarshalSSZ(raw))
blk, err := wrapper.WrappedSignedBeaconBlock(base)
require.NoError(t, err)
return blk
}
func unmarshalBellatrixState(t *testing.T, raw []byte) state.BeaconState {
base := &ethpb.BeaconStateBellatrix{}
require.NoError(t, base.UnmarshalSSZ(raw))
st, err := v3.InitializeFromProto(base)
require.NoError(t, err)
return st
}
func unmarshalBellatrixBlock(t *testing.T, raw []byte) block.SignedBeaconBlock {
base := &ethpb.BeaconBlockBellatrix{}
require.NoError(t, base.UnmarshalSSZ(raw))
blk, err := wrapper.WrappedSignedBeaconBlock(&ethpb.SignedBeaconBlockBellatrix{Block: base, Signature: make([]byte, fieldparams.BLSSignatureLength)})
require.NoError(t, err)
return blk
}
func unmarshalSignedBellatrixBlock(t *testing.T, raw []byte) block.SignedBeaconBlock {
base := &ethpb.SignedBeaconBlockBellatrix{}
require.NoError(t, base.UnmarshalSSZ(raw))
blk, err := wrapper.WrappedSignedBeaconBlock(base)
require.NoError(t, err)
return blk
}

View File

@@ -0,0 +1,58 @@
package forkchoice
import (
"context"
"testing"
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
coreTime "github.com/prysmaticlabs/prysm/beacon-chain/core/time"
testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray"
"github.com/prysmaticlabs/prysm/beacon-chain/operations/attestations"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/config/params"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
"github.com/prysmaticlabs/prysm/testing/require"
)
func startChainService(t *testing.T, st state.BeaconState, block block.SignedBeaconBlock) *blockchain.Service {
db := testDB.SetupDB(t)
ctx := context.Background()
require.NoError(t, db.SaveBlock(ctx, block))
r, err := block.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveGenesisBlockRoot(ctx, r))
require.NoError(t, db.SaveState(ctx, st, r))
cp := &ethpb.Checkpoint{
Epoch: coreTime.CurrentEpoch(st),
Root: r[:],
}
require.NoError(t, db.SaveJustifiedCheckpoint(ctx, cp))
require.NoError(t, db.SaveFinalizedCheckpoint(ctx, cp))
attPool, err := attestations.NewService(ctx, &attestations.Config{
Pool: attestations.NewPool(),
})
require.NoError(t, err)
depositCache, err := depositcache.New()
require.NoError(t, err)
opts := append([]blockchain.Option{},
blockchain.WithFinalizedStateAtStartUp(st),
blockchain.WithDatabase(db),
blockchain.WithAttestationService(attPool),
blockchain.WithForkChoiceStore(protoarray.New(0, 0, params.BeaconConfig().ZeroHash)),
blockchain.WithStateGen(stategen.New(db)),
blockchain.WithStateNotifier(&mock.MockStateNotifier{}),
blockchain.WithAttestationPool(attestations.NewPool()),
blockchain.WithDepositCache(depositCache),
)
service, err := blockchain.NewService(context.Background(), opts...)
require.NoError(t, err)
service.Start()
return service
}

View File

@@ -0,0 +1,29 @@
package forkchoice
type Step struct {
Tick *int `json:"tick"`
Block *string `json:"block"`
Valid *bool `json:"valid"`
Attestation *string `json:"attestation"`
Check *Check `json:"checks"`
}
type Check struct {
Time *int `json:"time"`
GenesisTime int `json:"genesis_time"`
ProposerBoostRoot *string `json:"proposer_boost_root"`
Head *SlotRoot `json:"head"`
JustifiedCheckPoint *EpochRoot `json:"justified_checkpoint"`
BestJustifiedCheckPoint *EpochRoot `json:"best_justified_checkpoint"`
FinalizedCheckPoint *EpochRoot `json:"finalized_checkpoint"`
}
type SlotRoot struct {
Slot int `json:"slot"`
Root string `json:"root"`
}
type EpochRoot struct {
Epoch int `json:"epoch"`
Root string `json:"root"`
}