prysm-pulse/beacon-chain/rpc/statefetcher/fetcher.go
Radosław Kapka 12403d249f
[Feature] - API Middleware (#8926)
* HTTP proxy server for Eth2 APIs (#8904)

* Implement API HTTP proxy server

* cleanup + more comments

* gateway will no longer be dependent on beaconv1

* handle error during ErrorJson type assertion

* simplify handling of endpoint data

* fix mux v1 route

* use URL encoding for all requests

* comment fieldProcessor

* fix failing test

* change proxy port to not interfere with e2e

* gzl

* simplify conditional expression

* Move appending custom error header to grpcutils package

* add api-middleware-port flag

* fix documentation for processField

* modify e2e port

* change field processing error message

* better error message for field processing

* simplify base64ToHexProcessor

* fix json structs

* Run several new endpoints through API middleware (#8922)

* Implement API HTTP proxy server

* cleanup + more comments

* gateway will no longer be dependent on beaconv1

* handle error during ErrorJson type assertion

* simplify handling of endpoint data

* fix mux v1 route

* use URL encoding for all requests

* comment fieldProcessor

* fix failing test

* change proxy port to not interfere with e2e

* gzl

* simplify conditional expression

* Move appending custom error header to grpcutils package

* add api-middleware-port flag

* fix documentation for processField

* modify e2e port

* change field processing error message

* better error message for field processing

* simplify base64ToHexProcessor

* fix json structs

* /eth/v1/beacon/states/{state_id}/validators

* /eth/v1/beacon/states/{state_id}/validators/{validator_id}

* /eth/v1/beacon/states/{state_id}/validator_balances

* /eth/v1/beacon/states/{state_id}/committees

* allow skipping base64-encoding for query params

* /eth/v1/beacon/pool/attestations

* replace break with continue

* Remove unused functions (#8924)

Co-authored-by: terence tsao <terence@prysmaticlabs.com>

* Process SSZ-serialized beacon state through API middleware (#8925)

* update field names

* Process SSZ-serialized beacon state through API middleware

* revert changes to go.mod and go.sum

* Revert "Merge branch '__develop' into feature/api-middleware"

This reverts commit 7c739a8fd71e2c1e3a14be85abd29a59b57ae9b5, reversing
changes made to 2d0f8e012ecb006888ed8e826b45625a3edc2eeb.

* update ethereumapis

* update validator field name

* update deps.bzl

* update json tags (#8942)

* Run `/node/syncing` through API Middleware (#8944)

* add IsSyncing field to grpc response

* run /node/syncing through the middleware

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Return HTTP status codes other than 200 and 500 from node and debug endpoints (#8937)

* error codes for node endpoints

* error codes for debug endpoints

* better comment about headers

* gzl

* review comments

* comment on return value

* update fakeChecker used for fuzz tests

* fix failing tests

* Allow to pass URL params literally, without encoding to base64 (#8938)

* Allow to pass URL params literally, without encoding to base64

* fix compile error

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Process SSZ-serialized beacon state through API middleware (#8925)

* update field names

* Process SSZ-serialized beacon state through API middleware

* revert changes to go.mod and go.sum

* Revert "Merge branch '__develop' into feature/api-middleware"

This reverts commit 7c739a8fd71e2c1e3a14be85abd29a59b57ae9b5, reversing
changes made to 2d0f8e012ecb006888ed8e826b45625a3edc2eeb.

* update ethereumapis

* update validator field name

* update deps.bzl

* update json tags (#8942)

* Run `/node/syncing` through API Middleware (#8944)

* add IsSyncing field to grpc response

* run /node/syncing through the middleware

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Return HTTP status codes other than 200 and 500 from node and debug endpoints (#8937)

* error codes for node endpoints

* error codes for debug endpoints

* better comment about headers

* gzl

* review comments

* comment on return value

* update fakeChecker used for fuzz tests

* fix failing tests

* Allow to pass URL params literally, without encoding to base64 (#8938)

* Allow to pass URL params literally, without encoding to base64

* fix compile error

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* unused import

* Return correct status codes from beacon endpoints (#8960)

* Various API Middleware fixes (#8963)

* Return correct status codes from `/states` endpoints

* better error messages in debug and node

* better error messages in state

* returning correct error codes from validator endpoints

* correct error codes for getting a block header

* gzl

* fix err variable name

* fix nil block comparison

* test fixes

* make status enum test better

* fix ineffectual assignment

* make PR unstuck

* return proper status codes

* return uppercase keys from /config/spec

* return lowercase validator status

* convert requested enum values to uppercase

* validator fixes

* Implement `/beacon/headers` endpoint (#8966)

* Refactor API Middleware into more manageable code  (#8984)

* move endpoint registration out of shared package

* divide main function into smaller components

* return early on error

* implement hooks

* implement custom handlers and add documentation

* fix test compile error

* restrict package visibility

* remove redundant error checking

* rename file

* API Middleware unit tests (#8998)

* move endpoint registration out of shared package

* divide main function into smaller components

* return early on error

* implement hooks

* implement custom handlers and add documentation

* fix test compile error

* restrict package visibility

* remove redundant error checking

* rename file

* api_middleware_processing

* endpoints

* gzl

* remove gazelle:ignore

* merge

* Implement SSZ version of `/blocks/{block_id}` (#8970)

* Implement SSZ version of `/blocks/{block_id}`

* add dependencies back

* fix indentation in deps.bzl

* parameterize ssz functions

* get block ssz

* update ethereumapis dependency

* gzl

* Do not reuse `Endpoint` structs between API calls (#9007)

* code refactor

* implement endpoint factory

* fix test

* fmt

* include pbs

* gaz

* test naming fixes

* remove unused code

* radek comments

* revert endpoint test

* bring back bytes test case

* move `signedBeaconBlock` to `migration` package

* change `fmt.Errorf` to `errors.Wrap`

* capitalize SSZ

* capitalize URL

* more review feedback

* rename `handleGetBlockSSZ` to `handleGetBeaconBlockSSZ`

* rename `IndexOutOfRangeError` to `ValidatorIndexOutOfRangeError`

* simplify parameter names

* test header

* more corrections

* properly allocate array capacity

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
Co-authored-by: Nishant Das <nishdas93@gmail.com>
2021-06-15 10:28:49 -05:00

291 lines
8.6 KiB
Go

package statefetcher
import (
"bytes"
"context"
"fmt"
"strconv"
"strings"
"github.com/pkg/errors"
types "github.com/prysmaticlabs/eth2-types"
"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/beacon-chain/db"
iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/shared/bytesutil"
)
// StateIdParseError represents an error scenario where a state ID could not be parsed.
type StateIdParseError struct {
message string
}
// NewStateIdParseError creates a new error instance.
func NewStateIdParseError(reason error) StateIdParseError {
return StateIdParseError{
message: errors.Wrapf(reason, "could not parse state ID").Error(),
}
}
// Error returns the underlying error message.
func (e *StateIdParseError) Error() string {
return e.message
}
// StateNotFoundError represents an error scenario where a state could not be found.
type StateNotFoundError struct {
message string
}
// NewStateNotFoundError creates a new error instance.
func NewStateNotFoundError(stateRootsSize int) StateNotFoundError {
return StateNotFoundError{
message: fmt.Sprintf("state not found in the last %d state roots", stateRootsSize),
}
}
// Error returns the underlying error message.
func (e *StateNotFoundError) Error() string {
return e.message
}
// StateRootNotFoundError represents an error scenario where a state root could not be found.
type StateRootNotFoundError struct {
message string
}
// NewStateRootNotFoundError creates a new error instance.
func NewStateRootNotFoundError(stateRootsSize int) StateNotFoundError {
return StateNotFoundError{
message: fmt.Sprintf("state root not found in the last %d state roots", stateRootsSize),
}
}
// Error returns the underlying error message.
func (e *StateRootNotFoundError) Error() string {
return e.message
}
// Fetcher is responsible for retrieving info related with the beacon chain.
type Fetcher interface {
State(ctx context.Context, stateId []byte) (iface.BeaconState, error)
StateRoot(ctx context.Context, stateId []byte) ([]byte, error)
}
// StateProvider is a real implementation of Fetcher.
type StateProvider struct {
BeaconDB db.ReadOnlyDatabase
ChainInfoFetcher blockchain.ChainInfoFetcher
GenesisTimeFetcher blockchain.TimeFetcher
StateGenService stategen.StateManager
}
// State returns the BeaconState for a given identifier. The identifier can be one of:
// - "head" (canonical head in node's view)
// - "genesis"
// - "finalized"
// - "justified"
// - <slot>
// - <hex encoded state root with '0x' prefix>
func (p *StateProvider) State(ctx context.Context, stateId []byte) (iface.BeaconState, error) {
var (
s iface.BeaconState
err error
)
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head":
s, err = p.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head state")
}
case "genesis":
s, err = p.BeaconDB.GenesisState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get genesis state")
}
case "finalized":
checkpoint := p.ChainInfoFetcher.FinalizedCheckpt()
s, err = p.StateGenService.StateByRoot(ctx, bytesutil.ToBytes32(checkpoint.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get finalized state")
}
case "justified":
checkpoint := p.ChainInfoFetcher.CurrentJustifiedCheckpt()
s, err = p.StateGenService.StateByRoot(ctx, bytesutil.ToBytes32(checkpoint.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get justified state")
}
default:
if len(stateId) == 32 {
s, err = p.stateByHex(ctx, stateId)
} else {
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
// ID format does not match any valid options.
e := NewStateIdParseError(parseErr)
return nil, &e
}
s, err = p.stateBySlot(ctx, types.Slot(slotNumber))
}
}
return s, err
}
// StateRoot returns a beacon state root for a given identifier. The identifier can be one of:
// - "head" (canonical head in node's view)
// - "genesis"
// - "finalized"
// - "justified"
// - <slot>
// - <hex encoded state root with '0x' prefix>
func (p *StateProvider) StateRoot(ctx context.Context, stateId []byte) (root []byte, err error) {
stateIdString := strings.ToLower(string(stateId))
switch stateIdString {
case "head":
root, err = p.headStateRoot(ctx)
case "genesis":
root, err = p.genesisStateRoot(ctx)
case "finalized":
root, err = p.finalizedStateRoot(ctx)
case "justified":
root, err = p.justifiedStateRoot(ctx)
default:
if len(stateId) == 32 {
root, err = p.stateRootByHex(ctx, stateId)
} else {
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)
if parseErr != nil {
e := NewStateIdParseError(parseErr)
// ID format does not match any valid options.
return nil, &e
}
root, err = p.stateRootBySlot(ctx, types.Slot(slotNumber))
}
}
return root, err
}
func (p *StateProvider) stateByHex(ctx context.Context, stateId []byte) (iface.BeaconState, error) {
headState, err := p.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head state")
}
for i, root := range headState.StateRoots() {
if bytes.Equal(root, stateId) {
blockRoot := headState.BlockRoots()[i]
return p.StateGenService.StateByRoot(ctx, bytesutil.ToBytes32(blockRoot))
}
}
stateNotFoundErr := NewStateNotFoundError(len(headState.StateRoots()))
return nil, &stateNotFoundErr
}
func (p *StateProvider) stateBySlot(ctx context.Context, slot types.Slot) (iface.BeaconState, error) {
currentSlot := p.GenesisTimeFetcher.CurrentSlot()
if slot > currentSlot {
return nil, errors.New("slot cannot be in the future")
}
state, err := p.StateGenService.StateBySlot(ctx, slot)
if err != nil {
return nil, errors.Wrap(err, "could not get state")
}
return state, nil
}
func (p *StateProvider) headStateRoot(ctx context.Context) ([]byte, error) {
b, err := p.ChainInfoFetcher.HeadBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) genesisStateRoot(ctx context.Context) ([]byte, error) {
b, err := p.BeaconDB.GenesisBlock(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get genesis block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) finalizedStateRoot(ctx context.Context) ([]byte, error) {
cp, err := p.BeaconDB.FinalizedCheckpoint(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get finalized checkpoint")
}
b, err := p.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get finalized block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) justifiedStateRoot(ctx context.Context) ([]byte, error) {
cp, err := p.BeaconDB.JustifiedCheckpoint(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get justified checkpoint")
}
b, err := p.BeaconDB.Block(ctx, bytesutil.ToBytes32(cp.Root))
if err != nil {
return nil, errors.Wrap(err, "could not get justified block")
}
if err := helpers.VerifyNilBeaconBlock(b); err != nil {
return nil, err
}
return b.Block().StateRoot(), nil
}
func (p *StateProvider) stateRootByHex(ctx context.Context, stateId []byte) ([]byte, error) {
var stateRoot [32]byte
copy(stateRoot[:], stateId)
headState, err := p.ChainInfoFetcher.HeadState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get head state")
}
for _, root := range headState.StateRoots() {
if bytes.Equal(root, stateRoot[:]) {
return stateRoot[:], nil
}
}
rootNotFoundErr := NewStateRootNotFoundError(len(headState.StateRoots()))
return nil, &rootNotFoundErr
}
func (p *StateProvider) stateRootBySlot(ctx context.Context, slot types.Slot) ([]byte, error) {
currentSlot := p.GenesisTimeFetcher.CurrentSlot()
if slot > currentSlot {
return nil, errors.New("slot cannot be in the future")
}
found, blks, err := p.BeaconDB.BlocksBySlot(ctx, slot)
if err != nil {
return nil, errors.Wrap(err, "could not get blocks")
}
if !found {
return nil, errors.New("no block exists")
}
if len(blks) != 1 {
return nil, errors.New("multiple blocks exist in same slot")
}
if blks[0] == nil || blks[0].Block() == nil {
return nil, errors.New("nil block")
}
return blks[0].Block().StateRoot(), nil
}