mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Compare commits
84 Commits
peerDAS-20
...
fix-comput
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1df3d0c7a3 | ||
|
|
f9bde3e097 | ||
|
|
294a24616b | ||
|
|
edd736fb9f | ||
|
|
13b469858d | ||
|
|
70110ecb86 | ||
|
|
4cfafdf861 | ||
|
|
7f4091e334 | ||
|
|
5addd5b3e9 | ||
|
|
9abe898784 | ||
|
|
391f6afab3 | ||
|
|
1f62a72068 | ||
|
|
5d0549c2e0 | ||
|
|
fc794f48fb | ||
|
|
aaa60012e1 | ||
|
|
c21203ec78 | ||
|
|
a8a642a7b1 | ||
|
|
15250d7d9c | ||
|
|
8792b33b75 | ||
|
|
4948035d7f | ||
|
|
8cda3f0589 | ||
|
|
e7d840e843 | ||
|
|
625308d7c5 | ||
|
|
523422ed84 | ||
|
|
2efb3065be | ||
|
|
dc538b9696 | ||
|
|
ca32889e1a | ||
|
|
a1b6059051 | ||
|
|
3a61df4a96 | ||
|
|
bdabbef300 | ||
|
|
c6362fa5a6 | ||
|
|
5688afc6a4 | ||
|
|
f0fe5bbab1 | ||
|
|
107c1f9e04 | ||
|
|
42be4ee3f5 | ||
|
|
dbd9141eca | ||
|
|
68fdcf9776 | ||
|
|
872269c7cb | ||
|
|
e60c5322ef | ||
|
|
e62ee87911 | ||
|
|
19a7c0b8c3 | ||
|
|
1eba10c0e2 | ||
|
|
fa559e30dd | ||
|
|
b42cc45e44 | ||
|
|
ac127c95c8 | ||
|
|
c30f94d4be | ||
|
|
099efc1f3c | ||
|
|
33e33978e3 | ||
|
|
ff952dae4e | ||
|
|
440b173feb | ||
|
|
f126d64c96 | ||
|
|
8f170f97ec | ||
|
|
da3159dd6a | ||
|
|
31ae744237 | ||
|
|
3b2d29dce4 | ||
|
|
ddafedc268 | ||
|
|
7e5738bfcd | ||
|
|
315c05b351 | ||
|
|
3662cf6009 | ||
|
|
98d8b50b0e | ||
|
|
1a1cc25bd1 | ||
|
|
bc9c7193a9 | ||
|
|
7ac3c01b5b | ||
|
|
ed6f69e868 | ||
|
|
222b360c66 | ||
|
|
170a864239 | ||
|
|
b5cfd0d35d | ||
|
|
28181710b0 | ||
|
|
2f756b7ec4 | ||
|
|
df4ca54a76 | ||
|
|
875e3e5e7d | ||
|
|
a5317f8117 | ||
|
|
38b92c0171 | ||
|
|
a03b34af77 | ||
|
|
4c14bd8be2 | ||
|
|
62b8e63a0a | ||
|
|
eec3b0b7fe | ||
|
|
2bffb83a00 | ||
|
|
45fd3eb1bf | ||
|
|
963a1b4cb7 | ||
|
|
77c845043d | ||
|
|
342bb0fcef | ||
|
|
93e6bd7929 | ||
|
|
3015eea4e3 |
33
.github/workflows/changelog.yml
vendored
33
.github/workflows/changelog.yml
vendored
@@ -1,33 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
changed_files:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check CHANGELOG.md
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: changelog modified
|
||||
id: changelog-modified
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
files: CHANGELOG.md
|
||||
|
||||
- name: List all changed files
|
||||
env:
|
||||
ALL_CHANGED_FILES: ${{ steps.changelog-modified.outputs.all_changed_files }}
|
||||
run: |
|
||||
if [[ ${ALL_CHANGED_FILES[*]} =~ (^|[[:space:]])"CHANGELOG.md"($|[[:space:]]) ]];
|
||||
then
|
||||
echo "CHANGELOG.md was modified.";
|
||||
exit 0;
|
||||
else
|
||||
echo "CHANGELOG.md was not modified.";
|
||||
echo "Please see CHANGELOG.md and follow the instructions to add your changes to that file."
|
||||
echo "In some rare scenarios, a changelog entry is not required and this CI check can be ignored."
|
||||
exit 1;
|
||||
fi
|
||||
@@ -26,7 +26,6 @@ approval_rules:
|
||||
only_changed_files:
|
||||
paths:
|
||||
- "*pb.go"
|
||||
- "*pb.gw.go"
|
||||
- "*.bazel"
|
||||
options:
|
||||
ignore_commits_by:
|
||||
@@ -69,7 +68,6 @@ approval_rules:
|
||||
changed_files:
|
||||
ignore:
|
||||
- "*pb.go"
|
||||
- "*pb.gw.go"
|
||||
- "*.bazel"
|
||||
options:
|
||||
ignore_commits_by:
|
||||
|
||||
@@ -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",
|
||||
|
||||
650
CHANGELOG.md
650
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
10
WORKSPACE
10
WORKSPACE
@@ -227,7 +227,7 @@ filegroup(
|
||||
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
|
||||
)
|
||||
|
||||
consensus_spec_version = "v1.5.0-alpha.5"
|
||||
consensus_spec_version = "v1.5.0-alpha.6"
|
||||
|
||||
bls_test_version = "v0.1.1"
|
||||
|
||||
@@ -243,7 +243,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-R9vG5HEL5eGMOAmbkKfJ2jfelNqL5V0xBUPiXOiGM6U=",
|
||||
integrity = "sha256-M7u/Ot/Vzorww+dFbHp0cxLyM2mezJjijCzq+LY3uvs=",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -259,7 +259,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-AEIiEOlf1XuxoRMCsN+kgJMo4LrS05+biTA1p/7Ro00=",
|
||||
integrity = "sha256-deOSeLRsmHXvkRp8n2bs3HXdkGUJWWqu8KFM/QABbZg=",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -275,7 +275,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-LH/Xr20yrJRYnbpjRGupMWTIOWt3cpxZJWXgThwVDsk=",
|
||||
integrity = "sha256-Zz7YCf6XVf57nzSEGq9ToflJFHM0lAGwhd18l9Rf3hA=",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -290,7 +290,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-mlytz4MPjKh0DwV7FMiAtnRbJw9B6o78/x66/vmnYc8=",
|
||||
integrity = "sha256-BoXckDxXnDcEmAjg/dQgf/tLiJsb6CT0aZvmWHFijrY=",
|
||||
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
|
||||
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -146,7 +146,7 @@ func (c *Client) do(ctx context.Context, method string, path string, body io.Rea
|
||||
|
||||
u := c.baseURL.ResolveReference(&url.URL{Path: path})
|
||||
|
||||
span.AddAttributes(trace.StringAttribute("url", u.String()),
|
||||
span.SetAttributes(trace.StringAttribute("url", u.String()),
|
||||
trace.StringAttribute("method", method))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
|
||||
@@ -259,7 +259,7 @@ func (c *Client) GetHeader(ctx context.Context, slot primitives.Slot, parentHash
|
||||
func (c *Client) RegisterValidator(ctx context.Context, svr []*ethpb.SignedValidatorRegistrationV1) error {
|
||||
ctx, span := trace.StartSpan(ctx, "builder.client.RegisterValidator")
|
||||
defer span.End()
|
||||
span.AddAttributes(trace.Int64Attribute("num_reqs", int64(len(svr))))
|
||||
span.SetAttributes(trace.Int64Attribute("num_reqs", int64(len(svr))))
|
||||
|
||||
if len(svr) == 0 {
|
||||
err := errors.Wrap(errMalformedRequest, "empty validator registration list")
|
||||
@@ -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")
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
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",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/api/gateway",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
"//validator:__subpackages__",
|
||||
],
|
||||
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"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -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...)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package gateway
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "gateway")
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
}
|
||||
|
||||
31
api/server/httprest/BUILD.bazel
Normal file
31
api/server/httprest/BUILD.bazel
Normal file
@@ -0,0 +1,31 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"log.go",
|
||||
"options.go",
|
||||
"server.go",
|
||||
],
|
||||
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_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["server_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
5
api/server/httprest/log.go
Normal file
5
api/server/httprest/log.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package httprest
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
var log = logrus.WithField("prefix", "httprest")
|
||||
44
api/server/httprest/options.go
Normal file
44
api/server/httprest/options.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package httprest
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/middleware"
|
||||
)
|
||||
|
||||
// Option is a http rest server functional parameter type.
|
||||
type Option func(g *Server) error
|
||||
|
||||
// WithMiddlewares sets the list of middlewares to be applied on routes.
|
||||
func WithMiddlewares(mw []middleware.Middleware) Option {
|
||||
return func(g *Server) error {
|
||||
g.cfg.middlewares = mw
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *http.ServeMux) 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
|
||||
}
|
||||
}
|
||||
101
api/server/httprest/server.go
Normal file
101
api/server/httprest/server.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package httprest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/middleware"
|
||||
"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
|
||||
middlewares []middleware.Middleware
|
||||
router http.Handler
|
||||
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
|
||||
handler = middleware.MiddlewareChain(g.cfg.router, g.cfg.middlewares)
|
||||
if g.cfg.timeout > 0*time.Second {
|
||||
defaultReadHeaderTimeout = g.cfg.timeout
|
||||
handler = http.TimeoutHandler(handler, g.cfg.timeout, "request timed out")
|
||||
}
|
||||
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
|
||||
}
|
||||
71
api/server/httprest/server_test.go
Normal file
71
api/server/httprest/server_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package httprest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"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)
|
||||
portStr := fmt.Sprintf("%d", port) // Convert port to string
|
||||
host := ctx.String(flags.HTTPServerHost.Name)
|
||||
address := net.JoinHostPort(host, portStr)
|
||||
handler := http.NewServeMux()
|
||||
opts := []Option{
|
||||
WithHTTPAddr(address),
|
||||
WithRouter(handler),
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
handler := http.NewServeMux()
|
||||
port := ctx.Int(flags.HTTPServerPort.Name)
|
||||
portStr := fmt.Sprintf("%d", port) // Convert port to string
|
||||
host := ctx.String(flags.HTTPServerHost.Name)
|
||||
address := net.JoinHostPort(host, portStr)
|
||||
|
||||
opts := []Option{
|
||||
WithHTTPAddr(address),
|
||||
WithRouter(handler),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -8,10 +8,7 @@ go_library(
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/api/server/middleware",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_rs_cors//:go_default_library",
|
||||
],
|
||||
deps = ["@com_github_rs_cors//:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
|
||||
@@ -5,10 +5,11 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
type Middleware func(http.Handler) http.Handler
|
||||
|
||||
// NormalizeQueryValuesHandler normalizes an input query of "key=value1,value2,value3" to "key=value1&key=value2&key=value3"
|
||||
func NormalizeQueryValuesHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -21,7 +22,7 @@ func NormalizeQueryValuesHandler(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// CorsHandler sets the cors settings on api endpoints
|
||||
func CorsHandler(allowOrigins []string) mux.MiddlewareFunc {
|
||||
func CorsHandler(allowOrigins []string) Middleware {
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: allowOrigins,
|
||||
AllowedMethods: []string{http.MethodPost, http.MethodGet, http.MethodDelete, http.MethodOptions},
|
||||
@@ -34,7 +35,7 @@ func CorsHandler(allowOrigins []string) mux.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// ContentTypeHandler checks request for the appropriate media types otherwise returning a http.StatusUnsupportedMediaType error
|
||||
func ContentTypeHandler(acceptedMediaTypes []string) mux.MiddlewareFunc {
|
||||
func ContentTypeHandler(acceptedMediaTypes []string) Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// skip the GET request
|
||||
@@ -67,7 +68,7 @@ func ContentTypeHandler(acceptedMediaTypes []string) mux.MiddlewareFunc {
|
||||
}
|
||||
|
||||
// AcceptHeaderHandler checks if the client's response preference is handled
|
||||
func AcceptHeaderHandler(serverAcceptedTypes []string) mux.MiddlewareFunc {
|
||||
func AcceptHeaderHandler(serverAcceptedTypes []string) Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
acceptHeader := r.Header.Get("Accept")
|
||||
@@ -110,3 +111,15 @@ func AcceptHeaderHandler(serverAcceptedTypes []string) mux.MiddlewareFunc {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func MiddlewareChain(h http.Handler, mw []Middleware) http.Handler {
|
||||
if len(mw) < 1 {
|
||||
return h
|
||||
}
|
||||
|
||||
wrapped := h
|
||||
for i := len(mw) - 1; i >= 0; i-- {
|
||||
wrapped = mw[i](wrapped)
|
||||
}
|
||||
return wrapped
|
||||
}
|
||||
|
||||
@@ -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,6 +34,9 @@ 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/migration:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
|
||||
@@ -365,6 +365,7 @@ type BeaconBlockBodyElectra struct {
|
||||
ExecutionPayload *ExecutionPayloadElectra `json:"execution_payload"`
|
||||
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
|
||||
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
|
||||
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
|
||||
}
|
||||
|
||||
type BlindedBeaconBlockElectra struct {
|
||||
@@ -403,6 +404,7 @@ type BlindedBeaconBlockBodyElectra struct {
|
||||
ExecutionPayloadHeader *ExecutionPayloadHeaderElectra `json:"execution_payload_header"`
|
||||
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
|
||||
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
|
||||
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
|
||||
}
|
||||
|
||||
type SignedBeaconBlockHeaderContainer struct {
|
||||
@@ -514,6 +516,8 @@ type ExecutionPayloadDeneb struct {
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
}
|
||||
|
||||
type ExecutionPayloadElectra = ExecutionPayloadDeneb
|
||||
|
||||
type ExecutionPayloadHeaderDeneb struct {
|
||||
ParentHash string `json:"parent_hash"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
@@ -534,48 +538,10 @@ type ExecutionPayloadHeaderDeneb struct {
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
}
|
||||
|
||||
type ExecutionPayloadElectra struct {
|
||||
ParentHash string `json:"parent_hash"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
StateRoot string `json:"state_root"`
|
||||
ReceiptsRoot string `json:"receipts_root"`
|
||||
LogsBloom string `json:"logs_bloom"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
BlockNumber string `json:"block_number"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
GasUsed string `json:"gas_used"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
ExtraData string `json:"extra_data"`
|
||||
BaseFeePerGas string `json:"base_fee_per_gas"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
Transactions []string `json:"transactions"`
|
||||
Withdrawals []*Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed string `json:"blob_gas_used"`
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
DepositRequests []*DepositRequest `json:"deposit_requests"`
|
||||
WithdrawalRequests []*WithdrawalRequest `json:"withdrawal_requests"`
|
||||
ConsolidationRequests []*ConsolidationRequest `json:"consolidation_requests"`
|
||||
}
|
||||
type ExecutionPayloadHeaderElectra = ExecutionPayloadHeaderDeneb
|
||||
|
||||
type ExecutionPayloadHeaderElectra struct {
|
||||
ParentHash string `json:"parent_hash"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
StateRoot string `json:"state_root"`
|
||||
ReceiptsRoot string `json:"receipts_root"`
|
||||
LogsBloom string `json:"logs_bloom"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
BlockNumber string `json:"block_number"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
GasUsed string `json:"gas_used"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
ExtraData string `json:"extra_data"`
|
||||
BaseFeePerGas string `json:"base_fee_per_gas"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
TransactionsRoot string `json:"transactions_root"`
|
||||
WithdrawalsRoot string `json:"withdrawals_root"`
|
||||
BlobGasUsed string `json:"blob_gas_used"`
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
DepositRequestsRoot string `json:"deposit_requests_root"`
|
||||
WithdrawalRequestsRoot string `json:"withdrawal_requests_root"`
|
||||
ConsolidationRequestsRoot string `json:"consolidation_requests_root"`
|
||||
type ExecutionRequests struct {
|
||||
Deposits []*DepositRequest `json:"deposits"`
|
||||
Withdrawals []*WithdrawalRequest `json:"withdrawals"`
|
||||
Consolidations []*ConsolidationRequest `json:"consolidations"`
|
||||
}
|
||||
|
||||
@@ -2088,27 +2088,31 @@ func (b *BeaconBlockElectra) ToConsensus() (*eth.BeaconBlockElectra, error) {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayload.ExcessBlobGas")
|
||||
}
|
||||
|
||||
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionPayload.DepositRequests))
|
||||
for i, d := range b.Body.ExecutionPayload.DepositRequests {
|
||||
if b.Body.ExecutionRequests == nil {
|
||||
return nil, server.NewDecodeError(errors.New("nil execution requests"), "Body.ExequtionRequests")
|
||||
}
|
||||
|
||||
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionRequests.Deposits))
|
||||
for i, d := range b.Body.ExecutionRequests.Deposits {
|
||||
depositRequests[i], err = d.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.DepositRequests[%d]", i))
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Deposits[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionPayload.WithdrawalRequests))
|
||||
for i, w := range b.Body.ExecutionPayload.WithdrawalRequests {
|
||||
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionRequests.Withdrawals))
|
||||
for i, w := range b.Body.ExecutionRequests.Withdrawals {
|
||||
withdrawalRequests[i], err = w.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.WithdrawalRequests[%d]", i))
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Withdrawals[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionPayload.ConsolidationRequests))
|
||||
for i, c := range b.Body.ExecutionPayload.ConsolidationRequests {
|
||||
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionRequests.Consolidations))
|
||||
for i, c := range b.Body.ExecutionRequests.Consolidations {
|
||||
consolidationRequests[i], err = c.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.ConsolidationRequests[%d]", i))
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Consolidations[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2151,29 +2155,31 @@ func (b *BeaconBlockElectra) ToConsensus() (*eth.BeaconBlockElectra, error) {
|
||||
SyncCommitteeSignature: syncCommitteeSig,
|
||||
},
|
||||
ExecutionPayload: &enginev1.ExecutionPayloadElectra{
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
Transactions: txs,
|
||||
Withdrawals: withdrawals,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
DepositRequests: depositRequests,
|
||||
WithdrawalRequests: withdrawalRequests,
|
||||
ConsolidationRequests: consolidationRequests,
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
Transactions: txs,
|
||||
Withdrawals: withdrawals,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
},
|
||||
BlsToExecutionChanges: blsChanges,
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Deposits: depositRequests,
|
||||
Withdrawals: withdrawalRequests,
|
||||
Consolidations: consolidationRequests,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -2383,17 +2389,31 @@ func (b *BlindedBeaconBlockElectra) ToConsensus() (*eth.BlindedBeaconBlockElectr
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayload.ExcessBlobGas")
|
||||
}
|
||||
payloadDepositRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.DepositRequestsRoot, fieldparams.RootLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.DepositRequestsRoot")
|
||||
if b.Body.ExecutionRequests == nil {
|
||||
return nil, server.NewDecodeError(errors.New("nil execution requests"), "Body.ExecutionRequests")
|
||||
}
|
||||
payloadWithdrawalRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.WithdrawalRequestsRoot, fieldparams.RootLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.WithdrawalRequestsRoot")
|
||||
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionRequests.Deposits))
|
||||
for i, d := range b.Body.ExecutionRequests.Deposits {
|
||||
depositRequests[i], err = d.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Deposits[%d]", i))
|
||||
}
|
||||
}
|
||||
payloadConsolidationRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.ConsolidationRequestsRoot, fieldparams.RootLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.ConsolidationRequestsRoot")
|
||||
|
||||
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionRequests.Withdrawals))
|
||||
for i, w := range b.Body.ExecutionRequests.Withdrawals {
|
||||
withdrawalRequests[i], err = w.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Withdrawals[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionRequests.Consolidations))
|
||||
for i, c := range b.Body.ExecutionRequests.Consolidations {
|
||||
consolidationRequests[i], err = c.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Consolidations[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
blsChanges, err := SignedBLSChangesToConsensus(b.Body.BLSToExecutionChanges)
|
||||
@@ -2436,29 +2456,31 @@ func (b *BlindedBeaconBlockElectra) ToConsensus() (*eth.BlindedBeaconBlockElectr
|
||||
SyncCommitteeSignature: syncCommitteeSig,
|
||||
},
|
||||
ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
TransactionsRoot: payloadTxsRoot,
|
||||
WithdrawalsRoot: payloadWithdrawalsRoot,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
DepositRequestsRoot: payloadDepositRequestsRoot,
|
||||
WithdrawalRequestsRoot: payloadWithdrawalRequestsRoot,
|
||||
ConsolidationRequestsRoot: payloadConsolidationRequestsRoot,
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
TransactionsRoot: payloadTxsRoot,
|
||||
WithdrawalsRoot: payloadWithdrawalsRoot,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
},
|
||||
BlsToExecutionChanges: blsChanges,
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Deposits: depositRequests,
|
||||
Withdrawals: withdrawalRequests,
|
||||
Consolidations: consolidationRequests,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -2963,10 +2985,19 @@ func BlindedBeaconBlockElectraFromConsensus(b *eth.BlindedBeaconBlockElectra) (*
|
||||
ExecutionPayloadHeader: payload,
|
||||
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ExecutionRequests),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExecutionRequestsFromConsensus(er *enginev1.ExecutionRequests) *ExecutionRequests {
|
||||
return &ExecutionRequests{
|
||||
Deposits: DepositRequestsFromConsensus(er.Deposits),
|
||||
Withdrawals: WithdrawalRequestsFromConsensus(er.Withdrawals),
|
||||
Consolidations: ConsolidationRequestsFromConsensus(er.Consolidations),
|
||||
}
|
||||
}
|
||||
|
||||
func SignedBlindedBeaconBlockElectraFromConsensus(b *eth.SignedBlindedBeaconBlockElectra) (*SignedBlindedBeaconBlockElectra, error) {
|
||||
block, err := BlindedBeaconBlockElectraFromConsensus(b.Message)
|
||||
if err != nil {
|
||||
@@ -3009,6 +3040,7 @@ func BeaconBlockElectraFromConsensus(b *eth.BeaconBlockElectra) (*BeaconBlockEle
|
||||
ExecutionPayload: payload,
|
||||
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ExecutionRequests),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -3112,39 +3144,7 @@ func ExecutionPayloadDenebFromConsensus(payload *enginev1.ExecutionPayloadDeneb)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExecutionPayloadElectraFromConsensus(payload *enginev1.ExecutionPayloadElectra) (*ExecutionPayloadElectra, error) {
|
||||
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transactions := make([]string, len(payload.Transactions))
|
||||
for i, tx := range payload.Transactions {
|
||||
transactions[i] = hexutil.Encode(tx)
|
||||
}
|
||||
|
||||
return &ExecutionPayloadElectra{
|
||||
ParentHash: hexutil.Encode(payload.ParentHash),
|
||||
FeeRecipient: hexutil.Encode(payload.FeeRecipient),
|
||||
StateRoot: hexutil.Encode(payload.StateRoot),
|
||||
ReceiptsRoot: hexutil.Encode(payload.ReceiptsRoot),
|
||||
LogsBloom: hexutil.Encode(payload.LogsBloom),
|
||||
PrevRandao: hexutil.Encode(payload.PrevRandao),
|
||||
BlockNumber: fmt.Sprintf("%d", payload.BlockNumber),
|
||||
GasLimit: fmt.Sprintf("%d", payload.GasLimit),
|
||||
GasUsed: fmt.Sprintf("%d", payload.GasUsed),
|
||||
Timestamp: fmt.Sprintf("%d", payload.Timestamp),
|
||||
ExtraData: hexutil.Encode(payload.ExtraData),
|
||||
BaseFeePerGas: baseFeePerGas,
|
||||
BlockHash: hexutil.Encode(payload.BlockHash),
|
||||
Transactions: transactions,
|
||||
Withdrawals: WithdrawalsFromConsensus(payload.Withdrawals),
|
||||
BlobGasUsed: fmt.Sprintf("%d", payload.BlobGasUsed),
|
||||
ExcessBlobGas: fmt.Sprintf("%d", payload.ExcessBlobGas),
|
||||
DepositRequests: DepositRequestsFromConsensus(payload.DepositRequests),
|
||||
WithdrawalRequests: WithdrawalRequestsFromConsensus(payload.WithdrawalRequests),
|
||||
ConsolidationRequests: ConsolidationRequestsFromConsensus(payload.ConsolidationRequests),
|
||||
}, nil
|
||||
}
|
||||
var ExecutionPayloadElectraFromConsensus = ExecutionPayloadDenebFromConsensus
|
||||
|
||||
func ExecutionPayloadHeaderFromConsensus(payload *enginev1.ExecutionPayloadHeader) (*ExecutionPayloadHeader, error) {
|
||||
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
|
||||
@@ -3222,32 +3222,4 @@ func ExecutionPayloadHeaderDenebFromConsensus(payload *enginev1.ExecutionPayload
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExecutionPayloadHeaderElectraFromConsensus(payload *enginev1.ExecutionPayloadHeaderElectra) (*ExecutionPayloadHeaderElectra, error) {
|
||||
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ExecutionPayloadHeaderElectra{
|
||||
ParentHash: hexutil.Encode(payload.ParentHash),
|
||||
FeeRecipient: hexutil.Encode(payload.FeeRecipient),
|
||||
StateRoot: hexutil.Encode(payload.StateRoot),
|
||||
ReceiptsRoot: hexutil.Encode(payload.ReceiptsRoot),
|
||||
LogsBloom: hexutil.Encode(payload.LogsBloom),
|
||||
PrevRandao: hexutil.Encode(payload.PrevRandao),
|
||||
BlockNumber: fmt.Sprintf("%d", payload.BlockNumber),
|
||||
GasLimit: fmt.Sprintf("%d", payload.GasLimit),
|
||||
GasUsed: fmt.Sprintf("%d", payload.GasUsed),
|
||||
Timestamp: fmt.Sprintf("%d", payload.Timestamp),
|
||||
ExtraData: hexutil.Encode(payload.ExtraData),
|
||||
BaseFeePerGas: baseFeePerGas,
|
||||
BlockHash: hexutil.Encode(payload.BlockHash),
|
||||
TransactionsRoot: hexutil.Encode(payload.TransactionsRoot),
|
||||
WithdrawalsRoot: hexutil.Encode(payload.WithdrawalsRoot),
|
||||
BlobGasUsed: fmt.Sprintf("%d", payload.BlobGasUsed),
|
||||
ExcessBlobGas: fmt.Sprintf("%d", payload.ExcessBlobGas),
|
||||
DepositRequestsRoot: hexutil.Encode(payload.DepositRequestsRoot),
|
||||
WithdrawalRequestsRoot: hexutil.Encode(payload.WithdrawalRequestsRoot),
|
||||
ConsolidationRequestsRoot: hexutil.Encode(payload.ConsolidationRequestsRoot),
|
||||
}, nil
|
||||
}
|
||||
var ExecutionPayloadHeaderElectraFromConsensus = ExecutionPayloadHeaderDenebFromConsensus
|
||||
|
||||
122
api/server/structs/conversions_lightclient.go
Normal file
122
api/server/structs/conversions_lightclient.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
v1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
|
||||
v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v5/proto/migration"
|
||||
)
|
||||
|
||||
func LightClientUpdateFromConsensus(update *v2.LightClientUpdate) (*LightClientUpdate, error) {
|
||||
attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal attested light client header")
|
||||
}
|
||||
finalizedHeader, err := lightClientHeaderContainerToJSON(update.FinalizedHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal finalized light client header")
|
||||
}
|
||||
|
||||
return &LightClientUpdate{
|
||||
AttestedHeader: attestedHeader,
|
||||
NextSyncCommittee: SyncCommitteeFromConsensus(migration.V2SyncCommitteeToV1Alpha1(update.NextSyncCommittee)),
|
||||
NextSyncCommitteeBranch: branchToJSON(update.NextSyncCommitteeBranch),
|
||||
FinalizedHeader: finalizedHeader,
|
||||
FinalityBranch: branchToJSON(update.FinalityBranch),
|
||||
SyncAggregate: syncAggregateToJSON(update.SyncAggregate),
|
||||
SignatureSlot: strconv.FormatUint(uint64(update.SignatureSlot), 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func LightClientFinalityUpdateFromConsensus(update *v2.LightClientFinalityUpdate) (*LightClientFinalityUpdate, error) {
|
||||
attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal attested light client header")
|
||||
}
|
||||
finalizedHeader, err := lightClientHeaderContainerToJSON(update.FinalizedHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal finalized light client header")
|
||||
}
|
||||
|
||||
return &LightClientFinalityUpdate{
|
||||
AttestedHeader: attestedHeader,
|
||||
FinalizedHeader: finalizedHeader,
|
||||
FinalityBranch: branchToJSON(update.FinalityBranch),
|
||||
SyncAggregate: syncAggregateToJSON(update.SyncAggregate),
|
||||
SignatureSlot: strconv.FormatUint(uint64(update.SignatureSlot), 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func LightClientOptimisticUpdateFromConsensus(update *v2.LightClientOptimisticUpdate) (*LightClientOptimisticUpdate, error) {
|
||||
attestedHeader, err := lightClientHeaderContainerToJSON(update.AttestedHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not marshal attested light client header")
|
||||
}
|
||||
|
||||
return &LightClientOptimisticUpdate{
|
||||
AttestedHeader: attestedHeader,
|
||||
SyncAggregate: syncAggregateToJSON(update.SyncAggregate),
|
||||
SignatureSlot: strconv.FormatUint(uint64(update.SignatureSlot), 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func branchToJSON(branchBytes [][]byte) []string {
|
||||
if branchBytes == nil {
|
||||
return nil
|
||||
}
|
||||
branch := make([]string, len(branchBytes))
|
||||
for i, root := range branchBytes {
|
||||
branch[i] = hexutil.Encode(root)
|
||||
}
|
||||
return branch
|
||||
}
|
||||
|
||||
func syncAggregateToJSON(input *v1.SyncAggregate) *SyncAggregate {
|
||||
return &SyncAggregate{
|
||||
SyncCommitteeBits: hexutil.Encode(input.SyncCommitteeBits),
|
||||
SyncCommitteeSignature: hexutil.Encode(input.SyncCommitteeSignature),
|
||||
}
|
||||
}
|
||||
|
||||
func lightClientHeaderContainerToJSON(container *v2.LightClientHeaderContainer) (json.RawMessage, error) {
|
||||
beacon, err := container.GetBeacon()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get beacon block header")
|
||||
}
|
||||
|
||||
var header any
|
||||
|
||||
switch t := (container.Header).(type) {
|
||||
case *v2.LightClientHeaderContainer_HeaderAltair:
|
||||
header = &LightClientHeader{Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon))}
|
||||
case *v2.LightClientHeaderContainer_HeaderCapella:
|
||||
execution, err := ExecutionPayloadHeaderCapellaFromConsensus(t.HeaderCapella.Execution)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header = &LightClientHeaderCapella{
|
||||
Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon)),
|
||||
Execution: execution,
|
||||
ExecutionBranch: branchToJSON(t.HeaderCapella.ExecutionBranch),
|
||||
}
|
||||
case *v2.LightClientHeaderContainer_HeaderDeneb:
|
||||
execution, err := ExecutionPayloadHeaderDenebFromConsensus(t.HeaderDeneb.Execution)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header = &LightClientHeaderDeneb{
|
||||
Beacon: BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(beacon)),
|
||||
Execution: execution,
|
||||
ExecutionBranch: branchToJSON(t.HeaderDeneb.ExecutionBranch),
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported header type %T", t)
|
||||
}
|
||||
|
||||
return json.Marshal(header)
|
||||
}
|
||||
@@ -674,7 +674,7 @@ func BeaconStateElectraFromConsensus(st beaconState.BeaconState) (*BeaconStateEl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcPayload, ok := execData.Proto().(*enginev1.ExecutionPayloadHeaderElectra)
|
||||
srcPayload, ok := execData.Proto().(*enginev1.ExecutionPayloadHeaderDeneb)
|
||||
if !ok {
|
||||
return nil, errPayloadHeaderNotFound
|
||||
}
|
||||
|
||||
@@ -96,21 +96,7 @@ type LightClientFinalityUpdateEvent struct {
|
||||
Data *LightClientFinalityUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type LightClientFinalityUpdate struct {
|
||||
AttestedHeader *BeaconBlockHeader `json:"attested_header"`
|
||||
FinalizedHeader *BeaconBlockHeader `json:"finalized_header"`
|
||||
FinalityBranch []string `json:"finality_branch"`
|
||||
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
|
||||
SignatureSlot string `json:"signature_slot"`
|
||||
}
|
||||
|
||||
type LightClientOptimisticUpdateEvent struct {
|
||||
Version string `json:"version"`
|
||||
Data *LightClientOptimisticUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type LightClientOptimisticUpdate struct {
|
||||
AttestedHeader *BeaconBlockHeader `json:"attested_header"`
|
||||
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
|
||||
SignatureSlot string `json:"signature_slot"`
|
||||
}
|
||||
|
||||
@@ -1,35 +1,73 @@
|
||||
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 LightClientUpdate struct {
|
||||
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 LightClientFinalityUpdate struct {
|
||||
AttestedHeader json.RawMessage `json:"attested_header"`
|
||||
FinalizedHeader json.RawMessage `json:"finalized_header"`
|
||||
FinalityBranch []string `json:"finality_branch"`
|
||||
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
|
||||
SignatureSlot string `json:"signature_slot"`
|
||||
}
|
||||
|
||||
type LightClientOptimisticUpdate struct {
|
||||
AttestedHeader json.RawMessage `json:"attested_header"`
|
||||
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
|
||||
SignatureSlot string `json:"signature_slot"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type LightClientUpdateWithVersion struct {
|
||||
type LightClientUpdateResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data *LightClientUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type LightClientUpdatesByRangeResponse struct {
|
||||
Updates []*LightClientUpdateWithVersion `json:"updates"`
|
||||
type LightClientFinalityUpdateResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data *LightClientFinalityUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type LightClientOptimisticUpdateResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data *LightClientOptimisticUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type LightClientUpdatesByRangeResponse struct {
|
||||
Updates []*LightClientUpdateResponse `json:"updates"`
|
||||
}
|
||||
|
||||
@@ -8,22 +8,20 @@ go_library(
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/async/event",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//time/mclock:go_default_library"],
|
||||
deps = [
|
||||
"//time/mclock:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//event:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"example_feed_test.go",
|
||||
"example_scope_test.go",
|
||||
"example_subscription_test.go",
|
||||
"feed_test.go",
|
||||
"subscription_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
],
|
||||
deps = ["//testing/require:go_default_library"],
|
||||
)
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package event_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/async/event"
|
||||
)
|
||||
|
||||
func ExampleFeed_acknowledgedEvents() {
|
||||
// This example shows how the return value of Send can be used for request/reply
|
||||
// interaction between event consumers and producers.
|
||||
var feed event.Feed
|
||||
type ackedEvent struct {
|
||||
i int
|
||||
ack chan<- struct{}
|
||||
}
|
||||
|
||||
// Consumers wait for events on the feed and acknowledge processing.
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
for i := 0; i < 3; i++ {
|
||||
ch := make(chan ackedEvent, 100)
|
||||
sub := feed.Subscribe(ch)
|
||||
go func() {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case ev := <-ch:
|
||||
fmt.Println(ev.i) // "process" the event
|
||||
ev.ack <- struct{}{}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// The producer sends values of type ackedEvent with increasing values of i.
|
||||
// It waits for all consumers to acknowledge before sending the next event.
|
||||
for i := 0; i < 3; i++ {
|
||||
acksignal := make(chan struct{})
|
||||
n := feed.Send(ackedEvent{i, acksignal})
|
||||
for ack := 0; ack < n; ack++ {
|
||||
<-acksignal
|
||||
}
|
||||
}
|
||||
// Output:
|
||||
// 0
|
||||
// 0
|
||||
// 0
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
// 2
|
||||
// 2
|
||||
// 2
|
||||
}
|
||||
@@ -14,241 +14,11 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package event contains an event feed implementation for process communication.
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
geth_event "github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
var errBadChannel = errors.New("event: Subscribe argument does not have sendable channel type")
|
||||
|
||||
// Feed implements one-to-many subscriptions where the carrier of events is a channel.
|
||||
// Values sent to a Feed are delivered to all subscribed channels simultaneously.
|
||||
//
|
||||
// Feeds can only be used with a single type. The type is determined by the first Send or
|
||||
// Subscribe operation. Subsequent calls to these methods panic if the type does not
|
||||
// match.
|
||||
//
|
||||
// The zero value is ready to use.
|
||||
type Feed struct {
|
||||
once sync.Once // ensures that init only runs once
|
||||
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
|
||||
removeSub chan interface{} // interrupts Send
|
||||
sendCases caseList // the active set of select cases used by Send
|
||||
|
||||
// The inbox holds newly subscribed channels until they are added to sendCases.
|
||||
mu sync.Mutex
|
||||
inbox caseList
|
||||
etype reflect.Type
|
||||
}
|
||||
|
||||
// This is the index of the first actual subscription channel in sendCases.
|
||||
// sendCases[0] is a SelectRecv case for the removeSub channel.
|
||||
const firstSubSendCase = 1
|
||||
|
||||
type feedTypeError struct {
|
||||
got, want reflect.Type
|
||||
op string
|
||||
}
|
||||
|
||||
func (e feedTypeError) Error() string {
|
||||
return "event: wrong type in " + e.op + " got " + e.got.String() + ", want " + e.want.String()
|
||||
}
|
||||
|
||||
func (f *Feed) init() {
|
||||
f.removeSub = make(chan interface{})
|
||||
f.sendLock = make(chan struct{}, 1)
|
||||
f.sendLock <- struct{}{}
|
||||
f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
|
||||
}
|
||||
|
||||
// Subscribe adds a channel to the feed. Future sends will be delivered on the channel
|
||||
// until the subscription is canceled. All channels added must have the same element type.
|
||||
//
|
||||
// The channel should have ample buffer space to avoid blocking other subscribers.
|
||||
// Slow subscribers are not dropped.
|
||||
func (f *Feed) Subscribe(channel interface{}) Subscription {
|
||||
f.once.Do(f.init)
|
||||
|
||||
chanval := reflect.ValueOf(channel)
|
||||
chantyp := chanval.Type()
|
||||
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 {
|
||||
panic(errBadChannel)
|
||||
}
|
||||
sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if !f.typecheck(chantyp.Elem()) {
|
||||
panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)})
|
||||
}
|
||||
// Add the select case to the inbox.
|
||||
// The next Send will add it to f.sendCases.
|
||||
cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval}
|
||||
f.inbox = append(f.inbox, cas)
|
||||
return sub
|
||||
}
|
||||
|
||||
// note: callers must hold f.mu
|
||||
func (f *Feed) typecheck(typ reflect.Type) bool {
|
||||
if f.etype == nil {
|
||||
f.etype = typ
|
||||
return true
|
||||
}
|
||||
// In the event the feed's type is an actual interface, we
|
||||
// perform an interface conformance check here.
|
||||
if f.etype.Kind() == reflect.Interface && typ.Implements(f.etype) {
|
||||
return true
|
||||
}
|
||||
return f.etype == typ
|
||||
}
|
||||
|
||||
func (f *Feed) remove(sub *feedSub) {
|
||||
// Delete from inbox first, which covers channels
|
||||
// that have not been added to f.sendCases yet.
|
||||
ch := sub.channel.Interface()
|
||||
f.mu.Lock()
|
||||
index := f.inbox.find(ch)
|
||||
if index != -1 {
|
||||
f.inbox = f.inbox.delete(index)
|
||||
f.mu.Unlock()
|
||||
return
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
select {
|
||||
case f.removeSub <- ch:
|
||||
// Send will remove the channel from f.sendCases.
|
||||
case <-f.sendLock:
|
||||
// No Send is in progress, delete the channel now that we have the send lock.
|
||||
f.sendCases = f.sendCases.delete(f.sendCases.find(ch))
|
||||
f.sendLock <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Send delivers to all subscribed channels simultaneously.
|
||||
// It returns the number of subscribers that the value was sent to.
|
||||
func (f *Feed) Send(value interface{}) (nsent int) {
|
||||
rvalue := reflect.ValueOf(value)
|
||||
|
||||
f.once.Do(f.init)
|
||||
<-f.sendLock
|
||||
|
||||
// Add new cases from the inbox after taking the send lock.
|
||||
f.mu.Lock()
|
||||
f.sendCases = append(f.sendCases, f.inbox...)
|
||||
f.inbox = nil
|
||||
|
||||
if !f.typecheck(rvalue.Type()) {
|
||||
f.sendLock <- struct{}{}
|
||||
f.mu.Unlock()
|
||||
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
// Set the sent value on all channels.
|
||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
||||
f.sendCases[i].Send = rvalue
|
||||
}
|
||||
|
||||
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
|
||||
// of sendCases. When a send succeeds, the corresponding case moves to the end of
|
||||
// 'cases' and it shrinks by one element.
|
||||
cases := f.sendCases
|
||||
for {
|
||||
// Fast path: try sending without blocking before adding to the select set.
|
||||
// This should usually succeed if subscribers are fast enough and have free
|
||||
// buffer space.
|
||||
for i := firstSubSendCase; i < len(cases); i++ {
|
||||
if cases[i].Chan.TrySend(rvalue) {
|
||||
nsent++
|
||||
cases = cases.deactivate(i)
|
||||
i--
|
||||
}
|
||||
}
|
||||
if len(cases) == firstSubSendCase {
|
||||
break
|
||||
}
|
||||
// Select on all the receivers, waiting for them to unblock.
|
||||
chosen, recv, _ := reflect.Select(cases)
|
||||
if chosen == 0 /* <-f.removeSub */ {
|
||||
index := f.sendCases.find(recv.Interface())
|
||||
f.sendCases = f.sendCases.delete(index)
|
||||
if index >= 0 && index < len(cases) {
|
||||
// Shrink 'cases' too because the removed case was still active.
|
||||
cases = f.sendCases[:len(cases)-1]
|
||||
}
|
||||
} else {
|
||||
cases = cases.deactivate(chosen)
|
||||
nsent++
|
||||
}
|
||||
}
|
||||
|
||||
// Forget about the sent value and hand off the send lock.
|
||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
||||
f.sendCases[i].Send = reflect.Value{}
|
||||
}
|
||||
f.sendLock <- struct{}{}
|
||||
return nsent
|
||||
}
|
||||
|
||||
type feedSub struct {
|
||||
feed *Feed
|
||||
channel reflect.Value
|
||||
errOnce sync.Once
|
||||
err chan error
|
||||
}
|
||||
|
||||
// Unsubscribe remove feed subscription.
|
||||
func (sub *feedSub) Unsubscribe() {
|
||||
sub.errOnce.Do(func() {
|
||||
sub.feed.remove(sub)
|
||||
close(sub.err)
|
||||
})
|
||||
}
|
||||
|
||||
// Err returns error channel.
|
||||
func (sub *feedSub) Err() <-chan error {
|
||||
return sub.err
|
||||
}
|
||||
|
||||
type caseList []reflect.SelectCase
|
||||
|
||||
// find returns the index of a case containing the given channel.
|
||||
func (cs caseList) find(channel interface{}) int {
|
||||
return slices.IndexFunc(cs, func(selectCase reflect.SelectCase) bool {
|
||||
return selectCase.Chan.Interface() == channel
|
||||
})
|
||||
}
|
||||
|
||||
// delete removes the given case from cs.
|
||||
func (cs caseList) delete(index int) caseList {
|
||||
return append(cs[:index], cs[index+1:]...)
|
||||
}
|
||||
|
||||
// deactivate moves the case at index into the non-accessible portion of the cs slice.
|
||||
func (cs caseList) deactivate(index int) caseList {
|
||||
last := len(cs) - 1
|
||||
cs[index], cs[last] = cs[last], cs[index]
|
||||
return cs[:last]
|
||||
}
|
||||
|
||||
// func (cs caseList) String() string {
|
||||
// s := "["
|
||||
// for i, cas := range cs {
|
||||
// if i != 0 {
|
||||
// s += ", "
|
||||
// }
|
||||
// switch cas.Dir {
|
||||
// case reflect.SelectSend:
|
||||
// s += fmt.Sprintf("%v<-", cas.Chan.Interface())
|
||||
// case reflect.SelectRecv:
|
||||
// s += fmt.Sprintf("<-%v", cas.Chan.Interface())
|
||||
// }
|
||||
// }
|
||||
// return s + "]"
|
||||
// }
|
||||
// Feed is a re-export of the go-ethereum event feed.
|
||||
type Feed = geth_event.Feed
|
||||
|
||||
@@ -1,509 +0,0 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
)
|
||||
|
||||
func TestFeedPanics(t *testing.T) {
|
||||
{
|
||||
var f Feed
|
||||
f.Send(2)
|
||||
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
|
||||
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
|
||||
// Validate it doesn't deadlock.
|
||||
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
ch := make(chan int)
|
||||
f.Subscribe(ch)
|
||||
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
|
||||
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
f.Send(2)
|
||||
want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))}
|
||||
assert.NoError(t, checkPanic(want, func() { f.Subscribe(make(chan uint64)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
assert.NoError(t, checkPanic(errBadChannel, func() { f.Subscribe(make(<-chan int)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
assert.NoError(t, checkPanic(errBadChannel, func() { f.Subscribe(0) }))
|
||||
}
|
||||
}
|
||||
|
||||
func checkPanic(want error, fn func()) (err error) {
|
||||
defer func() {
|
||||
panicResult := recover()
|
||||
if panicResult == nil {
|
||||
err = fmt.Errorf("didn't panic")
|
||||
} else if !reflect.DeepEqual(panicResult, want) {
|
||||
err = fmt.Errorf("panicked with wrong error: got %q, want %q", panicResult, want)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFeed(t *testing.T) {
|
||||
var feed Feed
|
||||
var done, subscribed sync.WaitGroup
|
||||
subscriber := func(i int) {
|
||||
defer done.Done()
|
||||
|
||||
subchan := make(chan int)
|
||||
sub := feed.Subscribe(subchan)
|
||||
timeout := time.NewTimer(2 * time.Second)
|
||||
subscribed.Done()
|
||||
|
||||
select {
|
||||
case v := <-subchan:
|
||||
if v != 1 {
|
||||
t.Errorf("%d: received value %d, want 1", i, v)
|
||||
}
|
||||
case <-timeout.C:
|
||||
t.Errorf("%d: receive timeout", i)
|
||||
}
|
||||
|
||||
sub.Unsubscribe()
|
||||
select {
|
||||
case _, ok := <-sub.Err():
|
||||
if ok {
|
||||
t.Errorf("%d: error channel not closed after unsubscribe", i)
|
||||
}
|
||||
case <-timeout.C:
|
||||
t.Errorf("%d: unsubscribe timeout", i)
|
||||
}
|
||||
}
|
||||
|
||||
const n = 1000
|
||||
done.Add(n)
|
||||
subscribed.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go subscriber(i)
|
||||
}
|
||||
subscribed.Wait()
|
||||
if nsent := feed.Send(1); nsent != n {
|
||||
t.Errorf("first send delivered %d times, want %d", nsent, n)
|
||||
}
|
||||
if nsent := feed.Send(2); nsent != 0 {
|
||||
t.Errorf("second send delivered %d times, want 0", nsent)
|
||||
}
|
||||
done.Wait()
|
||||
}
|
||||
|
||||
func TestFeedSubscribeSameChannel(t *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
done sync.WaitGroup
|
||||
ch = make(chan int)
|
||||
sub1 = feed.Subscribe(ch)
|
||||
sub2 = feed.Subscribe(ch)
|
||||
_ = feed.Subscribe(ch)
|
||||
)
|
||||
expectSends := func(value, n int) {
|
||||
if nsent := feed.Send(value); nsent != n {
|
||||
t.Errorf("send delivered %d times, want %d", nsent, n)
|
||||
}
|
||||
done.Done()
|
||||
}
|
||||
expectRecv := func(wantValue, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
if v := <-ch; v != wantValue {
|
||||
t.Errorf("received %d, want %d", v, wantValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done.Add(1)
|
||||
go expectSends(1, 3)
|
||||
expectRecv(1, 3)
|
||||
done.Wait()
|
||||
|
||||
sub1.Unsubscribe()
|
||||
|
||||
done.Add(1)
|
||||
go expectSends(2, 2)
|
||||
expectRecv(2, 2)
|
||||
done.Wait()
|
||||
|
||||
sub2.Unsubscribe()
|
||||
|
||||
done.Add(1)
|
||||
go expectSends(3, 1)
|
||||
expectRecv(3, 1)
|
||||
done.Wait()
|
||||
}
|
||||
|
||||
func TestFeedSubscribeBlockedPost(_ *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
nsends = 2000
|
||||
ch1 = make(chan int)
|
||||
ch2 = make(chan int)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
defer wg.Wait()
|
||||
|
||||
feed.Subscribe(ch1)
|
||||
wg.Add(nsends)
|
||||
for i := 0; i < nsends; i++ {
|
||||
go func() {
|
||||
feed.Send(99)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
sub2 := feed.Subscribe(ch2)
|
||||
defer sub2.Unsubscribe()
|
||||
|
||||
// We're done when ch1 has received N times.
|
||||
// The number of receives on ch2 depends on scheduling.
|
||||
for i := 0; i < nsends; {
|
||||
select {
|
||||
case <-ch1:
|
||||
i++
|
||||
case <-ch2:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeedUnsubscribeBlockedPost(_ *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
nsends = 200
|
||||
chans = make([]chan int, 2000)
|
||||
subs = make([]Subscription, len(chans))
|
||||
bchan = make(chan int)
|
||||
bsub = feed.Subscribe(bchan)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
for i := range chans {
|
||||
chans[i] = make(chan int, nsends)
|
||||
}
|
||||
|
||||
// Queue up some Sends. None of these can make progress while bchan isn't read.
|
||||
wg.Add(nsends)
|
||||
for i := 0; i < nsends; i++ {
|
||||
go func() {
|
||||
feed.Send(99)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
// Subscribe the other channels.
|
||||
for i, ch := range chans {
|
||||
subs[i] = feed.Subscribe(ch)
|
||||
}
|
||||
// Unsubscribe them again.
|
||||
for _, sub := range subs {
|
||||
sub.Unsubscribe()
|
||||
}
|
||||
// Unblock the Sends.
|
||||
bsub.Unsubscribe()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Checks that unsubscribing a channel during Send works even if that
|
||||
// channel has already been sent on.
|
||||
func TestFeedUnsubscribeSentChan(_ *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
ch1 = make(chan int)
|
||||
ch2 = make(chan int)
|
||||
sub1 = feed.Subscribe(ch1)
|
||||
sub2 = feed.Subscribe(ch2)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
defer sub2.Unsubscribe()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
feed.Send(0)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// Wait for the value on ch1.
|
||||
<-ch1
|
||||
// Unsubscribe ch1, removing it from the send cases.
|
||||
sub1.Unsubscribe()
|
||||
|
||||
// Receive ch2, finishing Send.
|
||||
<-ch2
|
||||
wg.Wait()
|
||||
|
||||
// Send again. This should send to ch2 only, so the wait group will unblock
|
||||
// as soon as a value is received on ch2.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
feed.Send(0)
|
||||
wg.Done()
|
||||
}()
|
||||
<-ch2
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestFeedUnsubscribeFromInbox(t *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
ch1 = make(chan int)
|
||||
ch2 = make(chan int)
|
||||
sub1 = feed.Subscribe(ch1)
|
||||
sub2 = feed.Subscribe(ch1)
|
||||
sub3 = feed.Subscribe(ch2)
|
||||
)
|
||||
assert.Equal(t, 3, len(feed.inbox))
|
||||
assert.Equal(t, 1, len(feed.sendCases), "sendCases is non-empty after unsubscribe")
|
||||
|
||||
sub1.Unsubscribe()
|
||||
sub2.Unsubscribe()
|
||||
sub3.Unsubscribe()
|
||||
assert.Equal(t, 0, len(feed.inbox), "Inbox is non-empty after unsubscribe")
|
||||
assert.Equal(t, 1, len(feed.sendCases), "sendCases is non-empty after unsubscribe")
|
||||
}
|
||||
|
||||
func BenchmarkFeedSend1000(b *testing.B) {
|
||||
var (
|
||||
done sync.WaitGroup
|
||||
feed Feed
|
||||
nsubs = 1000
|
||||
)
|
||||
subscriber := func(ch <-chan int) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-ch
|
||||
}
|
||||
done.Done()
|
||||
}
|
||||
done.Add(nsubs)
|
||||
for i := 0; i < nsubs; i++ {
|
||||
ch := make(chan int, 200)
|
||||
feed.Subscribe(ch)
|
||||
go subscriber(ch)
|
||||
}
|
||||
|
||||
// The actual benchmark.
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if feed.Send(i) != nsubs {
|
||||
panic("wrong number of sends")
|
||||
}
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
done.Wait()
|
||||
}
|
||||
|
||||
func TestFeed_Send(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
evFeed *Feed
|
||||
testSetup func(fd *Feed, t *testing.T, o interface{})
|
||||
obj interface{}
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "normal struct",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedWithPointer, 1)
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeedWithPointer{
|
||||
a: new(uint64),
|
||||
b: new(string),
|
||||
},
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "un-implemented interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface, 1)
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeedWithPointer{
|
||||
a: new(uint64),
|
||||
b: new(string),
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
{
|
||||
name: "semi-implemented interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface, 1)
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed2{
|
||||
a: 0,
|
||||
b: "",
|
||||
c: []byte{'A'},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
{
|
||||
name: "fully-implemented interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface)
|
||||
// Make it unbuffered to allow message to
|
||||
// pass through
|
||||
go func() {
|
||||
a := <-testChan
|
||||
if !reflect.DeepEqual(a, o) {
|
||||
t.Errorf("Got = %v, want = %v", a, o)
|
||||
}
|
||||
}()
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed{
|
||||
a: 0,
|
||||
b: "",
|
||||
},
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "fully-implemented interface with additional methods",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface)
|
||||
// Make it unbuffered to allow message to
|
||||
// pass through
|
||||
go func() {
|
||||
a := <-testChan
|
||||
if !reflect.DeepEqual(a, o) {
|
||||
t.Errorf("Got = %v, want = %v", a, o)
|
||||
}
|
||||
}()
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed3{
|
||||
a: 0,
|
||||
b: "",
|
||||
c: []byte{'A'},
|
||||
d: []byte{'B'},
|
||||
},
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "concrete types implementing the same interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeed, 1)
|
||||
// Make it unbuffered to allow message to
|
||||
// pass through
|
||||
go func() {
|
||||
a := <-testChan
|
||||
if !reflect.DeepEqual(a, o) {
|
||||
t.Errorf("Got = %v, want = %v", a, o)
|
||||
}
|
||||
}()
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed3{
|
||||
a: 0,
|
||||
b: "",
|
||||
c: []byte{'A'},
|
||||
d: []byte{'B'},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.expectPanic {
|
||||
t.Errorf("panic triggered when unexpected: %v", r)
|
||||
}
|
||||
} else {
|
||||
if tt.expectPanic {
|
||||
t.Error("panic not triggered when expected")
|
||||
}
|
||||
}
|
||||
}()
|
||||
tt.testSetup(tt.evFeed, t, tt.obj)
|
||||
if gotNsent := tt.evFeed.Send(tt.obj); gotNsent != 1 {
|
||||
t.Errorf("Send() = %v, want %v", gotNsent, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The following objects below are a collection of different
|
||||
// struct types to test with.
|
||||
type testFeed struct {
|
||||
a uint64
|
||||
b string
|
||||
}
|
||||
|
||||
func (testFeed) method1() {
|
||||
|
||||
}
|
||||
|
||||
func (testFeed) method2() {
|
||||
|
||||
}
|
||||
|
||||
type testFeedWithPointer struct {
|
||||
a *uint64
|
||||
b *string
|
||||
}
|
||||
|
||||
type testFeed2 struct {
|
||||
a uint64
|
||||
b string
|
||||
c []byte
|
||||
}
|
||||
|
||||
func (testFeed2) method1() {
|
||||
|
||||
}
|
||||
|
||||
type testFeed3 struct {
|
||||
a uint64
|
||||
b string
|
||||
c, d []byte
|
||||
}
|
||||
|
||||
func (testFeed3) method1() {
|
||||
|
||||
}
|
||||
|
||||
func (testFeed3) method2() {
|
||||
|
||||
}
|
||||
|
||||
func (testFeed3) method3() {
|
||||
|
||||
}
|
||||
|
||||
type testFeedIface interface {
|
||||
method1()
|
||||
method2()
|
||||
}
|
||||
@@ -6,6 +6,7 @@ go_library(
|
||||
"chain_info.go",
|
||||
"chain_info_forkchoice.go",
|
||||
"currently_syncing_block.go",
|
||||
"currently_syncing_execution_payload_envelope.go",
|
||||
"defragment.go",
|
||||
"error.go",
|
||||
"execution_engine.go",
|
||||
@@ -25,6 +26,8 @@ go_library(
|
||||
"receive_attestation.go",
|
||||
"receive_blob.go",
|
||||
"receive_block.go",
|
||||
"receive_execution_payload_envelope.go",
|
||||
"receive_payload_attestation_message.go",
|
||||
"service.go",
|
||||
"tracked_proposer.go",
|
||||
"weak_subjectivity_checks.go",
|
||||
@@ -43,6 +46,7 @@ go_library(
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epbs:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
@@ -110,6 +114,7 @@ go_test(
|
||||
"chain_info_norace_test.go",
|
||||
"chain_info_test.go",
|
||||
"checktags_test.go",
|
||||
"epbs_test.go",
|
||||
"error_test.go",
|
||||
"execution_engine_test.go",
|
||||
"forkchoice_update_execution_test.go",
|
||||
@@ -125,6 +130,7 @@ go_test(
|
||||
"process_block_test.go",
|
||||
"receive_attestation_test.go",
|
||||
"receive_block_test.go",
|
||||
"receive_execution_payload_envelope_test.go",
|
||||
"service_norace_test.go",
|
||||
"service_test.go",
|
||||
"setup_test.go",
|
||||
@@ -178,6 +184,7 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//testing/util/random:go_default_library",
|
||||
"//time:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
|
||||
@@ -42,7 +42,7 @@ type ForkchoiceFetcher interface {
|
||||
GetProposerHead() [32]byte
|
||||
SetForkChoiceGenesisTime(uint64)
|
||||
UpdateHead(context.Context, primitives.Slot)
|
||||
HighestReceivedBlockSlot() primitives.Slot
|
||||
HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte)
|
||||
ReceivedBlocksLastEpoch() (uint64, error)
|
||||
InsertNode(context.Context, state.BeaconState, [32]byte) error
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
@@ -50,6 +50,7 @@ type ForkchoiceFetcher interface {
|
||||
ProposerBoost() [32]byte
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
|
||||
GetPTCVote(root [32]byte) primitives.PTCStatus
|
||||
}
|
||||
|
||||
// TimeFetcher retrieves the Ethereum consensus data that's related to time.
|
||||
@@ -118,6 +119,12 @@ type OptimisticModeFetcher interface {
|
||||
IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error)
|
||||
}
|
||||
|
||||
// ExecutionPayloadFetcher defines a common interface that returns forkchoice
|
||||
// information about payload block hashes
|
||||
type ExecutionPayloadFetcher interface {
|
||||
HashInForkchoice([32]byte) bool
|
||||
}
|
||||
|
||||
// FinalizedCheckpt returns the latest finalized checkpoint from chain store.
|
||||
func (s *Service) FinalizedCheckpt() *ethpb.Checkpoint {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
@@ -203,7 +210,7 @@ func (s *Service) HeadState(ctx context.Context) (state.BeaconState, error) {
|
||||
defer s.headLock.RUnlock()
|
||||
|
||||
ok := s.hasHeadState()
|
||||
span.AddAttributes(trace.BoolAttribute("cache_hit", ok))
|
||||
span.SetAttributes(trace.BoolAttribute("cache_hit", ok))
|
||||
|
||||
if ok {
|
||||
return s.headState(ctx), nil
|
||||
@@ -225,7 +232,7 @@ func (s *Service) HeadStateReadOnly(ctx context.Context) (state.ReadOnlyBeaconSt
|
||||
defer s.headLock.RUnlock()
|
||||
|
||||
ok := s.hasHeadState()
|
||||
span.AddAttributes(trace.BoolAttribute("cache_hit", ok))
|
||||
span.SetAttributes(trace.BoolAttribute("cache_hit", ok))
|
||||
|
||||
if ok {
|
||||
return s.headStateReadOnly(ctx), nil
|
||||
@@ -399,6 +406,14 @@ func (s *Service) InForkchoice(root [32]byte) bool {
|
||||
return s.cfg.ForkChoiceStore.HasNode(root)
|
||||
}
|
||||
|
||||
// HashInForkchoice returns true if the given payload block hash is found in
|
||||
// forkchoice
|
||||
func (s *Service) HashInForkchoice(hash [32]byte) bool {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.HasHash(hash)
|
||||
}
|
||||
|
||||
// IsOptimisticForRoot takes the root as argument instead of the current head
|
||||
// and returns true if it is optimistic.
|
||||
func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
)
|
||||
|
||||
@@ -29,11 +30,11 @@ func (s *Service) SetForkChoiceGenesisTime(timestamp uint64) {
|
||||
s.cfg.ForkChoiceStore.SetGenesisTime(timestamp)
|
||||
}
|
||||
|
||||
// HighestReceivedBlockSlot returns the corresponding value from forkchoice
|
||||
func (s *Service) HighestReceivedBlockSlot() primitives.Slot {
|
||||
// HighestReceivedBlockSlotRoot returns the corresponding value from forkchoice
|
||||
func (s *Service) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlot()
|
||||
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlotRoot()
|
||||
}
|
||||
|
||||
// ReceivedBlocksLastEpoch returns the corresponding value from forkchoice
|
||||
@@ -99,3 +100,30 @@ func (s *Service) ParentRoot(root [32]byte) ([32]byte, error) {
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.ParentRoot(root)
|
||||
}
|
||||
|
||||
// GetPTCVote wraps a call to the corresponding method in forkchoice and checks
|
||||
// the currently syncing status
|
||||
// Warning: this method will return the current PTC status regardless of
|
||||
// timeliness. A client MUST call this method when about to submit a PTC
|
||||
// attestation, that is exactly at the threshold to submit the attestation.
|
||||
func (s *Service) GetPTCVote(root [32]byte) primitives.PTCStatus {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
f := s.cfg.ForkChoiceStore.GetPTCVote()
|
||||
s.cfg.ForkChoiceStore.RUnlock()
|
||||
if f != primitives.PAYLOAD_ABSENT {
|
||||
return f
|
||||
}
|
||||
f, isSyncing := s.payloadBeingSynced.isSyncing(root)
|
||||
if isSyncing {
|
||||
return f
|
||||
}
|
||||
return primitives.PAYLOAD_ABSENT
|
||||
}
|
||||
|
||||
// insertPayloadEnvelope wraps a locked call to the corresponding method in
|
||||
// forkchoice
|
||||
func (s *Service) insertPayloadEnvelope(envelope interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return s.cfg.ForkChoiceStore.InsertPayloadEnvelope(envelope)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
)
|
||||
|
||||
type currentlySyncingPayload struct {
|
||||
sync.Mutex
|
||||
roots map[[32]byte]primitives.PTCStatus
|
||||
}
|
||||
|
||||
func (b *currentlySyncingPayload) set(envelope interfaces.ROExecutionPayloadEnvelope) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
if envelope.PayloadWithheld() {
|
||||
b.roots[envelope.BeaconBlockRoot()] = primitives.PAYLOAD_WITHHELD
|
||||
} else {
|
||||
b.roots[envelope.BeaconBlockRoot()] = primitives.PAYLOAD_PRESENT
|
||||
}
|
||||
}
|
||||
|
||||
func (b *currentlySyncingPayload) unset(root [32]byte) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
delete(b.roots, root)
|
||||
}
|
||||
|
||||
func (b *currentlySyncingPayload) isSyncing(root [32]byte) (status primitives.PTCStatus, isSyncing bool) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
status, isSyncing = b.roots[root]
|
||||
return
|
||||
}
|
||||
18
beacon-chain/blockchain/epbs_test.go
Normal file
18
beacon-chain/blockchain/epbs_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestServiceGetPTCVote(t *testing.T) {
|
||||
c := ¤tlySyncingPayload{roots: make(map[[32]byte]primitives.PTCStatus)}
|
||||
s := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}, payloadBeingSynced: c}
|
||||
r := [32]byte{'r'}
|
||||
require.Equal(t, primitives.PAYLOAD_ABSENT, s.GetPTCVote(r))
|
||||
c.roots[r] = primitives.PAYLOAD_WITHHELD
|
||||
require.Equal(t, primitives.PAYLOAD_WITHHELD, s.GetPTCVote(r))
|
||||
}
|
||||
@@ -30,6 +30,9 @@ var (
|
||||
ErrNotCheckpoint = errors.New("not a checkpoint in forkchoice")
|
||||
// ErrNilHead is returned when no head is present in the blockchain service.
|
||||
ErrNilHead = errors.New("nil head")
|
||||
// errInvalidValidatorIndex is returned when a validator index is
|
||||
// invalid or unexpected
|
||||
errInvalidValidatorIndex = errors.New("invalid validator index")
|
||||
)
|
||||
|
||||
var errMaxBlobsExceeded = errors.New("Expected commitments in block exceeds MAX_BLOBS_PER_BLOCK")
|
||||
|
||||
@@ -2,7 +2,6 @@ package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -28,8 +27,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const blobCommitmentVersionKZG uint8 = 0x01
|
||||
|
||||
var defaultLatestValidHash = bytesutil.PadTo([]byte{0xff}, 32)
|
||||
|
||||
// notifyForkchoiceUpdate signals execution engine the fork choice updates. Execution engine should:
|
||||
@@ -39,7 +36,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
|
||||
}
|
||||
@@ -402,13 +399,7 @@ func kzgCommitmentsToVersionedHashes(body interfaces.ReadOnlyBeaconBlockBody) ([
|
||||
|
||||
versionedHashes := make([]common.Hash, len(commitments))
|
||||
for i, commitment := range commitments {
|
||||
versionedHashes[i] = ConvertKzgCommitmentToVersionedHash(commitment)
|
||||
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(commitment)
|
||||
}
|
||||
return versionedHashes, nil
|
||||
}
|
||||
|
||||
func ConvertKzgCommitmentToVersionedHash(commitment []byte) common.Hash {
|
||||
versionedHash := sha256.Sum256(commitment)
|
||||
versionedHash[0] = blobCommitmentVersionKZG
|
||||
return versionedHash
|
||||
}
|
||||
|
||||
@@ -1056,8 +1056,8 @@ func TestService_removeInvalidBlockAndState(t *testing.T) {
|
||||
|
||||
require.NoError(t, service.removeInvalidBlockAndState(ctx, [][32]byte{r1, r2}))
|
||||
|
||||
require.Equal(t, false, service.hasBlock(ctx, r1))
|
||||
require.Equal(t, false, service.hasBlock(ctx, r2))
|
||||
require.Equal(t, false, service.chainHasBlock(ctx, r1))
|
||||
require.Equal(t, false, service.chainHasBlock(ctx, r2))
|
||||
require.Equal(t, false, service.cfg.BeaconDB.HasStateSummary(ctx, r1))
|
||||
require.Equal(t, false, service.cfg.BeaconDB.HasStateSummary(ctx, r2))
|
||||
has, err := service.cfg.StateGen.HasState(ctx, r1)
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
payloadattribute "github.com/prysmaticlabs/prysm/v5/consensus-types/payload-attribute"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
func (s *Service) isNewHead(r [32]byte) bool {
|
||||
|
||||
@@ -182,6 +182,10 @@ var (
|
||||
Name: "chain_service_processing_milliseconds",
|
||||
Help: "Total time to call a chain service in ReceiveBlock()",
|
||||
})
|
||||
executionEngineProcessingTime = promauto.NewSummary(prometheus.SummaryOpts{
|
||||
Name: "execution_engine_processing_milliseconds",
|
||||
Help: "Total time to process an execution payload envelope in ReceiveExecutionPayloadEnvelope()",
|
||||
})
|
||||
dataAvailWaitedTime = promauto.NewSummary(prometheus.SummaryOpts{
|
||||
Name: "da_waited_time_milliseconds",
|
||||
Help: "Total time spent waiting for a data availability check in ReceiveBlock()",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
|
||||
@@ -69,6 +71,22 @@ func WithDepositCache(c cache.DepositCache) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithPayloadAttestationCache for payload attestation cache.
|
||||
func WithPayloadAttestationCache(c *cache.PayloadAttestationCache) Option {
|
||||
return func(s *Service) error {
|
||||
s.cfg.PayloadAttestationCache = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPayloadEnvelopeCache for payload envelope cache.
|
||||
func WithPayloadEnvelopeCache(c *sync.Map) Option {
|
||||
return func(s *Service) error {
|
||||
s.cfg.PayloadEnvelopeCache = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPayloadIDCache for payload ID cache.
|
||||
func WithPayloadIDCache(c *cache.PayloadIDCache) Option {
|
||||
return func(s *Service) error {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"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/proto/prysm/v1alpha1/attestation"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// OnAttestation is called whenever an attestation is received, verifies the attestation is valid and saves
|
||||
@@ -97,7 +97,7 @@ func (s *Service) OnAttestation(ctx context.Context, a ethpb.Att, disparity time
|
||||
// We assume trusted attestation in this function has verified signature.
|
||||
|
||||
// Update forkchoice store with the new attestation for updating weight.
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Target.Epoch)
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Slot)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
|
||||
}
|
||||
r := bytesutil.ToBytes32(a.GetData().BeaconBlockRoot)
|
||||
if s.cfg.ForkChoiceStore.HasNode(r) {
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Target.Epoch)
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Slot)
|
||||
} else if err := s.cfg.AttPool.SaveBlockAttestation(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ func (s *Service) sendLightClientFinalityUpdate(ctx context.Context, signed inte
|
||||
// Return the result
|
||||
result := ðpbv2.LightClientFinalityUpdateWithVersion{
|
||||
Version: ethpbv2.Version(signed.Version()),
|
||||
Data: lightclient.CreateLightClientFinalityUpdate(update),
|
||||
Data: update,
|
||||
}
|
||||
|
||||
// Send event
|
||||
@@ -227,7 +227,7 @@ func (s *Service) sendLightClientOptimisticUpdate(ctx context.Context, signed in
|
||||
// Return the result
|
||||
result := ðpbv2.LightClientOptimisticUpdateWithVersion{
|
||||
Version: ethpbv2.Version(signed.Version()),
|
||||
Data: lightclient.CreateLightClientOptimisticUpdate(update),
|
||||
Data: update,
|
||||
}
|
||||
|
||||
return s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
|
||||
@@ -177,7 +177,7 @@ func (s *Service) processAttestations(ctx context.Context, disparity time.Durati
|
||||
}
|
||||
|
||||
hasState := s.cfg.BeaconDB.HasStateSummary(ctx, bytesutil.ToBytes32(a.GetData().BeaconBlockRoot))
|
||||
hasBlock := s.hasBlock(ctx, bytesutil.ToBytes32(a.GetData().BeaconBlockRoot))
|
||||
hasBlock := s.chainHasBlock(ctx, bytesutil.ToBytes32(a.GetData().BeaconBlockRoot))
|
||||
if !(hasState && hasBlock) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -39,18 +39,30 @@ var epochsSinceFinalityExpandCache = primitives.Epoch(4)
|
||||
// BlockReceiver interface defines the methods of chain service for receiving and processing new blocks.
|
||||
type BlockReceiver interface {
|
||||
ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte, avs das.AvailabilityStore) error
|
||||
ReceiveExecutionPayloadEnvelope(ctx context.Context, env interfaces.ROExecutionPayloadEnvelope, avs das.AvailabilityStore) error
|
||||
ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock, avs das.AvailabilityStore) error
|
||||
HasBlock(ctx context.Context, root [32]byte) bool
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
BlockBeingSynced([32]byte) bool
|
||||
}
|
||||
|
||||
// PayloadAttestationReceiver defines methods of the chain service for receiving
|
||||
// and processing new payload attestations and payload attestation messages
|
||||
type PayloadAttestationReceiver interface {
|
||||
ReceivePayloadAttestationMessage(ctx context.Context, a *ethpb.PayloadAttestationMessage) error
|
||||
}
|
||||
|
||||
// BlobReceiver interface defines the methods of chain service for receiving new
|
||||
// blobs
|
||||
type BlobReceiver interface {
|
||||
ReceiveBlob(context.Context, blocks.VerifiedROBlob) error
|
||||
}
|
||||
|
||||
// ExecutionPayloadReceiver interface defines the methods of chain service for receiving `ROExecutionPayloadEnvelope`.
|
||||
type ExecutionPayloadReceiver interface {
|
||||
ReceiveExecutionPayloadEnvelope(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, _ das.AvailabilityStore) error
|
||||
}
|
||||
|
||||
// SlashingReceiver interface defines the methods of chain service for receiving validated slashing over the wire.
|
||||
type SlashingReceiver interface {
|
||||
ReceiveAttesterSlashing(ctx context.Context, slashing ethpb.AttSlashing)
|
||||
|
||||
152
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
152
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope is a function that defines the operations (minus pubsub)
|
||||
// that are performed on a received execution payload envelope. The operations consist of:
|
||||
// 1. Validate the payload, apply state transition.
|
||||
// 2. Apply fork choice to the processed payload
|
||||
// 3. Save latest head info
|
||||
func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, _ das.AvailabilityStore) error {
|
||||
receivedTime := time.Now()
|
||||
root := envelope.BeaconBlockRoot()
|
||||
s.payloadBeingSynced.set(envelope)
|
||||
defer s.payloadBeingSynced.unset(root)
|
||||
|
||||
preState, err := s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get prestate")
|
||||
}
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
var postState state.BeaconState
|
||||
eg.Go(func() error {
|
||||
if err := epbs.ValidatePayloadStateTransition(ctx, preState, envelope); err != nil {
|
||||
return errors.Wrap(err, "failed to validate consensus state transition function")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
var isValidPayload bool
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
isValidPayload, err = s.validateExecutionOnEnvelope(ctx, envelope)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not notify the engine of the new payload")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = isValidPayload
|
||||
_ = postState
|
||||
daStartTime := time.Now()
|
||||
// TODO: Add DA check
|
||||
daWaitedTime := time.Since(daStartTime)
|
||||
dataAvailWaitedTime.Observe(float64(daWaitedTime.Milliseconds()))
|
||||
// TODO: Add Head update, cache handling, postProcessing
|
||||
if err := s.insertPayloadEnvelope(envelope); err != nil {
|
||||
return errors.Wrap(err, "could not insert payload to forkchoice")
|
||||
}
|
||||
timeWithoutDaWait := time.Since(receivedTime) - daWaitedTime
|
||||
executionEngineProcessingTime.Observe(float64(timeWithoutDaWait.Milliseconds()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// notifyNewPayload signals execution engine on a new payload.
|
||||
// It returns true if the EL has returned VALID for the block
|
||||
func (s *Service) notifyNewEnvelope(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyNewPayload")
|
||||
defer span.End()
|
||||
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get execution payload")
|
||||
}
|
||||
|
||||
versionedHashes := envelope.VersionedHashes()
|
||||
root := envelope.BeaconBlockRoot()
|
||||
parentRoot, err := s.ParentRoot(root)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get parent block root")
|
||||
}
|
||||
pr := common.Hash(parentRoot)
|
||||
lastValidHash, err := s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &pr)
|
||||
switch {
|
||||
case err == nil:
|
||||
newPayloadValidNodeCount.Inc()
|
||||
return true, nil
|
||||
case errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus):
|
||||
newPayloadOptimisticNodeCount.Inc()
|
||||
log.WithFields(logrus.Fields{
|
||||
"payloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(payload.BlockHash())),
|
||||
}).Info("Called new payload with optimistic block")
|
||||
return false, nil
|
||||
case errors.Is(err, execution.ErrInvalidPayloadStatus):
|
||||
lvh := bytesutil.ToBytes32(lastValidHash)
|
||||
return false, invalidBlock{
|
||||
error: ErrInvalidPayload,
|
||||
lastValidHash: lvh,
|
||||
}
|
||||
default:
|
||||
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// validateExecutionOnEnvelope notifies the engine of the incoming execution payload and returns true if the payload is valid
|
||||
func (s *Service) validateExecutionOnEnvelope(ctx context.Context, e interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
isValidPayload, err := s.notifyNewEnvelope(ctx, e)
|
||||
if err == nil {
|
||||
return isValidPayload, nil
|
||||
}
|
||||
blockRoot := e.BeaconBlockRoot()
|
||||
parentRoot, rootErr := s.ParentRoot(blockRoot)
|
||||
if rootErr != nil {
|
||||
return false, errors.Wrap(rootErr, "could not get parent block root")
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
err = s.handleInvalidExecutionError(ctx, err, blockRoot, parentRoot)
|
||||
s.cfg.ForkChoiceStore.Unlock()
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (s *Service) getPayloadEnvelopePrestate(ctx context.Context, e interfaces.ROExecutionPayloadEnvelope) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.getPayloadEnvelopePreState")
|
||||
defer span.End()
|
||||
|
||||
// Verify incoming payload has a valid pre state.
|
||||
root := e.BeaconBlockRoot()
|
||||
// Verify the referred block is known to forkchoice
|
||||
if !s.InForkchoice(root) {
|
||||
return nil, errors.New("Cannot import execution payload envelope for unknown block")
|
||||
}
|
||||
if err := s.verifyBlkPreState(ctx, root); err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify payload prestate")
|
||||
}
|
||||
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, root)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get pre state")
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, errors.Wrap(err, "nil pre state")
|
||||
}
|
||||
return preState, nil
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
|
||||
mockExecution "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/testing"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
|
||||
)
|
||||
|
||||
func Test_getPayloadEnvelopePrestate(t *testing.T) {
|
||||
service, tr := minimalTestService(t)
|
||||
ctx, fcs := tr.ctx, tr.fcs
|
||||
|
||||
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
|
||||
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.BeaconBlockRoot = service.originBlockRoot[:]
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = service.getPayloadEnvelopePrestate(ctx, e)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_notifyNewEnvelope(t *testing.T) {
|
||||
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
ctx, fcs := tr.ctx, tr.fcs
|
||||
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.BeaconBlockRoot = service.originBlockRoot[:]
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
engine := &mockExecution.EngineClient{}
|
||||
service.cfg.ExecutionEngineCaller = engine
|
||||
isValidPayload, err := service.notifyNewEnvelope(ctx, e)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isValidPayload)
|
||||
}
|
||||
|
||||
func Test_validateExecutionOnEnvelope(t *testing.T) {
|
||||
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
ctx, fcs := tr.ctx, tr.fcs
|
||||
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.BeaconBlockRoot = service.originBlockRoot[:]
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
engine := &mockExecution.EngineClient{}
|
||||
service.cfg.ExecutionEngineCaller = engine
|
||||
isValidPayload, err := service.validateExecutionOnEnvelope(ctx, e)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isValidPayload)
|
||||
}
|
||||
|
||||
func Test_ReceiveExecutionPayloadEnvelope(t *testing.T) {
|
||||
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
ctx, fcs := tr.ctx, tr.fcs
|
||||
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
|
||||
require.NoError(t, service.saveGenesisData(ctx, gs))
|
||||
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
|
||||
post := gs.Copy()
|
||||
p := &enginev1.ExecutionPayloadEnvelope{
|
||||
Payload: &enginev1.ExecutionPayloadElectra{
|
||||
ParentHash: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
BeaconBlockRoot: service.originBlockRoot[:],
|
||||
BlobKzgCommitments: make([][]byte, 0),
|
||||
StateRoot: make([]byte, 32),
|
||||
}
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
das := &das.MockAvailabilityStore{}
|
||||
|
||||
blockHeader := post.LatestBlockHeader()
|
||||
prevStateRoot, err := post.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
blockHeader.StateRoot = prevStateRoot[:]
|
||||
require.NoError(t, post.SetLatestBlockHeader(blockHeader))
|
||||
stRoot, err := post.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
p.StateRoot = stRoot[:]
|
||||
engine := &mockExecution.EngineClient{}
|
||||
service.cfg.ExecutionEngineCaller = engine
|
||||
require.NoError(t, service.ReceiveExecutionPayloadEnvelope(ctx, e, das))
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
func (s *Service) ReceivePayloadAttestationMessage(ctx context.Context, a *eth.PayloadAttestationMessage) error {
|
||||
if err := helpers.ValidateNilPayloadAttestationMessage(a); err != nil {
|
||||
return err
|
||||
}
|
||||
root := [32]byte(a.Data.BeaconBlockRoot)
|
||||
st, err := s.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptc, err := helpers.GetPayloadTimelinessCommittee(ctx, st, a.Data.Slot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx := slices.Index(ptc, a.ValidatorIndex)
|
||||
if idx == -1 {
|
||||
return errInvalidValidatorIndex
|
||||
}
|
||||
if s.cfg.PayloadAttestationCache.Seen(root, uint64(primitives.ValidatorIndex(idx))) {
|
||||
return nil
|
||||
}
|
||||
return s.cfg.PayloadAttestationCache.Add(a, uint64(idx))
|
||||
}
|
||||
@@ -65,6 +65,7 @@ type Service struct {
|
||||
syncComplete chan struct{}
|
||||
blobNotifiers *blobNotifierMap
|
||||
blockBeingSynced *currentlySyncingBlock
|
||||
payloadBeingSynced *currentlySyncingPayload
|
||||
blobStorage *filesystem.BlobStorage
|
||||
lastPublishedLightClientEpoch primitives.Epoch
|
||||
}
|
||||
@@ -75,6 +76,8 @@ type config struct {
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
DepositCache cache.DepositCache
|
||||
PayloadAttestationCache *cache.PayloadAttestationCache
|
||||
PayloadEnvelopeCache *sync.Map
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
AttPool attestations.Pool
|
||||
@@ -179,6 +182,7 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
blobNotifiers: bn,
|
||||
cfg: &config{},
|
||||
blockBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
payloadBeingSynced: ¤tlySyncingPayload{roots: make(map[[32]byte]primitives.PTCStatus)},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(srv); err != nil {
|
||||
@@ -544,7 +548,7 @@ func (s *Service) saveGenesisData(ctx context.Context, genesisState state.Beacon
|
||||
// 2.) Check DB.
|
||||
// Checking 1.) is ten times faster than checking 2.)
|
||||
// this function requires a lock in forkchoice
|
||||
func (s *Service) hasBlock(ctx context.Context, root [32]byte) bool {
|
||||
func (s *Service) chainHasBlock(ctx context.Context, root [32]byte) bool {
|
||||
if s.cfg.ForkChoiceStore.HasNode(root) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -382,8 +382,8 @@ func TestHasBlock_ForkChoiceAndDB_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, r))
|
||||
|
||||
assert.Equal(t, false, s.hasBlock(ctx, [32]byte{}), "Should not have block")
|
||||
assert.Equal(t, true, s.hasBlock(ctx, r), "Should have block")
|
||||
assert.Equal(t, false, s.chainHasBlock(ctx, [32]byte{}), "Should not have block")
|
||||
assert.Equal(t, true, s.chainHasBlock(ctx, r), "Should have block")
|
||||
}
|
||||
|
||||
func TestServiceStop_SaveCachedBlocks(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,10 @@ load("@prysm//tools/go:def.bzl", "go_library")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = ["mock.go"],
|
||||
srcs = [
|
||||
"mock.go",
|
||||
"mock_epbs.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing",
|
||||
visibility = [
|
||||
"//beacon-chain:__subpackages__",
|
||||
|
||||
@@ -37,44 +37,50 @@ var ErrNilState = errors.New("nil state")
|
||||
|
||||
// ChainService defines the mock interface for testing
|
||||
type ChainService struct {
|
||||
NotFinalized bool
|
||||
Optimistic bool
|
||||
ValidAttestation bool
|
||||
ValidatorsRoot [32]byte
|
||||
PublicKey [fieldparams.BLSPubkeyLength]byte
|
||||
FinalizedCheckPoint *ethpb.Checkpoint
|
||||
CurrentJustifiedCheckPoint *ethpb.Checkpoint
|
||||
PreviousJustifiedCheckPoint *ethpb.Checkpoint
|
||||
Slot *primitives.Slot // Pointer because 0 is a useful value, so checking against it can be incorrect.
|
||||
Balance *precompute.Balance
|
||||
CanonicalRoots map[[32]byte]bool
|
||||
Fork *ethpb.Fork
|
||||
ETH1Data *ethpb.Eth1Data
|
||||
InitSyncBlockRoots map[[32]byte]bool
|
||||
DB db.Database
|
||||
State state.BeaconState
|
||||
Block interfaces.ReadOnlySignedBeaconBlock
|
||||
VerifyBlkDescendantErr error
|
||||
stateNotifier statefeed.Notifier
|
||||
BlocksReceived []interfaces.ReadOnlySignedBeaconBlock
|
||||
SyncCommitteeIndices []primitives.CommitteeIndex
|
||||
blockNotifier blockfeed.Notifier
|
||||
opNotifier opfeed.Notifier
|
||||
Root []byte
|
||||
SyncCommitteeDomain []byte
|
||||
SyncSelectionProofDomain []byte
|
||||
SyncContributionProofDomain []byte
|
||||
SyncCommitteePubkeys [][]byte
|
||||
Genesis time.Time
|
||||
ForkChoiceStore forkchoice.ForkChoicer
|
||||
ReceiveBlockMockErr error
|
||||
OptimisticCheckRootReceived [32]byte
|
||||
FinalizedRoots map[[32]byte]bool
|
||||
OptimisticRoots map[[32]byte]bool
|
||||
BlockSlot primitives.Slot
|
||||
SyncingRoot [32]byte
|
||||
Blobs []blocks.VerifiedROBlob
|
||||
TargetRoot [32]byte
|
||||
NotFinalized bool
|
||||
Optimistic bool
|
||||
ValidAttestation bool
|
||||
ValidatorsRoot [32]byte
|
||||
PublicKey [fieldparams.BLSPubkeyLength]byte
|
||||
FinalizedCheckPoint *ethpb.Checkpoint
|
||||
CurrentJustifiedCheckPoint *ethpb.Checkpoint
|
||||
PreviousJustifiedCheckPoint *ethpb.Checkpoint
|
||||
Slot *primitives.Slot // Pointer because 0 is a useful value, so checking against it can be incorrect.
|
||||
Balance *precompute.Balance
|
||||
CanonicalRoots map[[32]byte]bool
|
||||
Fork *ethpb.Fork
|
||||
ETH1Data *ethpb.Eth1Data
|
||||
InitSyncBlockRoots map[[32]byte]bool
|
||||
DB db.Database
|
||||
State state.BeaconState
|
||||
Block interfaces.ReadOnlySignedBeaconBlock
|
||||
ExecutionPayloadEnvelope interfaces.ROExecutionPayloadEnvelope
|
||||
VerifyBlkDescendantErr error
|
||||
stateNotifier statefeed.Notifier
|
||||
BlocksReceived []interfaces.ReadOnlySignedBeaconBlock
|
||||
SyncCommitteeIndices []primitives.CommitteeIndex
|
||||
blockNotifier blockfeed.Notifier
|
||||
opNotifier opfeed.Notifier
|
||||
Root []byte
|
||||
SyncCommitteeDomain []byte
|
||||
SyncSelectionProofDomain []byte
|
||||
SyncContributionProofDomain []byte
|
||||
SyncCommitteePubkeys [][]byte
|
||||
Genesis time.Time
|
||||
ForkChoiceStore forkchoice.ForkChoicer
|
||||
ReceiveBlockMockErr error
|
||||
ReceiveEnvelopeMockErr error
|
||||
OptimisticCheckRootReceived [32]byte
|
||||
FinalizedRoots map[[32]byte]bool
|
||||
OptimisticRoots map[[32]byte]bool
|
||||
BlockSlot primitives.Slot
|
||||
SyncingRoot [32]byte
|
||||
Blobs []blocks.VerifiedROBlob
|
||||
TargetRoot [32]byte
|
||||
HighestReceivedSlot primitives.Slot
|
||||
HighestReceivedRoot [32]byte
|
||||
PayloadStatus primitives.PTCStatus
|
||||
ReceivePayloadAttestationMessageErr error
|
||||
}
|
||||
|
||||
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
|
||||
@@ -567,12 +573,12 @@ func (s *ChainService) ReceivedBlocksLastEpoch() (uint64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// HighestReceivedBlockSlot mocks the same method in the chain service
|
||||
func (s *ChainService) HighestReceivedBlockSlot() primitives.Slot {
|
||||
// HighestReceivedBlockSlotRoot mocks the same method in the chain service
|
||||
func (s *ChainService) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.HighestReceivedBlockSlot()
|
||||
return s.ForkChoiceStore.HighestReceivedBlockSlotRoot()
|
||||
}
|
||||
return 0
|
||||
return s.HighestReceivedSlot, s.HighestReceivedRoot
|
||||
}
|
||||
|
||||
// InsertNode mocks the same method in the chain service
|
||||
@@ -632,3 +638,17 @@ func (c *ChainService) ReceiveBlob(_ context.Context, b blocks.VerifiedROBlob) e
|
||||
func (c *ChainService) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
|
||||
return c.TargetRoot, nil
|
||||
}
|
||||
|
||||
// HashInForkchoice mocks the same method in the chain service
|
||||
func (c *ChainService) HashInForkchoice([32]byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ReceivePayloadAttestationMessage mocks the same method in the chain service
|
||||
func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *ethpb.PayloadAttestationMessage) error {
|
||||
return c.ReceivePayloadAttestationMessageErr
|
||||
}
|
||||
|
||||
func (c *ChainService) GetPTCVote(root [32]byte) primitives.PTCStatus {
|
||||
return c.PayloadStatus
|
||||
}
|
||||
|
||||
25
beacon-chain/blockchain/testing/mock_epbs.go
Normal file
25
beacon-chain/blockchain/testing/mock_epbs.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
)
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope mocks the method in chain service.
|
||||
func (s *ChainService) ReceiveExecutionPayloadEnvelope(ctx context.Context, env interfaces.ROExecutionPayloadEnvelope, _ das.AvailabilityStore) error {
|
||||
if s.ReceiveBlockMockErr != nil {
|
||||
return s.ReceiveBlockMockErr
|
||||
}
|
||||
if s.State == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
if s.State.Slot() == env.Slot() {
|
||||
if err := s.State.SetLatestFullSlot(s.State.Slot()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.ExecutionPayloadEnvelope = env
|
||||
return nil
|
||||
}
|
||||
@@ -19,6 +19,7 @@ go_library(
|
||||
"//consensus-types/primitives: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",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
@@ -26,7 +27,6 @@ go_library(
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ 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"
|
||||
v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ErrNoBuilder is used when builder endpoint is not configured.
|
||||
|
||||
10
beacon-chain/cache/BUILD.bazel
vendored
10
beacon-chain/cache/BUILD.bazel
vendored
@@ -15,11 +15,13 @@ go_library(
|
||||
"doc.go",
|
||||
"error.go",
|
||||
"interfaces.go",
|
||||
"payload_attestation.go",
|
||||
"payload_id.go",
|
||||
"proposer_indices.go",
|
||||
"proposer_indices_disabled.go", # keep
|
||||
"proposer_indices_type.go",
|
||||
"registration.go",
|
||||
"signed_execution_header.go",
|
||||
"skip_slot_cache.go",
|
||||
"subnet_ids.go",
|
||||
"sync_committee.go",
|
||||
@@ -42,10 +44,13 @@ go_library(
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math: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",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
@@ -56,7 +61,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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -70,10 +74,12 @@ go_test(
|
||||
"checkpoint_state_test.go",
|
||||
"committee_fuzz_test.go",
|
||||
"committee_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"payload_id_test.go",
|
||||
"private_access_test.go",
|
||||
"proposer_indices_test.go",
|
||||
"registration_test.go",
|
||||
"signed_execution_header_test.go",
|
||||
"skip_slot_cache_test.go",
|
||||
"subnet_ids_test.go",
|
||||
"sync_committee_head_state_test.go",
|
||||
@@ -88,7 +94,9 @@ go_test(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
|
||||
2
beacon-chain/cache/common.go
vendored
2
beacon-chain/cache/common.go
vendored
@@ -17,6 +17,6 @@ func trim(queue *cache.FIFO, maxSize uint64) {
|
||||
}
|
||||
|
||||
// popProcessNoopFunc is a no-op function that never returns an error.
|
||||
func popProcessNoopFunc(_ interface{}) error {
|
||||
func popProcessNoopFunc(_ interface{}, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,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",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
@@ -26,7 +27,6 @@ go_library(
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_wealdtech_go_bytesutil//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -241,7 +241,7 @@ func (c *Cache) InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, bloc
|
||||
c.pendingDeposits = append(c.pendingDeposits,
|
||||
ðpb.DepositContainer{Deposit: d, Eth1BlockHeight: blockNum, Index: index, DepositRoot: depositRoot[:]})
|
||||
pendingDepositsCount.Set(float64(len(c.pendingDeposits)))
|
||||
span.AddAttributes(trace.Int64Attribute("count", int64(len(c.pendingDeposits))))
|
||||
span.SetAttributes(trace.Int64Attribute("count", int64(len(c.pendingDeposits))))
|
||||
}
|
||||
|
||||
// Deposits returns the cached internal deposit tree.
|
||||
@@ -304,7 +304,7 @@ func (c *Cache) PendingContainers(ctx context.Context, untilBlk *big.Int) []*eth
|
||||
return depositCntrs[i].Index < depositCntrs[j].Index
|
||||
})
|
||||
|
||||
span.AddAttributes(trace.Int64Attribute("count", int64(len(depositCntrs))))
|
||||
span.SetAttributes(trace.Int64Attribute("count", int64(len(depositCntrs))))
|
||||
|
||||
return depositCntrs
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"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/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
132
beacon-chain/cache/payload_attestation.go
vendored
Normal file
132
beacon-chain/cache/payload_attestation.go
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
var errNilPayloadAttestationMessage = errors.New("nil Payload Attestation Message")
|
||||
|
||||
// PayloadAttestationCache keeps a map of all the PTC votes that were seen,
|
||||
// already aggregated. The key is the beacon block root.
|
||||
type PayloadAttestationCache struct {
|
||||
root [32]byte
|
||||
attestations [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Seen returns true if a vote for the given Beacon Block Root has already been processed
|
||||
// for this Payload Timeliness Committee index. This will return true even if
|
||||
// the Payload status differs.
|
||||
func (p *PayloadAttestationCache) Seen(root [32]byte, idx uint64) bool {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
if p.root != root {
|
||||
return false
|
||||
}
|
||||
for _, agg := range p.attestations {
|
||||
if agg == nil {
|
||||
continue
|
||||
}
|
||||
if agg.AggregationBits.BitAt(idx) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// messageToPayloadAttestation creates a PayloadAttestation with a single
|
||||
// aggregated bit from the passed PayloadAttestationMessage
|
||||
func messageToPayloadAttestation(att *eth.PayloadAttestationMessage, idx uint64) *eth.PayloadAttestation {
|
||||
bits := primitives.NewPayloadAttestationAggregationBits()
|
||||
bits.SetBitAt(idx, true)
|
||||
data := ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: bytesutil.SafeCopyBytes(att.Data.BeaconBlockRoot),
|
||||
Slot: att.Data.Slot,
|
||||
PayloadStatus: att.Data.PayloadStatus,
|
||||
}
|
||||
return ð.PayloadAttestation{
|
||||
AggregationBits: bits,
|
||||
Data: data,
|
||||
Signature: bytesutil.SafeCopyBytes(att.Signature),
|
||||
}
|
||||
}
|
||||
|
||||
// aggregateSigFromMessage returns the aggregated signature from a Payload
|
||||
// Attestation by adding the passed signature in the PayloadAttestationMessage,
|
||||
// no signature validation is performed.
|
||||
func aggregateSigFromMessage(aggregated *eth.PayloadAttestation, message *eth.PayloadAttestationMessage) ([]byte, error) {
|
||||
aggSig, err := bls.SignatureFromBytesNoValidation(aggregated.Signature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := bls.SignatureFromBytesNoValidation(message.Signature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bls.AggregateSignatures([]bls.Signature{aggSig, sig}).Marshal(), nil
|
||||
}
|
||||
|
||||
// Add adds a PayloadAttestationMessage to the internal cache of aggregated
|
||||
// PayloadAttestations.
|
||||
// If the index has already been seen for this attestation status the function does nothing.
|
||||
// If the root is not the cached root, the function will clear the previous cache
|
||||
// This function assumes that the message has already been validated. In
|
||||
// particular that the signature is valid and that the block root corresponds to
|
||||
// the given slot in the attestation data.
|
||||
func (p *PayloadAttestationCache) Add(att *eth.PayloadAttestationMessage, idx uint64) error {
|
||||
if att == nil || att.Data == nil || att.Data.BeaconBlockRoot == nil {
|
||||
return errNilPayloadAttestationMessage
|
||||
}
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
root := [32]byte(att.Data.BeaconBlockRoot)
|
||||
if p.root != root {
|
||||
p.root = root
|
||||
p.attestations = [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{}
|
||||
}
|
||||
agg := p.attestations[att.Data.PayloadStatus]
|
||||
if agg == nil {
|
||||
p.attestations[att.Data.PayloadStatus] = messageToPayloadAttestation(att, idx)
|
||||
return nil
|
||||
}
|
||||
if agg.AggregationBits.BitAt(idx) {
|
||||
return nil
|
||||
}
|
||||
sig, err := aggregateSigFromMessage(agg, att)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
agg.Signature = sig
|
||||
agg.AggregationBits.SetBitAt(idx, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the aggregated PayloadAttestation for the given root and status
|
||||
// if the root doesn't exist or status is invalid, the function returns nil.
|
||||
func (p *PayloadAttestationCache) Get(root [32]byte, status primitives.PTCStatus) *eth.PayloadAttestation {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if p.root != root {
|
||||
return nil
|
||||
}
|
||||
if status >= primitives.PAYLOAD_INVALID_STATUS {
|
||||
return nil
|
||||
}
|
||||
|
||||
return eth.CopyPayloadAttestation(p.attestations[status])
|
||||
}
|
||||
|
||||
// Clear clears the internal map
|
||||
func (p *PayloadAttestationCache) Clear() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
p.root = [32]byte{}
|
||||
p.attestations = [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{}
|
||||
}
|
||||
143
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
143
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestPayloadAttestationCache(t *testing.T) {
|
||||
p := &PayloadAttestationCache{}
|
||||
|
||||
//Test Has seen
|
||||
root := [32]byte{'r'}
|
||||
idx := uint64(5)
|
||||
require.Equal(t, false, p.Seen(root, idx))
|
||||
|
||||
// Test Add
|
||||
msg := ð.PayloadAttestationMessage{
|
||||
Signature: bls.NewAggregateSignature().Marshal(),
|
||||
Data: ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: root[:],
|
||||
Slot: 1,
|
||||
PayloadStatus: primitives.PAYLOAD_PRESENT,
|
||||
},
|
||||
}
|
||||
|
||||
// Add new root
|
||||
require.NoError(t, p.Add(msg, idx))
|
||||
require.Equal(t, true, p.Seen(root, idx))
|
||||
require.Equal(t, root, p.root)
|
||||
att := p.attestations[primitives.PAYLOAD_PRESENT]
|
||||
indices := att.AggregationBits.BitIndices()
|
||||
require.DeepEqual(t, []int{int(idx)}, indices)
|
||||
singleSig := bytesutil.SafeCopyBytes(msg.Signature)
|
||||
require.DeepEqual(t, singleSig, att.Signature)
|
||||
|
||||
// Test Seen
|
||||
require.Equal(t, true, p.Seen(root, idx))
|
||||
require.Equal(t, false, p.Seen(root, idx+1))
|
||||
|
||||
// Add another attestation on the same data
|
||||
msg2 := ð.PayloadAttestationMessage{
|
||||
Signature: bls.NewAggregateSignature().Marshal(),
|
||||
Data: att.Data,
|
||||
}
|
||||
idx2 := uint64(7)
|
||||
require.NoError(t, p.Add(msg2, idx2))
|
||||
att = p.attestations[primitives.PAYLOAD_PRESENT]
|
||||
indices = att.AggregationBits.BitIndices()
|
||||
require.DeepEqual(t, []int{int(idx), int(idx2)}, indices)
|
||||
require.DeepNotEqual(t, att.Signature, msg.Signature)
|
||||
|
||||
// Try again the same index
|
||||
require.NoError(t, p.Add(msg2, idx2))
|
||||
att2 := p.attestations[primitives.PAYLOAD_PRESENT]
|
||||
indices = att.AggregationBits.BitIndices()
|
||||
require.DeepEqual(t, []int{int(idx), int(idx2)}, indices)
|
||||
require.DeepEqual(t, att, att2)
|
||||
|
||||
// Test Seen
|
||||
require.Equal(t, true, p.Seen(root, idx2))
|
||||
require.Equal(t, false, p.Seen(root, idx2+1))
|
||||
|
||||
// Add another payload status for a different payload status
|
||||
msg3 := ð.PayloadAttestationMessage{
|
||||
Signature: bls.NewAggregateSignature().Marshal(),
|
||||
Data: ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: root[:],
|
||||
Slot: 1,
|
||||
PayloadStatus: primitives.PAYLOAD_WITHHELD,
|
||||
},
|
||||
}
|
||||
idx3 := uint64(17)
|
||||
|
||||
require.NoError(t, p.Add(msg3, idx3))
|
||||
att3 := p.attestations[primitives.PAYLOAD_WITHHELD]
|
||||
indices3 := att3.AggregationBits.BitIndices()
|
||||
require.DeepEqual(t, []int{int(idx3)}, indices3)
|
||||
require.DeepEqual(t, singleSig, att3.Signature)
|
||||
|
||||
// Add a different root
|
||||
root2 := [32]byte{'s'}
|
||||
msg.Data.BeaconBlockRoot = root2[:]
|
||||
require.NoError(t, p.Add(msg, idx))
|
||||
require.Equal(t, root2, p.root)
|
||||
require.Equal(t, true, p.Seen(root2, idx))
|
||||
require.Equal(t, false, p.Seen(root, idx))
|
||||
att = p.attestations[primitives.PAYLOAD_PRESENT]
|
||||
indices = att.AggregationBits.BitIndices()
|
||||
require.DeepEqual(t, []int{int(idx)}, indices)
|
||||
}
|
||||
|
||||
func TestPayloadAttestationCache_Get(t *testing.T) {
|
||||
root := [32]byte{1, 2, 3}
|
||||
wrongRoot := [32]byte{4, 5, 6}
|
||||
status := primitives.PAYLOAD_PRESENT
|
||||
invalidStatus := primitives.PAYLOAD_INVALID_STATUS
|
||||
|
||||
cache := &PayloadAttestationCache{
|
||||
root: root,
|
||||
attestations: [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{
|
||||
{
|
||||
Signature: []byte{1},
|
||||
},
|
||||
{
|
||||
Signature: []byte{2},
|
||||
},
|
||||
{
|
||||
Signature: []byte{3},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("valid root and status", func(t *testing.T) {
|
||||
result := cache.Get(root, status)
|
||||
require.NotNil(t, result, "Expected a non-nil result")
|
||||
require.DeepEqual(t, cache.attestations[status], result)
|
||||
})
|
||||
|
||||
t.Run("invalid root", func(t *testing.T) {
|
||||
result := cache.Get(wrongRoot, status)
|
||||
require.IsNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("status out of bound", func(t *testing.T) {
|
||||
result := cache.Get(root, invalidStatus)
|
||||
require.IsNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("no attestation", func(t *testing.T) {
|
||||
emptyCache := &PayloadAttestationCache{
|
||||
root: root,
|
||||
attestations: [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{},
|
||||
}
|
||||
|
||||
result := emptyCache.Get(root, status)
|
||||
require.IsNil(t, result)
|
||||
})
|
||||
}
|
||||
2
beacon-chain/cache/registration.go
vendored
2
beacon-chain/cache/registration.go
vendored
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"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"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// RegistrationCache is used to store the cached results of an Validator Registration request.
|
||||
|
||||
76
beacon-chain/cache/signed_execution_header.go
vendored
Normal file
76
beacon-chain/cache/signed_execution_header.go
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
)
|
||||
|
||||
// ExecutionPayloadHeaders is used by the sync service to store signed execution payload headers after they pass validation,
|
||||
// and filter out subsequent headers with lower value.
|
||||
// The signed header from this cache could be used by the proposer when proposing the next slot.
|
||||
type ExecutionPayloadHeaders struct {
|
||||
headers map[primitives.Slot][]*enginev1.SignedExecutionPayloadHeader
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewExecutionPayloadHeaders() *ExecutionPayloadHeaders {
|
||||
return &ExecutionPayloadHeaders{
|
||||
headers: make(map[primitives.Slot][]*enginev1.SignedExecutionPayloadHeader),
|
||||
}
|
||||
}
|
||||
|
||||
// SaveSignedExecutionPayloadHeader saves the signed execution payload header to the cache.
|
||||
// The cache stores headers for up to two slots. If the input slot is higher than the lowest slot
|
||||
// currently in the cache, the lowest slot is removed to make space for the new header.
|
||||
// Only the highest value header for a given parent block hash will be stored.
|
||||
// This function assumes caller already checks header's slot is current or next slot, it doesn't account for slot validation.
|
||||
func (c *ExecutionPayloadHeaders) SaveSignedExecutionPayloadHeader(header *enginev1.SignedExecutionPayloadHeader) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
for s := range c.headers {
|
||||
if s+1 < header.Message.Slot {
|
||||
delete(c.headers, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Add or update the header in the map
|
||||
if _, ok := c.headers[header.Message.Slot]; !ok {
|
||||
c.headers[header.Message.Slot] = []*enginev1.SignedExecutionPayloadHeader{header}
|
||||
} else {
|
||||
found := false
|
||||
for i, h := range c.headers[header.Message.Slot] {
|
||||
if bytes.Equal(h.Message.ParentBlockHash, header.Message.ParentBlockHash) && bytes.Equal(h.Message.ParentBlockRoot, header.Message.ParentBlockRoot) {
|
||||
if header.Message.Value > h.Message.Value {
|
||||
c.headers[header.Message.Slot][i] = header
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
c.headers[header.Message.Slot] = append(c.headers[header.Message.Slot], header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SignedExecutionPayloadHeader returns the signed payload header for the given slot and parent block hash and block root.
|
||||
// Returns nil if the header is not found.
|
||||
// This should be used when the caller wants the header to match parent block hash and parent block root such as proposer choosing a header to propose.
|
||||
func (c *ExecutionPayloadHeaders) SignedExecutionPayloadHeader(slot primitives.Slot, parentBlockHash []byte, parentBlockRoot []byte) *enginev1.SignedExecutionPayloadHeader {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if headers, ok := c.headers[slot]; ok {
|
||||
for _, header := range headers {
|
||||
if bytes.Equal(header.Message.ParentBlockHash, parentBlockHash) && bytes.Equal(header.Message.ParentBlockRoot, parentBlockRoot) {
|
||||
return header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
243
beacon-chain/cache/signed_execution_header_test.go
vendored
Normal file
243
beacon-chain/cache/signed_execution_header_test.go
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func Test_SaveSignedExecutionPayloadHeader(t *testing.T) {
|
||||
t.Run("First header should be added to cache", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header)
|
||||
require.Equal(t, 1, len(c.headers))
|
||||
require.Equal(t, header, c.headers[1][0])
|
||||
})
|
||||
|
||||
t.Run("Second header with higher slot should be added, and both slots should be in cache", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header1 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header2 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header1)
|
||||
c.SaveSignedExecutionPayloadHeader(header2)
|
||||
require.Equal(t, 2, len(c.headers))
|
||||
require.Equal(t, header1, c.headers[1][0])
|
||||
require.Equal(t, header2, c.headers[2][0])
|
||||
})
|
||||
|
||||
t.Run("Third header with higher slot should replace the oldest slot", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header1 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header2 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header3 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 3,
|
||||
ParentBlockHash: []byte("parent3"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header1)
|
||||
c.SaveSignedExecutionPayloadHeader(header2)
|
||||
c.SaveSignedExecutionPayloadHeader(header3)
|
||||
require.Equal(t, 2, len(c.headers))
|
||||
require.Equal(t, header2, c.headers[2][0])
|
||||
require.Equal(t, header3, c.headers[3][0])
|
||||
})
|
||||
|
||||
t.Run("Header with same slot but higher value should replace the existing one", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header1 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header2 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 200,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header1)
|
||||
c.SaveSignedExecutionPayloadHeader(header2)
|
||||
require.Equal(t, 1, len(c.headers[2]))
|
||||
require.Equal(t, header2, c.headers[2][0])
|
||||
})
|
||||
|
||||
t.Run("Header with different parent block hash should be appended to the same slot", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header1 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header2 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 200,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header1)
|
||||
c.SaveSignedExecutionPayloadHeader(header2)
|
||||
require.Equal(t, 2, len(c.headers[2]))
|
||||
require.Equal(t, header1, c.headers[2][0])
|
||||
require.Equal(t, header2, c.headers[2][1])
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignedExecutionPayloadHeader(t *testing.T) {
|
||||
t.Run("Return header when slot and parentBlockHash match", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
ParentBlockRoot: []byte("root1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header)
|
||||
result := c.SignedExecutionPayloadHeader(1, []byte("parent1"), []byte("root1"))
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, header, result)
|
||||
})
|
||||
|
||||
t.Run("Return nil when no matching slot and parentBlockHash", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
ParentBlockRoot: []byte("root1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header)
|
||||
result := c.SignedExecutionPayloadHeader(2, []byte("parent2"), []byte("root1"))
|
||||
require.IsNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("Return nil when no matching slot and parentBlockRoot", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
ParentBlockRoot: []byte("root1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header)
|
||||
result := c.SignedExecutionPayloadHeader(2, []byte("parent1"), []byte("root2"))
|
||||
require.IsNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("Return header when there are two slots in the cache and a match is found", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header1 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header2 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 200,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header1)
|
||||
c.SaveSignedExecutionPayloadHeader(header2)
|
||||
|
||||
// Check for the first header
|
||||
result1 := c.SignedExecutionPayloadHeader(1, []byte("parent1"), []byte{})
|
||||
require.NotNil(t, result1)
|
||||
require.Equal(t, header1, result1)
|
||||
|
||||
// Check for the second header
|
||||
result2 := c.SignedExecutionPayloadHeader(2, []byte("parent2"), []byte{})
|
||||
require.NotNil(t, result2)
|
||||
require.Equal(t, header2, result2)
|
||||
})
|
||||
|
||||
t.Run("Return nil when slot is evicted from cache", func(t *testing.T) {
|
||||
c := NewExecutionPayloadHeaders()
|
||||
header1 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 1,
|
||||
ParentBlockHash: []byte("parent1"),
|
||||
Value: 100,
|
||||
},
|
||||
}
|
||||
header2 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 2,
|
||||
ParentBlockHash: []byte("parent2"),
|
||||
Value: 200,
|
||||
},
|
||||
}
|
||||
header3 := &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
Slot: 3,
|
||||
ParentBlockHash: []byte("parent3"),
|
||||
Value: 300,
|
||||
},
|
||||
}
|
||||
c.SaveSignedExecutionPayloadHeader(header1)
|
||||
c.SaveSignedExecutionPayloadHeader(header2)
|
||||
c.SaveSignedExecutionPayloadHeader(header3)
|
||||
|
||||
// The first slot should be evicted, so result should be nil
|
||||
result := c.SignedExecutionPayloadHeader(1, []byte("parent1"), []byte{})
|
||||
require.IsNil(t, result)
|
||||
|
||||
// The second slot should still be present
|
||||
result = c.SignedExecutionPayloadHeader(2, []byte("parent2"), []byte{})
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, header2, result)
|
||||
|
||||
// The third slot should be present
|
||||
result = c.SignedExecutionPayloadHeader(3, []byte("parent3"), []byte{})
|
||||
require.NotNil(t, result)
|
||||
require.Equal(t, header3, result)
|
||||
})
|
||||
}
|
||||
8
beacon-chain/cache/skip_slot_cache.go
vendored
8
beacon-chain/cache/skip_slot_cache.go
vendored
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
lruwrpr "github.com/prysmaticlabs/prysm/v5/cache/lru"
|
||||
"go.opencensus.io/trace"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -92,17 +92,17 @@ func (c *SkipSlotCache) Get(ctx context.Context, r [32]byte) (state.BeaconState,
|
||||
delay *= delayFactor
|
||||
delay = math.Min(delay, maxDelay)
|
||||
}
|
||||
span.AddAttributes(trace.BoolAttribute("inProgress", inProgress))
|
||||
span.SetAttributes(trace.BoolAttribute("inProgress", inProgress))
|
||||
|
||||
item, exists := c.cache.Get(r)
|
||||
|
||||
if exists && item != nil {
|
||||
skipSlotCacheHit.Inc()
|
||||
span.AddAttributes(trace.BoolAttribute("hit", true))
|
||||
span.SetAttributes(trace.BoolAttribute("hit", true))
|
||||
return item.(state.BeaconState).Copy(), nil
|
||||
}
|
||||
skipSlotCacheMiss.Inc()
|
||||
span.AddAttributes(trace.BoolAttribute("hit", false))
|
||||
span.SetAttributes(trace.BoolAttribute("hit", false))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//testing/util/random:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_google_gofuzz//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
@@ -213,6 +214,47 @@ func NewGenesisBlockForState(ctx context.Context, st state.BeaconState) (interfa
|
||||
},
|
||||
BlsToExecutionChanges: make([]*ethpb.SignedBLSToExecutionChange, 0),
|
||||
BlobKzgCommitments: make([][]byte, 0),
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Withdrawals: make([]*enginev1.WithdrawalRequest, 0),
|
||||
Deposits: make([]*enginev1.DepositRequest, 0),
|
||||
Consolidations: make([]*enginev1.ConsolidationRequest, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
})
|
||||
case *ethpb.BeaconStateEPBS:
|
||||
kzgs := make([][]byte, 0)
|
||||
kzgRoot, err := ssz.KzgCommitmentsRoot(kzgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockEpbs{
|
||||
Block: ðpb.BeaconBlockEpbs{
|
||||
ParentRoot: params.BeaconConfig().ZeroHash[:],
|
||||
StateRoot: root[:],
|
||||
Body: ðpb.BeaconBlockBodyEpbs{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, fieldparams.SyncCommitteeLength/8),
|
||||
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
SignedExecutionPayloadHeader: &enginev1.SignedExecutionPayloadHeader{
|
||||
Message: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
BlobKzgCommitmentsRoot: kzgRoot[:],
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
BlsToExecutionChanges: make([]*ethpb.SignedBLSToExecutionChange, 0),
|
||||
PayloadAttestations: make([]*ethpb.PayloadAttestation, 0),
|
||||
},
|
||||
},
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
|
||||
@@ -239,12 +239,47 @@ func verifyBlobCommitmentCount(body interfaces.ReadOnlyBeaconBlockBody) error {
|
||||
// GetBlockPayloadHash returns the hash of the execution payload of the block
|
||||
func GetBlockPayloadHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
|
||||
var payloadHash [32]byte
|
||||
if IsPreBellatrixVersion(blk.Version()) {
|
||||
return payloadHash, nil
|
||||
if blk.Version() >= version.EPBS {
|
||||
header, err := blk.Body().SignedExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return payloadHash, err
|
||||
}
|
||||
payload, err := header.Header()
|
||||
if err != nil {
|
||||
return payloadHash, err
|
||||
}
|
||||
return payload.BlockHash(), nil
|
||||
}
|
||||
payload, err := blk.Body().Execution()
|
||||
if err != nil {
|
||||
return payloadHash, err
|
||||
if blk.Version() >= version.Bellatrix {
|
||||
payload, err := blk.Body().Execution()
|
||||
if err != nil {
|
||||
return payloadHash, err
|
||||
}
|
||||
return bytesutil.ToBytes32(payload.BlockHash()), nil
|
||||
}
|
||||
return bytesutil.ToBytes32(payload.BlockHash()), nil
|
||||
return payloadHash, nil
|
||||
}
|
||||
|
||||
// GetBlockParentHash returns the hash of the parent execution payload
|
||||
func GetBlockParentHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
|
||||
var parentHash [32]byte
|
||||
if blk.Version() >= version.EPBS {
|
||||
header, err := blk.Body().SignedExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return parentHash, err
|
||||
}
|
||||
payload, err := header.Header()
|
||||
if err != nil {
|
||||
return parentHash, err
|
||||
}
|
||||
return payload.ParentBlockHash(), nil
|
||||
}
|
||||
if blk.Version() >= version.Bellatrix {
|
||||
payload, err := blk.Body().Execution()
|
||||
if err != nil {
|
||||
return parentHash, err
|
||||
}
|
||||
return bytesutil.ToBytes32(payload.ParentHash()), nil
|
||||
}
|
||||
return parentHash, nil
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
)
|
||||
@@ -115,15 +115,97 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func checkWithdrawalsAgainstPayload(
|
||||
executionData interfaces.ExecutionData,
|
||||
numExpected int,
|
||||
expectedRoot [32]byte,
|
||||
) error {
|
||||
var wdRoot [32]byte
|
||||
if executionData.IsBlinded() {
|
||||
r, err := executionData.WithdrawalsRoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
copy(wdRoot[:], r)
|
||||
} else {
|
||||
wds, err := executionData.Withdrawals()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get withdrawals")
|
||||
}
|
||||
|
||||
if len(wds) != numExpected {
|
||||
return fmt.Errorf("execution payload header has %d withdrawals when %d were expected", len(wds), numExpected)
|
||||
}
|
||||
|
||||
wdRoot, err = ssz.WithdrawalSliceRoot(wds, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
}
|
||||
if expectedRoot != wdRoot {
|
||||
return fmt.Errorf("expected withdrawals root %#x, got %#x", expectedRoot, wdRoot)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processWithdrawalStateTransition(
|
||||
st state.BeaconState,
|
||||
expectedWithdrawals []*enginev1.Withdrawal,
|
||||
partialWithdrawalsCount uint64,
|
||||
) (err error) {
|
||||
for _, withdrawal := range expectedWithdrawals {
|
||||
err := helpers.DecreaseBalance(st, withdrawal.ValidatorIndex, withdrawal.Amount)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not decrease balance")
|
||||
}
|
||||
}
|
||||
if st.Version() >= version.Electra {
|
||||
if err := st.DequeuePartialWithdrawals(partialWithdrawalsCount); err != nil {
|
||||
return fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(expectedWithdrawals) > 0 {
|
||||
if err := st.SetNextWithdrawalIndex(expectedWithdrawals[len(expectedWithdrawals)-1].Index + 1); err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal index")
|
||||
}
|
||||
}
|
||||
var nextValidatorIndex primitives.ValidatorIndex
|
||||
if uint64(len(expectedWithdrawals)) < params.BeaconConfig().MaxWithdrawalsPerPayload {
|
||||
nextValidatorIndex, err = st.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get next withdrawal validator index")
|
||||
}
|
||||
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
|
||||
} else {
|
||||
nextValidatorIndex = expectedWithdrawals[len(expectedWithdrawals)-1].ValidatorIndex + 1
|
||||
if nextValidatorIndex == primitives.ValidatorIndex(st.NumValidators()) {
|
||||
nextValidatorIndex = 0
|
||||
}
|
||||
}
|
||||
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
|
||||
return errors.Wrap(err, "could not set next withdrawal validator index")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessWithdrawals processes the validator withdrawals from the provided execution payload
|
||||
// into the beacon state.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
|
||||
// if state.fork.current_version >= EIP7732_FORK_VERSION :
|
||||
// if not is_parent_block_full(state): # [New in EIP-7732]
|
||||
// return
|
||||
//
|
||||
// expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251]
|
||||
//
|
||||
// assert len(payload.withdrawals) == len(expected_withdrawals)
|
||||
// if state.fork.current_version >= EIP7732_FORK_VERSION :
|
||||
// state.latest_withdrawals_root = hash_tree_root(expected_withdrawals) # [New in EIP-7732]
|
||||
// else :
|
||||
// assert len(payload.withdrawals) == len(expected_withdrawals)
|
||||
//
|
||||
// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
|
||||
// assert withdrawal == expected_withdrawal
|
||||
@@ -148,76 +230,39 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si
|
||||
// next_validator_index = ValidatorIndex(next_index % len(state.validators))
|
||||
// state.next_withdrawal_validator_index = next_validator_index
|
||||
func ProcessWithdrawals(st state.BeaconState, executionData interfaces.ExecutionData) (state.BeaconState, error) {
|
||||
if st.Version() >= version.EPBS {
|
||||
IsParentBlockFull, err := st.IsParentBlockFull()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not check if parent block is full")
|
||||
}
|
||||
|
||||
if !IsParentBlockFull {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
expectedWithdrawals, partialWithdrawalsCount, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get expected withdrawals")
|
||||
}
|
||||
|
||||
var wdRoot [32]byte
|
||||
if executionData.IsBlinded() {
|
||||
r, err := executionData.WithdrawalsRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
wdRoot = bytesutil.ToBytes32(r)
|
||||
} else {
|
||||
wds, err := executionData.Withdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals")
|
||||
}
|
||||
|
||||
if len(wds) != len(expectedWithdrawals) {
|
||||
return nil, fmt.Errorf("execution payload header has %d withdrawals when %d were expected", len(wds), len(expectedWithdrawals))
|
||||
}
|
||||
|
||||
wdRoot, err = ssz.WithdrawalSliceRoot(wds, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
}
|
||||
|
||||
expectedRoot, err := ssz.WithdrawalSliceRoot(expectedWithdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get expected withdrawals root")
|
||||
}
|
||||
if expectedRoot != wdRoot {
|
||||
return nil, fmt.Errorf("expected withdrawals root %#x, got %#x", expectedRoot, wdRoot)
|
||||
}
|
||||
|
||||
for _, withdrawal := range expectedWithdrawals {
|
||||
err := helpers.DecreaseBalance(st, withdrawal.ValidatorIndex, withdrawal.Amount)
|
||||
if st.Version() >= version.EPBS {
|
||||
err = st.SetLastWithdrawalsRoot(expectedRoot[:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not decrease balance")
|
||||
return nil, errors.Wrap(err, "could not set withdrawals root")
|
||||
}
|
||||
}
|
||||
|
||||
if st.Version() >= version.Electra {
|
||||
if err := st.DequeuePartialWithdrawals(partialWithdrawalsCount); err != nil {
|
||||
return nil, fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(expectedWithdrawals) > 0 {
|
||||
if err := st.SetNextWithdrawalIndex(expectedWithdrawals[len(expectedWithdrawals)-1].Index + 1); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set next withdrawal index")
|
||||
}
|
||||
}
|
||||
var nextValidatorIndex primitives.ValidatorIndex
|
||||
if uint64(len(expectedWithdrawals)) < params.BeaconConfig().MaxWithdrawalsPerPayload {
|
||||
nextValidatorIndex, err = st.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get next withdrawal validator index")
|
||||
}
|
||||
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
|
||||
} else {
|
||||
nextValidatorIndex = expectedWithdrawals[len(expectedWithdrawals)-1].ValidatorIndex + 1
|
||||
if nextValidatorIndex == primitives.ValidatorIndex(st.NumValidators()) {
|
||||
nextValidatorIndex = 0
|
||||
if err := checkWithdrawalsAgainstPayload(executionData, len(expectedWithdrawals), expectedRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set next withdrawal validator index")
|
||||
if err := processWithdrawalStateTransition(st, expectedWithdrawals, partialWithdrawalsCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
@@ -1132,13 +1133,407 @@ func TestProcessWithdrawals(t *testing.T) {
|
||||
checkPostState(t, test.Control, post)
|
||||
}
|
||||
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessWithdrawalsEPBS(t *testing.T) {
|
||||
const (
|
||||
currentEpoch = primitives.Epoch(10)
|
||||
epochInFuture = primitives.Epoch(12)
|
||||
epochInPast = primitives.Epoch(8)
|
||||
numValidators = 128
|
||||
notWithdrawableIndex = 127
|
||||
notPartiallyWithdrawable = 126
|
||||
maxSweep = uint64(80)
|
||||
)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
|
||||
type args struct {
|
||||
Name string
|
||||
NextWithdrawalValidatorIndex primitives.ValidatorIndex
|
||||
NextWithdrawalIndex uint64
|
||||
FullWithdrawalIndices []primitives.ValidatorIndex
|
||||
PendingPartialWithdrawalIndices []primitives.ValidatorIndex
|
||||
Withdrawals []*enginev1.Withdrawal
|
||||
PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // Electra
|
||||
LatestBlockHash []byte // EIP-7732
|
||||
}
|
||||
type control struct {
|
||||
NextWithdrawalValidatorIndex primitives.ValidatorIndex
|
||||
NextWithdrawalIndex uint64
|
||||
Balances map[uint64]uint64
|
||||
}
|
||||
type Test struct {
|
||||
Args args
|
||||
Control control
|
||||
}
|
||||
executionAddress := func(i primitives.ValidatorIndex) []byte {
|
||||
wc := make([]byte, 20)
|
||||
wc[19] = byte(i)
|
||||
return wc
|
||||
}
|
||||
withdrawalAmount := func(i primitives.ValidatorIndex) uint64 {
|
||||
return maxEffectiveBalance + uint64(i)*100000
|
||||
}
|
||||
fullWithdrawal := func(i primitives.ValidatorIndex, idx uint64) *enginev1.Withdrawal {
|
||||
return &enginev1.Withdrawal{
|
||||
Index: idx,
|
||||
ValidatorIndex: i,
|
||||
Address: executionAddress(i),
|
||||
Amount: withdrawalAmount(i),
|
||||
}
|
||||
}
|
||||
PendingPartialWithdrawal := func(i primitives.ValidatorIndex, idx uint64) *enginev1.Withdrawal {
|
||||
return &enginev1.Withdrawal{
|
||||
Index: idx,
|
||||
ValidatorIndex: i,
|
||||
Address: executionAddress(i),
|
||||
Amount: withdrawalAmount(i) - maxEffectiveBalance,
|
||||
}
|
||||
}
|
||||
tests := []Test{
|
||||
{
|
||||
Args: args{
|
||||
Name: "success no withdrawals",
|
||||
NextWithdrawalValidatorIndex: 10,
|
||||
NextWithdrawalIndex: 3,
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 90,
|
||||
NextWithdrawalIndex: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success one full withdrawal",
|
||||
NextWithdrawalIndex: 3,
|
||||
NextWithdrawalValidatorIndex: 5,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{70},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
fullWithdrawal(70, 3),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 85,
|
||||
NextWithdrawalIndex: 4,
|
||||
Balances: map[uint64]uint64{70: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success one partial withdrawal",
|
||||
NextWithdrawalIndex: 21,
|
||||
NextWithdrawalValidatorIndex: 120,
|
||||
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{7},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
PendingPartialWithdrawal(7, 21),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 72,
|
||||
NextWithdrawalIndex: 22,
|
||||
Balances: map[uint64]uint64{7: maxEffectiveBalance},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success many full withdrawals",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 4,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28, 1},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
fullWithdrawal(7, 22), fullWithdrawal(19, 23), fullWithdrawal(28, 24),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 84,
|
||||
NextWithdrawalIndex: 25,
|
||||
Balances: map[uint64]uint64{7: 0, 19: 0, 28: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "less than max sweep at end",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 4,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{80, 81, 82, 83},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
fullWithdrawal(80, 22), fullWithdrawal(81, 23), fullWithdrawal(82, 24),
|
||||
fullWithdrawal(83, 25),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 84,
|
||||
NextWithdrawalIndex: 26,
|
||||
Balances: map[uint64]uint64{80: 0, 81: 0, 82: 0, 83: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "less than max sweep and beginning",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 4,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{4, 5, 6},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
fullWithdrawal(4, 22), fullWithdrawal(5, 23), fullWithdrawal(6, 24),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 84,
|
||||
NextWithdrawalIndex: 25,
|
||||
Balances: map[uint64]uint64{4: 0, 5: 0, 6: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success many partial withdrawals",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 4,
|
||||
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
PendingPartialWithdrawal(7, 22), PendingPartialWithdrawal(19, 23), PendingPartialWithdrawal(28, 24),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 84,
|
||||
NextWithdrawalIndex: 25,
|
||||
Balances: map[uint64]uint64{
|
||||
7: maxEffectiveBalance,
|
||||
19: maxEffectiveBalance,
|
||||
28: maxEffectiveBalance,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success many withdrawals",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 88,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
|
||||
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{2, 1, 89, 15},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
PendingPartialWithdrawal(89, 22), PendingPartialWithdrawal(1, 23), PendingPartialWithdrawal(2, 24),
|
||||
fullWithdrawal(7, 25), PendingPartialWithdrawal(15, 26), fullWithdrawal(19, 27),
|
||||
fullWithdrawal(28, 28),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 40,
|
||||
NextWithdrawalIndex: 29,
|
||||
Balances: map[uint64]uint64{
|
||||
7: 0, 19: 0, 28: 0,
|
||||
2: maxEffectiveBalance, 1: maxEffectiveBalance, 89: maxEffectiveBalance,
|
||||
15: maxEffectiveBalance,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success many withdrawals with pending partial withdrawals in state",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 88,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
|
||||
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{2, 1, 89, 15},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
PendingPartialWithdrawal(89, 22), PendingPartialWithdrawal(1, 23), PendingPartialWithdrawal(2, 24),
|
||||
fullWithdrawal(7, 25), PendingPartialWithdrawal(15, 26), fullWithdrawal(19, 27),
|
||||
fullWithdrawal(28, 28),
|
||||
},
|
||||
PendingPartialWithdrawals: []*ethpb.PendingPartialWithdrawal{
|
||||
{
|
||||
Index: 11,
|
||||
Amount: withdrawalAmount(11) - maxEffectiveBalance,
|
||||
},
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 40,
|
||||
NextWithdrawalIndex: 29,
|
||||
Balances: map[uint64]uint64{
|
||||
7: 0, 19: 0, 28: 0,
|
||||
2: maxEffectiveBalance, 1: maxEffectiveBalance, 89: maxEffectiveBalance,
|
||||
15: maxEffectiveBalance,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Args: args{
|
||||
Name: "success more than max fully withdrawals",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 0,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 29, 35, 89},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
fullWithdrawal(1, 22), fullWithdrawal(2, 23), fullWithdrawal(3, 24),
|
||||
fullWithdrawal(4, 25), fullWithdrawal(5, 26), fullWithdrawal(6, 27),
|
||||
fullWithdrawal(7, 28), fullWithdrawal(8, 29), fullWithdrawal(9, 30),
|
||||
fullWithdrawal(21, 31), fullWithdrawal(22, 32), fullWithdrawal(23, 33),
|
||||
fullWithdrawal(24, 34), fullWithdrawal(25, 35), fullWithdrawal(26, 36),
|
||||
fullWithdrawal(27, 37),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 28,
|
||||
NextWithdrawalIndex: 38,
|
||||
Balances: map[uint64]uint64{
|
||||
1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0,
|
||||
21: 0, 22: 0, 23: 0, 24: 0, 25: 0, 26: 0, 27: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "success more than max partially withdrawals",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 0,
|
||||
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 29, 35, 89},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
PendingPartialWithdrawal(1, 22), PendingPartialWithdrawal(2, 23), PendingPartialWithdrawal(3, 24),
|
||||
PendingPartialWithdrawal(4, 25), PendingPartialWithdrawal(5, 26), PendingPartialWithdrawal(6, 27),
|
||||
PendingPartialWithdrawal(7, 28), PendingPartialWithdrawal(8, 29), PendingPartialWithdrawal(9, 30),
|
||||
PendingPartialWithdrawal(21, 31), PendingPartialWithdrawal(22, 32), PendingPartialWithdrawal(23, 33),
|
||||
PendingPartialWithdrawal(24, 34), PendingPartialWithdrawal(25, 35), PendingPartialWithdrawal(26, 36),
|
||||
PendingPartialWithdrawal(27, 37),
|
||||
},
|
||||
},
|
||||
Control: control{
|
||||
NextWithdrawalValidatorIndex: 28,
|
||||
NextWithdrawalIndex: 38,
|
||||
Balances: map[uint64]uint64{
|
||||
1: maxEffectiveBalance,
|
||||
2: maxEffectiveBalance,
|
||||
3: maxEffectiveBalance,
|
||||
4: maxEffectiveBalance,
|
||||
5: maxEffectiveBalance,
|
||||
6: maxEffectiveBalance,
|
||||
7: maxEffectiveBalance,
|
||||
8: maxEffectiveBalance,
|
||||
9: maxEffectiveBalance,
|
||||
21: maxEffectiveBalance,
|
||||
22: maxEffectiveBalance,
|
||||
23: maxEffectiveBalance,
|
||||
24: maxEffectiveBalance,
|
||||
25: maxEffectiveBalance,
|
||||
26: maxEffectiveBalance,
|
||||
27: maxEffectiveBalance,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Args: args{
|
||||
Name: "Parent Node is not full",
|
||||
NextWithdrawalIndex: 22,
|
||||
NextWithdrawalValidatorIndex: 4,
|
||||
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28, 1},
|
||||
Withdrawals: []*enginev1.Withdrawal{
|
||||
fullWithdrawal(7, 22), fullWithdrawal(19, 23), fullWithdrawal(28, 24),
|
||||
},
|
||||
LatestBlockHash: []byte{1, 2, 3},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
checkPostState := func(t *testing.T, expected control, st state.BeaconState) {
|
||||
l, err := st.NextWithdrawalValidatorIndex()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.NextWithdrawalValidatorIndex, l)
|
||||
|
||||
n, err := st.NextWithdrawalIndex()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected.NextWithdrawalIndex, n)
|
||||
balances := st.Balances()
|
||||
for idx, bal := range expected.Balances {
|
||||
require.Equal(t, bal, balances[idx])
|
||||
}
|
||||
}
|
||||
|
||||
prepareValidators := func(st state.BeaconState, arguments args) error {
|
||||
validators := make([]*ethpb.Validator, numValidators)
|
||||
if err := st.SetBalances(make([]uint64, numValidators)); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range validators {
|
||||
v := ðpb.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = epochInFuture
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
v.WithdrawalCredentials[31] = byte(i)
|
||||
if err := st.UpdateBalancesAtIndex(primitives.ValidatorIndex(i), v.EffectiveBalance-uint64(rand.Intn(1000))); err != nil {
|
||||
return err
|
||||
}
|
||||
validators[i] = v
|
||||
}
|
||||
for _, idx := range arguments.FullWithdrawalIndices {
|
||||
if idx != notWithdrawableIndex {
|
||||
validators[idx].WithdrawableEpoch = epochInPast
|
||||
}
|
||||
if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
}
|
||||
for _, idx := range arguments.PendingPartialWithdrawalIndices {
|
||||
validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return st.SetValidators(validators)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Args.Name, func(t *testing.T) {
|
||||
saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep
|
||||
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = maxSweep
|
||||
if test.Args.Withdrawals == nil {
|
||||
test.Args.Withdrawals = make([]*enginev1.Withdrawal, 0)
|
||||
}
|
||||
if test.Args.FullWithdrawalIndices == nil {
|
||||
test.Args.FullWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
|
||||
}
|
||||
if test.Args.PendingPartialWithdrawalIndices == nil {
|
||||
test.Args.PendingPartialWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
|
||||
}
|
||||
slot, err := slots.EpochStart(currentEpoch)
|
||||
require.NoError(t, err)
|
||||
var st state.BeaconState
|
||||
var p interfaces.ExecutionData
|
||||
spb := ðpb.BeaconStateEPBS{
|
||||
Slot: slot,
|
||||
NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex,
|
||||
NextWithdrawalIndex: test.Args.NextWithdrawalIndex,
|
||||
PendingPartialWithdrawals: test.Args.PendingPartialWithdrawals,
|
||||
LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderEPBS{
|
||||
BlockHash: []byte{},
|
||||
},
|
||||
LatestBlockHash: test.Args.LatestBlockHash,
|
||||
}
|
||||
st, err = state_native.InitializeFromProtoUnsafeEpbs(spb)
|
||||
require.NoError(t, err)
|
||||
env := random.ExecutionPayloadEnvelope(t)
|
||||
env.Payload.Withdrawals = test.Args.Withdrawals
|
||||
wp, err := consensusblocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
p, err = wp.Execution()
|
||||
require.NoError(t, err)
|
||||
err = prepareValidators(st, test.Args)
|
||||
require.NoError(t, err)
|
||||
post, err := blocks.ProcessWithdrawals(st, p)
|
||||
if test.Args.Name == "Parent Node is not full" {
|
||||
require.IsNil(t, post)
|
||||
require.IsNil(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
checkPostState(t, test.Control, post)
|
||||
}
|
||||
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessBLSToExecutionChanges(t *testing.T) {
|
||||
spb := ðpb.BeaconStateCapella{
|
||||
Fork: ðpb.Fork{
|
||||
|
||||
@@ -34,13 +34,13 @@ go_library(
|
||||
"//contracts/deposit:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ProcessPendingConsolidations implements the spec definition below. This method makes mutating
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/contracts/deposit"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ProcessDeposits is one of the operations performed on each processed
|
||||
|
||||
@@ -30,21 +30,21 @@ import (
|
||||
// or validator.effective_balance + UPWARD_THRESHOLD < balance
|
||||
// ):
|
||||
// validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, EFFECTIVE_BALANCE_LIMIT)
|
||||
func ProcessEffectiveBalanceUpdates(state state.BeaconState) error {
|
||||
func ProcessEffectiveBalanceUpdates(st state.BeaconState) error {
|
||||
effBalanceInc := params.BeaconConfig().EffectiveBalanceIncrement
|
||||
hysteresisInc := effBalanceInc / params.BeaconConfig().HysteresisQuotient
|
||||
downwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisDownwardMultiplier
|
||||
upwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisUpwardMultiplier
|
||||
|
||||
bals := state.Balances()
|
||||
bals := st.Balances()
|
||||
|
||||
// Update effective balances with hysteresis.
|
||||
validatorFunc := func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) {
|
||||
validatorFunc := func(idx int, val state.ReadOnlyValidator) (newVal *ethpb.Validator, err error) {
|
||||
if val == nil {
|
||||
return false, nil, fmt.Errorf("validator %d is nil in state", idx)
|
||||
return nil, fmt.Errorf("validator %d is nil in state", idx)
|
||||
}
|
||||
if idx >= len(bals) {
|
||||
return false, nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(state.Balances()))
|
||||
return nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(st.Balances()))
|
||||
}
|
||||
balance := bals[idx]
|
||||
|
||||
@@ -53,13 +53,13 @@ func ProcessEffectiveBalanceUpdates(state state.BeaconState) error {
|
||||
effectiveBalanceLimit = params.BeaconConfig().MaxEffectiveBalanceElectra
|
||||
}
|
||||
|
||||
if balance+downwardThreshold < val.EffectiveBalance || val.EffectiveBalance+upwardThreshold < balance {
|
||||
if balance+downwardThreshold < val.EffectiveBalance() || val.EffectiveBalance()+upwardThreshold < balance {
|
||||
effectiveBal := min(balance-balance%effBalanceInc, effectiveBalanceLimit)
|
||||
val.EffectiveBalance = effectiveBal
|
||||
return true, val, nil
|
||||
newVal = val.Copy()
|
||||
newVal.EffectiveBalance = effectiveBal
|
||||
}
|
||||
return false, val, nil
|
||||
return newVal, nil
|
||||
}
|
||||
|
||||
return state.ApplyToEveryValidator(validatorFunc)
|
||||
return st.ApplyToEveryValidator(validatorFunc)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,29 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestProcessEffectiveBalanceUpdates_SafeCopy(t *testing.T) {
|
||||
pb := ð.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
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"go.opencensus.io/trace"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
)
|
||||
|
||||
// Re-exports for methods that haven't changed in Electra.
|
||||
|
||||
@@ -78,23 +78,19 @@ func ProcessOperations(
|
||||
return nil, errors.Wrap(err, "could not process bls-to-execution changes")
|
||||
}
|
||||
// new in electra
|
||||
e, err := bb.Execution()
|
||||
requests, err := bb.ExecutionRequests()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution data from block")
|
||||
return nil, errors.Wrap(err, "could not get execution requests")
|
||||
}
|
||||
exe, ok := e.(interfaces.ExecutionDataElectra)
|
||||
if !ok {
|
||||
return nil, errors.New("could not cast execution data to electra execution data")
|
||||
}
|
||||
st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests())
|
||||
st, err = ProcessDepositRequests(ctx, st, requests.Deposits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process deposit receipts")
|
||||
}
|
||||
st, err = ProcessWithdrawalRequests(ctx, st, exe.WithdrawalRequests())
|
||||
st, err = ProcessWithdrawalRequests(ctx, st, requests.Withdrawals)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution layer withdrawal requests")
|
||||
}
|
||||
if err := ProcessConsolidationRequests(ctx, st, exe.ConsolidationRequests()); err != nil {
|
||||
if err := ProcessConsolidationRequests(ctx, st, requests.Consolidations); err != nil {
|
||||
return nil, fmt.Errorf("could not process consolidation requests: %w", err)
|
||||
}
|
||||
return st, nil
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
@@ -245,26 +244,23 @@ func UpgradeToElectra(beaconState state.BeaconState) (state.BeaconState, error)
|
||||
CurrentSyncCommittee: currentSyncCommittee,
|
||||
NextSyncCommittee: nextSyncCommittee,
|
||||
LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: payloadHeader.ParentHash(),
|
||||
FeeRecipient: payloadHeader.FeeRecipient(),
|
||||
StateRoot: payloadHeader.StateRoot(),
|
||||
ReceiptsRoot: payloadHeader.ReceiptsRoot(),
|
||||
LogsBloom: payloadHeader.LogsBloom(),
|
||||
PrevRandao: payloadHeader.PrevRandao(),
|
||||
BlockNumber: payloadHeader.BlockNumber(),
|
||||
GasLimit: payloadHeader.GasLimit(),
|
||||
GasUsed: payloadHeader.GasUsed(),
|
||||
Timestamp: payloadHeader.Timestamp(),
|
||||
ExtraData: payloadHeader.ExtraData(),
|
||||
BaseFeePerGas: payloadHeader.BaseFeePerGas(),
|
||||
BlockHash: payloadHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
DepositRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP6110]
|
||||
WithdrawalRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP7002]
|
||||
ConsolidationRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP7251]
|
||||
ParentHash: payloadHeader.ParentHash(),
|
||||
FeeRecipient: payloadHeader.FeeRecipient(),
|
||||
StateRoot: payloadHeader.StateRoot(),
|
||||
ReceiptsRoot: payloadHeader.ReceiptsRoot(),
|
||||
LogsBloom: payloadHeader.LogsBloom(),
|
||||
PrevRandao: payloadHeader.PrevRandao(),
|
||||
BlockNumber: payloadHeader.BlockNumber(),
|
||||
GasLimit: payloadHeader.GasLimit(),
|
||||
GasUsed: payloadHeader.GasUsed(),
|
||||
Timestamp: payloadHeader.Timestamp(),
|
||||
ExtraData: payloadHeader.ExtraData(),
|
||||
BaseFeePerGas: payloadHeader.BaseFeePerGas(),
|
||||
BlockHash: payloadHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
},
|
||||
NextWithdrawalIndex: wi,
|
||||
NextWithdrawalValidatorIndex: vi,
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
@@ -113,24 +112,21 @@ func TestUpgradeToElectra(t *testing.T) {
|
||||
wdRoot, err := prevHeader.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
wanted := &enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: prevHeader.ParentHash(),
|
||||
FeeRecipient: prevHeader.FeeRecipient(),
|
||||
StateRoot: prevHeader.StateRoot(),
|
||||
ReceiptsRoot: prevHeader.ReceiptsRoot(),
|
||||
LogsBloom: prevHeader.LogsBloom(),
|
||||
PrevRandao: prevHeader.PrevRandao(),
|
||||
BlockNumber: prevHeader.BlockNumber(),
|
||||
GasLimit: prevHeader.GasLimit(),
|
||||
GasUsed: prevHeader.GasUsed(),
|
||||
Timestamp: prevHeader.Timestamp(),
|
||||
ExtraData: prevHeader.ExtraData(),
|
||||
BaseFeePerGas: prevHeader.BaseFeePerGas(),
|
||||
BlockHash: prevHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
DepositRequestsRoot: bytesutil.Bytes32(0),
|
||||
WithdrawalRequestsRoot: bytesutil.Bytes32(0),
|
||||
ConsolidationRequestsRoot: bytesutil.Bytes32(0),
|
||||
ParentHash: prevHeader.ParentHash(),
|
||||
FeeRecipient: prevHeader.FeeRecipient(),
|
||||
StateRoot: prevHeader.StateRoot(),
|
||||
ReceiptsRoot: prevHeader.ReceiptsRoot(),
|
||||
LogsBloom: prevHeader.LogsBloom(),
|
||||
PrevRandao: prevHeader.PrevRandao(),
|
||||
BlockNumber: prevHeader.BlockNumber(),
|
||||
GasLimit: prevHeader.GasLimit(),
|
||||
GasUsed: prevHeader.GasUsed(),
|
||||
Timestamp: prevHeader.Timestamp(),
|
||||
ExtraData: prevHeader.ExtraData(),
|
||||
BaseFeePerGas: prevHeader.BaseFeePerGas(),
|
||||
BlockHash: prevHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
}
|
||||
require.DeepEqual(t, wanted, protoHeader)
|
||||
|
||||
|
||||
@@ -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"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ProcessWithdrawalRequests processes the validator withdrawals from the provided execution payload
|
||||
|
||||
38
beacon-chain/core/epbs/BUILD.bazel
Normal file
38
beacon-chain/core/epbs/BUILD.bazel
Normal file
@@ -0,0 +1,38 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"execution_payload_envelope.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/electra:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"attestation_test.go",
|
||||
"execution_payload_envelope_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//testing/util/random:go_default_library",
|
||||
],
|
||||
)
|
||||
13
beacon-chain/core/epbs/attestation.go
Normal file
13
beacon-chain/core/epbs/attestation.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package epbs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RemoveValidatorFlag removes validator flag from existing one.
|
||||
func RemoveValidatorFlag(flag, flagPosition uint8) (uint8, error) {
|
||||
if flagPosition > 7 {
|
||||
return flag, fmt.Errorf("flag position %d exceeds length", flagPosition)
|
||||
}
|
||||
return flag & ^(1 << flagPosition), nil
|
||||
}
|
||||
93
beacon-chain/core/epbs/attestation_test.go
Normal file
93
beacon-chain/core/epbs/attestation_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package epbs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestValidatorFlag_Remove(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
add []uint8
|
||||
remove []uint8
|
||||
expectedTrue []uint8
|
||||
expectedFalse []uint8
|
||||
}{
|
||||
{
|
||||
name: "none",
|
||||
add: []uint8{},
|
||||
remove: []uint8{},
|
||||
expectedTrue: []uint8{},
|
||||
expectedFalse: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
|
||||
},
|
||||
{
|
||||
name: "source",
|
||||
add: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
|
||||
remove: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
|
||||
expectedTrue: []uint8{},
|
||||
expectedFalse: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
|
||||
},
|
||||
{
|
||||
name: "source, target",
|
||||
add: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex},
|
||||
remove: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
|
||||
expectedTrue: []uint8{params.BeaconConfig().TimelyTargetFlagIndex},
|
||||
expectedFalse: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
|
||||
},
|
||||
{
|
||||
name: "source, target, head",
|
||||
add: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
|
||||
remove: []uint8{params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
|
||||
expectedTrue: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
|
||||
expectedFalse: []uint8{params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
flag := uint8(0)
|
||||
|
||||
// Add flags.
|
||||
for _, flagPosition := range test.add {
|
||||
flag, err = altair.AddValidatorFlag(flag, flagPosition)
|
||||
require.NoError(t, err)
|
||||
|
||||
has, err := altair.HasValidatorFlag(flag, flagPosition)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, has)
|
||||
}
|
||||
|
||||
// Remove flags.
|
||||
for _, flagPosition := range test.remove {
|
||||
flag, err = epbs.RemoveValidatorFlag(flag, flagPosition)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Check if flags are set correctly.
|
||||
for _, flagPosition := range test.expectedTrue {
|
||||
has, err := altair.HasValidatorFlag(flag, flagPosition)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, has)
|
||||
}
|
||||
for _, flagPosition := range test.expectedFalse {
|
||||
has, err := altair.HasValidatorFlag(flag, flagPosition)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, has)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatorFlag_Remove_ExceedsLength(t *testing.T) {
|
||||
_, err := epbs.RemoveValidatorFlag(0, 8)
|
||||
require.ErrorContains(t, "flag position 8 exceeds length", err)
|
||||
}
|
||||
|
||||
func TestValidatorFlag_Remove_NotSet(t *testing.T) {
|
||||
_, err := epbs.RemoveValidatorFlag(0, 1)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
126
beacon-chain/core/epbs/execution_payload_envelope.go
Normal file
126
beacon-chain/core/epbs/execution_payload_envelope.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package epbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
)
|
||||
|
||||
// ValidatePayloadStateTransition performs the process_execution_payload
|
||||
// function.
|
||||
func ValidatePayloadStateTransition(
|
||||
ctx context.Context,
|
||||
preState state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
if err := validateAgainstHeader(ctx, preState, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
committedHeader, err := preState.LatestExecutionPayloadHeaderEPBS()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateAgainstCommittedBid(committedHeader, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ProcessPayloadStateTransition(ctx, preState, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
return checkPostStateRoot(ctx, preState, envelope)
|
||||
}
|
||||
|
||||
func ProcessPayloadStateTransition(
|
||||
ctx context.Context,
|
||||
preState state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
er := envelope.ExecutionRequests()
|
||||
preState, err := electra.ProcessDepositRequests(ctx, preState, er.Deposits)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process deposit receipts")
|
||||
}
|
||||
preState, err = electra.ProcessWithdrawalRequests(ctx, preState, er.Withdrawals)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process ercution layer withdrawal requests")
|
||||
}
|
||||
if err := electra.ProcessConsolidationRequests(ctx, preState, er.Consolidations); err != nil {
|
||||
return errors.Wrap(err, "could not process consolidation requests")
|
||||
}
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload")
|
||||
}
|
||||
if err := preState.SetLatestBlockHash(payload.BlockHash()); err != nil {
|
||||
return err
|
||||
}
|
||||
return preState.SetLatestFullSlot(preState.Slot())
|
||||
}
|
||||
|
||||
func validateAgainstHeader(
|
||||
ctx context.Context,
|
||||
preState state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
blockHeader := preState.LatestBlockHeader()
|
||||
if blockHeader == nil {
|
||||
return errors.New("invalid nil latest block header")
|
||||
}
|
||||
if len(blockHeader.StateRoot) == 0 || [32]byte(blockHeader.StateRoot) == [32]byte{} {
|
||||
prevStateRoot, err := preState.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute previous state root")
|
||||
}
|
||||
blockHeader.StateRoot = prevStateRoot[:]
|
||||
if err := preState.SetLatestBlockHeader(blockHeader); err != nil {
|
||||
return errors.Wrap(err, "could not set latest block header")
|
||||
}
|
||||
}
|
||||
blockHeaderRoot, err := blockHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
beaconBlockRoot := envelope.BeaconBlockRoot()
|
||||
if blockHeaderRoot != beaconBlockRoot {
|
||||
return fmt.Errorf("beacon block root does not match previous header, got: %#x wanted: %#x", beaconBlockRoot, blockHeaderRoot)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAgainstCommittedBid(
|
||||
committedHeader *enginev1.ExecutionPayloadHeaderEPBS,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
builderIndex := envelope.BuilderIndex()
|
||||
if committedHeader.BuilderIndex != builderIndex {
|
||||
return errors.New("builder index does not match committed header")
|
||||
}
|
||||
kzgRoot, err := envelope.BlobKzgCommitmentsRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if [32]byte(committedHeader.BlobKzgCommitmentsRoot) != kzgRoot {
|
||||
return errors.New("blob KZG commitments root does not match committed header")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPostStateRoot(
|
||||
ctx context.Context,
|
||||
preState state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
stateRoot, err := preState.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envelopeStateRoot := envelope.StateRoot()
|
||||
if stateRoot != envelopeStateRoot {
|
||||
return errors.New("state root mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
108
beacon-chain/core/epbs/execution_payload_envelope_test.go
Normal file
108
beacon-chain/core/epbs/execution_payload_envelope_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package epbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
|
||||
)
|
||||
|
||||
func TestProcessPayloadStateTransition(t *testing.T) {
|
||||
bh := [32]byte{'h'}
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.Payload.BlockHash = bh[:]
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
validators := make([]*ethpb.Validator, 0)
|
||||
stpb := ðpb.BeaconStateEPBS{Slot: 3, Validators: validators}
|
||||
st, err := state_native.InitializeFromProtoUnsafeEpbs(stpb)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
|
||||
lbh, err := st.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{}, [32]byte(lbh))
|
||||
|
||||
require.NoError(t, ProcessPayloadStateTransition(ctx, st, e))
|
||||
|
||||
lbh, err = st.LatestBlockHash()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, bh, [32]byte(lbh))
|
||||
|
||||
lfs, err := st.LatestFullSlot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lfs, st.Slot())
|
||||
}
|
||||
|
||||
func Test_validateAgainstHeader(t *testing.T) {
|
||||
bh := [32]byte{'h'}
|
||||
payload := &enginev1.ExecutionPayloadElectra{BlockHash: bh[:]}
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.Payload = payload
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
stpb := ðpb.BeaconStateEPBS{Slot: 3}
|
||||
st, err := state_native.InitializeFromProtoUnsafeEpbs(stpb)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
require.ErrorContains(t, "invalid nil latest block header", validateAgainstHeader(ctx, st, e))
|
||||
|
||||
prest, _ := util.DeterministicGenesisStateEpbs(t, 64)
|
||||
br := [32]byte{'r'}
|
||||
p.BeaconBlockRoot = br[:]
|
||||
require.ErrorContains(t, "beacon block root does not match previous header", validateAgainstHeader(ctx, prest, e))
|
||||
|
||||
header := prest.LatestBlockHeader()
|
||||
require.NoError(t, err)
|
||||
headerRoot, err := header.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
p.BeaconBlockRoot = headerRoot[:]
|
||||
require.NoError(t, validateAgainstHeader(ctx, prest, e))
|
||||
}
|
||||
|
||||
func Test_validateAgainstCommittedBid(t *testing.T) {
|
||||
payload := &enginev1.ExecutionPayloadElectra{}
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.Payload = payload
|
||||
p.BuilderIndex = 1
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
h := &enginev1.ExecutionPayloadHeaderEPBS{}
|
||||
require.ErrorContains(t, "builder index does not match committed header", validateAgainstCommittedBid(h, e))
|
||||
|
||||
h.BuilderIndex = 1
|
||||
p.BlobKzgCommitments = make([][]byte, 6)
|
||||
for i := range p.BlobKzgCommitments {
|
||||
p.BlobKzgCommitments[i] = make([]byte, 48)
|
||||
}
|
||||
h.BlobKzgCommitmentsRoot = make([]byte, 32)
|
||||
require.ErrorContains(t, "blob KZG commitments root does not match committed header", validateAgainstCommittedBid(h, e))
|
||||
|
||||
root, err := e.BlobKzgCommitmentsRoot()
|
||||
require.NoError(t, err)
|
||||
h.BlobKzgCommitmentsRoot = root[:]
|
||||
require.NoError(t, validateAgainstCommittedBid(h, e))
|
||||
}
|
||||
|
||||
func TestCheckPostStateRoot(t *testing.T) {
|
||||
payload := &enginev1.ExecutionPayloadElectra{}
|
||||
p := random.ExecutionPayloadEnvelope(t)
|
||||
p.Payload = payload
|
||||
p.BuilderIndex = 1
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
st, _ := util.DeterministicGenesisStateEpbs(t, 64)
|
||||
p.StateRoot = make([]byte, 32)
|
||||
require.ErrorContains(t, "state root mismatch", checkPostStateRoot(ctx, st, e))
|
||||
root, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
p.StateRoot = root[:]
|
||||
require.NoError(t, checkPostStateRoot(ctx, st, e))
|
||||
}
|
||||
@@ -153,9 +153,9 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) (state.Be
|
||||
// penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
|
||||
// penalty = penalty_numerator // total_balance * increment
|
||||
// decrease_balance(state, ValidatorIndex(index), penalty)
|
||||
func ProcessSlashings(state state.BeaconState, slashingMultiplier uint64) (state.BeaconState, error) {
|
||||
currentEpoch := time.CurrentEpoch(state)
|
||||
totalBalance, err := helpers.TotalActiveBalance(state)
|
||||
func ProcessSlashings(st state.BeaconState, slashingMultiplier uint64) (state.BeaconState, error) {
|
||||
currentEpoch := time.CurrentEpoch(st)
|
||||
totalBalance, err := helpers.TotalActiveBalance(st)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get total active balance")
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func ProcessSlashings(state state.BeaconState, slashingMultiplier uint64) (state
|
||||
exitLength := params.BeaconConfig().EpochsPerSlashingsVector
|
||||
|
||||
// Compute the sum of state slashings
|
||||
slashings := state.Slashings()
|
||||
slashings := st.Slashings()
|
||||
totalSlashing := uint64(0)
|
||||
for _, slashing := range slashings {
|
||||
totalSlashing, err = math.Add64(totalSlashing, slashing)
|
||||
@@ -177,19 +177,27 @@ func ProcessSlashings(state state.BeaconState, slashingMultiplier uint64) (state
|
||||
// below equally.
|
||||
increment := params.BeaconConfig().EffectiveBalanceIncrement
|
||||
minSlashing := math.Min(totalSlashing*slashingMultiplier, totalBalance)
|
||||
err = state.ApplyToEveryValidator(func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) {
|
||||
correctEpoch := (currentEpoch + exitLength/2) == val.WithdrawableEpoch
|
||||
if val.Slashed && correctEpoch {
|
||||
penaltyNumerator := val.EffectiveBalance / increment * minSlashing
|
||||
bals := st.Balances()
|
||||
changed := false
|
||||
err = st.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error {
|
||||
correctEpoch := (currentEpoch + exitLength/2) == val.WithdrawableEpoch()
|
||||
if val.Slashed() && correctEpoch {
|
||||
penaltyNumerator := val.EffectiveBalance() / increment * minSlashing
|
||||
penalty := penaltyNumerator / totalBalance * increment
|
||||
if err := helpers.DecreaseBalance(state, primitives.ValidatorIndex(idx), penalty); err != nil {
|
||||
return false, val, err
|
||||
}
|
||||
return true, val, nil
|
||||
bals[idx] = helpers.DecreaseBalanceWithVal(bals[idx], penalty)
|
||||
changed = true
|
||||
}
|
||||
return false, val, nil
|
||||
return nil
|
||||
})
|
||||
return state, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if changed {
|
||||
if err := st.SetBalances(bals); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// ProcessEth1DataReset processes updates to ETH1 data votes during epoch processing.
|
||||
@@ -231,45 +239,43 @@ func ProcessEth1DataReset(state state.BeaconState) (state.BeaconState, error) {
|
||||
// or validator.effective_balance + UPWARD_THRESHOLD < balance
|
||||
// ):
|
||||
// validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
func ProcessEffectiveBalanceUpdates(state state.BeaconState) (state.BeaconState, error) {
|
||||
func ProcessEffectiveBalanceUpdates(st state.BeaconState) (state.BeaconState, error) {
|
||||
effBalanceInc := params.BeaconConfig().EffectiveBalanceIncrement
|
||||
maxEffBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
hysteresisInc := effBalanceInc / params.BeaconConfig().HysteresisQuotient
|
||||
downwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisDownwardMultiplier
|
||||
upwardThreshold := hysteresisInc * params.BeaconConfig().HysteresisUpwardMultiplier
|
||||
|
||||
bals := state.Balances()
|
||||
bals := st.Balances()
|
||||
|
||||
// Update effective balances with hysteresis.
|
||||
validatorFunc := func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) {
|
||||
validatorFunc := func(idx int, val state.ReadOnlyValidator) (newVal *ethpb.Validator, err error) {
|
||||
if val == nil {
|
||||
return false, nil, fmt.Errorf("validator %d is nil in state", idx)
|
||||
return nil, fmt.Errorf("validator %d is nil in state", idx)
|
||||
}
|
||||
if idx >= len(bals) {
|
||||
return false, nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(state.Balances()))
|
||||
return nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(st.Balances()))
|
||||
}
|
||||
balance := bals[idx]
|
||||
|
||||
if balance+downwardThreshold < val.EffectiveBalance || val.EffectiveBalance+upwardThreshold < balance {
|
||||
if balance+downwardThreshold < val.EffectiveBalance() || val.EffectiveBalance()+upwardThreshold < balance {
|
||||
effectiveBal := maxEffBalance
|
||||
if effectiveBal > balance-balance%effBalanceInc {
|
||||
effectiveBal = balance - balance%effBalanceInc
|
||||
}
|
||||
if effectiveBal != val.EffectiveBalance {
|
||||
newVal := ethpb.CopyValidator(val)
|
||||
if effectiveBal != val.EffectiveBalance() {
|
||||
newVal = val.Copy()
|
||||
newVal.EffectiveBalance = effectiveBal
|
||||
return true, newVal, nil
|
||||
}
|
||||
return false, val, nil
|
||||
}
|
||||
return false, val, nil
|
||||
return
|
||||
}
|
||||
|
||||
if err := state.ApplyToEveryValidator(validatorFunc); err != nil {
|
||||
if err := st.ApplyToEveryValidator(validatorFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return state, nil
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// ProcessSlashingsReset processes the total slashing balances updates during epoch processing.
|
||||
|
||||
@@ -31,7 +31,6 @@ go_library(
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"go.opencensus.io/trace"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
)
|
||||
|
||||
// New gets called at the beginning of process epoch cycle to return
|
||||
|
||||
@@ -5,9 +5,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/math"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// ProcessSlashingsPrecompute processes the slashed validators during epoch processing.
|
||||
@@ -44,18 +42,18 @@ func ProcessSlashingsPrecompute(s state.BeaconState, pBal *Balance) error {
|
||||
}
|
||||
|
||||
increment := params.BeaconConfig().EffectiveBalanceIncrement
|
||||
validatorFunc := func(idx int, val *ethpb.Validator) (bool, *ethpb.Validator, error) {
|
||||
correctEpoch := epochToWithdraw == val.WithdrawableEpoch
|
||||
if val.Slashed && correctEpoch {
|
||||
penaltyNumerator := val.EffectiveBalance / increment * minSlashing
|
||||
bals := s.Balances()
|
||||
validatorFunc := func(idx int, val state.ReadOnlyValidator) error {
|
||||
correctEpoch := epochToWithdraw == val.WithdrawableEpoch()
|
||||
if val.Slashed() && correctEpoch {
|
||||
penaltyNumerator := val.EffectiveBalance() / increment * minSlashing
|
||||
penalty := penaltyNumerator / pBal.ActiveCurrentEpoch * increment
|
||||
if err := helpers.DecreaseBalance(s, primitives.ValidatorIndex(idx), penalty); err != nil {
|
||||
return false, val, err
|
||||
}
|
||||
return true, val, nil
|
||||
bals[idx] = helpers.DecreaseBalanceWithVal(bals[idx], penalty)
|
||||
}
|
||||
return false, val, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.ApplyToEveryValidator(validatorFunc)
|
||||
if err := s.ReadFromEveryValidator(validatorFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.SetBalances(bals)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ go_library(
|
||||
"block.go",
|
||||
"genesis.go",
|
||||
"metrics.go",
|
||||
"payload_attestation.go",
|
||||
"randao.go",
|
||||
"rewards_penalties.go",
|
||||
"shuffle.go",
|
||||
@@ -20,11 +21,13 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/forkchoice/types:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/epbs:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
@@ -53,6 +56,8 @@ go_test(
|
||||
"attestation_test.go",
|
||||
"beacon_committee_test.go",
|
||||
"block_test.go",
|
||||
"exports_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"private_access_fuzz_noop_test.go", # keep
|
||||
"private_access_test.go",
|
||||
"randao_test.go",
|
||||
@@ -63,26 +68,33 @@ go_test(
|
||||
"validators_test.go",
|
||||
"weak_subjectivity_test.go",
|
||||
],
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
shard_count = 2,
|
||||
tags = ["CI_race_detection"],
|
||||
deps = [
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/forkchoice/types:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/epbs:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//testing/util/random:go_default_library",
|
||||
"//time:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
|
||||
@@ -213,6 +213,7 @@ type CommitteeAssignment struct {
|
||||
Committee []primitives.ValidatorIndex
|
||||
AttesterSlot primitives.Slot
|
||||
CommitteeIndex primitives.CommitteeIndex
|
||||
PtcSlot primitives.Slot
|
||||
}
|
||||
|
||||
// verifyAssignmentEpoch verifies if the given epoch is valid for assignment based on the provided state.
|
||||
@@ -294,7 +295,7 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
|
||||
if err := verifyAssignmentEpoch(epoch, state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startSlot, err := slots.EpochStart(epoch)
|
||||
slot, err := slots.EpochStart(epoch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -303,14 +304,17 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
|
||||
vals[v] = struct{}{}
|
||||
}
|
||||
assignments := make(map[primitives.ValidatorIndex]*CommitteeAssignment)
|
||||
|
||||
committees, err := BeaconCommittees(ctx, state, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute beacon committees")
|
||||
}
|
||||
ptcPerSlot, ptcMembersPerCommittee := PtcAllocation(len(committees))
|
||||
// Compute committee assignments for each slot in the epoch.
|
||||
for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ {
|
||||
committees, err := BeaconCommittees(ctx, state, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute beacon committees")
|
||||
}
|
||||
endSlot := slot + params.BeaconConfig().SlotsPerEpoch
|
||||
for {
|
||||
for j, committee := range committees {
|
||||
for _, vIndex := range committee {
|
||||
for i, vIndex := range committee {
|
||||
if _, ok := vals[vIndex]; !ok { // Skip if the validator is not in the provided validators slice.
|
||||
continue
|
||||
}
|
||||
@@ -320,8 +324,19 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
|
||||
assignments[vIndex].Committee = committee
|
||||
assignments[vIndex].AttesterSlot = slot
|
||||
assignments[vIndex].CommitteeIndex = primitives.CommitteeIndex(j)
|
||||
if uint64(j) < ptcPerSlot && uint64(i) < ptcMembersPerCommittee {
|
||||
assignments[vIndex].PtcSlot = slot
|
||||
}
|
||||
}
|
||||
}
|
||||
slot++
|
||||
if slot == endSlot {
|
||||
break
|
||||
}
|
||||
committees, err = BeaconCommittees(ctx, state, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute beacon committees")
|
||||
}
|
||||
}
|
||||
return assignments, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package helpers_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
field_params "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/container/slice"
|
||||
@@ -722,15 +724,26 @@ func TestCommitteeIndices(t *testing.T) {
|
||||
assert.DeepEqual(t, []primitives.CommitteeIndex{0, 1, 3}, indices)
|
||||
}
|
||||
|
||||
func TestAttestationCommittees(t *testing.T) {
|
||||
validators := make([]*ethpb.Validator, params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().TargetCommitteeSize))
|
||||
func TestCommitteeAssignments_PTC(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
// Create 10 committees. Total 40960 validators.
|
||||
committeeCount := uint64(10)
|
||||
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
|
||||
validators := make([]*ethpb.Validator, validatorCount)
|
||||
validatorIndices := make([]primitives.ValidatorIndex, validatorCount)
|
||||
|
||||
for i := 0; i < len(validators); i++ {
|
||||
k := make([]byte, 48)
|
||||
copy(k, strconv.Itoa(i))
|
||||
validators[i] = ðpb.Validator{
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
PublicKey: k,
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
validatorIndices[i] = primitives.ValidatorIndex(i)
|
||||
}
|
||||
|
||||
state, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{
|
||||
state, err := state_native.InitializeFromProtoEpbs(ðpb.BeaconStateEPBS{
|
||||
Validators: validators,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
})
|
||||
@@ -754,6 +767,31 @@ func TestAttestationCommittees(t *testing.T) {
|
||||
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[0])))
|
||||
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[1])))
|
||||
})
|
||||
as, err := helpers.CommitteeAssignments(context.Background(), state, 1, validatorIndices)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Capture all the slots and all the validator index that belonged in a PTC using a map for verification later.
|
||||
slotValidatorMap := make(map[primitives.Slot][]primitives.ValidatorIndex)
|
||||
for i, a := range as {
|
||||
slotValidatorMap[a.PtcSlot] = append(slotValidatorMap[a.PtcSlot], i)
|
||||
}
|
||||
|
||||
// Verify that all the slots have the correct number of PTC.
|
||||
for s, v := range slotValidatorMap {
|
||||
if s == 0 {
|
||||
continue
|
||||
}
|
||||
// Make sure all the PTC are the correct size from the map.
|
||||
require.Equal(t, len(v), field_params.PTCSize)
|
||||
|
||||
// Get the actual PTC from the beacon state using the helper function
|
||||
ptc, err := helpers.GetPayloadTimelinessCommittee(context.Background(), state, s)
|
||||
require.NoError(t, err)
|
||||
for _, index := range ptc {
|
||||
i := slices.Index(v, index)
|
||||
require.NotEqual(t, -1, i) // PTC not found from the assignment map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeaconCommittees(t *testing.T) {
|
||||
|
||||
12
beacon-chain/core/helpers/exports_test.go
Normal file
12
beacon-chain/core/helpers/exports_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package helpers
|
||||
|
||||
var (
|
||||
ErrNilMessage = errNilMessage
|
||||
ErrNilData = errNilData
|
||||
ErrNilBeaconBlockRoot = errNilBeaconBlockRoot
|
||||
ErrNilPayloadAttestation = errNilPayloadAttestation
|
||||
ErrNilSignature = errNilSignature
|
||||
ErrNilAggregationBits = errNilAggregationBits
|
||||
ErrPreEPBSState = errPreEPBSState
|
||||
ErrCommitteeOverflow = errCommitteeOverflow
|
||||
)
|
||||
296
beacon-chain/core/helpers/payload_attestation.go
Normal file
296
beacon-chain/core/helpers/payload_attestation.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
|
||||
"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/epbs"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/math"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
var (
|
||||
errNilMessage = errors.New("nil PayloadAttestationMessage")
|
||||
errNilData = errors.New("nil PayloadAttestationData")
|
||||
errNilBeaconBlockRoot = errors.New("nil BeaconBlockRoot")
|
||||
errNilPayloadAttestation = errors.New("nil PayloadAttestation")
|
||||
errNilSignature = errors.New("nil Signature")
|
||||
errNilAggregationBits = errors.New("nil AggregationBits")
|
||||
errPreEPBSState = errors.New("beacon state pre ePBS fork")
|
||||
errCommitteeOverflow = errors.New("beacon committee of insufficient size")
|
||||
)
|
||||
|
||||
// ValidateNilPayloadAttestationData checks if any composite field of the
|
||||
// payload attestation data is nil
|
||||
func ValidateNilPayloadAttestationData(data *eth.PayloadAttestationData) error {
|
||||
if data == nil {
|
||||
return errNilData
|
||||
}
|
||||
if data.BeaconBlockRoot == nil {
|
||||
return errNilBeaconBlockRoot
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateNilPayloadAttestationMessage checks if any composite field of the
|
||||
// payload attestation message is nil
|
||||
func ValidateNilPayloadAttestationMessage(att *eth.PayloadAttestationMessage) error {
|
||||
if att == nil {
|
||||
return errNilMessage
|
||||
}
|
||||
if att.Signature == nil {
|
||||
return errNilSignature
|
||||
}
|
||||
return ValidateNilPayloadAttestationData(att.Data)
|
||||
}
|
||||
|
||||
// ValidateNilPayloadAttestation checks if any composite field of the
|
||||
// payload attestation is nil
|
||||
func ValidateNilPayloadAttestation(att *eth.PayloadAttestation) error {
|
||||
if att == nil {
|
||||
return errNilPayloadAttestation
|
||||
}
|
||||
if att.AggregationBits == nil {
|
||||
return errNilAggregationBits
|
||||
}
|
||||
if att.Signature == nil {
|
||||
return errNilSignature
|
||||
}
|
||||
return ValidateNilPayloadAttestationData(att.Data)
|
||||
}
|
||||
|
||||
// InPayloadTimelinessCommittee returns whether the given index belongs to the
|
||||
// PTC computed from the passed state.
|
||||
func InPayloadTimelinessCommittee(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot, idx primitives.ValidatorIndex) (bool, error) {
|
||||
ptc, err := GetPayloadTimelinessCommittee(ctx, state, slot)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, i := range ptc {
|
||||
if i == idx {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetPayloadTimelinessCommittee returns the PTC for the given slot, computed from the passed state as in the
|
||||
// spec function `get_ptc`.
|
||||
func GetPayloadTimelinessCommittee(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot) (indices []primitives.ValidatorIndex, err error) {
|
||||
if state.Version() < version.EPBS {
|
||||
return nil, errPreEPBSState
|
||||
}
|
||||
committees, err := BeaconCommittees(ctx, state, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get beacon committees")
|
||||
}
|
||||
committeesPerSlot, membersPerCommittee := PtcAllocation(len(committees))
|
||||
for i, committee := range committees {
|
||||
if uint64(i) >= committeesPerSlot {
|
||||
return
|
||||
}
|
||||
if uint64(len(committee)) < membersPerCommittee {
|
||||
return nil, errCommitteeOverflow
|
||||
}
|
||||
indices = append(indices, committee[:membersPerCommittee]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PtcAllocation returns:
|
||||
// 1. The number of beacon committees that PTC will borrow from in a slot.
|
||||
// 2. The number of validators that PTC will borrow from in a beacon committee.
|
||||
func PtcAllocation(slotCommittees int) (committeesPerSlot, membersPerCommittee uint64) {
|
||||
committeesPerSlot = math.LargestPowerOfTwo(math.Min(uint64(slotCommittees), fieldparams.PTCSize))
|
||||
membersPerCommittee = fieldparams.PTCSize / committeesPerSlot
|
||||
return
|
||||
}
|
||||
|
||||
// GetPayloadAttestingIndices returns the set of attester indices corresponding to the given PayloadAttestation.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def get_payload_attesting_indices(state: BeaconState, slot: Slot,
|
||||
// payload_attestation: PayloadAttestation) -> Set[ValidatorIndex]:
|
||||
// """
|
||||
// Return the set of attesting indices corresponding to ``payload_attestation``.
|
||||
// """
|
||||
// ptc = get_ptc(state, slot)
|
||||
// return set(index for i, index in enumerate(ptc) if payload_attestation.aggregation_bits[i])
|
||||
func GetPayloadAttestingIndices(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot, att *eth.PayloadAttestation) (indices []primitives.ValidatorIndex, err error) {
|
||||
if state.Version() < version.EPBS {
|
||||
return nil, errPreEPBSState
|
||||
}
|
||||
|
||||
ptc, err := GetPayloadTimelinessCommittee(ctx, state, slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, validatorIndex := range ptc {
|
||||
if att.AggregationBits.BitAt(uint64(i)) {
|
||||
indices = append(indices, validatorIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetIndexedPayloadAttestation replaces a PayloadAttestation's AggregationBits with sorted AttestingIndices and returns an IndexedPayloadAttestation.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def get_indexed_payload_attestation(state: BeaconState, slot: Slot,
|
||||
// payload_attestation: PayloadAttestation) -> IndexedPayloadAttestation:
|
||||
// """
|
||||
// Return the indexed payload attestation corresponding to ``payload_attestation``.
|
||||
// """
|
||||
// attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation)
|
||||
//
|
||||
// return IndexedPayloadAttestation(
|
||||
// attesting_indices=sorted(attesting_indices),
|
||||
// data=payload_attestation.data,
|
||||
// signature=payload_attestation.signature,
|
||||
// )
|
||||
func GetIndexedPayloadAttestation(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot, att *eth.PayloadAttestation) (*epbs.IndexedPayloadAttestation, error) {
|
||||
if state.Version() < version.EPBS {
|
||||
return nil, errPreEPBSState
|
||||
}
|
||||
|
||||
attestingIndices, err := GetPayloadAttestingIndices(ctx, state, slot, att)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slices.Sort(attestingIndices)
|
||||
|
||||
return &epbs.IndexedPayloadAttestation{
|
||||
AttestingIndices: attestingIndices,
|
||||
Data: att.Data,
|
||||
Signature: att.Signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsValidIndexedPayloadAttestation validates the given IndexedPayloadAttestation.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def is_valid_indexed_payload_attestation(
|
||||
// state: BeaconState,
|
||||
// indexed_payload_attestation: IndexedPayloadAttestation) -> bool:
|
||||
// """
|
||||
// Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has
|
||||
// a valid aggregate signature.
|
||||
// """
|
||||
// # Verify the data is valid
|
||||
// if indexed_payload_attestation.data.payload_status >= PAYLOAD_INVALID_STATUS:
|
||||
// return False
|
||||
//
|
||||
// # Verify indices are sorted and unique
|
||||
// indices = indexed_payload_attestation.attesting_indices
|
||||
// if len(indices) == 0 or not indices == sorted(set(indices)):
|
||||
// return False
|
||||
//
|
||||
// # Verify aggregate signature
|
||||
// pubkeys = [state.validators[i].pubkey for i in indices]
|
||||
// domain = get_domain(state, DOMAIN_PTC_ATTESTER, None)
|
||||
// signing_root = compute_signing_root(indexed_payload_attestation.data, domain)
|
||||
// return bls.FastAggregateVerify(pubkeys, signing_root, indexed_payload_attestation.signature)
|
||||
func IsValidIndexedPayloadAttestation(state state.ReadOnlyBeaconState, att *epbs.IndexedPayloadAttestation) (bool, error) {
|
||||
if state.Version() < version.EPBS {
|
||||
return false, errPreEPBSState
|
||||
}
|
||||
|
||||
// Verify the data is valid.
|
||||
if att.Data.PayloadStatus >= primitives.PAYLOAD_INVALID_STATUS {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Verify indices are sorted and unique.
|
||||
indices := att.AttestingIndices
|
||||
slices.Sort(indices)
|
||||
if len(indices) == 0 || !slices.Equal(att.AttestingIndices, indices) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Verify aggregate signature.
|
||||
publicKeys := make([]bls.PublicKey, len(indices))
|
||||
for i, index := range indices {
|
||||
validator, err := state.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
publicKeyBytes := validator.PublicKey()
|
||||
publicKey, err := bls.PublicKeyFromBytes(publicKeyBytes[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
publicKeys[i] = publicKey
|
||||
}
|
||||
|
||||
domain, err := signing.Domain(
|
||||
state.Fork(),
|
||||
slots.ToEpoch(state.Slot()),
|
||||
params.BeaconConfig().DomainPTCAttester,
|
||||
state.GenesisValidatorsRoot(),
|
||||
)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
signingRoot, err := signing.ComputeSigningRoot(att.Data, domain)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
signature, err := bls.SignatureFromBytes(att.Signature)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return signature.FastAggregateVerify(publicKeys, signingRoot), nil
|
||||
}
|
||||
|
||||
// ValidatePayloadAttestationMessageSignature verifies the signature of a
|
||||
// payload attestation message.
|
||||
func ValidatePayloadAttestationMessageSignature(ctx context.Context, st state.ReadOnlyBeaconState, msg *eth.PayloadAttestationMessage) error {
|
||||
if err := ValidateNilPayloadAttestationMessage(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
val, err := st.ValidatorAtIndex(msg.ValidatorIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pub, err := bls.PublicKeyFromBytes(val.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig, err := bls.SignatureFromBytes(msg.Signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentEpoch := slots.ToEpoch(st.Slot())
|
||||
domain, err := signing.Domain(st.Fork(), currentEpoch, params.BeaconConfig().DomainPTCAttester, st.GenesisValidatorsRoot())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root, err := signing.ComputeSigningRoot(msg.Data, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sig.Verify(pub, root[:]) {
|
||||
return signing.ErrSigFailedToVerify
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user