prysm-pulse/validator/client/beacon-api/stream_blocks.go
james-prysm d6ae838bbf
replace receive slot with event stream (#13563)
* WIP

* event stream wip

* returning nil

* temp removing some tests

* wip health checks

* fixing conficts

* updating fields based on linting

* fixing more errors

* fixing mocks

* fixing more mocks

* fixing more linting

* removing white space for lint

* fixing log format

* gaz

* reverting changes on grpc

* fixing unit tests

* adding in tests for health tracker and event stream

* adding more tests for streaming slot

* gaz

* Update api/client/event/event_stream.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* review comments

* Update validator/client/runner.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/beacon-api/beacon_api_validator_client.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* addressing radek comments

* Update validator/client/validator.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* addressing review feedback

* moving things to below next slot ticker

* fixing tests

* update naming

* adding TODO comment

* Update api/client/beacon/health.go

Co-authored-by: Radosław Kapka <rkapka@wp.pl>

* addressing comments

* fixing broken linting

* fixing more import issues

* fixing more import issues

* linting

* updating based on radek's comments

* addressing more comments

* fixing nogo error

* fixing duplicate import

* gaz

* adding radek's review suggestion

* Update proto/prysm/v1alpha1/node.proto

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* preston review comments

* Update api/client/event/event_stream.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* Update validator/client/validator.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* addressing some more preston review items

* fixing tests for linting

* fixing missed linting

* updating based on feedback to simplify

* adding interface check at the top

* reverting some comments

* cleaning up intatiations

* reworking the health tracker

* fixing linting

* fixing more linting to adhear to interface

* adding interface check at the the top of the file

* fixing unit tests

* attempting to fix dependency cycle

* addressing radek's comment

* Update validator/client/beacon-api/beacon_api_validator_client.go

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>

* adding more tests and feedback items

* fixing TODO comment

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2024-03-13 13:01:05 +00:00

215 lines
7.0 KiB
Go

package beacon_api
import (
"bytes"
"context"
"encoding/json"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"google.golang.org/grpc"
)
type abstractSignedBlockResponseJson struct {
Version string `json:"version" enum:"true"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data json.RawMessage `json:"data"`
}
type streamBlocksAltairClient struct {
grpc.ClientStream
ctx context.Context
beaconApiClient beaconApiValidatorClient
streamBlocksRequest *ethpb.StreamBlocksRequest
prevBlockSlot primitives.Slot
pingDelay time.Duration
}
type headSignedBeaconBlockResult struct {
streamBlocksResponse *ethpb.StreamBlocksResponse
executionOptimistic bool
slot primitives.Slot
}
func (c beaconApiValidatorClient) streamBlocks(ctx context.Context, in *ethpb.StreamBlocksRequest, pingDelay time.Duration) ethpb.BeaconNodeValidator_StreamBlocksAltairClient {
return &streamBlocksAltairClient{
ctx: ctx,
beaconApiClient: c,
streamBlocksRequest: in,
pingDelay: pingDelay,
}
}
func (c *streamBlocksAltairClient) Recv() (*ethpb.StreamBlocksResponse, error) {
result, err := c.beaconApiClient.getHeadSignedBeaconBlock(c.ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to get latest signed block")
}
// We keep querying the beacon chain for the latest block until we receive a new slot
for (c.streamBlocksRequest.VerifiedOnly && result.executionOptimistic) || c.prevBlockSlot == result.slot {
select {
case <-time.After(c.pingDelay):
result, err = c.beaconApiClient.getHeadSignedBeaconBlock(c.ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to get latest signed block")
}
case <-c.ctx.Done():
return nil, errors.New("context canceled")
}
}
c.prevBlockSlot = result.slot
return result.streamBlocksResponse, nil
}
func (c beaconApiValidatorClient) getHeadSignedBeaconBlock(ctx context.Context) (*headSignedBeaconBlockResult, error) {
// Since we don't know yet what the json looks like, we unmarshal into an abstract structure that has only a version
// and a blob of data
signedBlockResponseJson := abstractSignedBlockResponseJson{}
if err := c.jsonRestHandler.Get(ctx, "/eth/v2/beacon/blocks/head", &signedBlockResponseJson); err != nil {
return nil, err
}
// Once we know what the consensus version is, we can go ahead and unmarshal into the specific structs unique to each version
decoder := json.NewDecoder(bytes.NewReader(signedBlockResponseJson.Data))
response := &ethpb.StreamBlocksResponse{}
var slot primitives.Slot
switch signedBlockResponseJson.Version {
case "phase0":
jsonPhase0Block := structs.SignedBeaconBlock{}
if err := decoder.Decode(&jsonPhase0Block); err != nil {
return nil, errors.Wrap(err, "failed to decode signed phase0 block response json")
}
phase0Block, err := c.beaconBlockConverter.ConvertRESTPhase0BlockToProto(jsonPhase0Block.Message)
if err != nil {
return nil, errors.Wrap(err, "failed to get signed phase0 block")
}
decodedSignature, err := hexutil.Decode(jsonPhase0Block.Signature)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode phase0 block signature `%s`", jsonPhase0Block.Signature)
}
response.Block = &ethpb.StreamBlocksResponse_Phase0Block{
Phase0Block: &ethpb.SignedBeaconBlock{
Signature: decodedSignature,
Block: phase0Block,
},
}
slot = phase0Block.Slot
case "altair":
jsonAltairBlock := structs.SignedBeaconBlockAltair{}
if err := decoder.Decode(&jsonAltairBlock); err != nil {
return nil, errors.Wrap(err, "failed to decode signed altair block response json")
}
altairBlock, err := c.beaconBlockConverter.ConvertRESTAltairBlockToProto(jsonAltairBlock.Message)
if err != nil {
return nil, errors.Wrap(err, "failed to get signed altair block")
}
decodedSignature, err := hexutil.Decode(jsonAltairBlock.Signature)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode altair block signature `%s`", jsonAltairBlock.Signature)
}
response.Block = &ethpb.StreamBlocksResponse_AltairBlock{
AltairBlock: &ethpb.SignedBeaconBlockAltair{
Signature: decodedSignature,
Block: altairBlock,
},
}
slot = altairBlock.Slot
case "bellatrix":
jsonBellatrixBlock := structs.SignedBeaconBlockBellatrix{}
if err := decoder.Decode(&jsonBellatrixBlock); err != nil {
return nil, errors.Wrap(err, "failed to decode signed bellatrix block response json")
}
bellatrixBlock, err := c.beaconBlockConverter.ConvertRESTBellatrixBlockToProto(jsonBellatrixBlock.Message)
if err != nil {
return nil, errors.Wrap(err, "failed to get signed bellatrix block")
}
decodedSignature, err := hexutil.Decode(jsonBellatrixBlock.Signature)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode bellatrix block signature `%s`", jsonBellatrixBlock.Signature)
}
response.Block = &ethpb.StreamBlocksResponse_BellatrixBlock{
BellatrixBlock: &ethpb.SignedBeaconBlockBellatrix{
Signature: decodedSignature,
Block: bellatrixBlock,
},
}
slot = bellatrixBlock.Slot
case "capella":
jsonCapellaBlock := structs.SignedBeaconBlockCapella{}
if err := decoder.Decode(&jsonCapellaBlock); err != nil {
return nil, errors.Wrap(err, "failed to decode signed capella block response json")
}
capellaBlock, err := c.beaconBlockConverter.ConvertRESTCapellaBlockToProto(jsonCapellaBlock.Message)
if err != nil {
return nil, errors.Wrap(err, "failed to get signed capella block")
}
decodedSignature, err := hexutil.Decode(jsonCapellaBlock.Signature)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode capella block signature `%s`", jsonCapellaBlock.Signature)
}
response.Block = &ethpb.StreamBlocksResponse_CapellaBlock{
CapellaBlock: &ethpb.SignedBeaconBlockCapella{
Signature: decodedSignature,
Block: capellaBlock,
},
}
slot = capellaBlock.Slot
case "deneb":
jsonDenebBlock := structs.SignedBeaconBlockDeneb{}
if err := decoder.Decode(&jsonDenebBlock); err != nil {
return nil, errors.Wrap(err, "failed to decode signed deneb block response json")
}
denebBlock, err := jsonDenebBlock.ToConsensus()
if err != nil {
return nil, errors.Wrap(err, "failed to get signed deneb block")
}
response.Block = &ethpb.StreamBlocksResponse_DenebBlock{
DenebBlock: &ethpb.SignedBeaconBlockDeneb{
Signature: denebBlock.Signature,
Block: denebBlock.Block,
},
}
slot = denebBlock.Block.Slot
default:
return nil, errors.Errorf("unsupported consensus version `%s`", signedBlockResponseJson.Version)
}
return &headSignedBeaconBlockResult{
streamBlocksResponse: response,
executionOptimistic: signedBlockResponseJson.ExecutionOptimistic,
slot: slot,
}, nil
}