Compare commits

...

3 Commits

Author SHA1 Message Date
Radosław Kapka
0cb46eb29a Do not HTR the state when checking for optimistic mode (#12143)
* initial impl

* review feedback

* fix tests

* review feedback

* some improvements

* tests and small improvements

* gzl

* one more review

* fix test

* fix other test

* get the roots instead of hashing them

* fix comment

* fix justified case

* fix all tests

* misc

* gzl

* fix broken tests

* use isOptimisticForRoot once we have the blockroot

* Fix is_not_finalized_when_head_is_optimistic but reviewing the logic first

* Fix is_not_finalized_when_head_is_optimistic

* better root tests

* move optimistic check before parsing root

* check for last validated checkpoint

* add right check for finalized

* fix finalized tests

* removed impossible condition

* fix TestGetSyncCommitteeDuties

* Use Ancestor from chaininfo

* fix test

---------

Co-authored-by: Potuz <potuz@prysmaticlabs.com>
Co-authored-by: Nishant Das <nishdas93@gmail.com>
Co-authored-by: rauljordan <raul@prysmaticlabs.com>
Co-authored-by: terence tsao <terence@prysmaticlabs.com>
2023-03-20 17:04:40 -03:00
Potuz
abe9e7fa7e mainnet capella epoch (#12144)
* mainnet capella epoch

* Update spec test to v1.3.0-rc5

---------

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-03-20 19:18:58 +00:00
Potuz
3536669a70 lock forkchoice on calls to Ancestor (#12162)
Co-authored-by: terencechain <terence@prysmaticlabs.com>
2023-03-20 15:51:42 -03:00
27 changed files with 567 additions and 136 deletions

View File

@@ -205,7 +205,7 @@ filegroup(
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
)
consensus_spec_version = "v1.3.0-rc.4"
consensus_spec_version = "v1.3.0-rc.5"
bls_test_version = "v0.1.1"
@@ -221,7 +221,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "519da3cbb181fe927e41b0d13c3aaad5f5f38fe0ba87ca51bd09a661c738bd6c",
sha256 = "266006512e71e62396e8f31be01639560c9d59a93c38220fd8f51fabefc8f5f3",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
)
@@ -237,7 +237,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "894404302d3d4b0f3080d3221204c19de4e837f1b129f468a66747103174412e",
sha256 = "2ebf483830165909cb7961562fd369dedf079997a4832cc215a543898a73aa46",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
)
@@ -253,7 +253,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "ca7a594a2f4be1103e01b5a1416f75a328b7555eae8b26308c07f80fa6d0f255",
sha256 = "333718ba5c907e0a99580caa8d28dd710543b3b271e4251581006d0e101fbce9",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
)
@@ -268,7 +268,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "b4ed6c077c5f0857361412515b319fc8b26730c7d701d3245b5e6849b3974a4f",
sha256 = "78b6925b5a4208e32385fa4387d2c27b381a8ddd18d66d5a7787e7846b86bfc8",
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
)

View File

