erigon-pulse/cl/beacon/handler/states.go
2024-01-01 22:18:11 +01:00

420 lines
14 KiB
Go

package handler
import (
"context"
"fmt"
"net/http"
"strconv"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/cl/beacon/beaconhttp"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cl/cltypes"
"github.com/ledgerwatch/erigon/cl/cltypes/solid"
"github.com/ledgerwatch/erigon/cl/persistence/beacon_indicies"
state_accessors "github.com/ledgerwatch/erigon/cl/persistence/state"
"github.com/ledgerwatch/erigon/cl/utils"
)
func (a *ApiHandler) blockRootFromStateId(ctx context.Context, tx kv.Tx, stateId *segmentID) (root libcommon.Hash, httpStatusErr int, err error) {
switch {
case stateId.head():
root, _, err = a.forkchoiceStore.GetHead()
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
return
case stateId.finalized():
root = a.forkchoiceStore.FinalizedCheckpoint().BlockRoot()
return
case stateId.justified():
root = a.forkchoiceStore.JustifiedCheckpoint().BlockRoot()
return
case stateId.genesis():
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, 0)
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
if root == (libcommon.Hash{}) {
return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("genesis block not found")
}
return
case stateId.getSlot() != nil:
root, err = beacon_indicies.ReadCanonicalBlockRoot(tx, *stateId.getSlot())
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
if root == (libcommon.Hash{}) {
return libcommon.Hash{}, http.StatusNotFound, fmt.Errorf("block not found %d", *stateId.getSlot())
}
return
case stateId.getRoot() != nil:
root, err = beacon_indicies.ReadBlockRootByStateRoot(tx, *stateId.getRoot())
if err != nil {
return libcommon.Hash{}, http.StatusInternalServerError, err
}
return
default:
return libcommon.Hash{}, http.StatusInternalServerError, fmt.Errorf("cannot parse state id")
}
}
type rootResponse struct {
Root libcommon.Hash `json:"root"`
}
func previousVersion(v clparams.StateVersion) clparams.StateVersion {
if v == clparams.Phase0Version {
return clparams.Phase0Version
}
return v - 1
}
func (a *ApiHandler) getStateFork(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
root, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
if err != nil {
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, root)
if err != nil {
return nil, err
}
if slot == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", root))
}
epoch := *slot / a.beaconChainCfg.SlotsPerEpoch
stateVersion := a.beaconChainCfg.GetCurrentStateVersion(epoch)
forkEpoch := a.beaconChainCfg.GetForkEpochByVersion(stateVersion)
currentVersion := a.beaconChainCfg.GetForkVersionByVersion(stateVersion)
previousVersion := a.beaconChainCfg.GetForkVersionByVersion(previousVersion(stateVersion))
return newBeaconResponse(&cltypes.Fork{
PreviousVersion: utils.Uint32ToBytes4(previousVersion),
CurrentVersion: utils.Uint32ToBytes4(currentVersion),
Epoch: forkEpoch,
}), nil
}
func (a *ApiHandler) getStateRoot(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
root, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
if err != nil {
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
stateRoot, err := beacon_indicies.ReadStateRootByBlockRoot(ctx, tx, root)
if err != nil {
return nil, err
}
if stateRoot == (libcommon.Hash{}) {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block header: %x", root))
}
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, root)
if err != nil {
return nil, err
}
if slot == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block header: %x", root))
}
canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, *slot)
if err != nil {
return nil, err
}
return newBeaconResponse(&rootResponse{Root: stateRoot}).
withFinalized(canonicalRoot == root && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
}
func (a *ApiHandler) getFullState(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
if err != nil {
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
state, err := a.forkchoiceStore.GetStateAtBlockRoot(blockRoot, true)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
if state == nil {
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
if err != nil {
return nil, err
}
// Sanity checks slot and canonical data.
if slot == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", blockRoot))
}
canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, *slot)
if err != nil {
return nil, err
}
if canonicalRoot != blockRoot {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read state: %x", blockRoot))
}
state, err := a.stateReader.ReadHistoricalState(ctx, tx, *slot)
if err != nil {
return nil, err
}
if state == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read state: %x", blockRoot))
}
return newBeaconResponse(state).withFinalized(true).withVersion(state.Version()), nil
}
return newBeaconResponse(state).withFinalized(false).withVersion(state.Version()), nil
}
type finalityCheckpointsResponse struct {
FinalizedCheckpoint solid.Checkpoint `json:"finalized_checkpoint"`
CurrentJustifiedCheckpoint solid.Checkpoint `json:"current_justified_checkpoint"`
PreviousJustifiedCheckpoint solid.Checkpoint `json:"previous_justified_checkpoint"`
}
func (a *ApiHandler) getFinalityCheckpoints(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
if err != nil {
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
if err != nil {
return nil, err
}
if slot == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", blockRoot))
}
ok, finalizedCheckpoint, currentJustifiedCheckpoint, previousJustifiedCheckpoint := a.forkchoiceStore.GetFinalityCheckpoints(blockRoot)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
if !ok {
currentJustifiedCheckpoint, previousJustifiedCheckpoint, finalizedCheckpoint, err = state_accessors.ReadCheckpoints(tx, a.beaconChainCfg.RoundSlotToEpoch(*slot))
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
if currentJustifiedCheckpoint == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read checkpoints: %x, %d", blockRoot, a.beaconChainCfg.RoundSlotToEpoch(*slot)))
}
}
version := a.beaconChainCfg.GetCurrentStateVersion(*slot / a.beaconChainCfg.SlotsPerEpoch)
canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, *slot)
if err != nil {
return nil, err
}
return newBeaconResponse(finalityCheckpointsResponse{
FinalizedCheckpoint: finalizedCheckpoint,
CurrentJustifiedCheckpoint: currentJustifiedCheckpoint,
PreviousJustifiedCheckpoint: previousJustifiedCheckpoint,
}).withFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()).withVersion(version), nil
}
type syncCommitteesResponse struct {
Validators []string `json:"validators"`
ValidatorAggregates [][]string `json:"validator_aggregates"`
}
func (a *ApiHandler) getSyncCommittees(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
if err != nil {
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
slot, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
if err != nil {
return nil, err
}
if slot == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", blockRoot))
}
// Code here
currentSyncCommittee, nextSyncCommittee, ok := a.forkchoiceStore.GetSyncCommittees(blockRoot)
if !ok {
syncCommitteeSlot := a.beaconChainCfg.RoundSlotToSyncCommitteePeriod(*slot)
// Check the main database if it cannot be found in the forkchoice store
currentSyncCommittee, err = state_accessors.ReadCurrentSyncCommittee(tx, syncCommitteeSlot)
if err != nil {
return nil, err
}
nextSyncCommittee, err = state_accessors.ReadNextSyncCommittee(tx, syncCommitteeSlot)
if err != nil {
return nil, err
}
if currentSyncCommittee == nil || nextSyncCommittee == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read sync committees: %x, %d", blockRoot, *slot))
}
}
// Now fetch the data we need
statePeriod := a.beaconChainCfg.SyncCommitteePeriod(*slot)
queryEpoch, err := uint64FromQueryParams(r, "epoch")
if err != nil {
return nil, err
}
committee := currentSyncCommittee.GetCommittee()
if queryEpoch != nil {
requestPeriod := a.beaconChainCfg.SyncCommitteePeriod(*queryEpoch * a.beaconChainCfg.SlotsPerEpoch)
if requestPeriod == statePeriod+1 {
committee = nextSyncCommittee.GetCommittee()
} else if requestPeriod != statePeriod {
return nil, fmt.Errorf("Epoch is outside the sync committee period of the state")
}
}
// Lastly construct the response
validatorsPerSubcommittee := a.beaconChainCfg.SyncCommitteeSize / a.beaconChainCfg.SyncCommitteeSubnetCount
response := syncCommitteesResponse{
Validators: make([]string, a.beaconChainCfg.SyncCommitteeSize),
ValidatorAggregates: make([][]string, a.beaconChainCfg.SyncCommitteeSubnetCount),
}
for i, publicKey := range committee {
// get the validator index of the committee
validatorIndex, ok, err := state_accessors.ReadValidatorIndexByPublicKey(tx, publicKey)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("could not read validator index: %x", publicKey)
}
idx := strconv.FormatInt(int64(validatorIndex), 10)
response.Validators[i] = idx
// add the index to the subcommittee
subCommitteeIndex := uint64(i) / validatorsPerSubcommittee
if len(response.ValidatorAggregates[subCommitteeIndex]) == 0 {
response.ValidatorAggregates[subCommitteeIndex] = make([]string, validatorsPerSubcommittee)
}
response.ValidatorAggregates[subCommitteeIndex][uint64(i)%validatorsPerSubcommittee] = idx
}
canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, *slot)
if err != nil {
return nil, err
}
return newBeaconResponse(response).withFinalized(canonicalRoot == blockRoot && *slot <= a.forkchoiceStore.FinalizedSlot()), nil
}
type randaoResponse struct {
Randao libcommon.Hash `json:"randao"`
}
func (a *ApiHandler) getRandao(w http.ResponseWriter, r *http.Request) (*beaconResponse, error) {
ctx := r.Context()
tx, err := a.indiciesDB.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
blockId, err := stateIdFromRequest(r)
if err != nil {
return nil, beaconhttp.NewEndpointError(http.StatusBadRequest, err.Error())
}
blockRoot, httpStatus, err := a.blockRootFromStateId(ctx, tx, blockId)
if err != nil {
return nil, beaconhttp.NewEndpointError(httpStatus, err.Error())
}
epochReq, err := uint64FromQueryParams(r, "epoch")
if err != nil {
return nil, err
}
slotPtr, err := beacon_indicies.ReadBlockSlotByBlockRoot(tx, blockRoot)
if err != nil {
return nil, err
}
if slotPtr == nil {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read block slot: %x", blockRoot))
}
slot := *slotPtr
epoch := slot / a.beaconChainCfg.SlotsPerEpoch
if epochReq != nil {
epoch = *epochReq
}
randaoMixes := a.randaoMixesPool.Get().(solid.HashListSSZ)
defer a.randaoMixesPool.Put(randaoMixes)
if a.forkchoiceStore.RandaoMixes(blockRoot, randaoMixes) {
mix := randaoMixes.Get(int(epoch % a.beaconChainCfg.EpochsPerHistoricalVector))
return newBeaconResponse(randaoResponse{Randao: mix}).withFinalized(slot <= a.forkchoiceStore.FinalizedSlot()), nil
}
// check if the block is canonical
canonicalRoot, err := beacon_indicies.ReadCanonicalBlockRoot(tx, slot)
if err != nil {
return nil, err
}
if canonicalRoot != blockRoot {
return nil, beaconhttp.NewEndpointError(http.StatusNotFound, fmt.Sprintf("could not read randao: %x", blockRoot))
}
mix, err := a.stateReader.ReadRandaoMixBySlotAndIndex(tx, slot, epoch%a.beaconChainCfg.EpochsPerHistoricalVector)
if err != nil {
return nil, err
}
return newBeaconResponse(randaoResponse{Randao: mix}).withFinalized(slot <= a.forkchoiceStore.FinalizedSlot()), nil
}