prysm-pulse/beacon-chain/rpc/eth/helpers/sync.go
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

194 lines
6.2 KiB
Go

package helpers
import (
"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/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/statefetcher"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync"
"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"
)
// ValidateSync checks whether the node is currently syncing and returns an error if it is.
// It also appends syncing info to gRPC headers.
func ValidateSync(
ctx context.Context,
syncChecker sync.Checker,
headFetcher blockchain.HeadFetcher,
timeFetcher blockchain.TimeFetcher,
optimisticModeFetcher blockchain.OptimisticModeFetcher,
) error {
if !syncChecker.Syncing() {
return nil
}
headSlot := headFetcher.HeadSlot()
isOptimistic, err := optimisticModeFetcher.IsOptimistic(ctx)
if err != nil {
return status.Errorf(codes.Internal, "Could not check optimistic status: %v", err)
}
syncDetailsContainer := &syncDetailsContainer{
SyncDetails: &SyncDetailsJson{
HeadSlot: strconv.FormatUint(uint64(headSlot), 10),
SyncDistance: strconv.FormatUint(uint64(timeFetcher.CurrentSlot()-headSlot), 10),
IsSyncing: true,
IsOptimistic: isOptimistic,
},
}
err = grpc.AppendCustomErrorHeader(ctx, syncDetailsContainer)
if err != nil {
return status.Errorf(
codes.Internal,
"Syncing to latest head, not ready to respond. Could not prepare sync details: %v",
err,
)
}
return status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
}
// 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))
}
}
}
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 true, errors.Wrap(err, "could not fetch state")
}
if st.Slot() == chainInfo.HeadSlot() {
return optimisticModeFetcher.IsOptimistic(ctx)
}
has, roots, err := database.BlockRootsBySlot(ctx, st.Slot())
if err != nil {
return true, errors.Wrapf(err, "could not get block roots for slot %d", st.Slot())
}
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.
type SyncDetailsJson struct {
HeadSlot string `json:"head_slot"`
SyncDistance string `json:"sync_distance"`
IsSyncing bool `json:"is_syncing"`
IsOptimistic bool `json:"is_optimistic"`
ElOffline bool `json:"el_offline"`
}
// SyncDetailsContainer is a wrapper for SyncDetails.
type syncDetailsContainer struct {
SyncDetails *SyncDetailsJson `json:"sync_details"`
}