Compare commits

...

18 Commits

Author SHA1 Message Date
Kasey Kirkham
34a46c3c12 wip 2024-09-11 13:07:37 -05:00
Kasey Kirkham
842969dfb8 go mod tidy 2024-09-10 16:46:56 -05:00
Kasey Kirkham
424a067f25 Radek and James feedback 2024-09-10 16:40:15 -05:00
Kasey Kirkham
c2ae175f92 sse implementation that sheds stuck clients 2024-09-09 15:02:21 -05:00
james-prysm
4c14bd8be2 looking at ways to reduce validator registration calls (#14371)
* looking at ways to reduce validator registration calls

* small mistake, should be epoch start

* adding more optimizations for reducing registration calls while covering more edgecases

* linting

* adding change log and force full push override

* fixing bug and adding tests

* changing if statement just to be safe

* potuz feedback for easier readability

* more review feedback for simplicity

* more review suggestions from potuz

* fix unit test

* reduce redundancy

* Update CHANGELOG.md

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

* small nitpick

* fixing typo

* updating logs

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2024-09-09 16:30:41 +00:00
Radosław Kapka
62b8e63a0a Compare with nil before invoking IsNil() on an interface (#14431)
* Compare with nil before invoking `IsNil()` on an interface

* changelog

* review
2024-09-06 19:39:33 +00:00
Rupam Dey
eec3b0b7fe feat: introduce Capella and Deneb full-node.md lc changes (#14376)
* feat: introduce Capella and Deneb `full-node.md` lc changes

* add switch-case and replace `[][]byte` with `[][]string`

* return version name in http header

* populate header and use `interfaces.ReadOnlyBeaconBlock`

* fix lint

* merge cases in switch case and replace `interfaces.ExecutionData` with `*ExecutionPayloadHeader`

* minor fixes

* refactor `createLightClientBootstrapCapella` and `createLightClientBootstrapDeneb`

* use lightclientheader instead of different versions

* fix failing `TestLightClientHandler_GetLightClientBootstrap` tests

* fix lint

* refactor handlers

* refactor handlers more

* refactor handlers even more

* create conversions_lightclient

* fix lint errors

* add deneb and capella proto headers

* update lightclientbootstrap proto struct to capella&deneb

* update usecases

* update usecases

* resolve panic in header.GetBeacon

* fix spacings

* refactor core/lightclient.go

* fix isBetterUpdate

* use errors.wrap instead of fmt.errorf

* changelog entry

* fix lint errors

* fix api structs to use json rawMessage

* inline unmarshal

* remove redundant nil check

* revert remove redundant nil check

* return error in newLightClientUpdateToJSON

* inline getExecutionData

* better error handling

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Inspector-Butters <mohamadbastin@gmail.com>
Co-authored-by: Bastin <43618253+Inspector-Butters@users.noreply.github.com>
2024-09-06 17:06:31 +00:00
james-prysm
2bffb83a00 gateway flag changes breaking release e2e (#14418)
* e2e release test breaks due to changes in flag naming

* changing approach to fix

* adding some small alias test

* fixing test, changelog, and flag name

* Update config_test.go

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

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2024-09-05 21:19:56 +00:00
james-prysm
45fd3eb1bf gRPC Gateway Removal (#14089)
* wip passing e2e

* reverting temp comment

* remove unneeded comments

* fixing merge errors

* fixing more bugs from merge

* fixing test

* WIP moving code around and fixing tests

* unused linting

* gaz

* temp removing these tests as we need placeholder/wrapper APIs for them with the removal of the gateway

* attempting to remove dependencies to gRPC gateway , 1 mroe left in deps.bzl

* renaming flags and other gateway services to http

* goimport

* fixing deepsource

* git mv

* Update validator/package/validator.yaml

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

* Update validator/package/validator.yaml

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

* Update cmd/beacon-chain/flags/base.go

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

* Update cmd/beacon-chain/flags/base.go

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

* Update cmd/beacon-chain/flags/base.go

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

* addressing feedback

* missed lint

* renaming import

* reversal based on feedback

* fixing web ui registration

* don't require mux handler

* gaz

* removing gRPC service from validator completely, merged with http service, renames are a work in progress

* updating go.sum

* linting

* trailing white space

* realized there was more cleanup i could do with code reuse

* adding wrapper for routes

* reverting version

* fixing dependencies from merging develop

* gaz

* fixing unit test

* fixing dependencies

* reverting unit test

* fixing conflict

* updating change log

* Update log.go

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

* gaz

* Update api/server/httprest/server.go

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

* addressing some feedback

* forgot to remove deprecated flag in usage

* gofmt

* fixing test

* fixing deepsource issue

* moving deprecated flag and adding timeout handler

* missed removal of a flag

* fixing test:

* Update CHANGELOG.md

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

* addressing feedback

* updating comments based on feedback

* removing unused field for now, we can add it back in if we need to use the option

* removing unused struct

* changing api-timeout flag based on feedback

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2024-09-04 15:40:31 +00:00
terence
963a1b4cb7 Fix electra balance update (#14410) 2024-09-03 14:41:40 +00:00
Rupam Dey
77c845043d fix: make some places use sync committee period instead of epoch (#14406)
* fix: make some places use sync committee period instead of epoch

* add fix details to `CHANGELOG.md`

* update `CHANGELOG.md` comment
2024-09-02 16:44:08 +00:00
Brandon Liu
342bb0fcef Add value for MaxBuilderEpochMissedSlots (#14334)
* add value for MaxBuilderEpochMissedSlots

* make usage for MaxBuilderEpochMisedSlots more friendly

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2024-08-30 15:08:50 +00:00
Preston Van Loon
93e6bd7929 Flip --enable-experimental-state to opt-out. (#14398) 2024-08-29 20:42:32 +00:00
Bastin
3015eea4e3 Fix lightclient header (#14389)
* change LCUpdate to use LCHeader

* fix api struct usages

* fix api struct finalized_header

* add lightclientheader to proto structs

* fix proto usages

* fix proto usages in events

* fix uppercase field in protobuf defenition

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2024-08-29 15:57:21 +00:00
Nishant Das
2f42f7e313 Add a Tracing Wrapper Package (#14207)
* Adds a wrapper package

* Gazelle

* Add in Empty Span

* Revert It Back

* Add back reference

* Set It As Empty

* fix missing import

* remove redundant alias

* remove unused

---------

Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>
Co-authored-by: Saolyn <sammy@rosso.red>
2024-08-27 20:00:53 +00:00
Preston Van Loon
a7c86a6d1b Update CONTRIBUTING.md to highlight no trivial changes (#14384) 2024-08-26 18:05:24 +00:00
Md Amaan
2399451869 Hardcoded GenesisValidatorsRoot (#14365)
* hardcoded GenesisValidatorsRoot

* added in mainnet.config

* updated desc

* added it in all testnets

* minor change

* added roots instead of empty and fn to compute byte32 from hex

* added in e2e testnet_config

* fixed test

* minor fix

* removed fn and added bytes output directly

* Add test for genesis validator root mainnet value

* removed root from minimal and testnet

* removed root

* Update CHANGELOG.md

* Fix bazel package visiblity

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
Co-authored-by: Preston Van Loon <preston@pvl.dev>
2024-08-26 16:50:29 +00:00
Taranpreet26311
19d9a1915d PR to update workflow check dependencies (#14379)
* PR to update workflow check dependencies

* Updated build checkout version to v4

* Updated to go 1.23.0

* Updated lint version to v1.60.3

* Revert to 1.22.3

* Updated go to 1.23

* revert

* Updated setup-go to v5

* Update lint to 1.60.2

* Revert changes

* Update Lint version to v1.60.3

* Update lint to go 1.23.0

* Update golanci.yml to 1.23.0

* Revert and keep to golang 1.22.4

* Disable mnd

* Downgrade to current version

* Add update to go 1.26

* Update to go 1.22.6

* Update .golangci.yml to 1.22.6
2024-08-26 16:25:38 +00:00
271 changed files with 6645 additions and 10532 deletions

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Go mod tidy checker
id: gomodtidy
@@ -27,11 +27,11 @@ jobs:
GO111MODULE: on
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Go 1.22
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: '1.22.3'
go-version: '1.22.6'
- name: Run Gosec Security Scanner
run: | # https://github.com/securego/gosec/issues/469
export PATH=$PATH:$(go env GOPATH)/bin
@@ -43,16 +43,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Go 1.22
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: '1.22.3'
go-version: '1.22.6'
id: go
- name: Golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v5
with:
version: v1.55.2
args: --config=.golangci.yml --out-${NO_FUTURE}format colored-line-number
@@ -62,13 +62,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: '1.22.3'
go-version: '1.22.6'
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Get dependencies
run: |

View File

@@ -6,7 +6,7 @@ run:
- proto
- tools/analyzers
timeout: 10m
go: '1.22.3'
go: '1.22.6'
linters:
enable-all: true

View File

@@ -55,13 +55,6 @@ alias(
visibility = ["//visibility:public"],
)
# Protobuf gRPC gateway compiler
alias(
name = "grpc_gateway_proto_compiler",
actual = "@com_github_grpc_ecosystem_grpc_gateway_v2//protoc-gen-grpc-gateway:go_gen_grpc_gateway",
visibility = ["//visibility:public"],
)
gometalinter(
name = "gometalinter",
config = "//:.gometalinter.json",

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@ Excited by our work and want to get involved in building out our sharding releas
You can explore our [Open Issues](https://github.com/prysmaticlabs/prysm/issues) in-the works for our different releases. Feel free to fork our repo and start creating PRs after assigning yourself to an issue of interest. We are always chatting on [Discord](https://discord.gg/CTYGPUJ) drop us a line there if you want to get more involved or have any questions on our implementation!
Please, do not send pull requests for trivial changes, such as typos, these will be rejected. These types of pull requests incur a cost to reviewers and do not provide much value to the project. If you are unsure, please open an issue first to discuss the change.
> [!IMPORTANT]
> Please, **do not send pull requests for trivial changes**, such as typos, these will be rejected. These types of pull requests incur a cost to reviewers and do not provide much value to the project. If you are unsure, please open an issue first to discuss the change.
## Contribution Steps

View File

@@ -385,7 +385,21 @@ load("@com_github_atlassian_bazel_tools//gometalinter:deps.bzl", "gometalinter_d
gometalinter_dependencies()
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
go_repository(
name = "com_github_r3labs_sse_v2",
importpath = "github.com/r3labs/sse/v2",
sum = "h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0=",
version = "v2.10.0",
)
go_repository(
name = "in_gopkg_cenkalti_backoff_v1",
importpath = "gopkg.in/cenkalti/backoff.v1",
sum = "h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=",
version = "v1.1.0",
)
gazelle_dependencies()

View File

@@ -21,6 +21,7 @@ go_library(
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
@@ -28,7 +29,6 @@ go_library(
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
],
)

View File

@@ -19,11 +19,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
log "github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
const (
@@ -278,7 +278,11 @@ func (c *Client) RegisterValidator(ctx context.Context, svr []*ethpb.SignedValid
}
_, err = c.do(ctx, http.MethodPost, postRegisterValidatorPath, bytes.NewBuffer(body))
return err
if err != nil {
return err
}
log.WithField("num_registrations", len(svr)).Info("successfully registered validator(s) on builder")
return nil
}
var errResponseVersionMismatch = errors.New("builder API response uses a different version than requested in " + api.VersionHeader + " header")

View File

@@ -1,212 +0,0 @@
// Package gateway defines a grpc-gateway server that serves HTTP-JSON traffic and acts a proxy between HTTP and gRPC.
package gateway
import (
"context"
"fmt"
"net"
"net/http"
"time"
"github.com/gorilla/mux"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/server/middleware"
"github.com/prysmaticlabs/prysm/v5/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
var _ runtime.Service = (*Gateway)(nil)
// PbMux serves grpc-gateway requests for selected patterns using registered protobuf handlers.
type PbMux struct {
Registrations []PbHandlerRegistration // Protobuf registrations to be registered in Mux.
Patterns []string // URL patterns that will be handled by Mux.
Mux *gwruntime.ServeMux // The router that will be used for grpc-gateway requests.
}
// PbHandlerRegistration is a function that registers a protobuf handler.
type PbHandlerRegistration func(context.Context, *gwruntime.ServeMux, *grpc.ClientConn) error
// MuxHandler is a function that implements the mux handler functionality.
type MuxHandler func(
h http.HandlerFunc,
w http.ResponseWriter,
req *http.Request,
)
// Config parameters for setting up the gateway service.
type config struct {
maxCallRecvMsgSize uint64
remoteCert string
gatewayAddr string
remoteAddr string
allowedOrigins []string
muxHandler MuxHandler
pbHandlers []*PbMux
router *mux.Router
timeout time.Duration
}
// Gateway is the gRPC gateway to serve HTTP JSON traffic as a proxy and forward it to the gRPC server.
type Gateway struct {
cfg *config
conn *grpc.ClientConn
server *http.Server
cancel context.CancelFunc
ctx context.Context
startFailure error
}
// New returns a new instance of the Gateway.
func New(ctx context.Context, opts ...Option) (*Gateway, error) {
g := &Gateway{
ctx: ctx,
cfg: &config{},
}
for _, opt := range opts {
if err := opt(g); err != nil {
return nil, err
}
}
if g.cfg.router == nil {
g.cfg.router = mux.NewRouter()
}
return g, nil
}
// Start the gateway service.
func (g *Gateway) Start() {
ctx, cancel := context.WithCancel(g.ctx)
g.cancel = cancel
conn, err := g.dial(ctx, "tcp", g.cfg.remoteAddr)
if err != nil {
log.WithError(err).Error("Failed to connect to gRPC server")
g.startFailure = err
return
}
g.conn = conn
for _, h := range g.cfg.pbHandlers {
for _, r := range h.Registrations {
if err := r(ctx, h.Mux, g.conn); err != nil {
log.WithError(err).Error("Failed to register handler")
g.startFailure = err
return
}
}
for _, p := range h.Patterns {
g.cfg.router.PathPrefix(p).Handler(h.Mux)
}
}
corsMux := middleware.CorsHandler(g.cfg.allowedOrigins).Middleware(g.cfg.router)
if g.cfg.muxHandler != nil {
g.cfg.router.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
g.cfg.muxHandler(corsMux.ServeHTTP, w, r)
})
}
g.server = &http.Server{
Addr: g.cfg.gatewayAddr,
Handler: corsMux,
ReadHeaderTimeout: time.Second,
}
go func() {
log.WithField("address", g.cfg.gatewayAddr).Info("Starting gRPC gateway")
if err := g.server.ListenAndServe(); err != http.ErrServerClosed {
log.WithError(err).Error("Failed to start gRPC gateway")
g.startFailure = err
return
}
}()
}
// Status of grpc gateway. Returns an error if this service is unhealthy.
func (g *Gateway) Status() error {
if g.startFailure != nil {
return g.startFailure
}
if s := g.conn.GetState(); s != connectivity.Ready {
return fmt.Errorf("grpc server is %s", s)
}
return nil
}
// Stop the gateway with a graceful shutdown.
func (g *Gateway) Stop() error {
if g.server != nil {
shutdownCtx, shutdownCancel := context.WithTimeout(g.ctx, 2*time.Second)
defer shutdownCancel()
if err := g.server.Shutdown(shutdownCtx); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Warn("Existing connections terminated")
} else {
log.WithError(err).Error("Failed to gracefully shut down server")
}
}
}
if g.cancel != nil {
g.cancel()
}
return nil
}
// dial the gRPC server.
func (g *Gateway) dial(ctx context.Context, network, addr string) (*grpc.ClientConn, error) {
switch network {
case "tcp":
return g.dialTCP(ctx, addr)
case "unix":
return g.dialUnix(ctx, addr)
default:
return nil, fmt.Errorf("unsupported network type %q", network)
}
}
// dialTCP creates a client connection via TCP.
// "addr" must be a valid TCP address with a port number.
func (g *Gateway) dialTCP(ctx context.Context, addr string) (*grpc.ClientConn, error) {
var security grpc.DialOption
if len(g.cfg.remoteCert) > 0 {
creds, err := credentials.NewClientTLSFromFile(g.cfg.remoteCert, "")
if err != nil {
return nil, err
}
security = grpc.WithTransportCredentials(creds)
} else {
// Use insecure credentials when there's no remote cert provided.
security = grpc.WithTransportCredentials(insecure.NewCredentials())
}
opts := []grpc.DialOption{
security,
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))),
}
return grpc.DialContext(ctx, addr, opts...)
}
// dialUnix creates a client connection via a unix domain socket.
// "addr" must be a valid path to the socket.
func (g *Gateway) dialUnix(ctx context.Context, addr string) (*grpc.ClientConn, error) {
d := func(addr string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout("unix", addr, timeout)
}
f := func(ctx context.Context, addr string) (net.Conn, error) {
if deadline, ok := ctx.Deadline(); ok {
return d(addr, time.Until(deadline))
}
return d(addr, 0)
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(f),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(g.cfg.maxCallRecvMsgSize))),
}
return grpc.DialContext(ctx, addr, opts...)
}

View File

@@ -1,107 +0,0 @@
package gateway
import (
"context"
"flag"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gorilla/mux"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli/v2"
)
func TestGateway_Customized(t *testing.T) {
r := mux.NewRouter()
cert := "cert"
origins := []string{"origin"}
size := uint64(100)
opts := []Option{
WithRouter(r),
WithRemoteCert(cert),
WithAllowedOrigins(origins),
WithMaxCallRecvMsgSize(size),
WithMuxHandler(func(
_ http.HandlerFunc,
_ http.ResponseWriter,
_ *http.Request,
) {
}),
}
g, err := New(context.Background(), opts...)
require.NoError(t, err)
assert.Equal(t, r, g.cfg.router)
assert.Equal(t, cert, g.cfg.remoteCert)
require.Equal(t, 1, len(g.cfg.allowedOrigins))
assert.Equal(t, origins[0], g.cfg.allowedOrigins[0])
assert.Equal(t, size, g.cfg.maxCallRecvMsgSize)
}
func TestGateway_StartStop(t *testing.T) {
hook := logTest.NewGlobal()
app := cli.App{}
set := flag.NewFlagSet("test", 0)
ctx := cli.NewContext(&app, set, nil)
gatewayPort := ctx.Int(flags.GRPCGatewayPort.Name)
gatewayHost := ctx.String(flags.GRPCGatewayHost.Name)
rpcHost := ctx.String(flags.RPCHost.Name)
selfAddress := fmt.Sprintf("%s:%d", rpcHost, ctx.Int(flags.RPCPort.Name))
gatewayAddress := fmt.Sprintf("%s:%d", gatewayHost, gatewayPort)
opts := []Option{
WithGatewayAddr(gatewayAddress),
WithRemoteAddr(selfAddress),
WithMuxHandler(func(
_ http.HandlerFunc,
_ http.ResponseWriter,
_ *http.Request,
) {
}),
}
g, err := New(context.Background(), opts...)
require.NoError(t, err)
g.Start()
go func() {
require.LogsContain(t, hook, "Starting gRPC gateway")
require.LogsDoNotContain(t, hook, "Starting API middleware")
}()
err = g.Stop()
require.NoError(t, err)
}
func TestGateway_NilHandler_NotFoundHandlerRegistered(t *testing.T) {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
ctx := cli.NewContext(&app, set, nil)
gatewayPort := ctx.Int(flags.GRPCGatewayPort.Name)
gatewayHost := ctx.String(flags.GRPCGatewayHost.Name)
rpcHost := ctx.String(flags.RPCHost.Name)
selfAddress := fmt.Sprintf("%s:%d", rpcHost, ctx.Int(flags.RPCPort.Name))
gatewayAddress := fmt.Sprintf("%s:%d", gatewayHost, gatewayPort)
opts := []Option{
WithGatewayAddr(gatewayAddress),
WithRemoteAddr(selfAddress),
}
g, err := New(context.Background(), opts...)
require.NoError(t, err)
writer := httptest.NewRecorder()
g.cfg.router.ServeHTTP(writer, &http.Request{Method: "GET", Host: "localhost", URL: &url.URL{Path: "/foo"}})
assert.Equal(t, http.StatusNotFound, writer.Code)
}

View File

@@ -1,5 +0,0 @@
package gateway
import "github.com/sirupsen/logrus"
var log = logrus.WithField("prefix", "gateway")

View File

@@ -1,30 +0,0 @@
package gateway
import (
"context"
"net/http"
"strconv"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/protobuf/proto"
)
func HttpResponseModifier(ctx context.Context, w http.ResponseWriter, _ proto.Message) error {
md, ok := gwruntime.ServerMetadataFromContext(ctx)
if !ok {
return nil
}
// set http status code
if vals := md.HeaderMD.Get("x-http-code"); len(vals) > 0 {
code, err := strconv.Atoi(vals[0])
if err != nil {
return err
}
// delete the headers to not expose any grpc-metadata in http response
delete(md.HeaderMD, "x-http-code")
delete(w.Header(), "Grpc-Metadata-X-Http-Code")
w.WriteHeader(code)
}
return nil
}

View File

@@ -1,79 +0,0 @@
package gateway
import (
"time"
"github.com/gorilla/mux"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)
type Option func(g *Gateway) error
func WithPbHandlers(handlers []*PbMux) Option {
return func(g *Gateway) error {
g.cfg.pbHandlers = handlers
return nil
}
}
func WithMuxHandler(m MuxHandler) Option {
return func(g *Gateway) error {
g.cfg.muxHandler = m
return nil
}
}
func WithGatewayAddr(addr string) Option {
return func(g *Gateway) error {
g.cfg.gatewayAddr = addr
return nil
}
}
func WithRemoteAddr(addr string) Option {
return func(g *Gateway) error {
g.cfg.remoteAddr = addr
return nil
}
}
// WithRouter allows adding a custom mux router to the gateway.
func WithRouter(r *mux.Router) Option {
return func(g *Gateway) error {
g.cfg.router = r
return nil
}
}
// WithAllowedOrigins allows adding a set of allowed origins to the gateway.
func WithAllowedOrigins(origins []string) Option {
return func(g *Gateway) error {
g.cfg.allowedOrigins = origins
return nil
}
}
// WithRemoteCert allows adding a custom certificate to the gateway,
func WithRemoteCert(cert string) Option {
return func(g *Gateway) error {
g.cfg.remoteCert = cert
return nil
}
}
// WithMaxCallRecvMsgSize allows specifying the maximum allowed gRPC message size.
func WithMaxCallRecvMsgSize(size uint64) Option {
return func(g *Gateway) error {
g.cfg.maxCallRecvMsgSize = size
return nil
}
}
// WithTimeout allows changing the timeout value for API calls.
func WithTimeout(seconds uint64) Option {
return func(g *Gateway) error {
g.cfg.timeout = time.Second * time.Duration(seconds)
gwruntime.DefaultContextTimeout = time.Second * time.Duration(seconds)
return nil
}
}

View File

@@ -22,9 +22,7 @@ go_test(
deps = [
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
],
)

View File

@@ -2,8 +2,6 @@ package grpc
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
@@ -81,16 +79,3 @@ func AppendHeaders(parent context.Context, headers []string) context.Context {
}
return parent
}
// AppendCustomErrorHeader sets a CustomErrorMetadataKey gRPC header on the passed in context,
// using the passed in error data as the header's value. The data is serialized as JSON.
func AppendCustomErrorHeader(ctx context.Context, errorData interface{}) error {
j, err := json.Marshal(errorData)
if err != nil {
return fmt.Errorf("could not marshal error data into JSON: %w", err)
}
if err := grpc.SetHeader(ctx, metadata.Pairs(CustomErrorMetadataKey, string(j))); err != nil {
return fmt.Errorf("could not set custom error header: %w", err)
}
return nil
}

View File

@@ -2,15 +2,11 @@ package grpc
import (
"context"
"encoding/json"
"strings"
"testing"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
@@ -62,17 +58,3 @@ func TestAppendHeaders(t *testing.T) {
assert.Equal(t, "value=1", md.Get("first")[0])
})
}
func TestAppendCustomErrorHeader(t *testing.T) {
stream := &runtime.ServerTransportStream{}
ctx := grpc.NewContextWithServerTransportStream(context.Background(), stream)
data := &customErrorData{Message: "foo"}
require.NoError(t, AppendCustomErrorHeader(ctx, data))
// The stream used in test setup sets the metadata key in lowercase.
value, ok := stream.Header()[strings.ToLower(CustomErrorMetadataKey)]
require.Equal(t, true, ok, "Failed to retrieve custom error metadata value")
expected, err := json.Marshal(data)
require.NoError(t, err)
assert.Equal(t, string(expected), value[0])
}

View File

@@ -3,34 +3,23 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"gateway.go",
"log.go",
"modifiers.go",
"options.go",
"server.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/api/gateway",
visibility = [
"//beacon-chain:__subpackages__",
"//validator:__subpackages__",
],
importpath = "github.com/prysmaticlabs/prysm/v5/api/server/httprest",
visibility = ["//visibility:public"],
deps = [
"//api/server/middleware:go_default_library",
"//runtime:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//connectivity:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
"@org_golang_google_grpc//credentials/insecure:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["gateway_test.go"],
srcs = ["server_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/beacon-chain/flags:go_default_library",

View File

@@ -0,0 +1,5 @@
package httprest
import "github.com/sirupsen/logrus"
var log = logrus.WithField("prefix", "httprest")

View File

@@ -0,0 +1,34 @@
package httprest
import (
"time"
"github.com/gorilla/mux"
)
// Option is a http rest server functional parameter type.
type Option func(g *Server) error
// WithHTTPAddr sets the full address ( host and port ) of the server.
func WithHTTPAddr(addr string) Option {
return func(g *Server) error {
g.cfg.httpAddr = addr
return nil
}
}
// WithRouter sets the internal router of the server, this is required.
func WithRouter(r *mux.Router) Option {
return func(g *Server) error {
g.cfg.router = r
return nil
}
}
// WithTimeout allows changing the timeout value for API calls.
func WithTimeout(duration time.Duration) Option {
return func(g *Server) error {
g.cfg.timeout = duration
return nil
}
}

View File

@@ -0,0 +1,101 @@
package httprest
import (
"context"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/runtime"
)
var _ runtime.Service = (*Server)(nil)
// Config parameters for setting up the http-rest service.
type config struct {
httpAddr string
router *mux.Router
timeout time.Duration
}
// Server serves HTTP traffic.
type Server struct {
cfg *config
server *http.Server
cancel context.CancelFunc
ctx context.Context
startFailure error
}
// New returns a new instance of the Server.
func New(ctx context.Context, opts ...Option) (*Server, error) {
g := &Server{
ctx: ctx,
cfg: &config{},
}
for _, opt := range opts {
if err := opt(g); err != nil {
return nil, err
}
}
if g.cfg.router == nil {
return nil, errors.New("router option not configured")
}
var handler http.Handler
defaultReadHeaderTimeout := time.Second
if g.cfg.timeout > 0*time.Second {
defaultReadHeaderTimeout = g.cfg.timeout
handler = http.TimeoutHandler(g.cfg.router, g.cfg.timeout, "request timed out")
} else {
handler = g.cfg.router
}
g.server = &http.Server{
Addr: g.cfg.httpAddr,
Handler: handler,
ReadHeaderTimeout: defaultReadHeaderTimeout,
}
return g, nil
}
// Start the http rest service.
func (g *Server) Start() {
g.ctx, g.cancel = context.WithCancel(g.ctx)
go func() {
log.WithField("address", g.cfg.httpAddr).Info("Starting HTTP server")
if err := g.server.ListenAndServe(); err != http.ErrServerClosed {
log.WithError(err).Error("Failed to start HTTP server")
g.startFailure = err
return
}
}()
}
// Status of the HTTP server. Returns an error if this service is unhealthy.
func (g *Server) Status() error {
if g.startFailure != nil {
return g.startFailure
}
return nil
}
// Stop the HTTP server with a graceful shutdown.
func (g *Server) Stop() error {
if g.server != nil {
shutdownCtx, shutdownCancel := context.WithTimeout(g.ctx, 2*time.Second)
defer shutdownCancel()
if err := g.server.Shutdown(shutdownCtx); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
log.Warn("Existing connections terminated")
} else {
log.WithError(err).Error("Failed to gracefully shut down server")
}
}
}
if g.cancel != nil {
g.cancel()
}
return nil
}

View File

@@ -0,0 +1,68 @@
package httprest
import (
"context"
"flag"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gorilla/mux"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
"github.com/urfave/cli/v2"
)
func TestServer_StartStop(t *testing.T) {
hook := logTest.NewGlobal()
app := cli.App{}
set := flag.NewFlagSet("test", 0)
ctx := cli.NewContext(&app, set, nil)
port := ctx.Int(flags.HTTPServerPort.Name)
host := ctx.String(flags.HTTPServerHost.Name)
address := fmt.Sprintf("%s:%d", host, port)
opts := []Option{
WithHTTPAddr(address),
WithRouter(mux.NewRouter()),
}
g, err := New(context.Background(), opts...)
require.NoError(t, err)
g.Start()
go func() {
require.LogsContain(t, hook, "Starting HTTP server")
require.LogsDoNotContain(t, hook, "Starting API middleware")
}()
err = g.Stop()
require.NoError(t, err)
}
func TestServer_NilHandler_NotFoundHandlerRegistered(t *testing.T) {
app := cli.App{}
set := flag.NewFlagSet("test", 0)
ctx := cli.NewContext(&app, set, nil)
port := ctx.Int(flags.HTTPServerPort.Name)
host := ctx.String(flags.HTTPServerHost.Name)
address := fmt.Sprintf("%s:%d", host, port)
opts := []Option{
WithHTTPAddr(address),
WithRouter(mux.NewRouter()),
}
g, err := New(context.Background(), opts...)
require.NoError(t, err)
writer := httptest.NewRecorder()
g.cfg.router.ServeHTTP(writer, &http.Request{Method: "GET", Host: "localhost", URL: &url.URL{Path: "/foo"}})
assert.Equal(t, http.StatusNotFound, writer.Code)
}

View File

@@ -6,6 +6,7 @@ go_library(
"block.go",
"conversions.go",
"conversions_block.go",
"conversions_lightclient.go",
"conversions_state.go",
"endpoints_beacon.go",
"endpoints_blob.go",
@@ -33,7 +34,10 @@ go_library(
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",

View File

@@ -15,7 +15,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/math"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
ethv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
var errNilValue = errors.New("nil value")
@@ -1508,3 +1511,101 @@ func PendingConsolidationsFromConsensus(cs []*eth.PendingConsolidation) []*Pendi
}
return consolidations
}
func HeadEventFromV1(event *ethv1.EventHead) *HeadEvent {
return &HeadEvent{
Slot: fmt.Sprintf("%d", event.Slot),
Block: hexutil.Encode(event.Block),
State: hexutil.Encode(event.State),
EpochTransition: event.EpochTransition,
ExecutionOptimistic: event.ExecutionOptimistic,
PreviousDutyDependentRoot: hexutil.Encode(event.PreviousDutyDependentRoot),
CurrentDutyDependentRoot: hexutil.Encode(event.CurrentDutyDependentRoot),
}
}
func FinalizedCheckpointEventFromV1(event *ethv1.EventFinalizedCheckpoint) *FinalizedCheckpointEvent {
return &FinalizedCheckpointEvent{
Block: hexutil.Encode(event.Block),
State: hexutil.Encode(event.State),
Epoch: fmt.Sprintf("%d", event.Epoch),
ExecutionOptimistic: event.ExecutionOptimistic,
}
}
func LightClientFinalityUpdateEventFromV2(event *ethv2.LightClientFinalityUpdateWithVersion) (*LightClientFinalityUpdateEvent, error) {
var finalityBranch []string
for _, b := range event.Data.FinalityBranch {
finalityBranch = append(finalityBranch, hexutil.Encode(b))
}
attestedBeacon, err := event.Data.AttestedHeader.GetBeacon()
if err != nil {
return nil, errors.Wrap(err, "could not get attested header")
}
finalizedBeacon, err := event.Data.FinalizedHeader.GetBeacon()
if err != nil {
return nil, errors.Wrap(err, "could not get finalized header")
}
return &LightClientFinalityUpdateEvent{
Version: version.String(int(event.Version)),
Data: &LightClientFinalityUpdate{
AttestedHeader: &BeaconBlockHeader{
Slot: fmt.Sprintf("%d", attestedBeacon.Slot),
ProposerIndex: fmt.Sprintf("%d", attestedBeacon.ProposerIndex),
ParentRoot: hexutil.Encode(attestedBeacon.ParentRoot),
StateRoot: hexutil.Encode(attestedBeacon.StateRoot),
BodyRoot: hexutil.Encode(attestedBeacon.BodyRoot),
},
FinalizedHeader: &BeaconBlockHeader{
Slot: fmt.Sprintf("%d", finalizedBeacon.Slot),
ProposerIndex: fmt.Sprintf("%d", finalizedBeacon.ProposerIndex),
ParentRoot: hexutil.Encode(finalizedBeacon.ParentRoot),
StateRoot: hexutil.Encode(finalizedBeacon.StateRoot),
BodyRoot: hexutil.Encode(finalizedBeacon.BodyRoot),
},
FinalityBranch: finalityBranch,
SyncAggregate: &SyncAggregate{
SyncCommitteeBits: hexutil.Encode(event.Data.SyncAggregate.SyncCommitteeBits),
SyncCommitteeSignature: hexutil.Encode(event.Data.SyncAggregate.SyncCommitteeSignature),
},
SignatureSlot: fmt.Sprintf("%d", event.Data.SignatureSlot),
},
}, nil
}
func LightClientOptimisticUpdateWithVersionFromV2(event *ethv2.LightClientOptimisticUpdateWithVersion) (*LightClientOptimisticUpdateEvent, error) {
attestedBeacon, err := event.Data.AttestedHeader.GetBeacon()
if err != nil {
return nil, errors.Wrap(err, "could not get attested header")
}
return &LightClientOptimisticUpdateEvent{
Version: version.String(int(event.Version)),
Data: &LightClientOptimisticUpdate{
AttestedHeader: &BeaconBlockHeader{
Slot: fmt.Sprintf("%d", attestedBeacon.Slot),
ProposerIndex: fmt.Sprintf("%d", attestedBeacon.ProposerIndex),
ParentRoot: hexutil.Encode(attestedBeacon.ParentRoot),
StateRoot: hexutil.Encode(attestedBeacon.StateRoot),
BodyRoot: hexutil.Encode(attestedBeacon.BodyRoot),
},
SyncAggregate: &SyncAggregate{
SyncCommitteeBits: hexutil.Encode(event.Data.SyncAggregate.SyncCommitteeBits),
SyncCommitteeSignature: hexutil.Encode(event.Data.SyncAggregate.SyncCommitteeSignature),
},
SignatureSlot: fmt.Sprintf("%d", event.Data.SignatureSlot),
},
}, nil
}
func EventChainReorgFromV1(event *ethv1.EventChainReorg) *ChainReorgEvent {
return &ChainReorgEvent{
Slot: fmt.Sprintf("%d", event.Slot),
Depth: fmt.Sprintf("%d", event.Depth),
OldHeadBlock: hexutil.Encode(event.OldHeadBlock),
NewHeadBlock: hexutil.Encode(event.NewHeadBlock),
OldHeadState: hexutil.Encode(event.OldHeadState),
NewHeadState: hexutil.Encode(event.NewHeadState),
Epoch: fmt.Sprintf("%d", event.Epoch),
ExecutionOptimistic: event.ExecutionOptimistic,
}
}

View File

@@ -0,0 +1,3 @@
package structs
//

View File

@@ -1,28 +1,42 @@
package structs
import "encoding/json"
type LightClientHeader struct {
Beacon *BeaconBlockHeader `json:"beacon"`
}
type LightClientHeaderCapella struct {
Beacon *BeaconBlockHeader `json:"beacon"`
Execution *ExecutionPayloadHeaderCapella `json:"execution"`
ExecutionBranch []string `json:"execution_branch"`
}
type LightClientHeaderDeneb struct {
Beacon *BeaconBlockHeader `json:"beacon"`
Execution *ExecutionPayloadHeaderDeneb `json:"execution"`
ExecutionBranch []string `json:"execution_branch"`
}
type LightClientBootstrap struct {
Header json.RawMessage `json:"header"`
CurrentSyncCommittee *SyncCommittee `json:"current_sync_committee"`
CurrentSyncCommitteeBranch []string `json:"current_sync_committee_branch"`
}
type LightClientBootstrapResponse struct {
Version string `json:"version"`
Data *LightClientBootstrap `json:"data"`
}
type LightClientBootstrap struct {
Header *LightClientHeader `json:"header"`
CurrentSyncCommittee *SyncCommittee `json:"current_sync_committee"`
CurrentSyncCommitteeBranch []string `json:"current_sync_committee_branch"`
}
type LightClientUpdate struct {
AttestedHeader *BeaconBlockHeader `json:"attested_header"`
NextSyncCommittee *SyncCommittee `json:"next_sync_committee,omitempty"`
FinalizedHeader *BeaconBlockHeader `json:"finalized_header,omitempty"`
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
NextSyncCommitteeBranch []string `json:"next_sync_committee_branch,omitempty"`
FinalityBranch []string `json:"finality_branch,omitempty"`
SignatureSlot string `json:"signature_slot"`
AttestedHeader json.RawMessage `json:"attested_header"`
NextSyncCommittee *SyncCommittee `json:"next_sync_committee,omitempty"`
FinalizedHeader json.RawMessage `json:"finalized_header,omitempty"`
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
NextSyncCommitteeBranch []string `json:"next_sync_committee_branch,omitempty"`
FinalityBranch []string `json:"finality_branch,omitempty"`
SignatureSlot string `json:"signature_slot"`
}
type LightClientUpdateWithVersion struct {

View File

@@ -81,6 +81,7 @@ go_library(
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",

View File

@@ -16,9 +16,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"go.opencensus.io/trace"
)
// ChainInfoFetcher defines a common interface for methods in blockchain service which

View File

@@ -21,11 +21,11 @@ import (
payloadattribute "github.com/prysmaticlabs/prysm/v5/consensus-types/payload-attribute"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
const blobCommitmentVersionKZG uint8 = 0x01
@@ -39,7 +39,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
ctx, span := trace.StartSpan(ctx, "blockChain.notifyForkchoiceUpdate")
defer span.End()
if arg.headBlock.IsNil() {
if arg.headBlock == nil || arg.headBlock.IsNil() {
log.Error("Head block is nil")
return nil, nil
}

View File

@@ -18,11 +18,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/math"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
// UpdateAndSaveHeadWithBalances updates the beacon state head after getting justified balanced from cache.

View File

@@ -46,7 +46,7 @@ func (s *Service) validateMergeBlock(ctx context.Context, b interfaces.ReadOnlyS
if err != nil {
return err
}
if payload.IsNil() {
if payload == nil || payload.IsNil() {
return errors.New("nil execution payload")
}
ok, err := canUseValidatedTerminalBlockHash(b.Block().Slot(), payload)

View File

@@ -25,12 +25,12 @@ import (
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
// A custom slot deadline for processing state slots in our cache.

View File

@@ -22,11 +22,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
mathutil "github.com/prysmaticlabs/prysm/v5/math"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
// CurrentSlot returns the current slot based on time.

View File

@@ -12,11 +12,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
// reorgLateBlockCountAttestations is the time until the end of the slot in which we count

View File

@@ -21,12 +21,12 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"go.opencensus.io/trace"
"golang.org/x/sync/errgroup"
)

View File

@@ -39,10 +39,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
prysmTime "github.com/prysmaticlabs/prysm/v5/time"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"go.opencensus.io/trace"
)
// Service represents a service that handles the internal

View File

@@ -159,6 +159,11 @@ func (msn *MockStateNotifier) StateFeed() *event.Feed {
return msn.feed
}
// NewSimpleStateNotifier makes a state feed without the custom mock feed machinery.
func NewSimpleStateNotifier() *MockStateNotifier {
return &MockStateNotifier{feed: new(event.Feed)}
}
// OperationNotifier mocks the same method in the chain service.
func (s *ChainService) OperationNotifier() opfeed.Notifier {
if s.opNotifier == nil {

View File

@@ -34,13 +34,13 @@ go_library(
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/attestation:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)

View File

@@ -14,9 +14,9 @@ import (
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation"
"go.opencensus.io/trace"
)
// ProcessAttestationsNoVerifySignature applies processing operations to a block's inner attestation

View File

@@ -10,7 +10,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/math"
"go.opencensus.io/trace"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
)
// AttDelta contains rewards and penalties for a single attestation.

View File

@@ -7,7 +7,7 @@ import (
e "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epoch"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epoch/precompute"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"go.opencensus.io/trace"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
)
// ProcessEpoch describes the per epoch operations that are performed on the beacon state.

View File

@@ -40,6 +40,7 @@ go_library(
"//encoding/bytesutil:go_default_library",
"//encoding/ssz:go_default_library",
"//math:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/forks:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
@@ -49,7 +50,6 @@ go_library(
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
],
)

View File

@@ -14,10 +14,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"go.opencensus.io/trace"
)
// ProcessAttestationsNoVerifySignature applies processing operations to a block's inner attestation

View File

@@ -55,8 +55,9 @@ func ProcessEffectiveBalanceUpdates(state state.BeaconState) error {
if balance+downwardThreshold < val.EffectiveBalance || val.EffectiveBalance+upwardThreshold < balance {
effectiveBal := min(balance-balance%effBalanceInc, effectiveBalanceLimit)
val.EffectiveBalance = effectiveBal
return true, val, nil
newVal := ethpb.CopyValidator(val)
newVal.EffectiveBalance = effectiveBal
return true, newVal, nil
}
return false, val, nil
}

View File

@@ -11,6 +11,29 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestProcessEffectiveBalanceUpdates_SafeCopy(t *testing.T) {
pb := &eth.BeaconStateElectra{
Validators: []*eth.Validator{
{
EffectiveBalance: params.BeaconConfig().MinActivationBalance,
WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte, 0x11},
},
},
Balances: []uint64{
params.BeaconConfig().MaxEffectiveBalanceElectra * 2,
},
}
st, err := state_native.InitializeFromProtoElectra(pb)
require.NoError(t, err)
copiedState := st.Copy()
err = electra.ProcessEffectiveBalanceUpdates(copiedState)
require.NoError(t, err)
require.Equal(t, st.Validators()[0].EffectiveBalance, params.BeaconConfig().MinActivationBalance)
require.Equal(t, copiedState.Validators()[0].EffectiveBalance, params.BeaconConfig().MaxEffectiveBalanceElectra)
}
func TestProcessEffectiveBalnceUpdates(t *testing.T) {
effBalanceInc := params.BeaconConfig().EffectiveBalanceIncrement
hysteresisInc := effBalanceInc / params.BeaconConfig().HysteresisQuotient

View File

@@ -24,6 +24,7 @@ go_library(
"//consensus-types/primitives:go_default_library",
"//math:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/attestation:go_default_library",
"//runtime/version:go_default_library",

View File

@@ -11,10 +11,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"go.opencensus.io/trace"
)
// ProcessAttestations process the attestations in state and update individual validator's pre computes,

View File

@@ -33,6 +33,7 @@ go_library(
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//time:go_default_library",
@@ -42,7 +43,6 @@ go_library(
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)

View File

@@ -16,11 +16,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
log "github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
var (

View File

@@ -7,13 +7,20 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//encoding/bytesutil:go_default_library",
"//encoding/ssz:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",
"//proto/migration:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)
@@ -24,7 +31,7 @@ go_test(
":go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
],

View File

@@ -4,12 +4,19 @@ import (
"bytes"
"fmt"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
"github.com/prysmaticlabs/prysm/v5/proto/migration"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"context"
@@ -32,13 +39,15 @@ const (
// signature_slot=update.signature_slot,
// )
func CreateLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientFinalityUpdate {
return &ethpbv2.LightClientFinalityUpdate{
finalityUpdate := &ethpbv2.LightClientFinalityUpdate{
AttestedHeader: update.AttestedHeader,
FinalizedHeader: update.FinalizedHeader,
FinalityBranch: update.FinalityBranch,
SyncAggregate: update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
}
return finalityUpdate
}
// CreateLightClientOptimisticUpdate - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_optimistic_update
@@ -50,11 +59,13 @@ func CreateLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2
// signature_slot=update.signature_slot,
// )
func CreateLightClientOptimisticUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientOptimisticUpdate {
return &ethpbv2.LightClientOptimisticUpdate{
optimisticUpdate := &ethpbv2.LightClientOptimisticUpdate{
AttestedHeader: update.AttestedHeader,
SyncAggregate: update.SyncAggregate,
SignatureSlot: update.SignatureSlot,
}
return optimisticUpdate
}
func NewLightClientOptimisticUpdateFromBeaconState(
@@ -71,7 +82,7 @@ func NewLightClientOptimisticUpdateFromBeaconState(
// assert sum(block.message.body.sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
syncAggregate, err := block.Block().Body().SyncAggregate()
if err != nil {
return nil, fmt.Errorf("could not get sync aggregate %w", err)
return nil, errors.Wrap(err, "could not get sync aggregate")
}
if syncAggregate.SyncCommitteeBits.Count() < params.BeaconConfig().MinSyncCommitteeParticipants {
@@ -87,18 +98,18 @@ func NewLightClientOptimisticUpdateFromBeaconState(
header := state.LatestBlockHeader()
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, fmt.Errorf("could not get state root %w", err)
return nil, errors.Wrap(err, "could not get state root")
}
header.StateRoot = stateRoot[:]
headerRoot, err := header.HashTreeRoot()
if err != nil {
return nil, fmt.Errorf("could not get header root %w", err)
return nil, errors.Wrap(err, "could not get header root")
}
blockRoot, err := block.Block().HashTreeRoot()
if err != nil {
return nil, fmt.Errorf("could not get block root %w", err)
return nil, errors.Wrap(err, "could not get block root")
}
if headerRoot != blockRoot {
@@ -116,38 +127,100 @@ func NewLightClientOptimisticUpdateFromBeaconState(
// attested_header.state_root = hash_tree_root(attested_state)
attestedStateRoot, err := attestedState.HashTreeRoot(ctx)
if err != nil {
return nil, fmt.Errorf("could not get attested state root %w", err)
return nil, errors.Wrap(err, "could not get attested state root")
}
attestedHeader.StateRoot = attestedStateRoot[:]
// assert hash_tree_root(attested_header) == block.message.parent_root
attestedHeaderRoot, err := attestedHeader.HashTreeRoot()
if err != nil {
return nil, fmt.Errorf("could not get attested header root %w", err)
return nil, errors.Wrap(err, "could not get attested header root")
}
if attestedHeaderRoot != block.Block().ParentRoot() {
return nil, fmt.Errorf("attested header root %#x not equal to block parent root %#x", attestedHeaderRoot, block.Block().ParentRoot())
}
// Return result
attestedHeaderResult := &ethpbv1.BeaconBlockHeader{
Slot: attestedHeader.Slot,
ProposerIndex: attestedHeader.ProposerIndex,
ParentRoot: attestedHeader.ParentRoot,
StateRoot: attestedHeader.StateRoot,
BodyRoot: attestedHeader.BodyRoot,
}
syncAggregateResult := &ethpbv1.SyncAggregate{
SyncCommitteeBits: syncAggregate.SyncCommitteeBits,
SyncCommitteeSignature: syncAggregate.SyncCommitteeSignature,
}
result := &ethpbv2.LightClientUpdate{
AttestedHeader: attestedHeaderResult,
SyncAggregate: syncAggregateResult,
SignatureSlot: block.Block().Slot(),
SyncAggregate: syncAggregateResult,
SignatureSlot: block.Block().Slot(),
}
switch block.Block().Version() {
case version.Altair, version.Bellatrix:
result.AttestedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{
Beacon: &ethpbv1.BeaconBlockHeader{
Slot: attestedHeader.Slot,
ProposerIndex: attestedHeader.ProposerIndex,
ParentRoot: attestedHeader.ParentRoot,
StateRoot: attestedHeader.StateRoot,
BodyRoot: attestedHeader.BodyRoot,
},
},
},
}
case version.Capella:
executionPayloadHeader, err := getExecutionPayloadHeaderCapella(block)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload header")
}
executionPayloadProof, err := blocks.PayloadProof(ctx, block.Block())
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
result.AttestedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderCapella{
HeaderCapella: &ethpbv2.LightClientHeaderCapella{
Beacon: &ethpbv1.BeaconBlockHeader{
Slot: attestedHeader.Slot,
ProposerIndex: attestedHeader.ProposerIndex,
ParentRoot: attestedHeader.ParentRoot,
StateRoot: attestedHeader.StateRoot,
BodyRoot: attestedHeader.BodyRoot,
},
Execution: executionPayloadHeader,
ExecutionBranch: executionPayloadProof,
},
},
}
case version.Deneb, version.Electra:
executionPayloadHeader, err := getExecutionPayloadHeaderDeneb(block)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload header")
}
executionPayloadProof, err := blocks.PayloadProof(ctx, block.Block())
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
result.AttestedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderDeneb{
HeaderDeneb: &ethpbv2.LightClientHeaderDeneb{
Beacon: &ethpbv1.BeaconBlockHeader{
Slot: attestedHeader.Slot,
ProposerIndex: attestedHeader.ProposerIndex,
ParentRoot: attestedHeader.ParentRoot,
StateRoot: attestedHeader.StateRoot,
BodyRoot: attestedHeader.BodyRoot,
},
Execution: executionPayloadHeader,
ExecutionBranch: executionPayloadProof,
},
},
}
default:
return nil, fmt.Errorf("unsupported block version %s", version.String(block.Block().Version()))
}
return result, nil
@@ -170,20 +243,20 @@ func NewLightClientFinalityUpdateFromBeaconState(
}
// Indicate finality whenever possible
var finalizedHeader *ethpbv1.BeaconBlockHeader
var finalizedHeaderBeacon *ethpbv1.BeaconBlockHeader
var finalityBranch [][]byte
if finalizedBlock != nil && !finalizedBlock.IsNil() {
if finalizedBlock.Block().Slot() != 0 {
tempFinalizedHeader, err := finalizedBlock.Header()
if err != nil {
return nil, fmt.Errorf("could not get finalized header %w", err)
return nil, errors.Wrap(err, "could not get finalized header")
}
finalizedHeader = migration.V1Alpha1SignedHeaderToV1(tempFinalizedHeader).GetMessage()
finalizedHeaderBeacon := migration.V1Alpha1SignedHeaderToV1(tempFinalizedHeader).GetMessage()
finalizedHeaderRoot, err := finalizedHeader.HashTreeRoot()
finalizedHeaderRoot, err := finalizedHeaderBeacon.HashTreeRoot()
if err != nil {
return nil, fmt.Errorf("could not get finalized header root %w", err)
return nil, errors.Wrap(err, "could not get finalized header root")
}
if finalizedHeaderRoot != bytesutil.ToBytes32(attestedState.FinalizedCheckpoint().Root) {
@@ -194,7 +267,7 @@ func NewLightClientFinalityUpdateFromBeaconState(
return nil, fmt.Errorf("invalid finalized header root %v", attestedState.FinalizedCheckpoint().Root)
}
finalizedHeader = &ethpbv1.BeaconBlockHeader{
finalizedHeaderBeacon = &ethpbv1.BeaconBlockHeader{
Slot: 0,
ProposerIndex: 0,
ParentRoot: make([]byte, 32),
@@ -206,10 +279,10 @@ func NewLightClientFinalityUpdateFromBeaconState(
var bErr error
finalityBranch, bErr = attestedState.FinalizedRootProof(ctx)
if bErr != nil {
return nil, fmt.Errorf("could not get finalized root proof %w", bErr)
return nil, errors.Wrap(bErr, "could not get finalized root proof")
}
} else {
finalizedHeader = &ethpbv1.BeaconBlockHeader{
finalizedHeaderBeacon = &ethpbv1.BeaconBlockHeader{
Slot: 0,
ProposerIndex: 0,
ParentRoot: make([]byte, 32),
@@ -223,11 +296,244 @@ func NewLightClientFinalityUpdateFromBeaconState(
}
}
result.FinalizedHeader = finalizedHeader
result.FinalityBranch = finalityBranch
switch block.Block().Version() {
case version.Altair, version.Bellatrix:
result.FinalizedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: finalizedHeaderBeacon},
},
}
result.FinalityBranch = finalityBranch
case version.Capella:
if finalizedBlock != nil && !finalizedBlock.IsNil() {
execution, err := getExecutionPayloadHeaderCapella(finalizedBlock)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload header")
}
executionBranch, err := blocks.PayloadProof(ctx, finalizedBlock.Block())
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
result.FinalizedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderCapella{
HeaderCapella: &ethpbv2.LightClientHeaderCapella{
Beacon: finalizedHeaderBeacon,
Execution: execution,
ExecutionBranch: executionBranch,
},
},
}
result.FinalityBranch = finalityBranch
} else {
execution := createEmptyExecutionPayloadHeaderCapella()
executionBranch := make([][]byte, 0)
result.FinalizedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderCapella{
HeaderCapella: &ethpbv2.LightClientHeaderCapella{
Beacon: finalizedHeaderBeacon,
Execution: execution,
ExecutionBranch: executionBranch,
},
},
}
result.FinalityBranch = finalityBranch
}
case version.Deneb, version.Electra:
if finalizedBlock != nil && !finalizedBlock.IsNil() {
execution, err := getExecutionPayloadHeaderDeneb(finalizedBlock)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload header")
}
executionBranch, err := blocks.PayloadProof(ctx, finalizedBlock.Block())
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
result.FinalizedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderDeneb{
HeaderDeneb: &ethpbv2.LightClientHeaderDeneb{
Beacon: finalizedHeaderBeacon,
Execution: execution,
ExecutionBranch: executionBranch,
},
},
}
result.FinalityBranch = finalityBranch
} else {
execution := createEmptyExecutionPayloadHeaderDeneb()
executionBranch := make([][]byte, 0)
result.FinalizedHeader = &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderDeneb{
HeaderDeneb: &ethpbv2.LightClientHeaderDeneb{
Beacon: finalizedHeaderBeacon,
Execution: execution,
ExecutionBranch: executionBranch,
},
},
}
result.FinalityBranch = finalityBranch
}
default:
return nil, fmt.Errorf("unsupported block version %s", version.String(block.Block().Version()))
}
return result, nil
}
func createEmptyExecutionPayloadHeaderCapella() *enginev1.ExecutionPayloadHeaderCapella {
return &enginev1.ExecutionPayloadHeaderCapella{
ParentHash: make([]byte, 32),
FeeRecipient: make([]byte, 20),
StateRoot: make([]byte, 32),
ReceiptsRoot: make([]byte, 32),
LogsBloom: make([]byte, 256),
PrevRandao: make([]byte, 32),
BlockNumber: 0,
GasLimit: 0,
GasUsed: 0,
Timestamp: 0,
ExtraData: make([]byte, 32),
BaseFeePerGas: make([]byte, 32),
BlockHash: make([]byte, 32),
TransactionsRoot: make([]byte, 32),
WithdrawalsRoot: make([]byte, 32),
}
}
func createEmptyExecutionPayloadHeaderDeneb() *enginev1.ExecutionPayloadHeaderDeneb {
return &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: make([]byte, 32),
FeeRecipient: make([]byte, 20),
StateRoot: make([]byte, 32),
ReceiptsRoot: make([]byte, 32),
LogsBloom: make([]byte, 256),
PrevRandao: make([]byte, 32),
BlockNumber: 0,
GasLimit: 0,
GasUsed: 0,
Timestamp: 0,
ExtraData: make([]byte, 32),
BaseFeePerGas: make([]byte, 32),
BlockHash: make([]byte, 32),
TransactionsRoot: make([]byte, 32),
WithdrawalsRoot: make([]byte, 32),
}
}
func getExecutionPayloadHeaderCapella(block interfaces.ReadOnlySignedBeaconBlock) (*enginev1.ExecutionPayloadHeaderCapella, error) {
payloadInterface, err := block.Block().Body().Execution()
if err != nil {
return nil, errors.Wrap(err, "could not get execution data")
}
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
if err != nil {
return nil, errors.Wrap(err, "could not get transactions")
}
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
transactionsRoot = transactionsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
withdrawalsRoot = withdrawalsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
execution := &enginev1.ExecutionPayloadHeaderCapella{
ParentHash: payloadInterface.ParentHash(),
FeeRecipient: payloadInterface.FeeRecipient(),
StateRoot: payloadInterface.StateRoot(),
ReceiptsRoot: payloadInterface.ReceiptsRoot(),
LogsBloom: payloadInterface.LogsBloom(),
PrevRandao: payloadInterface.PrevRandao(),
BlockNumber: payloadInterface.BlockNumber(),
GasLimit: payloadInterface.GasLimit(),
GasUsed: payloadInterface.GasUsed(),
Timestamp: payloadInterface.Timestamp(),
ExtraData: payloadInterface.ExtraData(),
BaseFeePerGas: payloadInterface.BaseFeePerGas(),
BlockHash: payloadInterface.BlockHash(),
TransactionsRoot: transactionsRoot,
WithdrawalsRoot: withdrawalsRoot,
}
return execution, nil
}
func getExecutionPayloadHeaderDeneb(block interfaces.ReadOnlySignedBeaconBlock) (*enginev1.ExecutionPayloadHeaderDeneb, error) {
payloadInterface, err := block.Block().Body().Execution()
if err != nil {
return nil, errors.Wrap(err, "could not get execution data")
}
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
if err != nil {
return nil, errors.Wrap(err, "could not get transactions")
}
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
transactionsRoot = transactionsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
withdrawalsRoot = withdrawalsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
execution := &enginev1.ExecutionPayloadHeaderDeneb{
ParentHash: payloadInterface.ParentHash(),
FeeRecipient: payloadInterface.FeeRecipient(),
StateRoot: payloadInterface.StateRoot(),
ReceiptsRoot: payloadInterface.ReceiptsRoot(),
LogsBloom: payloadInterface.LogsBloom(),
PrevRandao: payloadInterface.PrevRandao(),
BlockNumber: payloadInterface.BlockNumber(),
GasLimit: payloadInterface.GasLimit(),
GasUsed: payloadInterface.GasUsed(),
Timestamp: payloadInterface.Timestamp(),
ExtraData: payloadInterface.ExtraData(),
BaseFeePerGas: payloadInterface.BaseFeePerGas(),
BlockHash: payloadInterface.BlockHash(),
TransactionsRoot: transactionsRoot,
WithdrawalsRoot: withdrawalsRoot,
}
return execution, nil
}
func NewLightClientUpdateFromFinalityUpdate(update *ethpbv2.LightClientFinalityUpdate) *ethpbv2.LightClientUpdate {
return &ethpbv2.LightClientUpdate{
AttestedHeader: update.AttestedHeader,

View File

@@ -6,14 +6,13 @@ import (
lightClient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
v1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
)
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T) {
l := util.NewTestLightClient(t).SetupTest()
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconStateCapella(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella()
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState)
require.NoError(t, err)
@@ -24,12 +23,69 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T)
l.CheckSyncAggregate(update)
l.CheckAttestedHeader(update)
require.Equal(t, (*v1.BeaconBlockHeader)(nil), update.FinalizedHeader, "Finalized header is not nil")
require.Equal(t, (*v2.LightClientHeaderContainer)(nil), update.FinalizedHeader, "Finalized header is not nil")
require.DeepSSZEqual(t, ([][]byte)(nil), update.FinalityBranch, "Finality branch is not nil")
}
func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
l := util.NewTestLightClient(t).SetupTest()
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconStateAltair(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
l.CheckSyncAggregate(update)
l.CheckAttestedHeader(update)
require.Equal(t, (*v2.LightClientHeaderContainer)(nil), update.FinalizedHeader, "Finalized header is not nil")
require.DeepSSZEqual(t, ([][]byte)(nil), update.FinalityBranch, "Finality branch is not nil")
}
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconStateDeneb(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb()
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
l.CheckSyncAggregate(update)
l.CheckAttestedHeader(update)
require.Equal(t, (*v2.LightClientHeaderContainer)(nil), update.FinalizedHeader, "Finalized header is not nil")
require.DeepSSZEqual(t, ([][]byte)(nil), update.FinalityBranch, "Finality branch is not nil")
}
func TestLightClient_NewLightClientFinalityUpdateFromBeaconStateCapella(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella()
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, nil)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
l.CheckSyncAggregate(update)
l.CheckAttestedHeader(update)
zeroHash := params.BeaconConfig().ZeroHash[:]
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
require.NoError(t, err)
require.Equal(t, primitives.Slot(0), updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not zero")
require.Equal(t, primitives.ValidatorIndex(0), updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not zero")
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
for _, leaf := range update.FinalityBranch {
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
}
}
func TestLightClient_NewLightClientFinalityUpdateFromBeaconStateAltair(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, nil)
require.NoError(t, err)
@@ -42,13 +98,45 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
zeroHash := params.BeaconConfig().ZeroHash[:]
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
require.Equal(t, primitives.Slot(0), update.FinalizedHeader.Slot, "Finalized header slot is not zero")
require.Equal(t, primitives.ValidatorIndex(0), update.FinalizedHeader.ProposerIndex, "Finalized header proposer index is not zero")
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.ParentRoot, "Finalized header parent root is not zero")
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.StateRoot, "Finalized header state root is not zero")
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.BodyRoot, "Finalized header body root is not zero")
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
require.NoError(t, err)
require.Equal(t, primitives.Slot(0), updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not zero")
require.Equal(t, primitives.ValidatorIndex(0), updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not zero")
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
for _, leaf := range update.FinalityBranch {
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
}
}
func TestLightClient_NewLightClientFinalityUpdateFromBeaconStateDeneb(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb()
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, nil)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
l.CheckSyncAggregate(update)
l.CheckAttestedHeader(update)
zeroHash := params.BeaconConfig().ZeroHash[:]
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
require.NoError(t, err)
require.Equal(t, primitives.Slot(0), updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not zero")
require.Equal(t, primitives.ValidatorIndex(0), updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not zero")
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not zero")
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.GetHeaderDeneb().Execution.BlockHash, "Execution BlockHash is not zero")
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
for _, leaf := range update.FinalityBranch {
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
}
}
// TODO - add finality update tests with non-nil finalized block for different versions

View File

@@ -41,6 +41,7 @@ go_library(
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",

View File

@@ -25,8 +25,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"go.opencensus.io/trace"
goTrace "go.opencensus.io/trace"
)
type customProcessingFn func(context.Context, state.BeaconState) error
@@ -258,7 +259,7 @@ func cacheBestBeaconStateOnErrFn(highestSlot primitives.Slot, key [32]byte) cust
// if (state.slot + 1) % SLOTS_PER_EPOCH == 0:
// process_epoch(state)
// state.slot = Slot(state.slot + 1)
func ProcessSlotsCore(ctx context.Context, span *trace.Span, state state.BeaconState, slot primitives.Slot, fn customProcessingFn) (state.BeaconState, error) {
func ProcessSlotsCore(ctx context.Context, span *goTrace.Span, state state.BeaconState, slot primitives.Slot, fn customProcessingFn) (state.BeaconState, error) {
var err error
for state.Slot() < slot {
if fn != nil {

View File

@@ -16,8 +16,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"go.opencensus.io/trace"
)
// ExecuteStateTransitionNoVerifyAnySig defines the procedure for a state transition function.

View File

@@ -51,6 +51,7 @@ go_library(
"//io/file:go_default_library",
"//monitoring/progress:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/dbval:go_default_library",
"//proto/eth/v2:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",

View File

@@ -8,8 +8,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/io/file"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
const backupsDirectoryName = "backups"

View File

@@ -16,11 +16,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/container/slice"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
// used to represent errors for inconsistent slot ranges.

View File

@@ -8,9 +8,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
var errMissingStateForCheckpoint = errors.New("missing state summary for checkpoint root")

View File

@@ -5,9 +5,9 @@ import (
"errors"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
v2 "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
"google.golang.org/protobuf/proto"
)

View File

@@ -10,9 +10,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
var previousFinalizedCheckpointKey = []byte("previous-finalized-checkpoint")

View File

@@ -4,9 +4,9 @@ import (
"context"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
// SaveStateSummary saves a state summary object to the DB.

View File

@@ -6,8 +6,8 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
// lookupValuesForIndices takes in a list of indices and looks up

View File

@@ -3,9 +3,9 @@ package kv
import (
"context"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
// LastValidatedCheckpoint returns the latest fully validated checkpoint in beacon chain.

View File

@@ -50,6 +50,7 @@ go_library(
"//io/logs:go_default_library",
"//monitoring/clientstats:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network:go_default_library",
"//network/authorization:go_default_library",
"//proto/engine/v1:go_default_library",
@@ -70,7 +71,6 @@ go_library(
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_k8s_client_go//tools/cache:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
],
)

View File

@@ -10,7 +10,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/types"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"go.opencensus.io/trace"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
)
// searchThreshold to apply for when searching for blocks of a particular time. If the buffer

View File

@@ -21,11 +21,11 @@ import (
payloadattribute "github.com/prysmaticlabs/prysm/v5/consensus-types/payload-attribute"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
pb "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
"google.golang.org/protobuf/proto"
)
@@ -526,7 +526,7 @@ func (s *Service) ReconstructFullBellatrixBlockBatch(
func fullPayloadFromPayloadBody(
header interfaces.ExecutionData, body *pb.ExecutionPayloadBody, bVersion int,
) (interfaces.ExecutionData, error) {
if header.IsNil() || body == nil {
if header == nil || header.IsNil() || body == nil {
return nil, errors.New("execution block and header cannot be nil")
}

View File

@@ -67,7 +67,7 @@ func (r *blindedBlockReconstructor) addToBatch(b interfaces.ReadOnlySignedBeacon
if err != nil {
return err
}
if header.IsNil() {
if header == nil || header.IsNil() {
return errors.New("execution payload header in blinded block was nil")
}
r.orderedBlocks = append(r.orderedBlocks, &blockWithHeader{block: b, header: header})

View File

@@ -33,6 +33,7 @@ go_library(
"//consensus-types/forkchoice:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
@@ -40,7 +41,6 @@ go_library(
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)

View File

@@ -15,11 +15,11 @@ import (
forkchoice2 "github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
// New initializes a new fork choice store.

View File

@@ -9,8 +9,8 @@ import (
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"go.opencensus.io/trace"
)
// head starts from justified root and then follows the best descendant links

View File

@@ -1,27 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["helpers.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/gateway",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//api:go_default_library",
"//api/gateway:go_default_library",
"//cmd/beacon-chain/flags:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@org_golang_google_protobuf//encoding/protojson:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["helpers_test.go"],
embed = [":go_default_library"],
deps = [
"//api/gateway:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
],
)

View File

@@ -1,78 +0,0 @@
package gateway
import (
gwruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/gateway"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
ethpbalpha "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"google.golang.org/protobuf/encoding/protojson"
)
// MuxConfig contains configuration that should be used when registering the beacon node in the gateway.
type MuxConfig struct {
EthPbMux *gateway.PbMux
V1AlphaPbMux *gateway.PbMux
}
// DefaultConfig returns a fully configured MuxConfig with standard gateway behavior.
func DefaultConfig(enableDebugRPCEndpoints bool, httpModules string) MuxConfig {
var v1AlphaPbHandler, ethPbHandler *gateway.PbMux
if flags.EnableHTTPPrysmAPI(httpModules) {
v1AlphaRegistrations := []gateway.PbHandlerRegistration{
ethpbalpha.RegisterNodeHandler,
ethpbalpha.RegisterBeaconChainHandler,
ethpbalpha.RegisterBeaconNodeValidatorHandler,
ethpbalpha.RegisterHealthHandler,
}
if enableDebugRPCEndpoints {
v1AlphaRegistrations = append(v1AlphaRegistrations, ethpbalpha.RegisterDebugHandler)
}
v1AlphaMux := gwruntime.NewServeMux(
gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{
Marshaler: &gwruntime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
EmitUnpopulated: true,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
},
}),
gwruntime.WithMarshalerOption(
api.EventStreamMediaType, &gwruntime.EventSourceJSONPb{},
),
)
v1AlphaPbHandler = &gateway.PbMux{
Registrations: v1AlphaRegistrations,
Patterns: []string{"/eth/v1alpha1/", "/eth/v1alpha2/"},
Mux: v1AlphaMux,
}
}
if flags.EnableHTTPEthAPI(httpModules) {
ethRegistrations := []gateway.PbHandlerRegistration{}
ethMux := gwruntime.NewServeMux(
gwruntime.WithMarshalerOption(gwruntime.MIMEWildcard, &gwruntime.HTTPBodyMarshaler{
Marshaler: &gwruntime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
},
}),
)
ethPbHandler = &gateway.PbMux{
Registrations: ethRegistrations,
Patterns: []string{"/internal/eth/v1/", "/internal/eth/v2/"},
Mux: ethMux,
}
}
return MuxConfig{
EthPbMux: ethPbHandler,
V1AlphaPbMux: v1AlphaPbHandler,
}
}

View File

@@ -1,29 +0,0 @@
package gateway
import (
"testing"
"github.com/prysmaticlabs/prysm/v5/api/gateway"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestDefaultConfig(t *testing.T) {
t.Run("Without Prysm API", func(t *testing.T) {
cfg := DefaultConfig(true, "eth")
assert.NotNil(t, cfg.EthPbMux.Mux)
require.Equal(t, 2, len(cfg.EthPbMux.Patterns))
assert.Equal(t, "/internal/eth/v1/", cfg.EthPbMux.Patterns[0])
assert.Equal(t, 0, len(cfg.EthPbMux.Registrations))
assert.Equal(t, (*gateway.PbMux)(nil), cfg.V1AlphaPbMux)
})
t.Run("Without Eth API", func(t *testing.T) {
cfg := DefaultConfig(true, "prysm")
assert.Equal(t, (*gateway.PbMux)(nil), cfg.EthPbMux)
assert.NotNil(t, cfg.V1AlphaPbMux.Mux)
require.Equal(t, 2, len(cfg.V1AlphaPbMux.Patterns))
assert.Equal(t, "/eth/v1alpha1/", cfg.V1AlphaPbMux.Patterns[0])
assert.Equal(t, "/eth/v1alpha2/", cfg.V1AlphaPbMux.Patterns[1])
assert.Equal(t, 5, len(cfg.V1AlphaPbMux.Registrations))
})
}

View File

@@ -15,7 +15,7 @@ go_library(
"//cmd/beacon-chain:__subpackages__",
],
deps = [
"//api/gateway:go_default_library",
"//api/server/httprest:go_default_library",
"//api/server/middleware:go_default_library",
"//async/event:go_default_library",
"//beacon-chain/blockchain:go_default_library",
@@ -30,7 +30,6 @@ go_library(
"//beacon-chain/execution:go_default_library",
"//beacon-chain/forkchoice:go_default_library",
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
"//beacon-chain/gateway:go_default_library",
"//beacon-chain/monitor:go_default_library",
"//beacon-chain/node/registration:go_default_library",
"//beacon-chain/operations/attestations:go_default_library",

View File

@@ -228,3 +228,26 @@ func TestConfigureInterop(t *testing.T) {
})
}
}
func TestAliasFlag(t *testing.T) {
// Create a new app with the flag
app := &cli.App{
Flags: []cli.Flag{flags.HTTPServerHost},
Action: func(c *cli.Context) error {
// Test if the alias works and sets the flag correctly
if c.IsSet("grpc-gateway-host") && c.IsSet("http-host") {
return nil
}
return cli.Exit("Alias or flag not set", 1)
},
}
// Simulate command line arguments that include the alias
args := []string{"app", "--grpc-gateway-host", "config.yml"}
// Run the app with the simulated arguments
err := app.Run(args)
// Check if the alias set the flag correctly
assert.NoError(t, err)
}

View File

@@ -19,7 +19,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/gorilla/mux"
"github.com/pkg/errors"
apigateway "github.com/prysmaticlabs/prysm/v5/api/gateway"
"github.com/prysmaticlabs/prysm/v5/api/server/httprest"
"github.com/prysmaticlabs/prysm/v5/api/server/middleware"
"github.com/prysmaticlabs/prysm/v5/async/event"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
@@ -34,7 +34,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice"
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/gateway"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/monitor"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/node/registration"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations"
@@ -367,9 +366,9 @@ func registerServices(cliCtx *cli.Context, beacon *BeaconNode, synchronizer *sta
return errors.Wrap(err, "could not register RPC service")
}
log.Debugln("Registering GRPC Gateway Service")
if err := beacon.registerGRPCGateway(router); err != nil {
return errors.Wrap(err, "could not register GRPC gateway service")
log.Debugln("Registering HTTP Service")
if err := beacon.registerHTTPService(router); err != nil {
return errors.Wrap(err, "could not register HTTP service")
}
log.Debugln("Registering Validator Monitoring Service")
@@ -400,10 +399,10 @@ func initSyncWaiter(ctx context.Context, complete chan struct{}) func() error {
func newRouter(cliCtx *cli.Context) *mux.Router {
var allowedOrigins []string
if cliCtx.IsSet(flags.GPRCGatewayCorsDomain.Name) {
allowedOrigins = strings.Split(cliCtx.String(flags.GPRCGatewayCorsDomain.Name), ",")
if cliCtx.IsSet(flags.HTTPServerCorsDomain.Name) {
allowedOrigins = strings.Split(cliCtx.String(flags.HTTPServerCorsDomain.Name), ",")
} else {
allowedOrigins = strings.Split(flags.GPRCGatewayCorsDomain.Value, ",")
allowedOrigins = strings.Split(flags.HTTPServerCorsDomain.Value, ",")
}
r := mux.NewRouter()
r.Use(middleware.NormalizeQueryValuesHandler)
@@ -1044,43 +1043,18 @@ func (b *BeaconNode) registerPrometheusService(_ *cli.Context) error {
return b.services.RegisterService(service)
}
func (b *BeaconNode) registerGRPCGateway(router *mux.Router) error {
if b.cliCtx.Bool(flags.DisableGRPCGateway.Name) {
return nil
func (b *BeaconNode) registerHTTPService(router *mux.Router) error {
host := b.cliCtx.String(flags.HTTPServerHost.Name)
port := b.cliCtx.Int(flags.HTTPServerPort.Name)
address := net.JoinHostPort(host, strconv.Itoa(port))
opts := []httprest.Option{
httprest.WithRouter(router),
httprest.WithHTTPAddr(address),
}
gatewayHost := b.cliCtx.String(flags.GRPCGatewayHost.Name)
gatewayPort := b.cliCtx.Int(flags.GRPCGatewayPort.Name)
rpcHost := b.cliCtx.String(flags.RPCHost.Name)
rpcPort := b.cliCtx.Int(flags.RPCPort.Name)
enableDebugRPCEndpoints := !b.cliCtx.Bool(flags.DisableDebugRPCEndpoints.Name)
selfAddress := net.JoinHostPort(rpcHost, strconv.Itoa(rpcPort))
gatewayAddress := net.JoinHostPort(gatewayHost, strconv.Itoa(gatewayPort))
allowedOrigins := strings.Split(b.cliCtx.String(flags.GPRCGatewayCorsDomain.Name), ",")
selfCert := b.cliCtx.String(flags.CertFlag.Name)
maxCallSize := b.cliCtx.Uint64(cmd.GrpcMaxCallRecvMsgSizeFlag.Name)
httpModules := b.cliCtx.String(flags.HTTPModules.Name)
timeout := b.cliCtx.Int(cmd.ApiTimeoutFlag.Name)
gatewayConfig := gateway.DefaultConfig(enableDebugRPCEndpoints, httpModules)
muxs := make([]*apigateway.PbMux, 0)
if gatewayConfig.V1AlphaPbMux != nil {
muxs = append(muxs, gatewayConfig.V1AlphaPbMux)
if b.cliCtx.IsSet(cmd.ApiTimeoutFlag.Name) {
opts = append(opts, httprest.WithTimeout(b.cliCtx.Duration(cmd.ApiTimeoutFlag.Name)))
}
if gatewayConfig.EthPbMux != nil {
muxs = append(muxs, gatewayConfig.EthPbMux)
}
opts := []apigateway.Option{
apigateway.WithRouter(router),
apigateway.WithGatewayAddr(gatewayAddress),
apigateway.WithRemoteAddr(selfAddress),
apigateway.WithPbHandlers(muxs),
apigateway.WithRemoteCert(selfCert),
apigateway.WithMaxCallRecvMsgSize(maxCallSize),
apigateway.WithAllowedOrigins(allowedOrigins),
apigateway.WithTimeout(uint64(timeout)),
}
g, err := apigateway.New(b.ctx, opts...)
g, err := httprest.New(b.ctx, opts...)
if err != nil {
return err
}

View File

@@ -255,14 +255,14 @@ func TestCORS(t *testing.T) {
// Mock CLI context with a test CORS domain
app := cli.App{}
set := flag.NewFlagSet("test", 0)
set.String(flags.GPRCGatewayCorsDomain.Name, "http://allowed-example.com", "")
set.String(flags.HTTPServerCorsDomain.Name, "http://allowed-example.com", "")
cliCtx := cli.NewContext(&app, set, nil)
require.NoError(t, cliCtx.Set(flags.GPRCGatewayCorsDomain.Name, "http://allowed-example.com"))
require.NoError(t, cliCtx.Set(flags.HTTPServerCorsDomain.Name, "http://allowed-example.com"))
router := newRouter(cliCtx)
// Ensure a test route exists
router.HandleFunc("/some-path", func(w http.ResponseWriter, r *http.Request) {
router.HandleFunc("/some-path", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}).Methods(http.MethodGet)

View File

@@ -66,6 +66,7 @@ go_library(
"//io/file:go_default_library",
"//math:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network:go_default_library",
"//network/forks:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",

View File

@@ -14,10 +14,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
"google.golang.org/protobuf/proto"
)

View File

@@ -27,12 +27,12 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
leakybucket "github.com/prysmaticlabs/prysm/v5/container/leaky-bucket"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
prysmnetwork "github.com/prysmaticlabs/prysm/v5/network"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/metadata"
"github.com/prysmaticlabs/prysm/v5/runtime"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
var _ runtime.Service = (*Service)(nil)

View File

@@ -20,8 +20,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
mathutil "github.com/prysmaticlabs/prysm/v5/math"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
pb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"go.opencensus.io/trace"
)
var attestationSubnetCount = params.BeaconConfig().AttestationSubnetCount

View File

@@ -20,6 +20,7 @@ go_library(
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/core/transition:go_default_library",
"//config/params:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/httputil:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",
@@ -28,13 +29,16 @@ go_library(
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["events_test.go"],
srcs = [
"events_test.go",
"http_test.go",
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/blockchain/testing:go_default_library",
@@ -49,9 +53,9 @@ go_test(
"//consensus-types/primitives:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_r3labs_sse_v2//:go_default_library",
],
)

View File

@@ -1,11 +1,14 @@
package events
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
time2 "time"
"sync"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
@@ -16,19 +19,23 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
chaintime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"go.opencensus.io/trace"
log "github.com/sirupsen/logrus"
)
const chanBuffer = 1000
const (
InvalidTopic = "__invalid__"
// HeadTopic represents a new chain head event topic.
HeadTopic = "head"
// BlockTopic represents a new produced block event topic.
@@ -59,25 +66,84 @@ const (
LightClientOptimisticUpdateTopic = "light_client_optimistic_update"
)
const topicDataMismatch = "Event data type %T does not correspond to event topic %s"
var (
errInvalidTopicName = errors.New("invalid topic name")
errNoValidTopics = errors.New("no valid topics specified")
errSlowReader = errors.New("client failed to read fast enough to keep outgoing buffer below threshold")
errFinished = errors.New("event received after streamer shut down")
errNotRequested = errors.New("event not requested by client")
errUnhandledEventData = errors.New("unable to represent event data in the event stream")
)
const chanBuffer = 1000
// StreamingResponseWriter defines a type that can be used by the eventStreamer.
// This must be an http.ResponseWriter that supports flushing and hijacking.
type StreamingResponseWriter interface {
http.ResponseWriter
http.Flusher
}
var casesHandled = map[string]bool{
HeadTopic: true,
BlockTopic: true,
AttestationTopic: true,
VoluntaryExitTopic: true,
FinalizedCheckpointTopic: true,
ChainReorgTopic: true,
SyncCommitteeContributionTopic: true,
BLSToExecutionChangeTopic: true,
PayloadAttributesTopic: true,
BlobSidecarTopic: true,
ProposerSlashingTopic: true,
AttesterSlashingTopic: true,
LightClientFinalityUpdateTopic: true,
LightClientOptimisticUpdateTopic: true,
// The eventStreamer uses lazyReaders to defer serialization until the moment the value is ready to be written to the client.
type lazyReader func() io.Reader
var opsFeedEventTopics = map[feed.EventType]string{
operation.AggregatedAttReceived: AttestationTopic,
operation.UnaggregatedAttReceived: AttestationTopic,
operation.ExitReceived: VoluntaryExitTopic,
operation.SyncCommitteeContributionReceived: SyncCommitteeContributionTopic,
operation.BLSToExecutionChangeReceived: BLSToExecutionChangeTopic,
operation.BlobSidecarReceived: BlobSidecarTopic,
operation.AttesterSlashingReceived: AttesterSlashingTopic,
operation.ProposerSlashingReceived: ProposerSlashingTopic,
}
var stateFeedEventTopics = map[feed.EventType]string{
statefeed.NewHead: HeadTopic,
statefeed.MissedSlot: PayloadAttributesTopic,
statefeed.FinalizedCheckpoint: FinalizedCheckpointTopic,
statefeed.LightClientFinalityUpdate: LightClientFinalityUpdateTopic,
statefeed.LightClientOptimisticUpdate: LightClientOptimisticUpdateTopic,
statefeed.Reorg: ChainReorgTopic,
statefeed.BlockProcessed: BlockTopic,
}
var topicsForStateFeed = topicsForFeed(stateFeedEventTopics)
var topicsForOpsFeed = topicsForFeed(opsFeedEventTopics)
func topicsForFeed(em map[feed.EventType]string) map[string]bool {
topics := make(map[string]bool, len(em))
for _, topic := range em {
topics[topic] = true
}
return topics
}
type topicRequest struct {
topics map[string]bool
needStateFeed bool
needOpsFeed bool
}
func (req *topicRequest) requested(topic string) bool {
return req.topics[topic]
}
func newTopicRequest(topics []string) (*topicRequest, error) {
req := &topicRequest{topics: make(map[string]bool)}
for _, name := range topics {
if topicsForStateFeed[name] {
req.needStateFeed = true
} else if topicsForOpsFeed[name] {
req.needOpsFeed = true
} else {
return nil, errors.Wrapf(errInvalidTopicName, name)
}
req.topics[name] = true
}
if len(req.topics) == 0 || (!req.needStateFeed && !req.needOpsFeed) {
return nil, errNoValidTopics
}
return req, nil
}
// StreamEvents provides an endpoint to subscribe to the beacon node Server-Sent-Events stream.
@@ -88,356 +154,420 @@ func (s *Server) StreamEvents(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "events.StreamEvents")
defer span.End()
flusher, ok := w.(http.Flusher)
if !ok {
httputil.HandleError(w, "Streaming unsupported!", http.StatusInternalServerError)
topics, err := newTopicRequest(r.URL.Query()["topics"])
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
topics := r.URL.Query()["topics"]
if len(topics) == 0 {
httputil.HandleError(w, "No topics specified to subscribe to", http.StatusBadRequest)
return
eventsChan := make(chan *feed.Event, chanBuffer)
if topics.needOpsFeed {
opsSub := s.OperationNotifier.OperationFeed().Subscribe(eventsChan)
defer opsSub.Unsubscribe()
}
topicsMap := make(map[string]bool)
for _, topic := range topics {
if _, ok := casesHandled[topic]; !ok {
httputil.HandleError(w, fmt.Sprintf("Invalid topic: %s", topic), http.StatusBadRequest)
return
}
topicsMap[topic] = true
if topics.needStateFeed {
stateSub := s.StateNotifier.StateFeed().Subscribe(eventsChan)
defer stateSub.Unsubscribe()
}
// Subscribe to event feeds from information received in the beacon node runtime.
opsChan := make(chan *feed.Event, chanBuffer)
opsSub := s.OperationNotifier.OperationFeed().Subscribe(opsChan)
stateChan := make(chan *feed.Event, chanBuffer)
stateSub := s.StateNotifier.StateFeed().Subscribe(stateChan)
defer opsSub.Unsubscribe()
defer stateSub.Unsubscribe()
// Set up SSE response headers
w.Header().Set("Content-Type", api.EventStreamMediaType)
w.Header().Set("Connection", api.KeepAlive)
// Handle each event received and context cancellation.
// We send a keepalive dummy message immediately to prevent clients
// stalling while waiting for the first response chunk.
// After that we send a keepalive dummy message every SECONDS_PER_SLOT
// to prevent anyone (e.g. proxy servers) from closing connections.
if err := sendKeepalive(w, flusher); err != nil {
es, err := NewEventStreamer(ctx, w, chanBuffer, s.KeepAliveInterval)
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
keepaliveTicker := time2.NewTicker(time2.Duration(params.BeaconConfig().SecondsPerSlot) * time2.Second)
if err := es.Run(); err != nil {
es.Cleanup(err)
}
}
func NewEventStreamer(ctx context.Context, w http.ResponseWriter, buffSize int, ka time.Duration) (*eventStreamer, error) {
if ka == 0 {
ka = time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second
}
f, ok := w.(StreamingResponseWriter)
if !ok {
return nil, errors.New("beacon node misconfiguration: http stack may not support required response handling features, like flushing")
}
ctx, cancel := context.WithCancel(ctx)
return &eventStreamer{
ctx: ctx,
cancel: cancel,
w: f,
outbox: make(chan lazyReader, buffSize),
keepAlive: ka,
}, nil
}
type eventStreamer struct {
sync.Mutex
ctx context.Context
cancel func()
w StreamingResponseWriter
outbox chan lazyReader
finished chan struct{}
keepAlive time.Duration
}
func (es *eventStreamer) bufferEvents() {
for {
select {
case event := <-opsChan:
if err := handleBlockOperationEvents(w, flusher, topicsMap, event); err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
case event := <-stateChan:
if err := s.handleStateEvents(ctx, w, flusher, topicsMap, event); err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
case <-keepaliveTicker.C:
if err := sendKeepalive(w, flusher); err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
case <-ctx.Done():
case <-es.finished: // The finished channel is closed when the streamer hits an unrecoverable error state.
return
case <-es.ctx.Done():
return
case event := <-eventsChan:
lr, err := s.lazyReaderForEvent(ctx, event, topics)
if err != nil {
if !errors.Is(err, errNotRequested) {
log.WithError(err).Error("StreamEvents API endpoint received an event it was unable to handle.")
}
continue
}
// If the client can't keep up, the outbox will eventually completely fill, at which
// safeWrite will error, and we'll hit the below return statement, at which point the deferred
// Unsuscribe calls will be made and the event feed will stop writing to this channel.
// Since the outbox and event stream channels are separately buffered, the event subscription
// channel should stay relatively empty, which gives this loop time to unsubscribe
// and cleanup before the event stream channel fills and disrupts other readers.
if err := es.safeWrite(lr); err != nil {
log.WithField("event_type", fmt.Sprintf("%v", event.Data)).Warn("Unable to safely write event to stream, shutting down.")
return
}
}
}
}
func handleBlockOperationEvents(w http.ResponseWriter, flusher http.Flusher, requestedTopics map[string]bool, event *feed.Event) error {
switch event.Type {
case operation.AggregatedAttReceived:
if _, ok := requestedTopics[AttestationTopic]; !ok {
return nil
}
attData, ok := event.Data.(*operation.AggregatedAttReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, AttestationTopic)
}
att := structs.AttFromConsensus(attData.Attestation.Aggregate)
return send(w, flusher, AttestationTopic, att)
case operation.UnaggregatedAttReceived:
if _, ok := requestedTopics[AttestationTopic]; !ok {
return nil
}
attData, ok := event.Data.(*operation.UnAggregatedAttReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, AttestationTopic)
}
a, ok := attData.Attestation.(*eth.Attestation)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, AttestationTopic)
}
att := structs.AttFromConsensus(a)
return send(w, flusher, AttestationTopic, att)
case operation.ExitReceived:
if _, ok := requestedTopics[VoluntaryExitTopic]; !ok {
return nil
}
exitData, ok := event.Data.(*operation.ExitReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, VoluntaryExitTopic)
}
exit := structs.SignedExitFromConsensus(exitData.Exit)
return send(w, flusher, VoluntaryExitTopic, exit)
case operation.SyncCommitteeContributionReceived:
if _, ok := requestedTopics[SyncCommitteeContributionTopic]; !ok {
return nil
}
contributionData, ok := event.Data.(*operation.SyncCommitteeContributionReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, SyncCommitteeContributionTopic)
}
contribution := structs.SignedContributionAndProofFromConsensus(contributionData.Contribution)
return send(w, flusher, SyncCommitteeContributionTopic, contribution)
case operation.BLSToExecutionChangeReceived:
if _, ok := requestedTopics[BLSToExecutionChangeTopic]; !ok {
return nil
}
changeData, ok := event.Data.(*operation.BLSToExecutionChangeReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, BLSToExecutionChangeTopic)
}
return send(w, flusher, BLSToExecutionChangeTopic, structs.SignedBLSChangeFromConsensus(changeData.Change))
case operation.BlobSidecarReceived:
if _, ok := requestedTopics[BlobSidecarTopic]; !ok {
return nil
}
blobData, ok := event.Data.(*operation.BlobSidecarReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, BlobSidecarTopic)
}
versionedHash := blockchain.ConvertKzgCommitmentToVersionedHash(blobData.Blob.KzgCommitment)
blobEvent := &structs.BlobSidecarEvent{
BlockRoot: hexutil.Encode(blobData.Blob.BlockRootSlice()),
Index: fmt.Sprintf("%d", blobData.Blob.Index),
Slot: fmt.Sprintf("%d", blobData.Blob.Slot()),
VersionedHash: versionedHash.String(),
KzgCommitment: hexutil.Encode(blobData.Blob.KzgCommitment),
}
return send(w, flusher, BlobSidecarTopic, blobEvent)
case operation.AttesterSlashingReceived:
if _, ok := requestedTopics[AttesterSlashingTopic]; !ok {
return nil
}
attesterSlashingData, ok := event.Data.(*operation.AttesterSlashingReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, AttesterSlashingTopic)
}
slashing, ok := attesterSlashingData.AttesterSlashing.(*eth.AttesterSlashing)
if ok {
return send(w, flusher, AttesterSlashingTopic, structs.AttesterSlashingFromConsensus(slashing))
}
// TODO: extend to Electra
case operation.ProposerSlashingReceived:
if _, ok := requestedTopics[ProposerSlashingTopic]; !ok {
return nil
}
proposerSlashingData, ok := event.Data.(*operation.ProposerSlashingReceivedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, ProposerSlashingTopic)
}
return send(w, flusher, ProposerSlashingTopic, structs.ProposerSlashingFromConsensus(proposerSlashingData.ProposerSlashing))
func (es *eventStreamer) safeWrite(rf func() io.Reader) error {
if rf == nil {
return nil
}
select {
case <-es.finished:
return errFinished
case es.outbox <- rf:
return nil
default:
// If this is the case, the select case to write to the outbox could not proceed, meaning the outbox is full.
// If a reader can't keep up with the stream, we shut them down.
return errSlowReader
}
return nil
}
func (s *Server) handleStateEvents(ctx context.Context, w http.ResponseWriter, flusher http.Flusher, requestedTopics map[string]bool, event *feed.Event) error {
switch event.Type {
case statefeed.NewHead:
if _, ok := requestedTopics[HeadTopic]; ok {
headData, ok := event.Data.(*ethpb.EventHead)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, HeadTopic)
}
head := &structs.HeadEvent{
Slot: fmt.Sprintf("%d", headData.Slot),
Block: hexutil.Encode(headData.Block),
State: hexutil.Encode(headData.State),
EpochTransition: headData.EpochTransition,
ExecutionOptimistic: headData.ExecutionOptimistic,
PreviousDutyDependentRoot: hexutil.Encode(headData.PreviousDutyDependentRoot),
CurrentDutyDependentRoot: hexutil.Encode(headData.CurrentDutyDependentRoot),
}
return send(w, flusher, HeadTopic, head)
}
if _, ok := requestedTopics[PayloadAttributesTopic]; ok {
return s.sendPayloadAttributes(ctx, w, flusher)
}
case statefeed.MissedSlot:
if _, ok := requestedTopics[PayloadAttributesTopic]; ok {
return s.sendPayloadAttributes(ctx, w, flusher)
}
case statefeed.FinalizedCheckpoint:
if _, ok := requestedTopics[FinalizedCheckpointTopic]; !ok {
return nil
}
checkpointData, ok := event.Data.(*ethpb.EventFinalizedCheckpoint)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, FinalizedCheckpointTopic)
}
checkpoint := &structs.FinalizedCheckpointEvent{
Block: hexutil.Encode(checkpointData.Block),
State: hexutil.Encode(checkpointData.State),
Epoch: fmt.Sprintf("%d", checkpointData.Epoch),
ExecutionOptimistic: checkpointData.ExecutionOptimistic,
}
return send(w, flusher, FinalizedCheckpointTopic, checkpoint)
case statefeed.LightClientFinalityUpdate:
if _, ok := requestedTopics[LightClientFinalityUpdateTopic]; !ok {
return nil
}
updateData, ok := event.Data.(*ethpbv2.LightClientFinalityUpdateWithVersion)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, LightClientFinalityUpdateTopic)
}
func (es *eventStreamer) Run() error {
// Set up SSE response headers
es.w.Header().Set("Content-Type", api.EventStreamMediaType)
es.w.Header().Set("Connection", api.KeepAlive)
return es.outboxWriteLoop()
}
var finalityBranch []string
for _, b := range updateData.Data.FinalityBranch {
finalityBranch = append(finalityBranch, hexutil.Encode(b))
// newlineReader is used to write keep-alives to the client.
// keep-alives in the sse protocol are a single ':' colon followed by 2 newlines.
func newlineReader() io.Reader {
return bytes.NewBufferString(":\n\n")
}
// outboxWriteLoop runs in a separate goroutine. Its job is to write the values in the outbox to
// the client as fast as the client can read them.
func (es *eventStreamer) outboxWriteLoop() error {
// There are multiple points in this loop where we can receive an error and break out of the loop.
// In all cases, we need to call Cleanup to ensure:
// - any error received is logged.
// - the context is cancelled and the .finished channel is closed.
// - this signals the response handler to exit the handler func; stop reading events -> writing to the outbox.
var err error
defer es.Cleanup(err)
// Write a keepalive at the start to test the connection and simplify test setup.
if err := es.writeOutbox(nil); err != nil {
return err
}
kaT := time.NewTimer(es.keepAlive)
// Ensure the keepalive timer is stopped and drained if it has fired.
defer func() {
if !kaT.Stop() {
<-kaT.C
}
update := &structs.LightClientFinalityUpdateEvent{
Version: version.String(int(updateData.Version)),
Data: &structs.LightClientFinalityUpdate{
AttestedHeader: &structs.BeaconBlockHeader{
Slot: fmt.Sprintf("%d", updateData.Data.AttestedHeader.Slot),
ProposerIndex: fmt.Sprintf("%d", updateData.Data.AttestedHeader.ProposerIndex),
ParentRoot: hexutil.Encode(updateData.Data.AttestedHeader.ParentRoot),
StateRoot: hexutil.Encode(updateData.Data.AttestedHeader.StateRoot),
BodyRoot: hexutil.Encode(updateData.Data.AttestedHeader.BodyRoot),
},
FinalizedHeader: &structs.BeaconBlockHeader{
Slot: fmt.Sprintf("%d", updateData.Data.FinalizedHeader.Slot),
ProposerIndex: fmt.Sprintf("%d", updateData.Data.FinalizedHeader.ProposerIndex),
ParentRoot: hexutil.Encode(updateData.Data.FinalizedHeader.ParentRoot),
StateRoot: hexutil.Encode(updateData.Data.FinalizedHeader.StateRoot),
},
FinalityBranch: finalityBranch,
SyncAggregate: &structs.SyncAggregate{
SyncCommitteeBits: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeBits),
SyncCommitteeSignature: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeSignature),
},
SignatureSlot: fmt.Sprintf("%d", updateData.Data.SignatureSlot),
},
}()
for {
select {
case <-es.ctx.Done():
return es.ctx.Err()
case <-kaT.C:
err = es.writeOutbox(nil)
if err != nil {
return err
}
// In this case the timer doesn't need to be Stopped before the Reset call after the select statement,
// because the timer has already fired.
case lr := <-es.outbox:
err = es.writeOutbox(lr)
if err != nil {
return err
}
// We don't know if the timer fired concurrently to this case being ready, so we need to check the return
// of Stop and drain the timer channel if it fired. We won't need to do this in go 1.23.
if !kaT.Stop() {
<-kaT.C
}
}
return send(w, flusher, LightClientFinalityUpdateTopic, update)
case statefeed.LightClientOptimisticUpdate:
if _, ok := requestedTopics[LightClientOptimisticUpdateTopic]; !ok {
kaT.Reset(es.keepAlive)
}
}
func (es *eventStreamer) writeOutbox(first lazyReader) error {
needKeepAlive := true
if first != nil {
if _, err := io.Copy(es.w, first()); err != nil {
return err
}
needKeepAlive = false
}
// While the first event was being read by the client, further events may be queued in the outbox.
// We can drain them right away rather than go back out to the outer select statement, where the keepAlive timer
// may have fired, triggering an unnecessary extra keep-alive write and flush.
for {
select {
case <-es.ctx.Done():
return es.ctx.Err()
case rf := <-es.outbox:
if _, err := io.Copy(es.w, rf()); err != nil {
return err
}
needKeepAlive = false
default:
if needKeepAlive {
if _, err := io.Copy(es.w, newlineReader()); err != nil {
return err
}
}
es.w.Flush()
return nil
}
updateData, ok := event.Data.(*ethpbv2.LightClientOptimisticUpdateWithVersion)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, LightClientOptimisticUpdateTopic)
}
}
func (es *eventStreamer) Cleanup(err error) {
if err != nil {
log.WithError(err).Debug("Event streamer shutting down due to error.")
}
select {
case <-es.finished:
return
default:
es.cancel()
close(es.finished)
// note: we could hijack the connection and close it here. Does that cause issues? What are the benefits?
// A benefit of hijack and close is that it may force an error on the remote end, however just closing the context of the
// http handler may be sufficient to cause the remote http response reader to close.
}
}
func jsonMarshalReader(name string, v any) io.Reader {
d, err := json.Marshal(v)
if err != nil {
log.WithError(err).WithField("type_name", fmt.Sprintf("%T", v)).Error("Could not marshal event data.")
return nil
}
return bytes.NewBufferString("event: " + name + "\ndata: " + string(d) + "\n\n")
}
func topicForEvent(event *feed.Event) string {
switch event.Data.(type) {
case *operation.AggregatedAttReceivedData:
return AttestationTopic
case *operation.UnAggregatedAttReceivedData:
return AttestationTopic
case *operation.ExitReceivedData:
return VoluntaryExitTopic
case *operation.SyncCommitteeContributionReceivedData:
return SyncCommitteeContributionTopic
case *operation.BLSToExecutionChangeReceivedData:
return BLSToExecutionChangeTopic
case *operation.BlobSidecarReceivedData:
return BlobSidecarTopic
case *operation.AttesterSlashingReceivedData:
return AttesterSlashingTopic
case *operation.ProposerSlashingReceivedData:
return ProposerSlashingTopic
case *ethpb.EventHead:
return HeadTopic
case *ethpb.EventFinalizedCheckpoint:
return FinalizedCheckpointTopic
case *ethpbv2.LightClientFinalityUpdateWithVersion:
return LightClientFinalityUpdateTopic
case *ethpbv2.LightClientOptimisticUpdateWithVersion:
return LightClientOptimisticUpdateTopic
case *ethpb.EventChainReorg:
return ChainReorgTopic
case *statefeed.BlockProcessedData:
return BlockTopic
default:
if event.Type == statefeed.MissedSlot {
return PayloadAttributesTopic
}
update := &structs.LightClientOptimisticUpdateEvent{
Version: version.String(int(updateData.Version)),
Data: &structs.LightClientOptimisticUpdate{
AttestedHeader: &structs.BeaconBlockHeader{
Slot: fmt.Sprintf("%d", updateData.Data.AttestedHeader.Slot),
ProposerIndex: fmt.Sprintf("%d", updateData.Data.AttestedHeader.ProposerIndex),
ParentRoot: hexutil.Encode(updateData.Data.AttestedHeader.ParentRoot),
StateRoot: hexutil.Encode(updateData.Data.AttestedHeader.StateRoot),
BodyRoot: hexutil.Encode(updateData.Data.AttestedHeader.BodyRoot),
},
SyncAggregate: &structs.SyncAggregate{
SyncCommitteeBits: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeBits),
SyncCommitteeSignature: hexutil.Encode(updateData.Data.SyncAggregate.SyncCommitteeSignature),
},
SignatureSlot: fmt.Sprintf("%d", updateData.Data.SignatureSlot),
},
return InvalidTopic
}
}
func (s *Server) lazyReaderForEvent(ctx context.Context, event *feed.Event, topics *topicRequest) (lazyReader, error) {
eventName := topicForEvent(event)
if !topics.requested(eventName) {
return nil, errNotRequested
}
if eventName == PayloadAttributesTopic {
return s.currentPayloadAttributes(ctx)
}
switch v := event.Data.(type) {
case *ethpb.EventHead:
// The head event is a special case because, if the client requested the payload attributes topic,
// we send two event messages in reaction; the head event and the payload attributes.
headReader := func() io.Reader {
return jsonMarshalReader(eventName, structs.HeadEventFromV1(v))
}
return send(w, flusher, LightClientOptimisticUpdateTopic, update)
case statefeed.Reorg:
if _, ok := requestedTopics[ChainReorgTopic]; !ok {
return nil
// Don't do the expensive attr lookup unless the client requested it.
if !topics.requested(PayloadAttributesTopic) {
return headReader, nil
}
reorgData, ok := event.Data.(*ethpb.EventChainReorg)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, ChainReorgTopic)
}
reorg := &structs.ChainReorgEvent{
Slot: fmt.Sprintf("%d", reorgData.Slot),
Depth: fmt.Sprintf("%d", reorgData.Depth),
OldHeadBlock: hexutil.Encode(reorgData.OldHeadBlock),
NewHeadBlock: hexutil.Encode(reorgData.NewHeadBlock),
OldHeadState: hexutil.Encode(reorgData.OldHeadState),
NewHeadState: hexutil.Encode(reorgData.NewHeadState),
Epoch: fmt.Sprintf("%d", reorgData.Epoch),
ExecutionOptimistic: reorgData.ExecutionOptimistic,
}
return send(w, flusher, ChainReorgTopic, reorg)
case statefeed.BlockProcessed:
if _, ok := requestedTopics[BlockTopic]; !ok {
return nil
}
blkData, ok := event.Data.(*statefeed.BlockProcessedData)
if !ok {
return write(w, flusher, topicDataMismatch, event.Data, BlockTopic)
}
blockRoot, err := blkData.SignedBlock.Block().HashTreeRoot()
// Since payload attributes could change before the outbox is written, we need to do a blocking operation to
// get the current payload attributes right here.
attrReader, err := s.currentPayloadAttributes(ctx)
if err != nil {
return write(w, flusher, "Could not get block root: "+err.Error())
return nil, errors.Wrap(err, "could not get payload attributes for head event")
}
blk := &structs.BlockEvent{
Slot: fmt.Sprintf("%d", blkData.Slot),
Block: hexutil.Encode(blockRoot[:]),
ExecutionOptimistic: blkData.Optimistic,
return func() io.Reader {
return io.MultiReader(headReader(), attrReader())
}, nil
case *operation.AggregatedAttReceivedData:
return func() io.Reader {
att := structs.AttFromConsensus(v.Attestation.Aggregate)
return jsonMarshalReader(eventName, att)
}, nil
case *operation.UnAggregatedAttReceivedData:
att, ok := v.Attestation.(*eth.Attestation)
if !ok {
return nil, errors.Wrapf(errUnhandledEventData, "Unexpected type %T for the .Attestation field of UnAggregatedAttReceivedData", v.Attestation)
}
return send(w, flusher, BlockTopic, blk)
return func() io.Reader {
att := structs.AttFromConsensus(att)
return jsonMarshalReader(eventName, att)
}, nil
case *operation.ExitReceivedData:
return func() io.Reader {
return jsonMarshalReader(eventName, structs.SignedExitFromConsensus(v.Exit))
}, nil
case *operation.SyncCommitteeContributionReceivedData:
return func() io.Reader {
return jsonMarshalReader(eventName, structs.SignedContributionAndProofFromConsensus(v.Contribution))
}, nil
case *operation.BLSToExecutionChangeReceivedData:
return func() io.Reader {
return jsonMarshalReader(eventName, structs.SignedBLSChangeFromConsensus(v.Change))
}, nil
case *operation.BlobSidecarReceivedData:
return func() io.Reader {
versionedHash := blockchain.ConvertKzgCommitmentToVersionedHash(v.Blob.KzgCommitment)
return jsonMarshalReader(eventName, &structs.BlobSidecarEvent{
BlockRoot: hexutil.Encode(v.Blob.BlockRootSlice()),
Index: fmt.Sprintf("%d", v.Blob.Index),
Slot: fmt.Sprintf("%d", v.Blob.Slot()),
VersionedHash: versionedHash.String(),
KzgCommitment: hexutil.Encode(v.Blob.KzgCommitment),
})
}, nil
case *operation.AttesterSlashingReceivedData:
slashing, ok := v.AttesterSlashing.(*eth.AttesterSlashing)
if !ok {
return nil, errors.Wrapf(errUnhandledEventData, "Unexpected type %T for the .AttesterSlashing field of AttesterSlashingReceivedData", v.AttesterSlashing)
}
return func() io.Reader {
return jsonMarshalReader(eventName, structs.AttesterSlashingFromConsensus(slashing))
}, nil
case *operation.ProposerSlashingReceivedData:
return func() io.Reader {
return jsonMarshalReader(eventName, structs.ProposerSlashingFromConsensus(v.ProposerSlashing))
}, nil
case *ethpb.EventFinalizedCheckpoint:
return func() io.Reader {
return jsonMarshalReader(eventName, structs.FinalizedCheckpointEventFromV1(v))
}, nil
case *ethpbv2.LightClientFinalityUpdateWithVersion:
cv, err := structs.LightClientFinalityUpdateEventFromV2(v)
if err != nil {
return nil, errors.Wrap(err, "LightClientFinalityUpdateWithVersion event conversion failure")
}
return func() io.Reader {
return jsonMarshalReader(eventName, cv)
}, nil
case *ethpbv2.LightClientOptimisticUpdateWithVersion:
cv, err := structs.LightClientOptimisticUpdateWithVersionFromV2(v)
if err != nil {
return nil, errors.Wrap(err, "LightClientOptimisticUpdateWithVersion event conversion failure")
}
return func() io.Reader {
return jsonMarshalReader(eventName, cv)
}, nil
case *ethpb.EventChainReorg:
return func() io.Reader {
return jsonMarshalReader(eventName, structs.EventChainReorgFromV1(v))
}, nil
case *statefeed.BlockProcessedData:
blockRoot, err := v.SignedBlock.Block().HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not compute block root for BlockProcessedData state feed event")
}
return func() io.Reader {
blk := &structs.BlockEvent{
Slot: fmt.Sprintf("%d", v.Slot),
Block: hexutil.Encode(blockRoot[:]),
ExecutionOptimistic: v.Optimistic,
}
return jsonMarshalReader(eventName, blk)
}, nil
default:
return nil, errors.Wrapf(errUnhandledEventData, "event data type %T unsupported", v)
}
return nil
}
// This event stream is intended to be used by builders and relays.
// Parent fields are based on state at N_{current_slot}, while the rest of fields are based on state of N_{current_slot + 1}
func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWriter, flusher http.Flusher) error {
func (s *Server) currentPayloadAttributes(ctx context.Context) (lazyReader, error) {
headRoot, err := s.HeadFetcher.HeadRoot(ctx)
if err != nil {
return write(w, flusher, "Could not get head root: "+err.Error())
return nil, errors.Wrap(err, "could not get head root")
}
st, err := s.HeadFetcher.HeadState(ctx)
if err != nil {
return write(w, flusher, "Could not get head state: "+err.Error())
return nil, errors.Wrap(err, "could not get head state")
}
// advance the head state
headState, err := transition.ProcessSlotsIfPossible(ctx, st, s.ChainInfoFetcher.CurrentSlot()+1)
if err != nil {
return write(w, flusher, "Could not advance head state: "+err.Error())
return nil, errors.Wrap(err, "could not advance head state")
}
headBlock, err := s.HeadFetcher.HeadBlock(ctx)
if err != nil {
return write(w, flusher, "Could not get head block: "+err.Error())
return nil, errors.Wrap(err, "could not get head block")
}
headPayload, err := headBlock.Block().Body().Execution()
if err != nil {
return write(w, flusher, "Could not get execution payload: "+err.Error())
return nil, errors.Wrap(err, "could not get execution payload")
}
t, err := slots.ToTime(headState.GenesisTime(), headState.Slot())
if err != nil {
return write(w, flusher, "Could not get head state slot time: "+err.Error())
return nil, errors.Wrap(err, "could not get head state slot time")
}
prevRando, err := helpers.RandaoMix(headState, time.CurrentEpoch(headState))
prevRando, err := helpers.RandaoMix(headState, chaintime.CurrentEpoch(headState))
if err != nil {
return write(w, flusher, "Could not get head state randao mix: "+err.Error())
return nil, errors.Wrap(err, "could not get head state randao mix")
}
proposerIndex, err := helpers.BeaconProposerIndex(ctx, headState)
if err != nil {
return write(w, flusher, "Could not get head state proposer index: "+err.Error())
return nil, errors.Wrap(err, "could not get head state proposer index")
}
feeRecipient := params.BeaconConfig().DefaultFeeRecipient.Bytes()
tValidator, exists := s.TrackedValidatorsCache.Validator(proposerIndex)
@@ -455,7 +585,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
case version.Capella:
withdrawals, _, err := headState.ExpectedWithdrawals()
if err != nil {
return write(w, flusher, "Could not get head state expected withdrawals: "+err.Error())
return nil, errors.Wrap(err, "could not get head state expected withdrawals")
}
attributes = &structs.PayloadAttributesV2{
Timestamp: fmt.Sprintf("%d", t.Unix()),
@@ -466,11 +596,11 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
case version.Deneb, version.Electra:
withdrawals, _, err := headState.ExpectedWithdrawals()
if err != nil {
return write(w, flusher, "Could not get head state expected withdrawals: "+err.Error())
return nil, errors.Wrap(err, "could not get head state expected withdrawals")
}
parentRoot, err := headBlock.Block().HashTreeRoot()
if err != nil {
return write(w, flusher, "Could not get head block root: "+err.Error())
return nil, errors.Wrap(err, "could not get head block root")
}
attributes = &structs.PayloadAttributesV3{
Timestamp: fmt.Sprintf("%d", t.Unix()),
@@ -480,12 +610,12 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
ParentBeaconBlockRoot: hexutil.Encode(parentRoot[:]),
}
default:
return write(w, flusher, "Payload version %s is not supported", version.String(headState.Version()))
return nil, errors.Wrapf(err, "Payload version %s is not supported", version.String(headState.Version()))
}
attributesBytes, err := json.Marshal(attributes)
if err != nil {
return write(w, flusher, err.Error())
return nil, errors.Wrap(err, "errors marshaling payload attributes to json")
}
eventData := structs.PayloadAttributesEventData{
ProposerIndex: fmt.Sprintf("%d", proposerIndex),
@@ -497,31 +627,12 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
}
eventDataBytes, err := json.Marshal(eventData)
if err != nil {
return write(w, flusher, err.Error())
return nil, errors.Wrap(err, "errors marshaling payload attributes event data to json")
}
return send(w, flusher, PayloadAttributesTopic, &structs.PayloadAttributesEvent{
Version: version.String(headState.Version()),
Data: eventDataBytes,
})
}
func send(w http.ResponseWriter, flusher http.Flusher, name string, data interface{}) error {
j, err := json.Marshal(data)
if err != nil {
return write(w, flusher, "Could not marshal event to JSON: "+err.Error())
}
return write(w, flusher, "event: %s\ndata: %s\n\n", name, string(j))
}
func sendKeepalive(w http.ResponseWriter, flusher http.Flusher) error {
return write(w, flusher, ":\n\n")
}
func write(w http.ResponseWriter, flusher http.Flusher, format string, a ...any) error {
_, err := fmt.Fprintf(w, format, a...)
if err != nil {
return errors.Wrap(err, "could not write to response writer")
}
flusher.Flush()
return nil
return func() io.Reader {
return jsonMarshalReader(PayloadAttributesTopic, &structs.PayloadAttributesEvent{
Version: version.String(headState.Version()),
Data: eventDataBytes,
})
}, nil
}

View File

@@ -1,6 +1,7 @@
package events
import (
"context"
"fmt"
"io"
"math"
@@ -23,9 +24,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
sse "github.com/r3labs/sse/v2"
)
type flushableResponseRecorder struct {
@@ -37,14 +38,96 @@ func (f *flushableResponseRecorder) Flush() {
f.flushed = true
}
func NewFlushabaleResponseRecorder() *flushableResponseRecorder {
return &flushableResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
}
func requireAllEventsReceived(t *testing.T, events []*feed.Event, req *topicRequest, s *Server, w *StreamingResponseWriterRecorder) {
// maxBufferSize param copied from sse lib client code
sseR := sse.NewEventStreamReader(w.Body(), 1<<24)
testTimeout := time.NewTimer(60 * time.Second)
// Wait for the initial write and flush which will write the http header.
// This is also a signal that the server side has subscribed to the feed,
// so the setup code below can proceed with writing events.
select {
case status := <-w.StatusChan():
if status != http.StatusOK {
t.Fatalf("expected status OK, got %d", status)
}
case <-testTimeout.C:
t.Fatal("timed out waiting for events")
}
expected := make(map[string]bool)
for i := range events {
ev := events[i]
// serialize the event the same way the server will so that we can compare expectation to results.
top := topicForEvent(ev)
eb, err := s.lazyReaderForEvent(context.Background(), ev, req)
require.NoError(t, err)
exb, err := io.ReadAll(eb())
require.NoError(t, err)
exs := string(exb[0 : len(exb)-2]) // remove trailing double newline
if topicsForOpsFeed[top] {
// Send the event on the feed.
s.OperationNotifier.OperationFeed().Send(ev)
} else {
if !topicsForStateFeed[top] {
t.Fatalf("could not determine feed for topic: %s", top)
}
// Send the event on the feed.
s.StateNotifier.StateFeed().Send(ev)
}
expected[exs] = true
}
done := make(chan struct{})
go func() {
defer close(done)
for {
ev, err := sseR.ReadEvent()
if err == io.EOF {
return
}
require.NoError(t, err)
str := string(ev)
delete(expected, str)
if len(expected) == 0 {
return
}
}
}()
select {
case <-done:
break
case <-testTimeout.C:
t.Fatal("timed out waiting for events")
}
// Clean up the timeout timer.
if !testTimeout.Stop() {
<-testTimeout.C
}
require.Equal(t, 0, len(expected), "expected events not seen")
}
func (tr *topicRequest) testHttpRequest(_ *testing.T) *http.Request {
tq := make([]string, 0, len(tr.topics))
for topic := range tr.topics {
tq = append(tq, "topics="+topic)
}
return httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?%s", strings.Join(tq, "&")), nil)
}
func TestStreamEvents_OperationsEvents(t *testing.T) {
t.Run("operations", func(t *testing.T) {
s := &Server{
StateNotifier: &mockChain.MockStateNotifier{},
StateNotifier: mockChain.NewSimpleStateNotifier(),
OperationNotifier: &mockChain.MockOperationNotifier{},
}
topics := []string{
topics, err := newTopicRequest([]string{
AttestationTopic,
VoluntaryExitTopic,
SyncCommitteeContributionTopic,
@@ -52,240 +135,220 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
BlobSidecarTopic,
AttesterSlashingTopic,
ProposerSlashingTopic,
}
for i, topic := range topics {
topics[i] = "topics=" + topic
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?%s", strings.Join(topics, "&")), nil)
w := &flushableResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
})
require.NoError(t, err)
request := topics.testHttpRequest(t)
w := NewStreamingResponseWriterRecorder()
go func() {
s.StreamEvents(w, request)
}()
// wait for initiation of StreamEvents
time.Sleep(100 * time.Millisecond)
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.UnaggregatedAttReceived,
Data: &operation.UnAggregatedAttReceivedData{
Attestation: util.HydrateAttestation(&eth.Attestation{}),
},
})
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.AggregatedAttReceived,
Data: &operation.AggregatedAttReceivedData{
Attestation: &eth.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&eth.Attestation{}),
SelectionProof: make([]byte, 96),
},
},
})
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.ExitReceived,
Data: &operation.ExitReceivedData{
Exit: &eth.SignedVoluntaryExit{
Exit: &eth.VoluntaryExit{
Epoch: 0,
ValidatorIndex: 0,
},
Signature: make([]byte, 96),
},
},
})
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.SyncCommitteeContributionReceived,
Data: &operation.SyncCommitteeContributionReceivedData{
Contribution: &eth.SignedContributionAndProof{
Message: &eth.ContributionAndProof{
AggregatorIndex: 0,
Contribution: &eth.SyncCommitteeContribution{
Slot: 0,
BlockRoot: make([]byte, 32),
SubcommitteeIndex: 0,
AggregationBits: make([]byte, 16),
Signature: make([]byte, 96),
},
SelectionProof: make([]byte, 96),
},
Signature: make([]byte, 96),
},
},
})
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.BLSToExecutionChangeReceived,
Data: &operation.BLSToExecutionChangeReceivedData{
Change: &eth.SignedBLSToExecutionChange{
Message: &eth.BLSToExecutionChange{
ValidatorIndex: 0,
FromBlsPubkey: make([]byte, 48),
ToExecutionAddress: make([]byte, 20),
},
Signature: make([]byte, 96),
},
},
})
ro, err := blocks.NewROBlob(util.HydrateBlobSidecar(&eth.BlobSidecar{}))
require.NoError(t, err)
vblob := blocks.NewVerifiedROBlob(ro)
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.BlobSidecarReceived,
Data: &operation.BlobSidecarReceivedData{
Blob: &vblob,
},
})
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.AttesterSlashingReceived,
Data: &operation.AttesterSlashingReceivedData{
AttesterSlashing: &eth.AttesterSlashing{
Attestation_1: &eth.IndexedAttestation{
AttestingIndices: []uint64{0, 1},
Data: &eth.AttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
Source: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
Target: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
Attestation_2: &eth.IndexedAttestation{
AttestingIndices: []uint64{0, 1},
Data: &eth.AttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
Source: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
Target: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
events := []*feed.Event{
&feed.Event{
Type: operation.UnaggregatedAttReceived,
Data: &operation.UnAggregatedAttReceivedData{
Attestation: util.HydrateAttestation(&eth.Attestation{}),
},
},
&feed.Event{
Type: operation.AggregatedAttReceived,
Data: &operation.AggregatedAttReceivedData{
Attestation: &eth.AggregateAttestationAndProof{
AggregatorIndex: 0,
Aggregate: util.HydrateAttestation(&eth.Attestation{}),
SelectionProof: make([]byte, 96),
},
},
},
})
s.OperationNotifier.OperationFeed().Send(&feed.Event{
Type: operation.ProposerSlashingReceived,
Data: &operation.ProposerSlashingReceivedData{
ProposerSlashing: &eth.ProposerSlashing{
Header_1: &eth.SignedBeaconBlockHeader{
Header: &eth.BeaconBlockHeader{
ParentRoot: make([]byte, fieldparams.RootLength),
StateRoot: make([]byte, fieldparams.RootLength),
BodyRoot: make([]byte, fieldparams.RootLength),
&feed.Event{
Type: operation.ExitReceived,
Data: &operation.ExitReceivedData{
Exit: &eth.SignedVoluntaryExit{
Exit: &eth.VoluntaryExit{
Epoch: 0,
ValidatorIndex: 0,
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
Header_2: &eth.SignedBeaconBlockHeader{
Header: &eth.BeaconBlockHeader{
ParentRoot: make([]byte, fieldparams.RootLength),
StateRoot: make([]byte, fieldparams.RootLength),
BodyRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
Signature: make([]byte, 96),
},
},
},
})
time.Sleep(1 * time.Second)
request.Context().Done()
resp := w.Result()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NotNil(t, body)
assert.Equal(t, operationsResult, string(body))
&feed.Event{
Type: operation.SyncCommitteeContributionReceived,
Data: &operation.SyncCommitteeContributionReceivedData{
Contribution: &eth.SignedContributionAndProof{
Message: &eth.ContributionAndProof{
AggregatorIndex: 0,
Contribution: &eth.SyncCommitteeContribution{
Slot: 0,
BlockRoot: make([]byte, 32),
SubcommitteeIndex: 0,
AggregationBits: make([]byte, 16),
Signature: make([]byte, 96),
},
SelectionProof: make([]byte, 96),
},
Signature: make([]byte, 96),
},
},
},
&feed.Event{
Type: operation.BLSToExecutionChangeReceived,
Data: &operation.BLSToExecutionChangeReceivedData{
Change: &eth.SignedBLSToExecutionChange{
Message: &eth.BLSToExecutionChange{
ValidatorIndex: 0,
FromBlsPubkey: make([]byte, 48),
ToExecutionAddress: make([]byte, 20),
},
Signature: make([]byte, 96),
},
},
},
&feed.Event{
Type: operation.BlobSidecarReceived,
Data: &operation.BlobSidecarReceivedData{
Blob: &vblob,
},
},
&feed.Event{
Type: operation.AttesterSlashingReceived,
Data: &operation.AttesterSlashingReceivedData{
AttesterSlashing: &eth.AttesterSlashing{
Attestation_1: &eth.IndexedAttestation{
AttestingIndices: []uint64{0, 1},
Data: &eth.AttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
Source: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
Target: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
Attestation_2: &eth.IndexedAttestation{
AttestingIndices: []uint64{0, 1},
Data: &eth.AttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
Source: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
Target: &eth.Checkpoint{
Root: make([]byte, fieldparams.RootLength),
},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
},
},
&feed.Event{
Type: operation.ProposerSlashingReceived,
Data: &operation.ProposerSlashingReceivedData{
ProposerSlashing: &eth.ProposerSlashing{
Header_1: &eth.SignedBeaconBlockHeader{
Header: &eth.BeaconBlockHeader{
ParentRoot: make([]byte, fieldparams.RootLength),
StateRoot: make([]byte, fieldparams.RootLength),
BodyRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
Header_2: &eth.SignedBeaconBlockHeader{
Header: &eth.BeaconBlockHeader{
ParentRoot: make([]byte, fieldparams.RootLength),
StateRoot: make([]byte, fieldparams.RootLength),
BodyRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
},
},
}
requireAllEventsReceived(t, events, topics, s, w)
})
t.Run("state", func(t *testing.T) {
s := &Server{
StateNotifier: &mockChain.MockStateNotifier{},
StateNotifier: mockChain.NewSimpleStateNotifier(),
OperationNotifier: &mockChain.MockOperationNotifier{},
}
topics := []string{HeadTopic, FinalizedCheckpointTopic, ChainReorgTopic, BlockTopic}
for i, topic := range topics {
topics[i] = "topics=" + topic
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?%s", strings.Join(topics, "&")), nil)
w := &flushableResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
topics, err := newTopicRequest([]string{
HeadTopic,
FinalizedCheckpointTopic,
ChainReorgTopic,
BlockTopic,
})
require.NoError(t, err)
request := topics.testHttpRequest(t)
w := NewStreamingResponseWriterRecorder()
go func() {
s.StreamEvents(w, request)
}()
// wait for initiation of StreamEvents
time.Sleep(100 * time.Millisecond)
s.StateNotifier.StateFeed().Send(&feed.Event{
Type: statefeed.NewHead,
Data: &ethpb.EventHead{
Slot: 0,
Block: make([]byte, 32),
State: make([]byte, 32),
EpochTransition: true,
PreviousDutyDependentRoot: make([]byte, 32),
CurrentDutyDependentRoot: make([]byte, 32),
ExecutionOptimistic: false,
},
})
s.StateNotifier.StateFeed().Send(&feed.Event{
Type: statefeed.FinalizedCheckpoint,
Data: &ethpb.EventFinalizedCheckpoint{
Block: make([]byte, 32),
State: make([]byte, 32),
Epoch: 0,
ExecutionOptimistic: false,
},
})
s.StateNotifier.StateFeed().Send(&feed.Event{
Type: statefeed.Reorg,
Data: &ethpb.EventChainReorg{
Slot: 0,
Depth: 0,
OldHeadBlock: make([]byte, 32),
NewHeadBlock: make([]byte, 32),
OldHeadState: make([]byte, 32),
NewHeadState: make([]byte, 32),
Epoch: 0,
ExecutionOptimistic: false,
},
})
b, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlock(&eth.SignedBeaconBlock{}))
require.NoError(t, err)
s.StateNotifier.StateFeed().Send(&feed.Event{
Type: statefeed.BlockProcessed,
Data: &statefeed.BlockProcessedData{
Slot: 0,
BlockRoot: [32]byte{},
SignedBlock: b,
Verified: true,
Optimistic: false,
events := []*feed.Event{
&feed.Event{
Type: statefeed.BlockProcessed,
Data: &statefeed.BlockProcessedData{
Slot: 0,
BlockRoot: [32]byte{},
SignedBlock: b,
Verified: true,
Optimistic: false,
},
},
})
// wait for feed
time.Sleep(1 * time.Second)
request.Context().Done()
resp := w.Result()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NotNil(t, body)
assert.Equal(t, stateResult, string(body))
&feed.Event{
Type: statefeed.NewHead,
Data: &ethpb.EventHead{
Slot: 0,
Block: make([]byte, 32),
State: make([]byte, 32),
EpochTransition: true,
PreviousDutyDependentRoot: make([]byte, 32),
CurrentDutyDependentRoot: make([]byte, 32),
ExecutionOptimistic: false,
},
},
&feed.Event{
Type: statefeed.Reorg,
Data: &ethpb.EventChainReorg{
Slot: 0,
Depth: 0,
OldHeadBlock: make([]byte, 32),
NewHeadBlock: make([]byte, 32),
OldHeadState: make([]byte, 32),
NewHeadState: make([]byte, 32),
Epoch: 0,
ExecutionOptimistic: false,
},
},
&feed.Event{
Type: statefeed.FinalizedCheckpoint,
Data: &ethpb.EventFinalizedCheckpoint{
Block: make([]byte, 32),
State: make([]byte, 32),
Epoch: 0,
ExecutionOptimistic: false,
},
},
}
requireAllEventsReceived(t, events, topics, s, w)
})
t.Run("payload attributes", func(t *testing.T) {
type testCase struct {
name string
getState func() state.BeaconState
getBlock func() interfaces.SignedBeaconBlock
expected string
SetTrackedValidatorsCache func(*cache.TrackedValidatorsCache)
}
testCases := []testCase{
@@ -301,7 +364,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
require.NoError(t, err)
return b
},
expected: payloadAttributesBellatrixResult,
},
{
name: "capella",
@@ -315,7 +377,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
require.NoError(t, err)
return b
},
expected: payloadAttributesCapellaResult,
},
{
name: "deneb",
@@ -329,7 +390,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
require.NoError(t, err)
return b
},
expected: payloadAttributesDenebResult,
},
{
name: "electra",
@@ -343,7 +403,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
require.NoError(t, err)
return b
},
expected: payloadAttributesElectraResultWithTVC,
SetTrackedValidatorsCache: func(c *cache.TrackedValidatorsCache) {
c.Set(cache.TrackedValidator{
Active: true,
@@ -369,7 +428,7 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
}
s := &Server{
StateNotifier: &mockChain.MockStateNotifier{},
StateNotifier: mockChain.NewSimpleStateNotifier(),
OperationNotifier: &mockChain.MockOperationNotifier{},
HeadFetcher: mockChainService,
ChainInfoFetcher: mockChainService,
@@ -378,100 +437,16 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
if tc.SetTrackedValidatorsCache != nil {
tc.SetTrackedValidatorsCache(s.TrackedValidatorsCache)
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?topics=%s", PayloadAttributesTopic), nil)
w := &flushableResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
topics, err := newTopicRequest([]string{PayloadAttributesTopic})
require.NoError(t, err)
request := topics.testHttpRequest(t)
w := NewStreamingResponseWriterRecorder()
events := []*feed.Event{&feed.Event{Type: statefeed.MissedSlot}}
go func() {
s.StreamEvents(w, request)
}()
// wait for initiation of StreamEvents
time.Sleep(100 * time.Millisecond)
s.StateNotifier.StateFeed().Send(&feed.Event{Type: statefeed.MissedSlot})
// wait for feed
time.Sleep(1 * time.Second)
request.Context().Done()
resp := w.Result()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NotNil(t, body)
assert.Equal(t, tc.expected, string(body), "wrong result for "+tc.name)
requireAllEventsReceived(t, events, topics, s, w)
}
})
}
const operationsResult = `:
event: attestation
data: {"aggregation_bits":"0x00","data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
event: attestation
data: {"aggregation_bits":"0x00","data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
event: voluntary_exit
data: {"message":{"epoch":"0","validator_index":"0"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
event: contribution_and_proof
data: {"message":{"aggregator_index":"0","contribution":{"slot":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","subcommittee_index":"0","aggregation_bits":"0x00000000000000000000000000000000","signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"selection_proof":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
event: bls_to_execution_change
data: {"message":{"validator_index":"0","from_bls_pubkey":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","to_execution_address":"0x0000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
event: blob_sidecar
data: {"block_root":"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c","index":"0","slot":"0","kzg_commitment":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","versioned_hash":"0x01b0761f87b081d5cf10757ccc89f12be355c70e2e29df288b65b30710dcbcd1"}
event: attester_slashing
data: {"attestation_1":{"attesting_indices":["0","1"],"data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"attestation_2":{"attesting_indices":["0","1"],"data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}
event: proposer_slashing
data: {"signed_header_1":{"message":{"slot":"0","proposer_index":"0","parent_root":"0x0000000000000000000000000000000000000000000000000000000000000000","state_root":"0x0000000000000000000000000000000000000000000000000000000000000000","body_root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"signed_header_2":{"message":{"slot":"0","proposer_index":"0","parent_root":"0x0000000000000000000000000000000000000000000000000000000000000000","state_root":"0x0000000000000000000000000000000000000000000000000000000000000000","body_root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}
`
const stateResult = `:
event: head
data: {"slot":"0","block":"0x0000000000000000000000000000000000000000000000000000000000000000","state":"0x0000000000000000000000000000000000000000000000000000000000000000","epoch_transition":true,"execution_optimistic":false,"previous_duty_dependent_root":"0x0000000000000000000000000000000000000000000000000000000000000000","current_duty_dependent_root":"0x0000000000000000000000000000000000000000000000000000000000000000"}
event: finalized_checkpoint
data: {"block":"0x0000000000000000000000000000000000000000000000000000000000000000","state":"0x0000000000000000000000000000000000000000000000000000000000000000","epoch":"0","execution_optimistic":false}
event: chain_reorg
data: {"slot":"0","depth":"0","old_head_block":"0x0000000000000000000000000000000000000000000000000000000000000000","old_head_state":"0x0000000000000000000000000000000000000000000000000000000000000000","new_head_block":"0x0000000000000000000000000000000000000000000000000000000000000000","new_head_state":"0x0000000000000000000000000000000000000000000000000000000000000000","epoch":"0","execution_optimistic":false}
event: block
data: {"slot":"0","block":"0xeade62f0457b2fdf48e7d3fc4b60736688286be7c7a3ac4c9a16a5e0600bd9e4","execution_optimistic":false}
`
const payloadAttributesBellatrixResult = `:
event: payload_attributes
data: {"version":"bellatrix","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000"}}}
`
const payloadAttributesCapellaResult = `:
event: payload_attributes
data: {"version":"capella","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000","withdrawals":[]}}}
`
const payloadAttributesDenebResult = `:
event: payload_attributes
data: {"version":"deneb","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000","withdrawals":[],"parent_beacon_block_root":"0xbef96cb938fd48b2403d3e662664325abb0102ed12737cbb80d717520e50cf4a"}}}
`
const payloadAttributesElectraResultWithTVC = `:
event: payload_attributes
data: {"version":"electra","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0xd2dbd02e4efe087d7d195de828b9dd25f19a89c9","withdrawals":[],"parent_beacon_block_root":"0x66d641f7eae038f2dd28081b09d2ba279462cc47655c7b7e1fd1159a50c8eb32"}}}
`

View File

@@ -0,0 +1,75 @@
package events
import (
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
type StreamingResponseWriterRecorder struct {
http.ResponseWriter
r io.Reader
w io.Writer
statusWritten *int
status chan int
bodyRecording []byte
flushed bool
}
func (w *StreamingResponseWriterRecorder) StatusChan() chan int {
return w.status
}
func NewStreamingResponseWriterRecorder() *StreamingResponseWriterRecorder {
r, w := io.Pipe()
return &StreamingResponseWriterRecorder{
ResponseWriter: httptest.NewRecorder(),
r: r,
w: w,
status: make(chan int, 1),
}
}
// Write implements http.ResponseWriter.
func (w *StreamingResponseWriterRecorder) Write(data []byte) (int, error) {
w.WriteHeader(http.StatusOK)
n, err := w.w.Write(data)
if err != nil {
return n, err
}
return w.ResponseWriter.Write(data)
}
// WriteHeader implements http.ResponseWriter.
func (w *StreamingResponseWriterRecorder) WriteHeader(statusCode int) {
if w.statusWritten != nil {
return
}
w.statusWritten = &statusCode
w.status <- statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
func (w *StreamingResponseWriterRecorder) Body() io.Reader {
return w.r
}
func (w *StreamingResponseWriterRecorder) RequireStatus(t *testing.T, status int) {
if w.statusWritten == nil {
t.Fatal("WriteHeader was not called")
}
require.Equal(t, status, *w.statusWritten)
}
func (w *StreamingResponseWriterRecorder) Flush() {
fw, ok := w.ResponseWriter.(http.Flusher)
if ok {
fw.Flush()
}
w.flushed = true
}
var _ http.ResponseWriter = &StreamingResponseWriterRecorder{}

View File

@@ -4,6 +4,8 @@
package events
import (
"time"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
@@ -18,4 +20,5 @@ type Server struct {
HeadFetcher blockchain.HeadFetcher
ChainInfoFetcher blockchain.ChainInfoFetcher
TrackedValidatorsCache *cache.TrackedValidatorsCache
KeepAliveInterval time.Duration
}

View File

@@ -10,14 +10,11 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/helpers",
visibility = ["//visibility:public"],
deps = [
"//api/grpc:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/rpc/lookup: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/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
@@ -40,14 +37,12 @@ go_test(
],
embed = [":go_default_library"],
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",
@@ -62,7 +57,5 @@ go_test(
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)

View File

@@ -8,58 +8,15 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/grpc"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ValidateSyncGRPC checks whether the node is currently syncing and returns an error if it is.
// It also appends syncing info to gRPC headers.
func ValidateSyncGRPC(
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 := &structs.SyncDetailsContainer{
Data: &structs.SyncDetails{
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,

View File

@@ -3,19 +3,15 @@ package helpers
import (
"context"
"strconv"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
grpcutil "github.com/prysmaticlabs/prysm/v5/api/grpc"
chainmock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
dbtest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
syncmock "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/initial-sync/testing"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
@@ -26,53 +22,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"google.golang.org/grpc"
)
func TestValidateSync(t *testing.T) {
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
t.Run("syncing", func(t *testing.T) {
syncChecker := &syncmock.Sync{
IsSyncing: true,
}
headSlot := primitives.Slot(100)
st, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, st.SetSlot(50))
chainService := &chainmock.ChainService{
Slot: &headSlot,
State: st,
}
err = ValidateSyncGRPC(ctx, syncChecker, chainService, chainService, chainService)
require.NotNil(t, err)
sts, ok := grpc.ServerTransportStreamFromContext(ctx).(*runtime.ServerTransportStream)
require.Equal(t, true, ok, "type assertion failed")
md := sts.Header()
v, ok := md[strings.ToLower(grpcutil.CustomErrorMetadataKey)]
require.Equal(t, true, ok, "could not retrieve custom error metadata value")
assert.DeepEqual(
t,
[]string{`{"data":{"head_slot":"50","sync_distance":"50","is_syncing":true,"is_optimistic":false,"el_offline":false}}`},
v,
)
})
t.Run("not syncing", func(t *testing.T) {
syncChecker := &syncmock.Sync{
IsSyncing: false,
}
headSlot := primitives.Slot(100)
st, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, st.SetSlot(50))
chainService := &chainmock.ChainService{
Slot: &headSlot,
State: st,
}
err = ValidateSyncGRPC(ctx, syncChecker, nil, nil, chainService)
require.NoError(t, err)
})
}
func TestIsOptimistic(t *testing.T) {
ctx := context.Background()

View File

@@ -10,6 +10,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/light-client",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//api:go_default_library",
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
@@ -19,8 +20,11 @@ go_library(
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/ssz:go_default_library",
"//network/httputil:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/eth/v2:go_default_library",
@@ -29,6 +33,7 @@ go_library(
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_wealdtech_go_bytesutil//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],

View File

@@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
@@ -46,16 +48,16 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques
return
}
bootstrap, err := createLightClientBootstrap(ctx, state)
bootstrap, err := createLightClientBootstrap(ctx, state, blk.Block())
if err != nil {
httputil.HandleError(w, "could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError)
return
}
response := &structs.LightClientBootstrapResponse{
Version: version.String(blk.Version()),
Data: bootstrap,
}
w.Header().Set(api.VersionHeader, version.String(version.Deneb))
httputil.WriteJson(w, response)
}
@@ -351,27 +353,27 @@ func (s *Server) getLightClientEventBlock(ctx context.Context, minSignaturesRequ
// Get the current state
state, err := s.HeadFetcher.HeadState(ctx)
if err != nil {
return nil, fmt.Errorf("could not get head state %w", err)
return nil, errors.Wrap(err, "could not get head state")
}
// Get the block
latestBlockHeader := *state.LatestBlockHeader()
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, fmt.Errorf("could not get state root %w", err)
return nil, errors.Wrap(err, "could not get state root")
}
latestBlockHeader.StateRoot = stateRoot[:]
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
if err != nil {
return nil, fmt.Errorf("could not get latest block header root %w", err)
return nil, errors.Wrap(err, "could not get latest block header root")
}
block, err := s.Blocker.Block(ctx, latestBlockHeaderRoot[:])
if err != nil {
return nil, fmt.Errorf("could not get latest block %w", err)
return nil, errors.Wrap(err, "could not get latest block")
}
if block == nil {
return nil, fmt.Errorf("latest block is nil")
return nil, errors.New("latest block is nil")
}
// Loop through the blocks until we find a block that satisfies minSignaturesRequired requirement
@@ -385,10 +387,10 @@ func (s *Server) getLightClientEventBlock(ctx context.Context, minSignaturesRequ
parentRoot := block.Block().ParentRoot()
block, err = s.Blocker.Block(ctx, parentRoot[:])
if err != nil {
return nil, fmt.Errorf("could not get parent block %w", err)
return nil, errors.Wrap(err, "could not get parent block")
}
if block == nil {
return nil, fmt.Errorf("parent block is nil")
return nil, errors.New("parent block is nil")
}
// Get the number of sync committee signatures

File diff suppressed because it is too large Load Diff

View File

@@ -2,17 +2,24 @@ package lightclient
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strconv"
"github.com/pkg/errors"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
v1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
@@ -20,7 +27,21 @@ import (
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// createLightClientBootstrap - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_bootstrap
func createLightClientBootstrap(ctx context.Context, state state.BeaconState, blk interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
switch blk.Version() {
case version.Phase0:
return nil, fmt.Errorf("light client bootstrap is not supported for phase0")
case version.Altair, version.Bellatrix:
return createLightClientBootstrapAltair(ctx, state)
case version.Capella:
return createLightClientBootstrapCapella(ctx, state, blk)
case version.Deneb, version.Electra:
return createLightClientBootstrapDeneb(ctx, state, blk)
}
return nil, fmt.Errorf("unsupported block version %s", version.String(blk.Version()))
}
// createLightClientBootstrapAltair - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_bootstrap
// def create_light_client_bootstrap(state: BeaconState) -> LightClientBootstrap:
//
// assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
@@ -37,7 +58,7 @@ import (
// current_sync_committee=state.current_sync_committee,
// current_sync_committee_branch=compute_merkle_proof_for_state(state, CURRENT_SYNC_COMMITTEE_INDEX)
// )
func createLightClientBootstrap(ctx context.Context, state state.BeaconState) (*structs.LightClientBootstrap, error) {
func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconState) (*structs.LightClientBootstrap, error) {
// assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().AltairForkEpoch {
return nil, fmt.Errorf("light client bootstrap is not supported before Altair, invalid slot %d", state.Slot())
@@ -52,14 +73,14 @@ func createLightClientBootstrap(ctx context.Context, state state.BeaconState) (*
// Prepare data
currentSyncCommittee, err := state.CurrentSyncCommittee()
if err != nil {
return nil, fmt.Errorf("could not get current sync committee: %s", err.Error())
return nil, errors.Wrap(err, "could not get current sync committee")
}
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
if err != nil {
return nil, fmt.Errorf("could not get current sync committee proof: %s", err.Error())
return nil, errors.Wrap(err, "could not get current sync committee proof")
}
branch := make([]string, fieldparams.NextSyncCommitteeBranchDepth)
@@ -78,13 +99,249 @@ func createLightClientBootstrap(ctx context.Context, state state.BeaconState) (*
// Above shared util function won't calculate state root, so we need to do it manually
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, fmt.Errorf("could not get state root: %s", err.Error())
return nil, errors.Wrap(err, "could not get state root")
}
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
headerJson, err := json.Marshal(header)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
// Return result
result := &structs.LightClientBootstrap{
Header: header,
Header: headerJson,
CurrentSyncCommittee: committee,
CurrentSyncCommitteeBranch: branch,
}
return result, nil
}
func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
// assert compute_epoch_at_slot(state.slot) >= CAPELLA_FORK_EPOCH
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().CapellaForkEpoch {
return nil, fmt.Errorf("creating Capella light client bootstrap is not supported before Capella, invalid slot %d", state.Slot())
}
// assert state.slot == state.latest_block_header.slot
latestBlockHeader := state.LatestBlockHeader()
if state.Slot() != latestBlockHeader.Slot {
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
}
// Prepare data
currentSyncCommittee, err := state.CurrentSyncCommittee()
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee")
}
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee proof")
}
branch := make([]string, fieldparams.NextSyncCommitteeBranchDepth)
for i, proof := range currentSyncCommitteeProof {
branch[i] = hexutil.Encode(proof)
}
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
payloadInterface, err := block.Body().Execution()
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload")
}
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
if err != nil {
return nil, errors.Wrap(err, "could not get transactions")
}
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
transactionsRoot = transactionsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
withdrawalsRoot = withdrawalsRootArray[:]
}
executionPayloadHeader := &structs.ExecutionPayloadHeaderCapella{
ParentHash: hexutil.Encode(payloadInterface.ParentHash()),
FeeRecipient: hexutil.Encode(payloadInterface.FeeRecipient()),
StateRoot: hexutil.Encode(payloadInterface.StateRoot()),
ReceiptsRoot: hexutil.Encode(payloadInterface.ReceiptsRoot()),
LogsBloom: hexutil.Encode(payloadInterface.LogsBloom()),
PrevRandao: hexutil.Encode(payloadInterface.PrevRandao()),
BlockNumber: hexutil.EncodeUint64(payloadInterface.BlockNumber()),
GasLimit: hexutil.EncodeUint64(payloadInterface.GasLimit()),
GasUsed: hexutil.EncodeUint64(payloadInterface.GasUsed()),
Timestamp: hexutil.EncodeUint64(payloadInterface.Timestamp()),
ExtraData: hexutil.Encode(payloadInterface.ExtraData()),
BaseFeePerGas: hexutil.Encode(payloadInterface.BaseFeePerGas()),
BlockHash: hexutil.Encode(payloadInterface.BlockHash()),
TransactionsRoot: hexutil.Encode(transactionsRoot),
WithdrawalsRoot: hexutil.Encode(withdrawalsRoot),
}
executionPayloadProof, err := blocks.PayloadProof(ctx, block)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
executionPayloadProofStr := make([]string, len(executionPayloadProof))
for i, proof := range executionPayloadProof {
executionPayloadProofStr[i] = hexutil.Encode(proof)
}
header := &structs.LightClientHeaderCapella{
Beacon: beacon,
Execution: executionPayloadHeader,
ExecutionBranch: executionPayloadProofStr,
}
// Above shared util function won't calculate state root, so we need to do it manually
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
headerJson, err := json.Marshal(header)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
// Return result
result := &structs.LightClientBootstrap{
Header: headerJson,
CurrentSyncCommittee: committee,
CurrentSyncCommitteeBranch: branch,
}
return result, nil
}
func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
// assert compute_epoch_at_slot(state.slot) >= DENEB_FORK_EPOCH
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().DenebForkEpoch {
return nil, fmt.Errorf("creating Deneb light client bootstrap is not supported before Deneb, invalid slot %d", state.Slot())
}
// assert state.slot == state.latest_block_header.slot
latestBlockHeader := state.LatestBlockHeader()
if state.Slot() != latestBlockHeader.Slot {
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
}
// Prepare data
currentSyncCommittee, err := state.CurrentSyncCommittee()
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee")
}
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get current sync committee proof")
}
branch := make([]string, fieldparams.NextSyncCommitteeBranchDepth)
for i, proof := range currentSyncCommitteeProof {
branch[i] = hexutil.Encode(proof)
}
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
payloadInterface, err := block.Body().Execution()
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload")
}
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
if err != nil {
return nil, errors.Wrap(err, "could not get transactions")
}
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
transactionsRoot = transactionsRootArray[:]
} else if err != nil {
return nil, errors.Wrap(err, "could not get transactions root")
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensus_types.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
withdrawalsRoot = withdrawalsRootArray[:]
}
executionPayloadHeader := &structs.ExecutionPayloadHeaderDeneb{
ParentHash: hexutil.Encode(payloadInterface.ParentHash()),
FeeRecipient: hexutil.Encode(payloadInterface.FeeRecipient()),
StateRoot: hexutil.Encode(payloadInterface.StateRoot()),
ReceiptsRoot: hexutil.Encode(payloadInterface.ReceiptsRoot()),
LogsBloom: hexutil.Encode(payloadInterface.LogsBloom()),
PrevRandao: hexutil.Encode(payloadInterface.PrevRandao()),
BlockNumber: hexutil.EncodeUint64(payloadInterface.BlockNumber()),
GasLimit: hexutil.EncodeUint64(payloadInterface.GasLimit()),
GasUsed: hexutil.EncodeUint64(payloadInterface.GasUsed()),
Timestamp: hexutil.EncodeUint64(payloadInterface.Timestamp()),
ExtraData: hexutil.Encode(payloadInterface.ExtraData()),
BaseFeePerGas: hexutil.Encode(payloadInterface.BaseFeePerGas()),
BlockHash: hexutil.Encode(payloadInterface.BlockHash()),
TransactionsRoot: hexutil.Encode(transactionsRoot),
WithdrawalsRoot: hexutil.Encode(withdrawalsRoot),
}
executionPayloadProof, err := blocks.PayloadProof(ctx, block)
if err != nil {
return nil, errors.Wrap(err, "could not get execution payload proof")
}
executionPayloadProofStr := make([]string, len(executionPayloadProof))
for i, proof := range executionPayloadProof {
executionPayloadProofStr[i] = hexutil.Encode(proof)
}
header := &structs.LightClientHeaderDeneb{
Beacon: beacon,
Execution: executionPayloadHeader,
ExecutionBranch: executionPayloadProofStr,
}
// Above shared util function won't calculate state root, so we need to do it manually
stateRoot, err := state.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get state root")
}
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
headerJson, err := json.Marshal(header)
if err != nil {
return nil, errors.Wrap(err, "could not convert header to raw message")
}
// Return result
result := &structs.LightClientBootstrap{
Header: headerJson,
CurrentSyncCommittee: committee,
CurrentSyncCommitteeBranch: branch,
}
@@ -166,15 +423,19 @@ func createLightClientUpdate(
var nextSyncCommitteeBranch [][]byte
// update_signature_period = compute_sync_committee_period(compute_epoch_at_slot(block.message.slot))
updateSignaturePeriod := slots.ToEpoch(block.Block().Slot())
updateSignaturePeriod := slots.SyncCommitteePeriod(slots.ToEpoch(block.Block().Slot()))
// update_attested_period = compute_sync_committee_period(compute_epoch_at_slot(attested_header.slot))
updateAttestedPeriod := slots.ToEpoch(result.AttestedHeader.Slot)
resultAttestedHeaderBeacon, err := result.AttestedHeader.GetBeacon()
if err != nil {
return nil, errors.Wrap(err, "could not get attested header beacon")
}
updateAttestedPeriod := slots.SyncCommitteePeriod(slots.ToEpoch(resultAttestedHeaderBeacon.Slot))
if updateAttestedPeriod == updateSignaturePeriod {
tempNextSyncCommittee, err := attestedState.NextSyncCommittee()
if err != nil {
return nil, fmt.Errorf("could not get next sync committee: %s", err.Error())
return nil, errors.Wrap(err, "could not get next sync committee")
}
nextSyncCommittee = &v2.SyncCommittee{
@@ -184,7 +445,7 @@ func createLightClientUpdate(
nextSyncCommitteeBranch, err = attestedState.NextSyncCommitteeProof(ctx)
if err != nil {
return nil, fmt.Errorf("could not get next sync committee proof: %s", err.Error())
return nil, errors.Wrap(err, "could not get next sync committee proof")
}
} else {
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
@@ -205,7 +466,11 @@ func createLightClientUpdate(
result.NextSyncCommittee = nextSyncCommittee
result.NextSyncCommitteeBranch = nextSyncCommitteeBranch
return newLightClientUpdateToJSON(result), nil
res, err := newLightClientUpdateToJSON(result)
if err != nil {
return nil, errors.Wrap(err, "could not convert light client update to JSON")
}
return res, nil
}
func newLightClientFinalityUpdateFromBeaconState(
@@ -219,7 +484,11 @@ func newLightClientFinalityUpdateFromBeaconState(
return nil, err
}
return newLightClientUpdateToJSON(result), nil
res, err := newLightClientUpdateToJSON(result)
if err != nil {
return nil, errors.Wrap(err, "could not convert light client update to JSON")
}
return res, nil
}
func newLightClientOptimisticUpdateFromBeaconState(
@@ -232,42 +501,11 @@ func newLightClientOptimisticUpdateFromBeaconState(
return nil, err
}
return newLightClientUpdateToJSON(result), nil
}
func NewLightClientBootstrapFromJSON(bootstrapJSON *structs.LightClientBootstrap) (*v2.LightClientBootstrap, error) {
bootstrap := &v2.LightClientBootstrap{}
var err error
v1Alpha1Header, err := bootstrapJSON.Header.Beacon.ToConsensus()
res, err := newLightClientUpdateToJSON(result)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "could not convert light client update to JSON")
}
bootstrap.Header = migration.V1Alpha1HeaderToV1(v1Alpha1Header)
currentSyncCommittee, err := bootstrapJSON.CurrentSyncCommittee.ToConsensus()
if err != nil {
return nil, err
}
bootstrap.CurrentSyncCommittee = migration.V1Alpha1SyncCommitteeToV2(currentSyncCommittee)
if bootstrap.CurrentSyncCommitteeBranch, err = branchFromJSON(bootstrapJSON.CurrentSyncCommitteeBranch); err != nil {
return nil, err
}
return bootstrap, nil
}
func branchFromJSON(branch []string) ([][]byte, error) {
var branchBytes [][]byte
for _, root := range branch {
branch, err := hexutil.Decode(root)
if err != nil {
return nil, err
}
branchBytes = append(branchBytes, branch)
}
return branchBytes, nil
return res, nil
}
func branchToJSON(branchBytes [][]byte) []string {
@@ -291,9 +529,9 @@ func syncAggregateToJSON(input *v1.SyncAggregate) *structs.SyncAggregate {
}
}
func newLightClientUpdateToJSON(input *v2.LightClientUpdate) *structs.LightClientUpdate {
func newLightClientUpdateToJSON(input *v2.LightClientUpdate) (*structs.LightClientUpdate, error) {
if input == nil {
return nil
return nil, errors.New("input is nil")
}
var nextSyncCommittee *structs.SyncCommittee
@@ -303,18 +541,36 @@ func newLightClientUpdateToJSON(input *v2.LightClientUpdate) *structs.LightClien
var finalizedHeader *structs.BeaconBlockHeader
if input.FinalizedHeader != nil {
finalizedHeader = structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(input.FinalizedHeader))
inputFinalizedHeaderBeacon, err := input.FinalizedHeader.GetBeacon()
if err != nil {
return nil, errors.Wrap(err, "could not get finalized header beacon")
}
finalizedHeader = structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(inputFinalizedHeaderBeacon))
}
return &structs.LightClientUpdate{
AttestedHeader: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(input.AttestedHeader)),
inputAttestedHeaderBeacon, err := input.AttestedHeader.GetBeacon()
if err != nil {
return nil, errors.Wrap(err, "could not get attested header beacon")
}
attestedHeaderJson, err := json.Marshal(&structs.LightClientHeader{Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(inputAttestedHeaderBeacon))})
if err != nil {
return nil, errors.Wrap(err, "could not convert attested header to raw message")
}
finalizedHeaderJson, err := json.Marshal(&structs.LightClientHeader{Beacon: finalizedHeader})
if err != nil {
return nil, errors.Wrap(err, "could not convert finalized header to raw message")
}
result := &structs.LightClientUpdate{
AttestedHeader: attestedHeaderJson,
NextSyncCommittee: nextSyncCommittee,
NextSyncCommitteeBranch: branchToJSON(input.NextSyncCommitteeBranch),
FinalizedHeader: finalizedHeader,
FinalizedHeader: finalizedHeaderJson,
FinalityBranch: branchToJSON(input.FinalityBranch),
SyncAggregate: syncAggregateToJSON(input.SyncAggregate),
SignatureSlot: strconv.FormatUint(uint64(input.SignatureSlot), 10),
}
return result, nil
}
func IsSyncCommitteeUpdate(update *v2.LightClientUpdate) bool {
@@ -327,7 +583,7 @@ func IsFinalityUpdate(update *v2.LightClientUpdate) bool {
return !reflect.DeepEqual(update.FinalityBranch, finalityBranch)
}
func IsBetterUpdate(newUpdate, oldUpdate *v2.LightClientUpdate) bool {
func IsBetterUpdate(newUpdate, oldUpdate *v2.LightClientUpdate) (bool, error) {
maxActiveParticipants := newUpdate.SyncAggregate.SyncCommitteeBits.Len()
newNumActiveParticipants := newUpdate.SyncAggregate.SyncCommitteeBits.Count()
oldNumActiveParticipants := oldUpdate.SyncAggregate.SyncCommitteeBits.Count()
@@ -335,45 +591,63 @@ func IsBetterUpdate(newUpdate, oldUpdate *v2.LightClientUpdate) bool {
oldHasSupermajority := oldNumActiveParticipants*3 >= maxActiveParticipants*2
if newHasSupermajority != oldHasSupermajority {
return newHasSupermajority
return newHasSupermajority, nil
}
if !newHasSupermajority && newNumActiveParticipants != oldNumActiveParticipants {
return newNumActiveParticipants > oldNumActiveParticipants
return newNumActiveParticipants > oldNumActiveParticipants, nil
}
newUpdateAttestedHeaderBeacon, err := newUpdate.AttestedHeader.GetBeacon()
if err != nil {
return false, errors.Wrap(err, "could not get attested header beacon")
}
oldUpdateAttestedHeaderBeacon, err := oldUpdate.AttestedHeader.GetBeacon()
if err != nil {
return false, errors.Wrap(err, "could not get attested header beacon")
}
// Compare presence of relevant sync committee
newHasRelevantSyncCommittee := IsSyncCommitteeUpdate(newUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.AttestedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.SignatureSlot)))
oldHasRelevantSyncCommittee := IsSyncCommitteeUpdate(oldUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.AttestedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.SignatureSlot)))
newHasRelevantSyncCommittee := IsSyncCommitteeUpdate(newUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.SignatureSlot)))
oldHasRelevantSyncCommittee := IsSyncCommitteeUpdate(oldUpdate) && (slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.SignatureSlot)))
if newHasRelevantSyncCommittee != oldHasRelevantSyncCommittee {
return newHasRelevantSyncCommittee
return newHasRelevantSyncCommittee, nil
}
// Compare indication of any finality
newHasFinality := IsFinalityUpdate(newUpdate)
oldHasFinality := IsFinalityUpdate(oldUpdate)
if newHasFinality != oldHasFinality {
return newHasFinality
return newHasFinality, nil
}
newUpdateFinalizedHeaderBeacon, err := newUpdate.FinalizedHeader.GetBeacon()
if err != nil {
return false, errors.Wrap(err, "could not get finalized header beacon")
}
oldUpdateFinalizedHeaderBeacon, err := oldUpdate.FinalizedHeader.GetBeacon()
if err != nil {
return false, errors.Wrap(err, "could not get finalized header beacon")
}
// Compare sync committee finality
if newHasFinality {
newHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.FinalizedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdate.AttestedHeader.Slot))
oldHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.FinalizedHeader.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdate.AttestedHeader.Slot))
newHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateFinalizedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(newUpdateAttestedHeaderBeacon.Slot))
oldHasSyncCommitteeFinality := slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateFinalizedHeaderBeacon.Slot)) == slots.SyncCommitteePeriod(slots.ToEpoch(oldUpdateAttestedHeaderBeacon.Slot))
if newHasSyncCommitteeFinality != oldHasSyncCommitteeFinality {
return newHasSyncCommitteeFinality
return newHasSyncCommitteeFinality, nil
}
}
// Tiebreaker 1: Sync committee participation beyond supermajority
if newNumActiveParticipants != oldNumActiveParticipants {
return newNumActiveParticipants > oldNumActiveParticipants
return newNumActiveParticipants > oldNumActiveParticipants, nil
}
// Tiebreaker 2: Prefer older data (fewer changes to best)
if newUpdate.AttestedHeader.Slot != oldUpdate.AttestedHeader.Slot {
return newUpdate.AttestedHeader.Slot < oldUpdate.AttestedHeader.Slot
if newUpdateAttestedHeaderBeacon.Slot != oldUpdateAttestedHeaderBeacon.Slot {
return newUpdateAttestedHeaderBeacon.Slot < oldUpdateAttestedHeaderBeacon.Slot, nil
}
return newUpdate.SignatureSlot < oldUpdate.SignatureSlot
return newUpdate.SignatureSlot < oldUpdate.SignatureSlot, nil
}

View File

@@ -94,8 +94,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: make([][]byte, fieldparams.NextSyncCommitteeBranchDepth),
SignatureSlot: 9999,
@@ -104,8 +108,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000001,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000001,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 1000000,
@@ -118,8 +126,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000001,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000001,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 1000000,
@@ -128,8 +140,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: make([][]byte, fieldparams.NextSyncCommitteeBranchDepth),
SignatureSlot: 9999,
@@ -142,8 +158,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
@@ -153,8 +173,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
@@ -168,8 +192,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
@@ -179,8 +207,12 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
@@ -194,28 +226,44 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 999999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
}},
},
},
},
expectedResult: true,
@@ -226,28 +274,44 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 999999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: false,
@@ -258,28 +322,44 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: true,
@@ -290,28 +370,44 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b01111100, 0b1}, // [0,1,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: false,
@@ -322,28 +418,44 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: true,
@@ -354,60 +466,92 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 999999,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: false,
},
{
name: "none of the above conditions are met and new signature's slot is lesser than old signature's slot",
name: "none of the above conditions are met and new signature's slot is less than old signature's slot",
oldUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9998,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: true,
@@ -418,28 +562,44 @@ func TestIsBetterUpdate(t *testing.T) {
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9998,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
newUpdate: &ethpbv2.LightClientUpdate{
SyncAggregate: &ethpbv1.SyncAggregate{
SyncCommitteeBits: []byte{0b00111100, 0b1}, // [0,0,1,1,1,1,0,0]
},
AttestedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
AttestedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 1000000,
}},
},
},
NextSyncCommitteeBranch: createNonEmptySyncCommitteeBranch(),
SignatureSlot: 9999,
FinalityBranch: createNonEmptyFinalityBranch(),
FinalizedHeader: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
FinalizedHeader: &ethpbv2.LightClientHeaderContainer{
Header: &ethpbv2.LightClientHeaderContainer_HeaderAltair{
HeaderAltair: &ethpbv2.LightClientHeader{Beacon: &ethpbv1.BeaconBlockHeader{
Slot: 9999,
}},
},
},
},
expectedResult: false,
@@ -448,7 +608,9 @@ func TestIsBetterUpdate(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.expectedResult, IsBetterUpdate(testCase.newUpdate, testCase.oldUpdate))
result, err := IsBetterUpdate(testCase.newUpdate, testCase.oldUpdate)
assert.NoError(t, err)
assert.Equal(t, testCase.expectedResult, result)
})
}
}

View File

@@ -37,6 +37,7 @@ go_library(
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/httputil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",

View File

@@ -28,11 +28,11 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
validator2 "github.com/prysmaticlabs/prysm/v5/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
ethpbalpha "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

View File

@@ -12,13 +12,13 @@ go_library(
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/sync:go_default_library",
"//io/logs:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"@com_github_golang_protobuf//ptypes/empty",
"@com_github_golang_protobuf//ptypes/timestamp",
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
@@ -47,9 +47,7 @@ go_test(
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
"@org_golang_google_grpc//reflection:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",

View File

@@ -21,9 +21,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync"
"github.com/prysmaticlabs/prysm/v5/io/logs"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"go.opencensus.io/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"

View File

@@ -3,14 +3,12 @@ package node
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
dbutil "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
@@ -24,7 +22,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -92,7 +89,7 @@ func TestNodeServer_GetImplementedServices(t *testing.T) {
res, err := ns.ListImplementedServices(context.Background(), &emptypb.Empty{})
require.NoError(t, err)
// We verify the services include the node service + the registered reflection service.
// We verify the services include the node service + the 2 registered reflection services.
assert.Equal(t, 2, len(res.Services))
}
@@ -206,11 +203,6 @@ func TestNodeServer_GetHealth(t *testing.T) {
input: &mockSync.Sync{IsSyncing: false},
wantedErr: "service unavailable",
},
{
name: "custom sync status",
input: &mockSync.Sync{IsSyncing: true},
customStatus: 206,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -220,21 +212,11 @@ func TestNodeServer_GetHealth(t *testing.T) {
}
ethpb.RegisterNodeServer(server, ns)
reflection.Register(server)
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &runtime.ServerTransportStream{})
_, err := ns.GetHealth(ctx, &ethpb.HealthRequest{SyncingStatus: tt.customStatus})
_, err := ns.GetHealth(context.Background(), &ethpb.HealthRequest{SyncingStatus: tt.customStatus})
if tt.wantedErr == "" {
require.NoError(t, err)
return
}
if tt.customStatus != 0 {
// Assuming the call was successful, now extract the headers
headers, _ := metadata.FromIncomingContext(ctx)
// Check for the specific header
values, ok := headers["x-http-code"]
require.Equal(t, true, ok && len(values) > 0)
require.Equal(t, fmt.Sprintf("%d", tt.customStatus), values[0])
}
require.ErrorContains(t, tt.wantedErr, err)
})
}

View File

@@ -79,6 +79,7 @@ go_library(
"//encoding/ssz:go_default_library",
"//math:go_default_library",
"//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/forks:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/eth/v1:go_default_library",

Some files were not shown because too many files have changed in this diff Show More