@@ -28,6 +28,15 @@ type ChainInfoFetcher interface {
CanonicalFetcher
ForkFetcher
HeadDomainFetcher
ForkchoiceFetcher
}
// ForkchoiceFetcher defines a common interface for methods that access directly
// forkchoice information. These typically require a lock and external callers
// are requested to call methods within this blockchain package that takes care
// of locking forkchoice
type ForkchoiceFetcher interface {
Ancestor(context.Context, []byte, primitives.Slot) ([]byte, error)
}
// HeadUpdater defines a common interface for methods in blockchain service
@@ -436,6 +445,41 @@ func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool,
return !isCanonical, nil
}
// Ancestor returns the block root of an ancestry block from the input block root.
//
// Spec pseudocode definition:
//
// def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
// block = store.blocks[root]
// if block.slot > slot:
// return get_ancestor(store, block.parent_root, slot)
// elif block.slot == slot:
// return root
// else:
// # root is older than queried slot, thus a skip slot. Return most recent root prior to slot
// return root
func (s *Service) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
ctx, span := trace.StartSpan(ctx, "blockChain.ancestor")
defer span.End()
r := bytesutil.ToBytes32(root)
// Get ancestor root from fork choice store instead of recursively looking up blocks in DB.
// This is most optimal outcome.
s.ForkChoicer().RLock()
ar, err := s.cfg.ForkChoiceStore.AncestorRoot(ctx, r, slot)
s.ForkChoicer().RUnlock()
if err != nil {
// Try getting ancestor root from DB when failed to retrieve from fork choice store.
// This is the second line of defense for retrieving ancestor root.
ar, err = s.ancestorByDB(ctx, r, slot)
if err != nil {
return nil, err
}
}
return ar[:], nil
}
// SetGenesisTime sets the genesis time of beacon chain.
func (s *Service) SetGenesisTime(t time.Time) {
s.genesisTime = t

View File

@@ -146,39 +146,6 @@ func (s *Service) updateFinalized(ctx context.Context, cp *ethpb.Checkpoint) err
return nil
}
// ancestor returns the block root of an ancestry block from the input block root.
//
// Spec pseudocode definition:
//
// def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
// block = store.blocks[root]
// if block.slot > slot:
// return get_ancestor(store, block.parent_root, slot)
// elif block.slot == slot:
// return root
// else:
// # root is older than queried slot, thus a skip slot. Return most recent root prior to slot
// return root
func (s *Service) ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
ctx, span := trace.StartSpan(ctx, "blockChain.ancestor")
defer span.End()
r := bytesutil.ToBytes32(root)
// Get ancestor root from fork choice store instead of recursively looking up blocks in DB.
// This is most optimal outcome.
ar, err := s.cfg.ForkChoiceStore.AncestorRoot(ctx, r, slot)
if err != nil {
// Try getting ancestor root from DB when failed to retrieve from fork choice store.
// This is the second line of defense for retrieving ancestor root.
ar, err = s.ancestorByDB(ctx, r, slot)
if err != nil {
return nil, err
}
}
return ar[:], nil
}
// This retrieves an ancestor root using DB. The look up is recursively looking up DB. Slower than `ancestorByForkChoiceStore`.
func (s *Service) ancestorByDB(ctx context.Context, r [32]byte, slot primitives.Slot) (root [32]byte, err error) {
ctx, span := trace.StartSpan(ctx, "blockChain.ancestorByDB")

View File

@@ -600,14 +600,14 @@ func TestAncestor_HandleSkipSlot(t *testing.T) {
}
// Slots 100 to 200 are skip slots. Requesting root at 150 will yield root at 100. The last physical block.
r, err := service.ancestor(context.Background(), r200[:], 150)
r, err := service.Ancestor(context.Background(), r200[:], 150)
require.NoError(t, err)
if bytesutil.ToBytes32(r) != r100 {
t.Error("Did not get correct root")
}
// Slots 1 to 100 are skip slots. Requesting root at 50 will yield root at 1. The last physical block.
r, err = service.ancestor(context.Background(), r200[:], 50)
r, err = service.Ancestor(context.Background(), r200[:], 50)
require.NoError(t, err)
if bytesutil.ToBytes32(r) != r1 {
t.Error("Did not get correct root")
@@ -648,7 +648,7 @@ func TestAncestor_CanUseForkchoice(t *testing.T) {
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
}
r, err := service.ancestor(context.Background(), r200[:], 150)
r, err := service.Ancestor(context.Background(), r200[:], 150)
require.NoError(t, err)
if bytesutil.ToBytes32(r) != r100 {
t.Error("Did not get correct root")
@@ -696,7 +696,7 @@ func TestAncestor_CanUseDB(t *testing.T) {
require.NoError(t, err)
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
r, err := service.ancestor(context.Background(), r200[:], 150)
r, err := service.Ancestor(context.Background(), r200[:], 150)
require.NoError(t, err)
if bytesutil.ToBytes32(r) != r100 {
t.Error("Did not get correct root")

View File

@@ -56,7 +56,7 @@ func (s *Service) VerifyLmdFfgConsistency(ctx context.Context, a *ethpb.Attestat
if err != nil {
return err
}
r, err := s.ancestor(ctx, a.Data.BeaconBlockRoot, targetSlot)
r, err := s.Ancestor(ctx, a.Data.BeaconBlockRoot, targetSlot)
if err != nil {
return err
}

View File

@@ -67,6 +67,7 @@ type ChainService struct {
ReceiveBlockMockErr error
OptimisticCheckRootReceived [32]byte
FinalizedRoots map[[32]byte]bool
OptimisticRoots map[[32]byte]bool
}
// ForkChoicer mocks the same method in the chain service
@@ -74,6 +75,11 @@ func (s *ChainService) ForkChoicer() forkchoice.ForkChoicer {
return s.ForkChoiceStore
}
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
r, err := s.ForkChoicer().AncestorRoot(ctx, bytesutil.ToBytes32(root), slot)
return r[:], err
}
// StateNotifier mocks the same method in the chain service.
func (s *ChainService) StateNotifier() statefeed.Notifier {
if s.stateNotifier == nil {
@@ -457,7 +463,7 @@ func (s *ChainService) InForkchoice(_ [32]byte) bool {
// IsOptimisticForRoot mocks the same method in the chain service.
func (s *ChainService) IsOptimisticForRoot(_ context.Context, root [32]byte) (bool, error) {
s.OptimisticCheckRootReceived = root
return s.Optimistic, nil
return s.OptimisticRoots[root], nil
}
// UpdateHead mocks the same method in the chain service.

View File

@@ -520,8 +520,10 @@ func TestServer_GetBlindedBlock(t *testing.T) {
Block: chainBlk,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
@@ -734,8 +736,10 @@ func TestServer_GetBlindedBlockSSZ(t *testing.T) {
Block: chainBlk,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,

View File

@@ -471,6 +471,9 @@ func TestServer_GetBlockHeader(t *testing.T) {
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
@@ -634,6 +637,9 @@ func TestServer_ListBlockHeaders(t *testing.T) {
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(blkContainers[30].BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
@@ -1666,6 +1672,9 @@ func TestServer_GetBlockV2(t *testing.T) {
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
@@ -1927,6 +1936,9 @@ func TestServer_GetBlockSSZV2(t *testing.T) {
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
@@ -2098,6 +2110,9 @@ func TestServer_GetBlockRoot(t *testing.T) {
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
@@ -2494,6 +2509,9 @@ func TestServer_ListBlockAttestations(t *testing.T) {
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(headBlock.BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,

View File

@@ -71,7 +71,7 @@ func (bs *Server) GetStateRoot(ctx context.Context, req *ethpb.StateRequest) (*e
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}
@@ -100,7 +100,7 @@ func (bs *Server) GetStateFork(ctx context.Context, req *ethpb.StateRequest) (*e
return nil, helpers.PrepareStateFetchGRPCError(err)
}
fork := st.Fork()
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}
@@ -131,7 +131,7 @@ func (bs *Server) GetFinalityCheckpoints(ctx context.Context, req *ethpb.StateRe
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}
@@ -186,7 +186,7 @@ func (bs *Server) GetRandao(ctx context.Context, req *eth2.RandaoRequest) (*eth2
return nil, status.Errorf(codes.Internal, "Could not get randao mix at index %d", idx)
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}

View File

@@ -94,7 +94,7 @@ func TestGetStateRoot(t *testing.T) {
}
resp, err := server.GetStateRoot(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -121,7 +121,7 @@ func TestGetStateRoot(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetStateRoot(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -155,7 +155,7 @@ func TestGetStateRoot(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetStateRoot(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -189,7 +189,7 @@ func TestGetStateFork(t *testing.T) {
}
resp, err := server.GetStateFork(ctx, &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -218,7 +218,7 @@ func TestGetStateFork(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetStateFork(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -251,7 +251,7 @@ func TestGetStateFork(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetStateFork(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -292,7 +292,7 @@ func TestGetFinalityCheckpoints(t *testing.T) {
}
resp, err := server.GetFinalityCheckpoints(ctx, &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -323,7 +323,7 @@ func TestGetFinalityCheckpoints(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetFinalityCheckpoints(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -356,7 +356,7 @@ func TestGetFinalityCheckpoints(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetFinalityCheckpoints(context.Background(), &eth.StateRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -398,17 +398,17 @@ func TestGetRandao(t *testing.T) {
}
t.Run("no epoch requested", func(t *testing.T) {
resp, err := server.GetRandao(ctx, &eth2.RandaoRequest{StateId: make([]byte, 0)})
resp, err := server.GetRandao(ctx, &eth2.RandaoRequest{StateId: []byte("head")})
require.NoError(t, err)
assert.DeepEqual(t, mixCurrent, resp.Data.Randao)
})
t.Run("current epoch requested", func(t *testing.T) {
resp, err := server.GetRandao(ctx, &eth2.RandaoRequest{StateId: make([]byte, 0), Epoch: &epochCurrent})
resp, err := server.GetRandao(ctx, &eth2.RandaoRequest{StateId: []byte("head"), Epoch: &epochCurrent})
require.NoError(t, err)
assert.DeepEqual(t, mixCurrent, resp.Data.Randao)
})
t.Run("old epoch requested", func(t *testing.T) {
resp, err := server.GetRandao(ctx, &eth2.RandaoRequest{StateId: make([]byte, 0), Epoch: &epochOld})
resp, err := server.GetRandao(ctx, &eth2.RandaoRequest{StateId: []byte("head"), Epoch: &epochOld})
require.NoError(t, err)
assert.DeepEqual(t, mixOld, resp.Data.Randao)
})
@@ -450,7 +450,7 @@ func TestGetRandao(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetRandao(context.Background(), &eth2.RandaoRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -482,7 +482,7 @@ func TestGetRandao(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetRandao(context.Background(), &eth2.RandaoRequest{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)

View File

@@ -91,7 +91,7 @@ func (bs *Server) ListSyncCommittees(ctx context.Context, req *ethpbv2.StateSync
return nil, status.Errorf(codes.Internal, "Could not extract sync subcommittees: %v", err)
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}

View File

@@ -161,7 +161,8 @@ func TestListSyncCommittees(t *testing.T) {
require.NoError(t, err)
db := dbTest.SetupDB(t)
chainService := &mock.ChainService{}
stSlot := st.Slot()
chainService := &mock.ChainService{Slot: &stSlot}
s := &Server{
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
@@ -173,6 +174,7 @@ func TestListSyncCommittees(t *testing.T) {
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
ChainInfoFetcher: chainService,
}
req := &ethpbv2.StateSyncCommitteesRequest{StateId: stRoot[:]}
resp, err := s.ListSyncCommittees(ctx, req)
@@ -205,7 +207,8 @@ func TestListSyncCommittees(t *testing.T) {
util.SaveBlock(t, ctx, db, blk)
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
chainService := &mock.ChainService{Optimistic: true}
stSlot := st.Slot()
chainService := &mock.ChainService{Optimistic: true, Slot: &stSlot}
s := &Server{
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
Genesis: time.Now(),
@@ -217,6 +220,7 @@ func TestListSyncCommittees(t *testing.T) {
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
ChainInfoFetcher: chainService,
}
resp, err := s.ListSyncCommittees(ctx, req)
require.NoError(t, err)
@@ -234,10 +238,12 @@ func TestListSyncCommittees(t *testing.T) {
headerRoot, err := st.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
stSlot := st.Slot()
chainService := &mock.ChainService{
FinalizedRoots: map[[32]byte]bool{
headerRoot: true,
},
Slot: &stSlot,
}
s := &Server{
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
@@ -250,6 +256,7 @@ func TestListSyncCommittees(t *testing.T) {
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
ChainInfoFetcher: chainService,
}
resp, err := s.ListSyncCommittees(ctx, req)
require.NoError(t, err)
@@ -310,7 +317,7 @@ func TestListSyncCommitteesFuture(t *testing.T) {
FinalizationFetcher: chainService,
BeaconDB: db,
}
req := &ethpbv2.StateSyncCommitteesRequest{}
req := &ethpbv2.StateSyncCommitteesRequest{StateId: []byte("head")}
epoch := 2 * params.BeaconConfig().EpochsPerSyncCommitteePeriod
req.Epoch = &epoch
_, err := s.ListSyncCommittees(ctx, req)

View File

@@ -57,7 +57,7 @@ func (bs *Server) GetValidator(ctx context.Context, req *ethpb.StateValidatorReq
return nil, status.Error(codes.NotFound, "Could not find validator")
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}
@@ -86,7 +86,7 @@ func (bs *Server) ListValidators(ctx context.Context, req *ethpb.StateValidators
return nil, handleValContainerErr(err)
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}
@@ -155,7 +155,7 @@ func (bs *Server) ListValidatorBalances(ctx context.Context, req *ethpb.Validato
}
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}
@@ -220,7 +220,7 @@ func (bs *Server) ListCommittees(ctx context.Context, req *ethpb.StateCommittees
}
}
isOptimistic, err := helpers.IsOptimistic(ctx, st, bs.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.StateFetcher, bs.ChainInfoFetcher, bs.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}

View File

@@ -41,7 +41,7 @@ func (ds *Server) GetBeaconStateV2(ctx context.Context, req *ethpbv2.BeaconState
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
isOptimistic, err := helpers.IsOptimistic(ctx, beaconSt, ds.OptimisticModeFetcher)
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, ds.OptimisticModeFetcher, ds.StateFetcher, ds.ChainInfoFetcher, ds.BeaconDB)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}

View File

@@ -37,7 +37,7 @@ func TestGetBeaconStateV2(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetBeaconStateV2(context.Background(), &ethpbv2.BeaconStateRequestV2{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -55,7 +55,7 @@ func TestGetBeaconStateV2(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetBeaconStateV2(context.Background(), &ethpbv2.BeaconStateRequestV2{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -73,7 +73,7 @@ func TestGetBeaconStateV2(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetBeaconStateV2(context.Background(), &ethpbv2.BeaconStateRequestV2{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -91,7 +91,7 @@ func TestGetBeaconStateV2(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetBeaconStateV2(context.Background(), &ethpbv2.BeaconStateRequestV2{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -117,7 +117,7 @@ func TestGetBeaconStateV2(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetBeaconStateV2(context.Background(), &ethpbv2.BeaconStateRequestV2{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -150,7 +150,7 @@ func TestGetBeaconStateV2(t *testing.T) {
BeaconDB: db,
}
resp, err := server.GetBeaconStateV2(context.Background(), &ethpbv2.BeaconStateRequestV2{
StateId: make([]byte, 0),
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
@@ -293,7 +293,13 @@ func TestListForkChoiceHeadsV2(t *testing.T) {
}
t.Run("optimistic head", func(t *testing.T) {
chainService := &blockchainmock.ChainService{Optimistic: true}
chainService := &blockchainmock.ChainService{
Optimistic: true,
OptimisticRoots: make(map[[32]byte]bool),
}
for _, sr := range expectedSlotsAndRoots {
chainService.OptimisticRoots[sr.Root] = true
}
server := &Server{
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,

View File

@@ -18,4 +18,5 @@ type Server struct {
OptimisticModeFetcher blockchain.OptimisticModeFetcher
ForkFetcher blockchain.ForkFetcher
FinalizationFetcher blockchain.FinalizationFetcher
ChainInfoFetcher blockchain.ChainInfoFetcher
}

View File

@@ -12,13 +12,16 @@ go_library(
deps = [
"//api/grpc:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/rpc/statefetcher:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/eth/v1:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
@@ -35,12 +38,21 @@ go_test(
deps = [
"//api/grpc:go_default_library",
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
"//beacon-chain/rpc/testutil:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//beacon-chain/sync/initial-sync/testing:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/migration:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",

View File

@@ -1,16 +1,19 @@
package helpers
import (
"bytes"
"context"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api/grpc"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/statefetcher"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -53,26 +56,126 @@ func ValidateSync(
return status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
}
// IsOptimistic checks whether the latest block header of the passed in beacon state is the header of an optimistic block.
func IsOptimistic(ctx context.Context, st state.BeaconState, optimisticModeFetcher blockchain.OptimisticModeFetcher) (bool, error) {
header := st.LatestBlockHeader()
// This happens when the block at the state's slot is not missing.
if bytes.Equal(header.StateRoot, params.BeaconConfig().ZeroHash[:]) {
root, err := st.HashTreeRoot(ctx)
if err != nil {
return false, errors.Wrap(err, "could not get state root")
// IsOptimistic checks whether the beacon state's block is optimistic.
func IsOptimistic(
ctx context.Context,
stateId []byte,
optimisticModeFetcher blockchain.OptimisticModeFetcher,
stateFetcher statefetcher.Fetcher,
chainInfo blockchain.ChainInfoFetcher,
database db.ReadOnlyDatabase,
) (bool, error) {
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head":
return optimisticModeFetcher.IsOptimistic(ctx)
case "genesis":
return false, nil
case "finalized":
fcp := chainInfo.FinalizedCheckpt()
if fcp == nil {
return true, errors.New("received nil finalized checkpoint")
}
return optimisticModeFetcher.IsOptimisticForRoot(ctx, bytesutil.ToBytes32(fcp.Root))
case "justified":
jcp := chainInfo.CurrentJustifiedCheckpt()
if jcp == nil {
return true, errors.New("received nil justified checkpoint")
}
return optimisticModeFetcher.IsOptimisticForRoot(ctx, bytesutil.ToBytes32(jcp.Root))
default:
if len(stateId) == 32 {
return isStateRootOptimistic(ctx, stateId, optimisticModeFetcher, stateFetcher, chainInfo, database)
} else {
optimistic, err := optimisticModeFetcher.IsOptimistic(ctx)
if err != nil {
return true, errors.Wrap(err, "could not check optimistic status")
}
if !optimistic {
return false, nil
}
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
// ID format does not match any valid options.
e := statefetcher.NewStateIdParseError(parseErr)
return true, &e
}
fcp := chainInfo.FinalizedCheckpt()
if fcp == nil {
return true, errors.New("received nil finalized checkpoint")
}
finalizedSlot, err := slots.EpochStart(fcp.Epoch)
if err != nil {
return true, errors.Wrap(err, "could not get head state's finalized slot")
}
lastValidatedCheckpoint, err := database.LastValidatedCheckpoint(ctx)
if err != nil {
return true, errors.Wrap(err, "could not get last validated checkpoint")
}
validatedSlot, err := slots.EpochStart(lastValidatedCheckpoint.Epoch)
if err != nil {
return true, errors.Wrap(err, "could not get last validated slot")
}
if primitives.Slot(slotNumber) <= validatedSlot {
return false, nil
}
// if the finalized checkpoint is higher than the last
// validated checkpoint, we are syncing and have synced
// a finalization being optimistic
if validatedSlot < finalizedSlot {
return true, nil
}
if primitives.Slot(slotNumber) == chainInfo.HeadSlot() {
// We know the head is optimistic because we checked it above.
return true, nil
}
headRoot, err := chainInfo.HeadRoot(ctx)
if err != nil {
return true, errors.Wrap(err, "could not get head root")
}
r, err := chainInfo.Ancestor(ctx, headRoot, primitives.Slot(slotNumber))
if err != nil {
return true, errors.Wrap(err, "could not get ancestor root")
}
return optimisticModeFetcher.IsOptimisticForRoot(ctx, bytesutil.ToBytes32(r))
}
header.StateRoot = root[:]
}
headRoot, err := header.HashTreeRoot()
}
func isStateRootOptimistic(
ctx context.Context,
stateId []byte,
optimisticModeFetcher blockchain.OptimisticModeFetcher,
stateFetcher statefetcher.Fetcher,
chainInfo blockchain.ChainInfoFetcher,
database db.ReadOnlyDatabase,
) (bool, error) {
st, err := stateFetcher.State(ctx, stateId)
if err != nil {
return false, errors.Wrap(err, "could not get header root")
return true, errors.Wrap(err, "could not fetch state")
}
isOptimistic, err := optimisticModeFetcher.IsOptimisticForRoot(ctx, headRoot)
if st.Slot() == chainInfo.HeadSlot() {
return optimisticModeFetcher.IsOptimistic(ctx)
}
has, roots, err := database.BlockRootsBySlot(ctx, st.Slot())
if err != nil {
return false, errors.Wrap(err, "could not check if block is optimistic")
return true, errors.Wrapf(err, "could not get block roots for slot %d", st.Slot())
}
return isOptimistic, nil
if !has {
return true, errors.New("no block roots returned from the database")
}
for _, r := range roots {
b, err := database.Block(ctx, r)
if err != nil {
return true, errors.Wrapf(err, "could not obtain block")
}
if bytesutil.ToBytes32(stateId) != b.Block().StateRoot() {
continue
}
return optimisticModeFetcher.IsOptimisticForRoot(ctx, r)
}
// No block matching requested state root, return true.
return true, nil
}
// SyncDetailsJson contains information about node sync status.

View File

@@ -2,14 +2,26 @@ package helpers
import (
"context"
"strconv"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
grpcutil "github.com/prysmaticlabs/prysm/v4/api/grpc"
chainmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
dbtest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
syncmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
@@ -62,44 +74,257 @@ func TestValidateSync(t *testing.T) {
func TestIsOptimistic(t *testing.T) {
ctx := context.Background()
st, err := util.NewBeaconState()
require.NoError(t, err)
t.Run("optimistic", func(t *testing.T) {
mockOptSyncFetcher := &chainmock.ChainService{Optimistic: true}
o, err := IsOptimistic(ctx, st, mockOptSyncFetcher)
t.Run("head optimistic", func(t *testing.T) {
cs := &chainmock.ChainService{Optimistic: true}
o, err := IsOptimistic(ctx, []byte("head"), cs, nil, nil, nil)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("not optimistic", func(t *testing.T) {
mockOptSyncFetcher := &chainmock.ChainService{Optimistic: false}
o, err := IsOptimistic(ctx, st, mockOptSyncFetcher)
t.Run("head not optimistic", func(t *testing.T) {
cs := &chainmock.ChainService{Optimistic: false}
o, err := IsOptimistic(ctx, []byte("head"), cs, nil, nil, nil)
require.NoError(t, err)
assert.Equal(t, false, o)
})
t.Run("zero state root", func(t *testing.T) {
zeroRootSt, err := util.NewBeaconState()
t.Run("genesis", func(t *testing.T) {
o, err := IsOptimistic(ctx, []byte("genesis"), nil, nil, nil, nil)
require.NoError(t, err)
h := zeroRootSt.LatestBlockHeader()
h.StateRoot = make([]byte, 32)
require.NoError(t, zeroRootSt.SetLatestBlockHeader(h))
mockOptSyncFetcher := &chainmock.ChainService{}
_, err = IsOptimistic(ctx, st, mockOptSyncFetcher)
require.NoError(t, err)
assert.DeepEqual(
t,
[32]byte{0xfc, 0x0, 0xe9, 0x6d, 0xb, 0x8b, 0x2, 0x2f, 0x61, 0xeb, 0x92, 0x10, 0xfd, 0x80, 0x84, 0x2b, 0x26, 0x61, 0xdc, 0x94, 0x5f, 0x7a, 0xf0, 0x0, 0xbc, 0x38, 0x6, 0x38, 0x71, 0x95, 0x43, 0x1},
mockOptSyncFetcher.OptimisticCheckRootReceived,
)
assert.Equal(t, false, o)
})
t.Run("non-zero state root", func(t *testing.T) {
mockOptSyncFetcher := &chainmock.ChainService{}
_, err = IsOptimistic(ctx, st, mockOptSyncFetcher)
require.NoError(t, err)
assert.DeepEqual(
t,
[32]byte{0xfc, 0x0, 0xe9, 0x6d, 0xb, 0x8b, 0x2, 0x2f, 0x61, 0xeb, 0x92, 0x10, 0xfd, 0x80, 0x84, 0x2b, 0x26, 0x61, 0xdc, 0x94, 0x5f, 0x7a, 0xf0, 0x0, 0xbc, 0x38, 0x6, 0x38, 0x71, 0x95, 0x43, 0x1},
mockOptSyncFetcher.OptimisticCheckRootReceived,
)
t.Run("finalized", func(t *testing.T) {
t.Run("finalized checkpoint is optimistic", func(t *testing.T) {
st, err := util.NewBeaconState()
require.NoError(t, err)
cs := &chainmock.ChainService{Optimistic: true, FinalizedCheckPoint: &eth.Checkpoint{}, OptimisticRoots: map[[32]byte]bool{[32]byte{}: true}}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, []byte("finalized"), cs, mf, cs, nil)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("finalized checkpoint is not optimistic", func(t *testing.T) {
st, err := util.NewBeaconState()
require.NoError(t, err)
cs := &chainmock.ChainService{Optimistic: true, FinalizedCheckPoint: &eth.Checkpoint{}}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, []byte("finalized"), cs, mf, cs, nil)
require.NoError(t, err)
assert.Equal(t, false, o)
})
})
t.Run("justified", func(t *testing.T) {
t.Run("justified checkpoint is optimistic", func(t *testing.T) {
st, err := util.NewBeaconState()
require.NoError(t, err)
cs := &chainmock.ChainService{Optimistic: true, CurrentJustifiedCheckPoint: &eth.Checkpoint{}, OptimisticRoots: map[[32]byte]bool{[32]byte{}: true}}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, []byte("justified"), cs, mf, cs, nil)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("justified checkpoint is not optimistic", func(t *testing.T) {
st, err := util.NewBeaconState()
require.NoError(t, err)
cs := &chainmock.ChainService{Optimistic: true, CurrentJustifiedCheckPoint: &eth.Checkpoint{}}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, []byte("justified"), cs, mf, cs, nil)
require.NoError(t, err)
assert.Equal(t, false, o)
})
})
t.Run("root", func(t *testing.T) {
t.Run("is head and head is optimistic", func(t *testing.T) {
st, err := util.NewBeaconState()
require.NoError(t, err)
cs := &chainmock.ChainService{Optimistic: true}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, bytesutil.PadTo([]byte("root"), 32), cs, mf, cs, nil)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("is head and head is not optimistic", func(t *testing.T) {
st, err := util.NewBeaconState()
require.NoError(t, err)
cs := &chainmock.ChainService{Optimistic: false}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, bytesutil.PadTo([]byte("root"), 32), cs, mf, cs, nil)
require.NoError(t, err)
assert.Equal(t, false, o)
})
t.Run("root is optimistic", func(t *testing.T) {
b, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
require.NoError(t, err)
b.SetStateRoot(bytesutil.PadTo([]byte("root"), 32))
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveBlock(ctx, b))
fetcherSt, err := util.NewBeaconState()
require.NoError(t, err)
chainSt, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, chainSt.SetSlot(fieldparams.SlotsPerEpoch))
bRoot, err := b.Block().HashTreeRoot()
require.NoError(t, err)
cs := &chainmock.ChainService{State: chainSt, OptimisticRoots: map[[32]byte]bool{bRoot: true}}
mf := &testutil.MockFetcher{BeaconState: fetcherSt}
o, err := IsOptimistic(ctx, bytesutil.PadTo([]byte("root"), 32), cs, mf, cs, db)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("root is not optimistic", func(t *testing.T) {
b, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
require.NoError(t, err)
b.SetStateRoot(bytesutil.PadTo([]byte("root"), 32))
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveBlock(ctx, b))
fetcherSt, err := util.NewBeaconState()
require.NoError(t, err)
chainSt, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, chainSt.SetSlot(fieldparams.SlotsPerEpoch))
cs := &chainmock.ChainService{State: chainSt}
mf := &testutil.MockFetcher{BeaconState: fetcherSt}
o, err := IsOptimistic(ctx, bytesutil.PadTo([]byte("root"), 32), cs, mf, cs, db)
require.NoError(t, err)
assert.Equal(t, false, o)
})
t.Run("no canonical blocks", func(t *testing.T) {
b, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
require.NoError(t, err)
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveBlock(ctx, b))
fetcherSt, err := util.NewBeaconState()
require.NoError(t, err)
chainSt, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, chainSt.SetSlot(fieldparams.SlotsPerEpoch))
cs := &chainmock.ChainService{Optimistic: false, State: chainSt, CanonicalRoots: map[[32]byte]bool{}}
mf := &testutil.MockFetcher{BeaconState: fetcherSt}
o, err := IsOptimistic(ctx, bytesutil.PadTo([]byte("root"), 32), nil, mf, cs, db)
require.NoError(t, err)
assert.Equal(t, true, o)
})
})
t.Run("slot", func(t *testing.T) {
t.Run("head is not optimistic", func(t *testing.T) {
cs := &chainmock.ChainService{Optimistic: false}
o, err := IsOptimistic(ctx, []byte("0"), cs, nil, nil, nil)
require.NoError(t, err)
assert.Equal(t, false, o)
})
t.Run("is before validated slot when head is optimistic", func(t *testing.T) {
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &eth.StateSummary{Slot: fieldparams.SlotsPerEpoch, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &eth.Checkpoint{Epoch: 1, Root: []byte("root")}))
cs := &chainmock.ChainService{Optimistic: true, FinalizedCheckPoint: &eth.Checkpoint{Epoch: 1}}
o, err := IsOptimistic(ctx, []byte("0"), cs, nil, cs, db)
require.NoError(t, err)
assert.Equal(t, false, o)
})
t.Run("is equal to validated slot when head is optimistic", func(t *testing.T) {
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &eth.StateSummary{Slot: fieldparams.SlotsPerEpoch, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &eth.Checkpoint{Epoch: 1, Root: []byte("root")}))
cs := &chainmock.ChainService{Optimistic: true, FinalizedCheckPoint: &eth.Checkpoint{Epoch: 1}}
o, err := IsOptimistic(ctx, []byte("32"), cs, nil, cs, db)
require.NoError(t, err)
assert.Equal(t, false, o)
})
t.Run("is after validated slot and validated slot is before finalized slot", func(t *testing.T) {
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &eth.StateSummary{Slot: fieldparams.SlotsPerEpoch, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &eth.Checkpoint{Epoch: 1, Root: []byte("root")}))
cs := &chainmock.ChainService{Optimistic: true, FinalizedCheckPoint: &eth.Checkpoint{Epoch: 2}}
o, err := IsOptimistic(ctx, []byte("33"), cs, nil, cs, db)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("is head", func(t *testing.T) {
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &eth.StateSummary{Slot: fieldparams.SlotsPerEpoch, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &eth.Checkpoint{Epoch: 1, Root: []byte("root")}))
fetcherSt, err := util.NewBeaconState()
require.NoError(t, err)
chainSt, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, chainSt.SetSlot(fieldparams.SlotsPerEpoch*2))
cs := &chainmock.ChainService{Optimistic: true, State: chainSt, FinalizedCheckPoint: &eth.Checkpoint{Epoch: 0}}
mf := &testutil.MockFetcher{BeaconState: fetcherSt}
o, err := IsOptimistic(ctx, []byte(strconv.Itoa(fieldparams.SlotsPerEpoch*2)), cs, mf, cs, db)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("ancestor is optimistic", func(t *testing.T) {
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &eth.StateSummary{Slot: fieldparams.SlotsPerEpoch, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &eth.Checkpoint{Epoch: 1, Root: []byte("root")}))
r := bytesutil.ToBytes32([]byte("root"))
fcs := doublylinkedtree.New()
finalizedCheckpt := &eth.Checkpoint{Epoch: 0}
st, root, err := prepareForkchoiceState(fieldparams.SlotsPerEpoch*2, r, [32]byte{}, [32]byte{}, finalizedCheckpt, finalizedCheckpt)
require.NoError(t, err)
require.NoError(t, fcs.InsertNode(ctx, st, root))
headRoot := [32]byte{'r'}
st, root, err = prepareForkchoiceState(fieldparams.SlotsPerEpoch*2+1, headRoot, r, [32]byte{}, finalizedCheckpt, finalizedCheckpt)
require.NoError(t, err)
require.NoError(t, fcs.InsertNode(ctx, st, root))
cs := &chainmock.ChainService{Root: headRoot[:], Optimistic: true, ForkChoiceStore: fcs, OptimisticRoots: map[[32]byte]bool{r: true}, FinalizedCheckPoint: finalizedCheckpt}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, []byte(strconv.Itoa(fieldparams.SlotsPerEpoch*2)), cs, mf, cs, db)
require.NoError(t, err)
assert.Equal(t, true, o)
})
t.Run("ancestor is not optimistic", func(t *testing.T) {
db := dbtest.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &eth.StateSummary{Slot: fieldparams.SlotsPerEpoch, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &eth.Checkpoint{Epoch: 1, Root: []byte("root")}))
r := bytesutil.ToBytes32([]byte("root"))
fcs := doublylinkedtree.New()
finalizedCheckpt := &eth.Checkpoint{Epoch: 0}
st, root, err := prepareForkchoiceState(fieldparams.SlotsPerEpoch*2, r, [32]byte{}, [32]byte{}, finalizedCheckpt, finalizedCheckpt)
require.NoError(t, err)
require.NoError(t, fcs.InsertNode(ctx, st, root))
headRoot := [32]byte{'r'}
st, root, err = prepareForkchoiceState(fieldparams.SlotsPerEpoch*2+1, headRoot, r, [32]byte{}, finalizedCheckpt, finalizedCheckpt)
require.NoError(t, err)
require.NoError(t, fcs.InsertNode(ctx, st, root))
cs := &chainmock.ChainService{Root: headRoot[:], Optimistic: true, ForkChoiceStore: fcs, OptimisticRoots: map[[32]byte]bool{r: false}, FinalizedCheckPoint: finalizedCheckpt}
mf := &testutil.MockFetcher{BeaconState: st}
o, err := IsOptimistic(ctx, []byte(strconv.Itoa(fieldparams.SlotsPerEpoch*2)), cs, mf, cs, db)
require.NoError(t, err)
assert.Equal(t, false, o)
})
})
}
// prepareForkchoiceState prepares a beacon state with the given data to mock
// insert into forkchoice
func prepareForkchoiceState(
slot primitives.Slot,
blockRoot [32]byte,
parentRoot [32]byte,
payloadHash [32]byte,
justified *eth.Checkpoint,
finalized *eth.Checkpoint,
) (state.BeaconState, [32]byte, error) {
blockHeader := &eth.BeaconBlockHeader{
ParentRoot: parentRoot[:],
}
executionHeader := &enginev1.ExecutionPayloadHeader{
BlockHash: payloadHash[:],
}
base := &eth.BeaconStateBellatrix{
Slot: slot,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
BlockRoots: make([][]byte, 1),
CurrentJustifiedCheckpoint: justified,
FinalizedCheckpoint: finalized,
LatestExecutionPayloadHeader: executionHeader,
LatestBlockHeader: blockHeader,
}
base.BlockRoots[0] = append(base.BlockRoots[0], blockRoot[:]...)
st, err := state_native.InitializeFromProtoBellatrix(base)
return st, blockRoot, err
}

View File

@@ -13,6 +13,7 @@ go_library(
"//beacon-chain/builder:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/db/kv:go_default_library",
"//beacon-chain/operations/attestations:go_default_library",
"//beacon-chain/operations/synccommittee:go_default_library",

View File

@@ -3,6 +3,7 @@ package validator
import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/attestations"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/synccommittee"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
@@ -26,4 +27,6 @@ type Server struct {
SyncCommitteePool synccommittee.Pool
V1Alpha1Server *v1alpha1validator.Server
ProposerSlotIndexCache *cache.ProposerPayloadIDsCache
ChainInfoFetcher blockchain.ChainInfoFetcher
BeaconDB db.ReadOnlyDatabase
}

View File

@@ -278,7 +278,14 @@ func (vs *Server) GetSyncCommitteeDuties(ctx context.Context, req *ethpbv2.SyncC
return nil, status.Errorf(codes.Internal, "Could not get duties: %v", err)
}
isOptimistic, err := rpchelpers.IsOptimistic(ctx, st, vs.OptimisticModeFetcher)
isOptimistic, err := rpchelpers.IsOptimistic(
ctx,
[]byte(strconv.FormatUint(uint64(slot), 10)),
vs.OptimisticModeFetcher,
vs.StateFetcher,
vs.ChainInfoFetcher,
vs.BeaconDB,
)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
}

View File

@@ -466,7 +466,6 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
}
require.NoError(t, st.SetNextSyncCommittee(nextCommittee))
db := dbutil.SetupDB(t)
mockChainService := &mockChain.ChainService{Genesis: genesisTime}
vs := &Server{
@@ -636,6 +635,10 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
})
t.Run("execution optimistic", func(t *testing.T) {
db := dbutil.SetupDB(t)
require.NoError(t, db.SaveStateSummary(ctx, &ethpbalpha.StateSummary{Slot: 0, Root: []byte("root")}))
require.NoError(t, db.SaveLastValidatedCheckpoint(ctx, &ethpbalpha.Checkpoint{Epoch: 0, Root: []byte("root")}))
parentRoot := [32]byte{'a'}
blk := util.NewBeaconBlock()
blk.Block.ParentRoot = parentRoot[:]
@@ -644,16 +647,34 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
util.SaveBlock(t, ctx, db, blk)
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
mockChainService := &mockChain.ChainService{Genesis: genesisTime, Optimistic: true}
slot, err := slots.EpochStart(1)
require.NoError(t, err)
state, err := util.NewBeaconStateBellatrix()
require.NoError(t, err)
require.NoError(t, state.SetSlot(slot))
mockChainService := &mockChain.ChainService{
Genesis: genesisTime,
Optimistic: true,
Slot: &slot,
FinalizedCheckPoint: &ethpbalpha.Checkpoint{
Root: root[:],
Epoch: 1,
},
State: state,
}
vs := &Server{
StateFetcher: &testutil.MockFetcher{BeaconState: st},
SyncChecker: &mockSync.Sync{IsSyncing: false},
TimeFetcher: mockChainService,
HeadFetcher: mockChainService,
OptimisticModeFetcher: mockChainService,
ChainInfoFetcher: mockChainService,
BeaconDB: db,
}
req := &ethpbv2.SyncCommitteeDutiesRequest{
Epoch: 0,
Epoch: 1,
Index: []primitives.ValidatorIndex{1},
}
resp, err := vs.GetSyncCommitteeDuties(ctx, req)

View File

@@ -244,6 +244,8 @@ func (s *Service) Start() {
},
SyncCommitteePool: s.cfg.SyncCommitteeObjectPool,
ProposerSlotIndexCache: s.cfg.ProposerIdsCache,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
BeaconDB: s.cfg.BeaconDB,
}
nodeServer := &nodev1alpha1.Server{
@@ -362,6 +364,7 @@ func (s *Service) Start() {
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
ForkFetcher: s.cfg.ForkFetcher,
FinalizationFetcher: s.cfg.FinalizationFetcher,
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
}
ethpbv1alpha1.RegisterDebugServer(s.grpcServer, debugServer)
ethpbservice.RegisterBeaconDebugServer(s.grpcServer, debugServerV1)

View File

@@ -100,6 +100,7 @@ type blockchainService interface {
blockchain.CanonicalFetcher
blockchain.OptimisticModeFetcher
blockchain.SlashingReceiver
blockchain.ForkchoiceFetcher
}
// Service is responsible for handling all run time p2p related operations as the

View File

@@ -1286,12 +1286,17 @@ func Test_validateBellatrixBeaconBlockParentValidation(t *testing.T) {
msg.Signature, err = signing.ComputeDomainAndSign(beaconState, 0, msg.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[proposerIdx])
require.NoError(t, err)
blk, err := blocks.NewSignedBeaconBlock(msg)
require.NoError(t, err)
chainService := &mock.ChainService{Genesis: time.Unix(int64(beaconState.GenesisTime()), 0),
Optimistic: true,
OptimisticRoots: make(map[[32]byte]bool),
FinalizedCheckPoint: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
}}
chainService.OptimisticRoots[blk.Block().ParentRoot()] = true
r := &Service{
cfg: &config{
beaconDB: db,
@@ -1304,9 +1309,6 @@ func Test_validateBellatrixBeaconBlockParentValidation(t *testing.T) {
seenBlockCache: lruwrpr.New(10),
badBlockCache: lruwrpr.New(10),
}
blk, err := blocks.NewSignedBeaconBlock(msg)
require.NoError(t, err)
require.ErrorContains(t, "parent of the block is optimistic", r.validateBellatrixBeaconBlock(ctx, beaconState, blk.Block()))
}

View File

@@ -210,7 +210,7 @@ var mainnetBeaconConfig = &BeaconChainConfig{
BellatrixForkVersion: []byte{2, 0, 0, 0},
BellatrixForkEpoch: mainnetBellatrixForkEpoch,
CapellaForkVersion: []byte{3, 0, 0, 0},
CapellaForkEpoch: math.MaxUint64,
CapellaForkEpoch: 194048,
// New values introduced in Altair hard fork 1.
// Participation flag indices.