mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Compare commits
59 Commits
v4.0.4-rc.
...
scenario_l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a221d2cbe5 | ||
|
|
152d21059e | ||
|
|
2b410893a0 | ||
|
|
826267310e | ||
|
|
d5057cfb42 | ||
|
|
8d01cf2ec1 | ||
|
|
e4e315da94 | ||
|
|
0a4e42545e | ||
|
|
6fa2d768b5 | ||
|
|
0f228896b0 | ||
|
|
6896b41963 | ||
|
|
3bf6abe27c | ||
|
|
c1391f0de3 | ||
|
|
6672d1499a | ||
|
|
33cf52831c | ||
|
|
d543e9be00 | ||
|
|
0669050ffa | ||
|
|
ceff0c2024 | ||
|
|
c32b581e8e | ||
|
|
e516a2004f | ||
|
|
cb65d8af96 | ||
|
|
70152bf476 | ||
|
|
8aa688729d | ||
|
|
1ffc92999f | ||
|
|
2dcef85f97 | ||
|
|
52da7b3de6 | ||
|
|
be16b64535 | ||
|
|
f4d3939b62 | ||
|
|
245d8a29e0 | ||
|
|
666188dfea | ||
|
|
cfa64ae013 | ||
|
|
cd0f814f2e | ||
|
|
abc81e6dde | ||
|
|
6b26183e73 | ||
|
|
7fe935e94d | ||
|
|
e0e7c71eb5 | ||
|
|
c80019bd0b | ||
|
|
8dfb92c605 | ||
|
|
9d192a3608 | ||
|
|
51bde7a845 | ||
|
|
385a317902 | ||
|
|
b84dd40ba9 | ||
|
|
aeaa72fdc2 | ||
|
|
ddc1e48e05 | ||
|
|
f91159337b | ||
|
|
537236e1c9 | ||
|
|
73e4bdccbb | ||
|
|
f54bd64bdd | ||
|
|
d907cae595 | ||
|
|
be23773924 | ||
|
|
29f6de1e96 | ||
|
|
955a21fea4 | ||
|
|
b4f1fea029 | ||
|
|
f1b88d005d | ||
|
|
ee612d958a | ||
|
|
09e22538f9 | ||
|
|
3b9e974a45 | ||
|
|
ad749a40b6 | ||
|
|
9b13454457 |
@@ -4,14 +4,14 @@
|
||||
[](https://goreportcard.com/report/github.com/prysmaticlabs/prysm)
|
||||
[](https://github.com/ethereum/consensus-specs/tree/v1.3.0)
|
||||
[](https://github.com/ethereum/execution-apis/tree/v1.0.0-beta.2/src/engine)
|
||||
[](https://discord.gg/CTYGPUJ)
|
||||
[](https://discord.gg/prysmaticlabs)
|
||||
[](https://www.gitpoap.io/gh/prysmaticlabs/prysm)
|
||||
|
||||
This is the core repository for Prysm, a [Golang](https://golang.org/) implementation of the [Ethereum Consensus](https://ethereum.org/en/eth2/) specification, developed by [Prysmatic Labs](https://prysmaticlabs.com). See the [Changelog](https://github.com/prysmaticlabs/prysm/releases) for details of the latest releases and upcoming breaking changes.
|
||||
|
||||
### Getting Started
|
||||
|
||||
A detailed set of installation and usage instructions as well as breakdowns of each individual component are available in the [official documentation portal](https://docs.prylabs.network). If you still have questions, feel free to stop by our [Discord](https://discord.gg/CTYGPUJ).
|
||||
A detailed set of installation and usage instructions as well as breakdowns of each individual component are available in the [official documentation portal](https://docs.prylabs.network). If you still have questions, feel free to stop by our [Discord](https://discord.gg/prysmaticlabs).
|
||||
|
||||
### Staking on Mainnet
|
||||
|
||||
|
||||
10
WORKSPACE
10
WORKSPACE
@@ -205,7 +205,7 @@ filegroup(
|
||||
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
|
||||
)
|
||||
|
||||
consensus_spec_version = "v1.3.0"
|
||||
consensus_spec_version = "v1.4.0-alpha.1"
|
||||
|
||||
bls_test_version = "v0.1.1"
|
||||
|
||||
@@ -221,7 +221,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
sha256 = "1c806e04ac5e3779032c06a6009350b3836b6809bb23812993d6ececd7047cf5",
|
||||
sha256 = "1118a663be4a00ba00f0635eb20287157f2b2f993aed64335bfbcd04af424c2b",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -237,7 +237,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
sha256 = "2b42796dc5ccd9f1246032d0c17663e20f70334ff7e00325f0fc3af28cb24186",
|
||||
sha256 = "acde6e10940d14f22277eda5b55b65a24623ac88e4c7a2e34134a6069f5eea82",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -253,7 +253,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
sha256 = "231e3371e81ce9acde65d2910ec4580587e74dbbcfcbd9c675e473e022deec8a",
|
||||
sha256 = "49c022f3a3478cea849ba8f877a9f7e4c1ded549edddc09993550bbc5bb192e1",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -268,7 +268,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
sha256 = "219b74d95664ea7e8dfbf31162dfa206b9c0cf45919ea86db5fa0f8902977e3c",
|
||||
sha256 = "c3e246ff01f6b7b9e9e41939954a6ff89dfca7297415f88781809165fa83267c",
|
||||
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
|
||||
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
20
api/client/BUILD.bazel
Normal file
20
api/client/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"client.go",
|
||||
"errors.go",
|
||||
"options.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/api/client",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@com_github_pkg_errors//:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["client_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//testing/require:go_default_library"],
|
||||
)
|
||||
@@ -6,11 +6,11 @@ go_library(
|
||||
"checkpoint.go",
|
||||
"client.go",
|
||||
"doc.go",
|
||||
"errors.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/api/client/beacon",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
@@ -39,6 +39,7 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/client:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
base "github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz/detect"
|
||||
"github.com/prysmaticlabs/prysm/v4/io/file"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
@@ -18,6 +20,8 @@ import (
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
var errCheckpointBlockMismatch = errors.New("mismatch between checkpoint sync state and block")
|
||||
|
||||
// OriginData represents the BeaconState and ReadOnlySignedBeaconBlock necessary to start an empty Beacon Node
|
||||
// using Checkpoint Sync.
|
||||
type OriginData struct {
|
||||
@@ -74,37 +78,40 @@ func DownloadFinalizedData(ctx context.Context, client *Client) (*OriginData, er
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error unmarshaling finalized state to correct version")
|
||||
}
|
||||
if s.Slot() != s.LatestBlockHeader().Slot {
|
||||
return nil, fmt.Errorf("finalized state slot does not match latest block header slot %d != %d", s.Slot(), s.LatestBlockHeader().Slot)
|
||||
}
|
||||
|
||||
sr, err := s.HashTreeRoot(ctx)
|
||||
slot := s.LatestBlockHeader().Slot
|
||||
bb, err := client.GetBlock(ctx, IdFromSlot(slot))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compute htr for finalized state at slot=%d", s.Slot())
|
||||
}
|
||||
header := s.LatestBlockHeader()
|
||||
header.StateRoot = sr[:]
|
||||
br, err := header.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error while computing block root using state data")
|
||||
}
|
||||
|
||||
bb, err := client.GetBlock(ctx, IdFromRoot(br))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error requesting block by root = %#x", br)
|
||||
return nil, errors.Wrapf(err, "error requesting block by slot = %d", slot)
|
||||
}
|
||||
b, err := vu.UnmarshalBeaconBlock(bb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to unmarshal block to a supported type using the detected fork schedule")
|
||||
}
|
||||
realBlockRoot, err := b.Block().HashTreeRoot()
|
||||
br, err := b.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error computing hash_tree_root of retrieved block")
|
||||
}
|
||||
bodyRoot, err := b.Block().Body().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error computing hash_tree_root of retrieved block body")
|
||||
}
|
||||
|
||||
log.Printf("BeaconState slot=%d, Block slot=%d", s.Slot(), b.Block().Slot())
|
||||
log.Printf("BeaconState htr=%#x, Block state_root=%#x", sr, b.Block().StateRoot())
|
||||
log.Printf("BeaconState latest_block_header htr=%#x, block htr=%#x", br, realBlockRoot)
|
||||
sbr := bytesutil.ToBytes32(s.LatestBlockHeader().BodyRoot)
|
||||
if sbr != bodyRoot {
|
||||
return nil, errors.Wrapf(errCheckpointBlockMismatch, "state body root = %#x, block body root = %#x", sbr, bodyRoot)
|
||||
}
|
||||
sr, err := s.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to compute htr for finalized state at slot=%d", s.Slot())
|
||||
}
|
||||
|
||||
log.
|
||||
WithField("block_slot", b.Block().Slot()).
|
||||
WithField("state_slot", s.Slot()).
|
||||
WithField("state_root", sr).
|
||||
WithField("block_root", br).
|
||||
Info("Downloaded checkpoint sync state and block.")
|
||||
return &OriginData{
|
||||
st: s,
|
||||
b: b,
|
||||
@@ -140,7 +147,7 @@ func ComputeWeakSubjectivityCheckpoint(ctx context.Context, client *Client) (*We
|
||||
ws, err := client.GetWeakSubjectivity(ctx)
|
||||
if err != nil {
|
||||
// a 404/405 is expected if querying an endpoint that doesn't support the weak subjectivity checkpoint api
|
||||
if !errors.Is(err, ErrNotOK) {
|
||||
if !errors.Is(err, base.ErrNotOK) {
|
||||
return nil, errors.Wrap(err, "unexpected API response for prysm-only weak subjectivity checkpoint API")
|
||||
}
|
||||
// fall back to vanilla Beacon Node API method
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
blocktest "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks/testing"
|
||||
@@ -66,11 +66,7 @@ func TestMarshalToEnvelope(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFallbackVersionCheck(t *testing.T) {
|
||||
c := &Client{
|
||||
hc: &http.Client{},
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
c.hc.Transport = &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
trans := &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case getNodeVersionPath:
|
||||
@@ -88,12 +84,13 @@ func TestFallbackVersionCheck(t *testing.T) {
|
||||
case getWeakSubjectivityPath:
|
||||
res.StatusCode = http.StatusNotFound
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}}
|
||||
|
||||
c, err := NewClient("http://localhost:3500", client.WithRoundTripper(trans))
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
_, err := ComputeWeakSubjectivityCheckpoint(ctx, c)
|
||||
_, err = ComputeWeakSubjectivityCheckpoint(ctx, c)
|
||||
require.ErrorIs(t, err, errUnsupportedPrysmCheckpointVersion)
|
||||
}
|
||||
|
||||
@@ -170,44 +167,41 @@ func TestDownloadWeakSubjectivityCheckpoint(t *testing.T) {
|
||||
Epoch: epoch,
|
||||
}
|
||||
|
||||
hc := &http.Client{
|
||||
Transport: &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case getWeakSubjectivityPath:
|
||||
res.StatusCode = http.StatusOK
|
||||
cp := struct {
|
||||
Epoch string `json:"epoch"`
|
||||
Root string `json:"root"`
|
||||
}{
|
||||
Epoch: fmt.Sprintf("%d", slots.ToEpoch(b.Block().Slot())),
|
||||
Root: fmt.Sprintf("%#x", bRoot),
|
||||
}
|
||||
wsr := struct {
|
||||
Checkpoint interface{} `json:"ws_checkpoint"`
|
||||
StateRoot string `json:"state_root"`
|
||||
}{
|
||||
Checkpoint: cp,
|
||||
StateRoot: fmt.Sprintf("%#x", wRoot),
|
||||
}
|
||||
rb, err := marshalToEnvelope(wsr)
|
||||
require.NoError(t, err)
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(rb))
|
||||
case renderGetStatePath(IdFromSlot(wSlot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(wsSerialized))
|
||||
case renderGetBlockPath(IdFromRoot(bRoot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serBlock))
|
||||
trans := &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case getWeakSubjectivityPath:
|
||||
res.StatusCode = http.StatusOK
|
||||
cp := struct {
|
||||
Epoch string `json:"epoch"`
|
||||
Root string `json:"root"`
|
||||
}{
|
||||
Epoch: fmt.Sprintf("%d", slots.ToEpoch(b.Block().Slot())),
|
||||
Root: fmt.Sprintf("%#x", bRoot),
|
||||
}
|
||||
wsr := struct {
|
||||
Checkpoint interface{} `json:"ws_checkpoint"`
|
||||
StateRoot string `json:"state_root"`
|
||||
}{
|
||||
Checkpoint: cp,
|
||||
StateRoot: fmt.Sprintf("%#x", wRoot),
|
||||
}
|
||||
rb, err := marshalToEnvelope(wsr)
|
||||
require.NoError(t, err)
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(rb))
|
||||
case renderGetStatePath(IdFromSlot(wSlot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(wsSerialized))
|
||||
case renderGetBlockPath(IdFromRoot(bRoot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serBlock))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}},
|
||||
}
|
||||
c := &Client{
|
||||
hc: hc,
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
return res, nil
|
||||
}}
|
||||
|
||||
c, err := NewClient("http://localhost:3500", client.WithRoundTripper(trans))
|
||||
require.NoError(t, err)
|
||||
|
||||
wsd, err := ComputeWeakSubjectivityCheckpoint(ctx, c)
|
||||
require.NoError(t, err)
|
||||
@@ -266,42 +260,39 @@ func TestDownloadBackwardsCompatibleCombined(t *testing.T) {
|
||||
wsSerialized, err := wst.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
hc := &http.Client{
|
||||
Transport: &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case getNodeVersionPath:
|
||||
res.StatusCode = http.StatusOK
|
||||
b := bytes.NewBuffer(nil)
|
||||
d := struct {
|
||||
Version string `json:"version"`
|
||||
}{
|
||||
Version: "Lighthouse/v0.1.5 (Linux x86_64)",
|
||||
}
|
||||
encoded, err := marshalToEnvelope(d)
|
||||
require.NoError(t, err)
|
||||
b.Write(encoded)
|
||||
res.Body = io.NopCloser(b)
|
||||
case getWeakSubjectivityPath:
|
||||
res.StatusCode = http.StatusNotFound
|
||||
case renderGetStatePath(IdHead):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serialized))
|
||||
case renderGetStatePath(IdFromSlot(wSlot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(wsSerialized))
|
||||
case renderGetBlockPath(IdFromRoot(bRoot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serBlock))
|
||||
trans := &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case getNodeVersionPath:
|
||||
res.StatusCode = http.StatusOK
|
||||
b := bytes.NewBuffer(nil)
|
||||
d := struct {
|
||||
Version string `json:"version"`
|
||||
}{
|
||||
Version: "Lighthouse/v0.1.5 (Linux x86_64)",
|
||||
}
|
||||
encoded, err := marshalToEnvelope(d)
|
||||
require.NoError(t, err)
|
||||
b.Write(encoded)
|
||||
res.Body = io.NopCloser(b)
|
||||
case getWeakSubjectivityPath:
|
||||
res.StatusCode = http.StatusNotFound
|
||||
case renderGetStatePath(IdHead):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serialized))
|
||||
case renderGetStatePath(IdFromSlot(wSlot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(wsSerialized))
|
||||
case renderGetBlockPath(IdFromRoot(bRoot)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serBlock))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}},
|
||||
}
|
||||
c := &Client{
|
||||
hc: hc,
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
return res, nil
|
||||
}}
|
||||
|
||||
c, err := NewClient("http://localhost:3500", client.WithRoundTripper(trans))
|
||||
require.NoError(t, err)
|
||||
|
||||
wsPub, err := ComputeWeakSubjectivityCheckpoint(ctx, c)
|
||||
require.NoError(t, err)
|
||||
@@ -315,21 +306,16 @@ func TestGetWeakSubjectivityEpochFromHead(t *testing.T) {
|
||||
st, expectedEpoch := defaultTestHeadState(t, params.MainnetConfig())
|
||||
serialized, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
hc := &http.Client{
|
||||
Transport: &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case renderGetStatePath(IdHead):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serialized))
|
||||
}
|
||||
return res, nil
|
||||
}},
|
||||
}
|
||||
c := &Client{
|
||||
hc: hc,
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
trans := &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
if req.URL.Path == renderGetStatePath(IdHead) {
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(serialized))
|
||||
}
|
||||
return res, nil
|
||||
}}
|
||||
c, err := NewClient("http://localhost:3500", client.WithRoundTripper(trans))
|
||||
require.NoError(t, err)
|
||||
actualEpoch, err := getWeakSubjectivityEpochFromHead(context.Background(), c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedEpoch, actualEpoch)
|
||||
@@ -448,29 +434,24 @@ func TestDownloadFinalizedData(t *testing.T) {
|
||||
ms, err := st.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
hc := &http.Client{
|
||||
Transport: &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case renderGetStatePath(IdFinalized):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(ms))
|
||||
case renderGetBlockPath(IdFromRoot(br)):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(mb))
|
||||
default:
|
||||
res.StatusCode = http.StatusInternalServerError
|
||||
res.Body = io.NopCloser(bytes.NewBufferString(""))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}},
|
||||
}
|
||||
c := &Client{
|
||||
hc: hc,
|
||||
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
|
||||
}
|
||||
trans := &testRT{rt: func(req *http.Request) (*http.Response, error) {
|
||||
res := &http.Response{Request: req}
|
||||
switch req.URL.Path {
|
||||
case renderGetStatePath(IdFinalized):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(ms))
|
||||
case renderGetBlockPath(IdFromSlot(b.Block().Slot())):
|
||||
res.StatusCode = http.StatusOK
|
||||
res.Body = io.NopCloser(bytes.NewBuffer(mb))
|
||||
default:
|
||||
res.StatusCode = http.StatusInternalServerError
|
||||
res.Body = io.NopCloser(bytes.NewBufferString(""))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}}
|
||||
c, err := NewClient("http://localhost:3500", client.WithRoundTripper(trans))
|
||||
require.NoError(t, err)
|
||||
// sanity check before we go through checkpoint
|
||||
// make sure we can download the state and unmarshal it with the VersionedUnmarshaler
|
||||
sb, err := c.GetState(ctx, IdFinalized)
|
||||
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
@@ -14,8 +12,8 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/network/forks"
|
||||
v1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
|
||||
@@ -54,8 +52,6 @@ const (
|
||||
IdFinalized StateOrBlockId = "finalized"
|
||||
)
|
||||
|
||||
var ErrMalformedHostname = errors.New("hostname must include port, separated by one colon, like example.com:3500")
|
||||
|
||||
// IdFromRoot encodes a block root in the format expected by the API in places where a root can be used to identify
|
||||
// a BeaconState or SignedBeaconBlock.
|
||||
func IdFromRoot(r [32]byte) StateOrBlockId {
|
||||
@@ -85,96 +81,22 @@ func idTemplate(ts string) func(StateOrBlockId) string {
|
||||
return f
|
||||
}
|
||||
|
||||
// ClientOpt is a functional option for the Client type (http.Client wrapper)
|
||||
type ClientOpt func(*Client)
|
||||
|
||||
// WithTimeout sets the .Timeout attribute of the wrapped http.Client.
|
||||
func WithTimeout(timeout time.Duration) ClientOpt {
|
||||
return func(c *Client) {
|
||||
c.hc.Timeout = timeout
|
||||
}
|
||||
func renderGetBlockPath(id StateOrBlockId) string {
|
||||
return path.Join(getSignedBlockPath, string(id))
|
||||
}
|
||||
|
||||
// Client provides a collection of helper methods for calling the Eth Beacon Node API endpoints.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
baseURL *url.URL
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// NewClient constructs a new client with the provided options (ex WithTimeout).
|
||||
// `host` is the base host + port used to construct request urls. This value can be
|
||||
// a URL string, or NewClient will assume an http endpoint if just `host:port` is used.
|
||||
func NewClient(host string, opts ...ClientOpt) (*Client, error) {
|
||||
u, err := urlForHost(host)
|
||||
// NewClient returns a new Client that includes functions for rest calls to Beacon API.
|
||||
func NewClient(host string, opts ...client.ClientOpt) (*Client, error) {
|
||||
c, err := client.NewClient(host, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
hc: &http.Client{},
|
||||
baseURL: u,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func urlForHost(h string) (*url.URL, error) {
|
||||
// try to parse as url (being permissive)
|
||||
u, err := url.Parse(h)
|
||||
if err == nil && u.Host != "" {
|
||||
return u, nil
|
||||
}
|
||||
// try to parse as host:port
|
||||
host, port, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
return nil, ErrMalformedHostname
|
||||
}
|
||||
return &url.URL{Host: fmt.Sprintf("%s:%s", host, port), Scheme: "http"}, nil
|
||||
}
|
||||
|
||||
// NodeURL returns a human-readable string representation of the beacon node base url.
|
||||
func (c *Client) NodeURL() string {
|
||||
return c.baseURL.String()
|
||||
}
|
||||
|
||||
type reqOption func(*http.Request)
|
||||
|
||||
func withSSZEncoding() reqOption {
|
||||
return func(req *http.Request) {
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
}
|
||||
}
|
||||
|
||||
// get is a generic, opinionated GET function to reduce boilerplate amongst the getters in this package.
|
||||
func (c *Client) get(ctx context.Context, path string, opts ...reqOption) ([]byte, error) {
|
||||
u := c.baseURL.ResolveReference(&url.URL{Path: path})
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(req)
|
||||
}
|
||||
r, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = r.Body.Close()
|
||||
}()
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, non200Err(r)
|
||||
}
|
||||
b, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error reading http response body from GetBlock")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func renderGetBlockPath(id StateOrBlockId) string {
|
||||
return path.Join(getSignedBlockPath, string(id))
|
||||
return &Client{c}, nil
|
||||
}
|
||||
|
||||
// GetBlock retrieves the SignedBeaconBlock for the given block id.
|
||||
@@ -184,7 +106,7 @@ func renderGetBlockPath(id StateOrBlockId) string {
|
||||
// The return value contains the ssz-encoded bytes.
|
||||
func (c *Client) GetBlock(ctx context.Context, blockId StateOrBlockId) ([]byte, error) {
|
||||
blockPath := renderGetBlockPath(blockId)
|
||||
b, err := c.get(ctx, blockPath, withSSZEncoding())
|
||||
b, err := c.Get(ctx, blockPath, client.WithSSZEncoding())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error requesting state by id = %s", blockId)
|
||||
}
|
||||
@@ -199,7 +121,7 @@ var getBlockRootTpl = idTemplate(getBlockRootPath)
|
||||
// for the named identifiers.
|
||||
func (c *Client) GetBlockRoot(ctx context.Context, blockId StateOrBlockId) ([32]byte, error) {
|
||||
rootPath := getBlockRootTpl(blockId)
|
||||
b, err := c.get(ctx, rootPath)
|
||||
b, err := c.Get(ctx, rootPath)
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrapf(err, "error requesting block root by id = %s", blockId)
|
||||
}
|
||||
@@ -222,7 +144,7 @@ var getForkTpl = idTemplate(getForkForStatePath)
|
||||
// <slot>, <hex encoded blockRoot with 0x prefix>. Variables of type StateOrBlockId are exported by this package
|
||||
// for the named identifiers.
|
||||
func (c *Client) GetFork(ctx context.Context, stateId StateOrBlockId) (*ethpb.Fork, error) {
|
||||
body, err := c.get(ctx, getForkTpl(stateId))
|
||||
body, err := c.Get(ctx, getForkTpl(stateId))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error requesting fork by state id = %s", stateId)
|
||||
}
|
||||
@@ -238,7 +160,7 @@ func (c *Client) GetFork(ctx context.Context, stateId StateOrBlockId) (*ethpb.Fo
|
||||
|
||||
// GetForkSchedule retrieve all forks, past present and future, of which this node is aware.
|
||||
func (c *Client) GetForkSchedule(ctx context.Context) (forks.OrderedSchedule, error) {
|
||||
body, err := c.get(ctx, getForkSchedulePath)
|
||||
body, err := c.Get(ctx, getForkSchedulePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error requesting fork schedule")
|
||||
}
|
||||
@@ -256,7 +178,7 @@ func (c *Client) GetForkSchedule(ctx context.Context) (forks.OrderedSchedule, er
|
||||
|
||||
// GetConfigSpec retrieve the current configs of the network used by the beacon node.
|
||||
func (c *Client) GetConfigSpec(ctx context.Context) (*v1.SpecResponse, error) {
|
||||
body, err := c.get(ctx, getConfigSpecPath)
|
||||
body, err := c.Get(ctx, getConfigSpecPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error requesting configSpecPath")
|
||||
}
|
||||
@@ -279,7 +201,7 @@ var versionRE = regexp.MustCompile(`^(\w+)/(v\d+\.\d+\.\d+[-a-zA-Z0-9]*)\s*/?(.*
|
||||
func parseNodeVersion(v string) (*NodeVersion, error) {
|
||||
groups := versionRE.FindStringSubmatch(v)
|
||||
if len(groups) != 4 {
|
||||
return nil, errors.Wrapf(ErrInvalidNodeVersion, "could not be parsed: %s", v)
|
||||
return nil, errors.Wrapf(client.ErrInvalidNodeVersion, "could not be parsed: %s", v)
|
||||
}
|
||||
return &NodeVersion{
|
||||
implementation: groups[1],
|
||||
@@ -291,7 +213,7 @@ func parseNodeVersion(v string) (*NodeVersion, error) {
|
||||
// GetNodeVersion requests that the beacon node identify information about its implementation in a format
|
||||
// similar to a HTTP User-Agent field. ex: Lighthouse/v0.1.5 (Linux x86_64)
|
||||
func (c *Client) GetNodeVersion(ctx context.Context) (*NodeVersion, error) {
|
||||
b, err := c.get(ctx, getNodeVersionPath)
|
||||
b, err := c.Get(ctx, getNodeVersionPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error requesting node version")
|
||||
}
|
||||
@@ -318,7 +240,7 @@ func renderGetStatePath(id StateOrBlockId) string {
|
||||
// The return value contains the ssz-encoded bytes.
|
||||
func (c *Client) GetState(ctx context.Context, stateId StateOrBlockId) ([]byte, error) {
|
||||
statePath := path.Join(getStatePath, string(stateId))
|
||||
b, err := c.get(ctx, statePath, withSSZEncoding())
|
||||
b, err := c.Get(ctx, statePath, client.WithSSZEncoding())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error requesting state by id = %s", stateId)
|
||||
}
|
||||
@@ -331,7 +253,7 @@ func (c *Client) GetState(ctx context.Context, stateId StateOrBlockId) ([]byte,
|
||||
// - finds the highest non-skipped block preceding the epoch
|
||||
// - returns the htr of the found block and returns this + the value of state_root from the block
|
||||
func (c *Client) GetWeakSubjectivity(ctx context.Context) (*WeakSubjectivityData, error) {
|
||||
body, err := c.get(ctx, getWeakSubjectivityPath)
|
||||
body, err := c.Get(ctx, getWeakSubjectivityPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -362,7 +284,7 @@ func (c *Client) GetWeakSubjectivity(ctx context.Context) (*WeakSubjectivityData
|
||||
// SubmitChangeBLStoExecution calls a beacon API endpoint to set the withdrawal addresses based on the given signed messages.
|
||||
// If the API responds with something other than OK there will be failure messages associated to the corresponding request message.
|
||||
func (c *Client) SubmitChangeBLStoExecution(ctx context.Context, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
|
||||
u := c.baseURL.ResolveReference(&url.URL{Path: changeBLStoExecutionPath})
|
||||
u := c.BaseURL().ResolveReference(&url.URL{Path: changeBLStoExecutionPath})
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal JSON")
|
||||
@@ -372,7 +294,7 @@ func (c *Client) SubmitChangeBLStoExecution(ctx context.Context, request []*apim
|
||||
return errors.Wrap(err, "invalid format, failed to create new POST request object")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := c.hc.Do(req)
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -401,7 +323,7 @@ func (c *Client) SubmitChangeBLStoExecution(ctx context.Context, request []*apim
|
||||
// GetBLStoExecutionChanges gets all the set withdrawal messages in the node's operation pool.
|
||||
// Returns a struct representation of json response.
|
||||
func (c *Client) GetBLStoExecutionChanges(ctx context.Context) (*apimiddleware.BLSToExecutionChangesPoolResponseJson, error) {
|
||||
body, err := c.get(ctx, changeBLStoExecutionPath)
|
||||
body, err := c.Get(ctx, changeBLStoExecutionPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -413,23 +335,6 @@ func (c *Client) GetBLStoExecutionChanges(ctx context.Context) (*apimiddleware.B
|
||||
return poolResponse, nil
|
||||
}
|
||||
|
||||
func non200Err(response *http.Response) error {
|
||||
bodyBytes, err := io.ReadAll(response.Body)
|
||||
var body string
|
||||
if err != nil {
|
||||
body = "(Unable to read response body.)"
|
||||
} else {
|
||||
body = "response body:\n" + string(bodyBytes)
|
||||
}
|
||||
msg := fmt.Sprintf("code=%d, url=%s, body=%s", response.StatusCode, response.Request.URL, body)
|
||||
switch response.StatusCode {
|
||||
case 404:
|
||||
return errors.Wrap(ErrNotFound, msg)
|
||||
default:
|
||||
return errors.Wrap(ErrNotOK, msg)
|
||||
}
|
||||
}
|
||||
|
||||
type forkResponse struct {
|
||||
PreviousVersion string `json:"previous_version"`
|
||||
CurrentVersion string `json:"current_version"`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
@@ -17,17 +18,17 @@ func TestParseNodeVersion(t *testing.T) {
|
||||
{
|
||||
name: "empty string",
|
||||
v: "",
|
||||
err: ErrInvalidNodeVersion,
|
||||
err: client.ErrInvalidNodeVersion,
|
||||
},
|
||||
{
|
||||
name: "Prysm as the version string",
|
||||
v: "Prysm",
|
||||
err: ErrInvalidNodeVersion,
|
||||
err: client.ErrInvalidNodeVersion,
|
||||
},
|
||||
{
|
||||
name: "semver only",
|
||||
v: "v2.0.6",
|
||||
err: ErrInvalidNodeVersion,
|
||||
err: client.ErrInvalidNodeVersion,
|
||||
},
|
||||
{
|
||||
name: "complete version",
|
||||
@@ -91,7 +92,7 @@ func TestValidHostname(t *testing.T) {
|
||||
{
|
||||
name: "hostname without port",
|
||||
hostArg: "mydomain.org",
|
||||
err: ErrMalformedHostname,
|
||||
err: client.ErrMalformedHostname,
|
||||
},
|
||||
{
|
||||
name: "hostname with port",
|
||||
@@ -132,7 +133,7 @@ func TestValidHostname(t *testing.T) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.joined, cl.baseURL.ResolveReference(&url.URL{Path: c.path}).String())
|
||||
require.Equal(t, c.joined, cl.BaseURL().ResolveReference(&url.URL{Path: c.path}).String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package beacon
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
// ErrNotOK is used to indicate when an HTTP request to the Beacon Node API failed with any non-2xx response code.
|
||||
// More specific errors may be returned, but an error in reaction to a non-2xx response will always wrap ErrNotOK.
|
||||
var ErrNotOK = errors.New("did not receive 2xx response from API")
|
||||
|
||||
// ErrNotFound specifically means that a '404 - NOT FOUND' response was received from the API.
|
||||
var ErrNotFound = errors.Wrap(ErrNotOK, "recv 404 NotFound response from API")
|
||||
|
||||
// ErrInvalidNodeVersion indicates that the /eth/v1/node/version api response format was not recognized.
|
||||
var ErrInvalidNodeVersion = errors.New("invalid node version response")
|
||||
@@ -3,7 +3,6 @@ package builder
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ssz "github.com/prysmaticlabs/fastssz"
|
||||
consensus_types "github.com/prysmaticlabs/prysm/v4/consensus-types"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
@@ -162,9 +161,6 @@ func WrappedBuilderBidCapella(p *ethpb.BuilderBidCapella) (Bid, error) {
|
||||
|
||||
// Header returns the execution data interface.
|
||||
func (b builderBidCapella) Header() (interfaces.ExecutionData, error) {
|
||||
if b.p == nil {
|
||||
return nil, errors.New("builder bid is nil")
|
||||
}
|
||||
// We have to convert big endian to little endian because the value is coming from the execution layer.
|
||||
v := big.NewInt(0).SetBytes(bytesutil.ReverseByteOrder(b.p.Value))
|
||||
return blocks.WrappedExecutionPayloadHeaderCapella(b.p.Header, math.WeiToGwei(v))
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
@@ -36,7 +35,6 @@ const (
|
||||
var errMalformedHostname = errors.New("hostname must include port, separated by one colon, like example.com:3500")
|
||||
var errMalformedRequest = errors.New("required request data are missing")
|
||||
var errNotBlinded = errors.New("submitted block is not blinded")
|
||||
var submitBlindedBlockTimeout = 3 * time.Second
|
||||
|
||||
// ClientOpt is a functional option for the Client type (http.Client wrapper)
|
||||
type ClientOpt func(*Client)
|
||||
@@ -292,8 +290,6 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
|
||||
return nil, errors.Wrap(err, "error encoding the SignedBlindedBeaconBlockBellatrix value body in SubmitBlindedBlock")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, submitBlindedBlockTimeout)
|
||||
defer cancel()
|
||||
versionOpt := func(r *http.Request) {
|
||||
r.Header.Add("Eth-Consensus-Version", version.String(version.Bellatrix))
|
||||
}
|
||||
@@ -325,8 +321,6 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
|
||||
return nil, errors.Wrap(err, "error encoding the SignedBlindedBeaconBlockCapella value body in SubmitBlindedBlockCapella")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, submitBlindedBlockTimeout)
|
||||
defer cancel()
|
||||
versionOpt := func(r *http.Request) {
|
||||
r.Header.Add("Eth-Consensus-Version", version.String(version.Capella))
|
||||
}
|
||||
|
||||
@@ -14,14 +14,17 @@ import (
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// SignedValidatorRegistration a struct for signed validator registrations.
|
||||
type SignedValidatorRegistration struct {
|
||||
*eth.SignedValidatorRegistrationV1
|
||||
}
|
||||
|
||||
// ValidatorRegistration a struct for validator registrations.
|
||||
type ValidatorRegistration struct {
|
||||
*eth.ValidatorRegistrationV1
|
||||
}
|
||||
|
||||
// MarshalJSON returns a json representation copy of signed validator registration.
|
||||
func (r *SignedValidatorRegistration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Message *ValidatorRegistration `json:"message"`
|
||||
@@ -32,6 +35,7 @@ func (r *SignedValidatorRegistration) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON returns a byte representation of signed validator registration from json.
|
||||
func (r *SignedValidatorRegistration) UnmarshalJSON(b []byte) error {
|
||||
if r.SignedValidatorRegistrationV1 == nil {
|
||||
r.SignedValidatorRegistrationV1 = ð.SignedValidatorRegistrationV1{}
|
||||
@@ -48,6 +52,7 @@ func (r *SignedValidatorRegistration) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns a json representation copy of validator registration.
|
||||
func (r *ValidatorRegistration) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
|
||||
@@ -62,6 +67,7 @@ func (r *ValidatorRegistration) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON returns a byte representation of validator registration from json.
|
||||
func (r *ValidatorRegistration) UnmarshalJSON(b []byte) error {
|
||||
if r.ValidatorRegistrationV1 == nil {
|
||||
r.ValidatorRegistrationV1 = ð.ValidatorRegistrationV1{}
|
||||
@@ -92,6 +98,7 @@ func (r *ValidatorRegistration) UnmarshalJSON(b []byte) error {
|
||||
var errInvalidUint256 = errors.New("invalid Uint256")
|
||||
var errDecodeUint256 = errors.New("unable to decode into Uint256")
|
||||
|
||||
// Uint256 a wrapper representation of big.Int
|
||||
type Uint256 struct {
|
||||
*big.Int
|
||||
}
|
||||
@@ -118,7 +125,7 @@ func sszBytesToUint256(b []byte) (Uint256, error) {
|
||||
return Uint256{Int: bi}, nil
|
||||
}
|
||||
|
||||
// SSZBytes creates an ssz-style (little-endian byte slice) representation of the Uint256
|
||||
// SSZBytes creates an ssz-style (little-endian byte slice) representation of the Uint256.
|
||||
func (s Uint256) SSZBytes() []byte {
|
||||
if !isValidUint256(s.Int) {
|
||||
return []byte{}
|
||||
@@ -126,6 +133,7 @@ func (s Uint256) SSZBytes() []byte {
|
||||
return bytesutil.PadTo(bytesutil.ReverseByteOrder(s.Int.Bytes()), 32)
|
||||
}
|
||||
|
||||
// UnmarshalJSON takes in a byte array and unmarshals the value in Uint256
|
||||
func (s *Uint256) UnmarshalJSON(t []byte) error {
|
||||
start := 0
|
||||
end := len(t)
|
||||
@@ -138,6 +146,7 @@ func (s *Uint256) UnmarshalJSON(t []byte) error {
|
||||
return s.UnmarshalText(t[start:end])
|
||||
}
|
||||
|
||||
// UnmarshalText takes in a byte array and unmarshals the text in Uint256
|
||||
func (s *Uint256) UnmarshalText(t []byte) error {
|
||||
if s.Int == nil {
|
||||
s.Int = big.NewInt(0)
|
||||
@@ -153,6 +162,7 @@ func (s *Uint256) UnmarshalText(t []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON returns a json byte representation of Uint256.
|
||||
func (s Uint256) MarshalJSON() ([]byte, error) {
|
||||
t, err := s.MarshalText()
|
||||
if err != nil {
|
||||
@@ -163,6 +173,7 @@ func (s Uint256) MarshalJSON() ([]byte, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MarshalText returns a text byte representation of Uint256.
|
||||
func (s Uint256) MarshalText() ([]byte, error) {
|
||||
if !isValidUint256(s.Int) {
|
||||
return nil, errors.Wrapf(errInvalidUint256, "value=%s", s.Int)
|
||||
@@ -170,22 +181,27 @@ func (s Uint256) MarshalText() ([]byte, error) {
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
// Uint64String is a custom type that allows marshalling from text to uint64 and vice versa.
|
||||
type Uint64String uint64
|
||||
|
||||
// UnmarshalText takes a byte array and unmarshals the text in Uint64String.
|
||||
func (s *Uint64String) UnmarshalText(t []byte) error {
|
||||
u, err := strconv.ParseUint(string(t), 10, 64)
|
||||
*s = Uint64String(u)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalText returns a byte representation of the text from Uint64String.
|
||||
func (s Uint64String) MarshalText() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%d", s)), nil
|
||||
}
|
||||
|
||||
// VersionResponse is a JSON representation of a field in the builder API header response.
|
||||
type VersionResponse struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// ExecHeaderResponse is a JSON representation of the builder API header response for Bellatrix.
|
||||
type ExecHeaderResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data struct {
|
||||
@@ -194,6 +210,7 @@ type ExecHeaderResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// ToProto returns a SignedBuilderBid from ExecHeaderResponse for Bellatrix.
|
||||
func (ehr *ExecHeaderResponse) ToProto() (*eth.SignedBuilderBid, error) {
|
||||
bb, err := ehr.Data.Message.ToProto()
|
||||
if err != nil {
|
||||
@@ -205,6 +222,7 @@ func (ehr *ExecHeaderResponse) ToProto() (*eth.SignedBuilderBid, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToProto returns a BuilderBid Proto for Bellatrix.
|
||||
func (bb *BuilderBid) ToProto() (*eth.BuilderBid, error) {
|
||||
header, err := bb.Header.ToProto()
|
||||
if err != nil {
|
||||
@@ -217,31 +235,34 @@ func (bb *BuilderBid) ToProto() (*eth.BuilderBid, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToProto returns a ExecutionPayloadHeader for Bellatrix.
|
||||
func (h *ExecutionPayloadHeader) ToProto() (*v1.ExecutionPayloadHeader, error) {
|
||||
return &v1.ExecutionPayloadHeader{
|
||||
ParentHash: h.ParentHash,
|
||||
FeeRecipient: h.FeeRecipient,
|
||||
StateRoot: h.StateRoot,
|
||||
ReceiptsRoot: h.ReceiptsRoot,
|
||||
LogsBloom: h.LogsBloom,
|
||||
PrevRandao: h.PrevRandao,
|
||||
ParentHash: bytesutil.SafeCopyBytes(h.ParentHash),
|
||||
FeeRecipient: bytesutil.SafeCopyBytes(h.FeeRecipient),
|
||||
StateRoot: bytesutil.SafeCopyBytes(h.StateRoot),
|
||||
ReceiptsRoot: bytesutil.SafeCopyBytes(h.ReceiptsRoot),
|
||||
LogsBloom: bytesutil.SafeCopyBytes(h.LogsBloom),
|
||||
PrevRandao: bytesutil.SafeCopyBytes(h.PrevRandao),
|
||||
BlockNumber: uint64(h.BlockNumber),
|
||||
GasLimit: uint64(h.GasLimit),
|
||||
GasUsed: uint64(h.GasUsed),
|
||||
Timestamp: uint64(h.Timestamp),
|
||||
ExtraData: h.ExtraData,
|
||||
BaseFeePerGas: h.BaseFeePerGas.SSZBytes(),
|
||||
BlockHash: h.BlockHash,
|
||||
TransactionsRoot: h.TransactionsRoot,
|
||||
ExtraData: bytesutil.SafeCopyBytes(h.ExtraData),
|
||||
BaseFeePerGas: bytesutil.SafeCopyBytes(h.BaseFeePerGas.SSZBytes()),
|
||||
BlockHash: bytesutil.SafeCopyBytes(h.BlockHash),
|
||||
TransactionsRoot: bytesutil.SafeCopyBytes(h.TransactionsRoot),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuilderBid is part of ExecHeaderResponse for Bellatrix.
|
||||
type BuilderBid struct {
|
||||
Header *ExecutionPayloadHeader `json:"header"`
|
||||
Value Uint256 `json:"value"`
|
||||
Pubkey hexutil.Bytes `json:"pubkey"`
|
||||
}
|
||||
|
||||
// ExecutionPayloadHeader is a field in BuilderBid.
|
||||
type ExecutionPayloadHeader struct {
|
||||
ParentHash hexutil.Bytes `json:"parent_hash"`
|
||||
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
|
||||
@@ -260,6 +281,7 @@ type ExecutionPayloadHeader struct {
|
||||
*v1.ExecutionPayloadHeader
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON bytes representation of ExecutionPayloadHeader.
|
||||
func (h *ExecutionPayloadHeader) MarshalJSON() ([]byte, error) {
|
||||
type MarshalCaller ExecutionPayloadHeader
|
||||
baseFeePerGas, err := sszBytesToUint256(h.ExecutionPayloadHeader.BaseFeePerGas)
|
||||
@@ -284,6 +306,7 @@ func (h *ExecutionPayloadHeader) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON takes in a JSON byte array and sets ExecutionPayloadHeader.
|
||||
func (h *ExecutionPayloadHeader) UnmarshalJSON(b []byte) error {
|
||||
type UnmarshalCaller ExecutionPayloadHeader
|
||||
uc := &UnmarshalCaller{}
|
||||
@@ -297,11 +320,13 @@ func (h *ExecutionPayloadHeader) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecPayloadResponse is the builder API /eth/v1/builder/blinded_blocks for Bellatrix.
|
||||
type ExecPayloadResponse struct {
|
||||
Version string `json:"version"`
|
||||
Data ExecutionPayload `json:"data"`
|
||||
}
|
||||
|
||||
// ExecutionPayload is a field of ExecPayloadResponse
|
||||
type ExecutionPayload struct {
|
||||
ParentHash hexutil.Bytes `json:"parent_hash"`
|
||||
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
|
||||
@@ -319,29 +344,31 @@ type ExecutionPayload struct {
|
||||
Transactions []hexutil.Bytes `json:"transactions"`
|
||||
}
|
||||
|
||||
// ToProto returns a ExecutionPayload Proto from ExecPayloadResponse
|
||||
func (r *ExecPayloadResponse) ToProto() (*v1.ExecutionPayload, error) {
|
||||
return r.Data.ToProto()
|
||||
}
|
||||
|
||||
// ToProto returns a ExecutionPayload Proto
|
||||
func (p *ExecutionPayload) ToProto() (*v1.ExecutionPayload, error) {
|
||||
txs := make([][]byte, len(p.Transactions))
|
||||
for i := range p.Transactions {
|
||||
txs[i] = p.Transactions[i]
|
||||
txs[i] = bytesutil.SafeCopyBytes(p.Transactions[i])
|
||||
}
|
||||
return &v1.ExecutionPayload{
|
||||
ParentHash: p.ParentHash,
|
||||
FeeRecipient: p.FeeRecipient,
|
||||
StateRoot: p.StateRoot,
|
||||
ReceiptsRoot: p.ReceiptsRoot,
|
||||
LogsBloom: p.LogsBloom,
|
||||
PrevRandao: p.PrevRandao,
|
||||
ParentHash: bytesutil.SafeCopyBytes(p.ParentHash),
|
||||
FeeRecipient: bytesutil.SafeCopyBytes(p.FeeRecipient),
|
||||
StateRoot: bytesutil.SafeCopyBytes(p.StateRoot),
|
||||
ReceiptsRoot: bytesutil.SafeCopyBytes(p.ReceiptsRoot),
|
||||
LogsBloom: bytesutil.SafeCopyBytes(p.LogsBloom),
|
||||
PrevRandao: bytesutil.SafeCopyBytes(p.PrevRandao),
|
||||
BlockNumber: uint64(p.BlockNumber),
|
||||
GasLimit: uint64(p.GasLimit),
|
||||
GasUsed: uint64(p.GasUsed),
|
||||
Timestamp: uint64(p.Timestamp),
|
||||
ExtraData: p.ExtraData,
|
||||
BaseFeePerGas: p.BaseFeePerGas.SSZBytes(),
|
||||
BlockHash: p.BlockHash,
|
||||
ExtraData: bytesutil.SafeCopyBytes(p.ExtraData),
|
||||
BaseFeePerGas: bytesutil.SafeCopyBytes(p.BaseFeePerGas.SSZBytes()),
|
||||
BlockHash: bytesutil.SafeCopyBytes(p.BlockHash),
|
||||
Transactions: txs,
|
||||
}, nil
|
||||
}
|
||||
@@ -355,22 +382,22 @@ func FromProto(payload *v1.ExecutionPayload) (ExecutionPayload, error) {
|
||||
}
|
||||
txs := make([]hexutil.Bytes, len(payload.Transactions))
|
||||
for i := range payload.Transactions {
|
||||
txs[i] = payload.Transactions[i]
|
||||
txs[i] = bytesutil.SafeCopyBytes(payload.Transactions[i])
|
||||
}
|
||||
return ExecutionPayload{
|
||||
ParentHash: payload.ParentHash,
|
||||
FeeRecipient: payload.FeeRecipient,
|
||||
StateRoot: payload.StateRoot,
|
||||
ReceiptsRoot: payload.ReceiptsRoot,
|
||||
LogsBloom: payload.LogsBloom,
|
||||
PrevRandao: payload.PrevRandao,
|
||||
ParentHash: bytesutil.SafeCopyBytes(payload.ParentHash),
|
||||
FeeRecipient: bytesutil.SafeCopyBytes(payload.FeeRecipient),
|
||||
StateRoot: bytesutil.SafeCopyBytes(payload.StateRoot),
|
||||
ReceiptsRoot: bytesutil.SafeCopyBytes(payload.ReceiptsRoot),
|
||||
LogsBloom: bytesutil.SafeCopyBytes(payload.LogsBloom),
|
||||
PrevRandao: bytesutil.SafeCopyBytes(payload.PrevRandao),
|
||||
BlockNumber: Uint64String(payload.BlockNumber),
|
||||
GasLimit: Uint64String(payload.GasLimit),
|
||||
GasUsed: Uint64String(payload.GasUsed),
|
||||
Timestamp: Uint64String(payload.Timestamp),
|
||||
ExtraData: payload.ExtraData,
|
||||
ExtraData: bytesutil.SafeCopyBytes(payload.ExtraData),
|
||||
BaseFeePerGas: bFee,
|
||||
BlockHash: payload.BlockHash,
|
||||
BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash),
|
||||
Transactions: txs,
|
||||
}, nil
|
||||
}
|
||||
@@ -384,36 +411,37 @@ func FromProtoCapella(payload *v1.ExecutionPayloadCapella) (ExecutionPayloadCape
|
||||
}
|
||||
txs := make([]hexutil.Bytes, len(payload.Transactions))
|
||||
for i := range payload.Transactions {
|
||||
txs[i] = payload.Transactions[i]
|
||||
txs[i] = bytesutil.SafeCopyBytes(payload.Transactions[i])
|
||||
}
|
||||
withdrawals := make([]Withdrawal, len(payload.Withdrawals))
|
||||
for i, w := range payload.Withdrawals {
|
||||
withdrawals[i] = Withdrawal{
|
||||
Index: Uint256{Int: big.NewInt(0).SetUint64(w.Index)},
|
||||
ValidatorIndex: Uint256{Int: big.NewInt(0).SetUint64(uint64(w.ValidatorIndex))},
|
||||
Address: w.Address,
|
||||
Address: bytesutil.SafeCopyBytes(w.Address),
|
||||
Amount: Uint256{Int: big.NewInt(0).SetUint64(w.Amount)},
|
||||
}
|
||||
}
|
||||
return ExecutionPayloadCapella{
|
||||
ParentHash: payload.ParentHash,
|
||||
FeeRecipient: payload.FeeRecipient,
|
||||
StateRoot: payload.StateRoot,
|
||||
ReceiptsRoot: payload.ReceiptsRoot,
|
||||
LogsBloom: payload.LogsBloom,
|
||||
PrevRandao: payload.PrevRandao,
|
||||
ParentHash: bytesutil.SafeCopyBytes(payload.ParentHash),
|
||||
FeeRecipient: bytesutil.SafeCopyBytes(payload.FeeRecipient),
|
||||
StateRoot: bytesutil.SafeCopyBytes(payload.StateRoot),
|
||||
ReceiptsRoot: bytesutil.SafeCopyBytes(payload.ReceiptsRoot),
|
||||
LogsBloom: bytesutil.SafeCopyBytes(payload.LogsBloom),
|
||||
PrevRandao: bytesutil.SafeCopyBytes(payload.PrevRandao),
|
||||
BlockNumber: Uint64String(payload.BlockNumber),
|
||||
GasLimit: Uint64String(payload.GasLimit),
|
||||
GasUsed: Uint64String(payload.GasUsed),
|
||||
Timestamp: Uint64String(payload.Timestamp),
|
||||
ExtraData: payload.ExtraData,
|
||||
ExtraData: bytesutil.SafeCopyBytes(payload.ExtraData),
|
||||
BaseFeePerGas: bFee,
|
||||
BlockHash: payload.BlockHash,
|
||||
BlockHash: bytesutil.SafeCopyBytes(payload.BlockHash),
|
||||
Transactions: txs,
|
||||
Withdrawals: withdrawals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecHeaderResponseCapella is the response of builder API /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey} for Capella.
|
||||
type ExecHeaderResponseCapella struct {
|
||||
Data struct {
|
||||
Signature hexutil.Bytes `json:"signature"`
|
||||
@@ -421,6 +449,7 @@ type ExecHeaderResponseCapella struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// ToProto returns a SignedBuilderBidCapella Proto from ExecHeaderResponseCapella.
|
||||
func (ehr *ExecHeaderResponseCapella) ToProto() (*eth.SignedBuilderBidCapella, error) {
|
||||
bb, err := ehr.Data.Message.ToProto()
|
||||
if err != nil {
|
||||
@@ -428,10 +457,11 @@ func (ehr *ExecHeaderResponseCapella) ToProto() (*eth.SignedBuilderBidCapella, e
|
||||
}
|
||||
return ð.SignedBuilderBidCapella{
|
||||
Message: bb,
|
||||
Signature: ehr.Data.Signature,
|
||||
Signature: bytesutil.SafeCopyBytes(ehr.Data.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToProto returns a BuilderBidCapella Proto.
|
||||
func (bb *BuilderBidCapella) ToProto() (*eth.BuilderBidCapella, error) {
|
||||
header, err := bb.Header.ToProto()
|
||||
if err != nil {
|
||||
@@ -439,37 +469,40 @@ func (bb *BuilderBidCapella) ToProto() (*eth.BuilderBidCapella, error) {
|
||||
}
|
||||
return ð.BuilderBidCapella{
|
||||
Header: header,
|
||||
Value: bb.Value.SSZBytes(),
|
||||
Pubkey: bb.Pubkey,
|
||||
Value: bytesutil.SafeCopyBytes(bb.Value.SSZBytes()),
|
||||
Pubkey: bytesutil.SafeCopyBytes(bb.Pubkey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToProto returns a ExecutionPayloadHeaderCapella Proto
|
||||
func (h *ExecutionPayloadHeaderCapella) ToProto() (*v1.ExecutionPayloadHeaderCapella, error) {
|
||||
return &v1.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: h.ParentHash,
|
||||
FeeRecipient: h.FeeRecipient,
|
||||
StateRoot: h.StateRoot,
|
||||
ReceiptsRoot: h.ReceiptsRoot,
|
||||
LogsBloom: h.LogsBloom,
|
||||
PrevRandao: h.PrevRandao,
|
||||
ParentHash: bytesutil.SafeCopyBytes(h.ParentHash),
|
||||
FeeRecipient: bytesutil.SafeCopyBytes(h.FeeRecipient),
|
||||
StateRoot: bytesutil.SafeCopyBytes(h.StateRoot),
|
||||
ReceiptsRoot: bytesutil.SafeCopyBytes(h.ReceiptsRoot),
|
||||
LogsBloom: bytesutil.SafeCopyBytes(h.LogsBloom),
|
||||
PrevRandao: bytesutil.SafeCopyBytes(h.PrevRandao),
|
||||
BlockNumber: uint64(h.BlockNumber),
|
||||
GasLimit: uint64(h.GasLimit),
|
||||
GasUsed: uint64(h.GasUsed),
|
||||
Timestamp: uint64(h.Timestamp),
|
||||
ExtraData: h.ExtraData,
|
||||
BaseFeePerGas: h.BaseFeePerGas.SSZBytes(),
|
||||
BlockHash: h.BlockHash,
|
||||
TransactionsRoot: h.TransactionsRoot,
|
||||
WithdrawalsRoot: h.WithdrawalsRoot,
|
||||
ExtraData: bytesutil.SafeCopyBytes(h.ExtraData),
|
||||
BaseFeePerGas: bytesutil.SafeCopyBytes(h.BaseFeePerGas.SSZBytes()),
|
||||
BlockHash: bytesutil.SafeCopyBytes(h.BlockHash),
|
||||
TransactionsRoot: bytesutil.SafeCopyBytes(h.TransactionsRoot),
|
||||
WithdrawalsRoot: bytesutil.SafeCopyBytes(h.WithdrawalsRoot),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuilderBidCapella is field of ExecHeaderResponseCapella.
|
||||
type BuilderBidCapella struct {
|
||||
Header *ExecutionPayloadHeaderCapella `json:"header"`
|
||||
Value Uint256 `json:"value"`
|
||||
Pubkey hexutil.Bytes `json:"pubkey"`
|
||||
}
|
||||
|
||||
// ExecutionPayloadHeaderCapella is a field in BuilderBidCapella.
|
||||
type ExecutionPayloadHeaderCapella struct {
|
||||
ParentHash hexutil.Bytes `json:"parent_hash"`
|
||||
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
|
||||
@@ -489,6 +522,7 @@ type ExecutionPayloadHeaderCapella struct {
|
||||
*v1.ExecutionPayloadHeaderCapella
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte representation of ExecutionPayloadHeaderCapella.
|
||||
func (h *ExecutionPayloadHeaderCapella) MarshalJSON() ([]byte, error) {
|
||||
type MarshalCaller ExecutionPayloadHeaderCapella
|
||||
baseFeePerGas, err := sszBytesToUint256(h.ExecutionPayloadHeaderCapella.BaseFeePerGas)
|
||||
@@ -514,6 +548,7 @@ func (h *ExecutionPayloadHeaderCapella) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON takes a JSON byte array and sets ExecutionPayloadHeaderCapella.
|
||||
func (h *ExecutionPayloadHeaderCapella) UnmarshalJSON(b []byte) error {
|
||||
type UnmarshalCaller ExecutionPayloadHeaderCapella
|
||||
uc := &UnmarshalCaller{}
|
||||
@@ -527,11 +562,13 @@ func (h *ExecutionPayloadHeaderCapella) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecPayloadResponseCapella is the builder API /eth/v1/builder/blinded_blocks for Capella.
|
||||
type ExecPayloadResponseCapella struct {
|
||||
Version string `json:"version"`
|
||||
Data ExecutionPayloadCapella `json:"data"`
|
||||
}
|
||||
|
||||
// ExecutionPayloadCapella is a field of ExecPayloadResponseCapella.
|
||||
type ExecutionPayloadCapella struct {
|
||||
ParentHash hexutil.Bytes `json:"parent_hash"`
|
||||
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
|
||||
@@ -550,43 +587,46 @@ type ExecutionPayloadCapella struct {
|
||||
Withdrawals []Withdrawal `json:"withdrawals"`
|
||||
}
|
||||
|
||||
// ToProto returns a ExecutionPayloadCapella Proto.
|
||||
func (r *ExecPayloadResponseCapella) ToProto() (*v1.ExecutionPayloadCapella, error) {
|
||||
return r.Data.ToProto()
|
||||
}
|
||||
|
||||
// ToProto returns a ExecutionPayloadCapella Proto.
|
||||
func (p *ExecutionPayloadCapella) ToProto() (*v1.ExecutionPayloadCapella, error) {
|
||||
txs := make([][]byte, len(p.Transactions))
|
||||
for i := range p.Transactions {
|
||||
txs[i] = p.Transactions[i]
|
||||
txs[i] = bytesutil.SafeCopyBytes(p.Transactions[i])
|
||||
}
|
||||
withdrawals := make([]*v1.Withdrawal, len(p.Withdrawals))
|
||||
for i, w := range p.Withdrawals {
|
||||
withdrawals[i] = &v1.Withdrawal{
|
||||
Index: w.Index.Uint64(),
|
||||
ValidatorIndex: types.ValidatorIndex(w.ValidatorIndex.Uint64()),
|
||||
Address: w.Address,
|
||||
Address: bytesutil.SafeCopyBytes(w.Address),
|
||||
Amount: w.Amount.Uint64(),
|
||||
}
|
||||
}
|
||||
return &v1.ExecutionPayloadCapella{
|
||||
ParentHash: p.ParentHash,
|
||||
FeeRecipient: p.FeeRecipient,
|
||||
StateRoot: p.StateRoot,
|
||||
ReceiptsRoot: p.ReceiptsRoot,
|
||||
LogsBloom: p.LogsBloom,
|
||||
PrevRandao: p.PrevRandao,
|
||||
ParentHash: bytesutil.SafeCopyBytes(p.ParentHash),
|
||||
FeeRecipient: bytesutil.SafeCopyBytes(p.FeeRecipient),
|
||||
StateRoot: bytesutil.SafeCopyBytes(p.StateRoot),
|
||||
ReceiptsRoot: bytesutil.SafeCopyBytes(p.ReceiptsRoot),
|
||||
LogsBloom: bytesutil.SafeCopyBytes(p.LogsBloom),
|
||||
PrevRandao: bytesutil.SafeCopyBytes(p.PrevRandao),
|
||||
BlockNumber: uint64(p.BlockNumber),
|
||||
GasLimit: uint64(p.GasLimit),
|
||||
GasUsed: uint64(p.GasUsed),
|
||||
Timestamp: uint64(p.Timestamp),
|
||||
ExtraData: p.ExtraData,
|
||||
BaseFeePerGas: p.BaseFeePerGas.SSZBytes(),
|
||||
BlockHash: p.BlockHash,
|
||||
ExtraData: bytesutil.SafeCopyBytes(p.ExtraData),
|
||||
BaseFeePerGas: bytesutil.SafeCopyBytes(p.BaseFeePerGas.SSZBytes()),
|
||||
BlockHash: bytesutil.SafeCopyBytes(p.BlockHash),
|
||||
Transactions: txs,
|
||||
Withdrawals: withdrawals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Withdrawal is a field of ExecutionPayloadCapella.
|
||||
type Withdrawal struct {
|
||||
Index Uint256 `json:"index"`
|
||||
ValidatorIndex Uint256 `json:"validator_index"`
|
||||
@@ -594,18 +634,22 @@ type Withdrawal struct {
|
||||
Amount Uint256 `json:"amount"`
|
||||
}
|
||||
|
||||
// SignedBlindedBeaconBlockBellatrix is the request object for builder API /eth/v1/builder/blinded_blocks.
|
||||
type SignedBlindedBeaconBlockBellatrix struct {
|
||||
*eth.SignedBlindedBeaconBlockBellatrix
|
||||
}
|
||||
|
||||
// BlindedBeaconBlockBellatrix is a field in SignedBlindedBeaconBlockBellatrix.
|
||||
type BlindedBeaconBlockBellatrix struct {
|
||||
*eth.BlindedBeaconBlockBellatrix
|
||||
}
|
||||
|
||||
// BlindedBeaconBlockBodyBellatrix is a field in BlindedBeaconBlockBellatrix.
|
||||
type BlindedBeaconBlockBodyBellatrix struct {
|
||||
*eth.BlindedBeaconBlockBodyBellatrix
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of SignedBlindedBeaconBlockBellatrix.
|
||||
func (r *SignedBlindedBeaconBlockBellatrix) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Message *BlindedBeaconBlockBellatrix `json:"message"`
|
||||
@@ -616,6 +660,7 @@ func (r *SignedBlindedBeaconBlockBellatrix) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockBellatrix.
|
||||
func (b *BlindedBeaconBlockBellatrix) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Slot string `json:"slot"`
|
||||
@@ -632,10 +677,12 @@ func (b *BlindedBeaconBlockBellatrix) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// ProposerSlashing is a field in BlindedBeaconBlockBodyCapella.
|
||||
type ProposerSlashing struct {
|
||||
*eth.ProposerSlashing
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of ProposerSlashing.
|
||||
func (s *ProposerSlashing) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
SignedHeader1 *SignedBeaconBlockHeader `json:"signed_header_1"`
|
||||
@@ -646,10 +693,12 @@ func (s *ProposerSlashing) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// SignedBeaconBlockHeader is a field of ProposerSlashing.
|
||||
type SignedBeaconBlockHeader struct {
|
||||
*eth.SignedBeaconBlockHeader
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of SignedBeaconBlockHeader.
|
||||
func (h *SignedBeaconBlockHeader) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Header *BeaconBlockHeader `json:"message"`
|
||||
@@ -660,10 +709,12 @@ func (h *SignedBeaconBlockHeader) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// BeaconBlockHeader is a field of SignedBeaconBlockHeader.
|
||||
type BeaconBlockHeader struct {
|
||||
*eth.BeaconBlockHeader
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of BeaconBlockHeader.
|
||||
func (h *BeaconBlockHeader) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Slot string `json:"slot"`
|
||||
@@ -680,10 +731,12 @@ func (h *BeaconBlockHeader) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// IndexedAttestation is a field of AttesterSlashing.
|
||||
type IndexedAttestation struct {
|
||||
*eth.IndexedAttestation
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of IndexedAttestation.
|
||||
func (a *IndexedAttestation) MarshalJSON() ([]byte, error) {
|
||||
indices := make([]string, len(a.IndexedAttestation.AttestingIndices))
|
||||
for i := range a.IndexedAttestation.AttestingIndices {
|
||||
@@ -700,10 +753,12 @@ func (a *IndexedAttestation) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// AttesterSlashing is a field of a Beacon Block Body.
|
||||
type AttesterSlashing struct {
|
||||
*eth.AttesterSlashing
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of AttesterSlashing.
|
||||
func (s *AttesterSlashing) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Attestation1 *IndexedAttestation `json:"attestation_1"`
|
||||
@@ -714,10 +769,12 @@ func (s *AttesterSlashing) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Checkpoint is a field of AttestationData.
|
||||
type Checkpoint struct {
|
||||
*eth.Checkpoint
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of Checkpoint.
|
||||
func (c *Checkpoint) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Epoch string `json:"epoch"`
|
||||
@@ -728,10 +785,12 @@ func (c *Checkpoint) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// AttestationData is a field of IndexedAttestation.
|
||||
type AttestationData struct {
|
||||
*eth.AttestationData
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of AttestationData.
|
||||
func (a *AttestationData) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Slot string `json:"slot"`
|
||||
@@ -748,10 +807,12 @@ func (a *AttestationData) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Attestation is a field of Beacon Block Body.
|
||||
type Attestation struct {
|
||||
*eth.Attestation
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of Attestation.
|
||||
func (a *Attestation) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
AggregationBits hexutil.Bytes `json:"aggregation_bits"`
|
||||
@@ -764,10 +825,12 @@ func (a *Attestation) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// DepositData is a field of Deposit.
|
||||
type DepositData struct {
|
||||
*eth.Deposit_Data
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of DepositData.
|
||||
func (d *DepositData) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
PublicKey hexutil.Bytes `json:"pubkey"`
|
||||
@@ -782,10 +845,12 @@ func (d *DepositData) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Deposit is a field of Beacon Block Body.
|
||||
type Deposit struct {
|
||||
*eth.Deposit
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of Deposit.
|
||||
func (d *Deposit) MarshalJSON() ([]byte, error) {
|
||||
proof := make([]hexutil.Bytes, len(d.Proof))
|
||||
for i := range d.Proof {
|
||||
@@ -800,10 +865,12 @@ func (d *Deposit) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// SignedVoluntaryExit is a field of Beacon Block Body.
|
||||
type SignedVoluntaryExit struct {
|
||||
*eth.SignedVoluntaryExit
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of SignedVoluntaryExit.
|
||||
func (sve *SignedVoluntaryExit) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Message *VoluntaryExit `json:"message"`
|
||||
@@ -814,10 +881,12 @@ func (sve *SignedVoluntaryExit) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// VoluntaryExit is a field in SignedVoluntaryExit
|
||||
type VoluntaryExit struct {
|
||||
*eth.VoluntaryExit
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of VoluntaryExit
|
||||
func (ve *VoluntaryExit) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Epoch string `json:"epoch"`
|
||||
@@ -828,10 +897,12 @@ func (ve *VoluntaryExit) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// SyncAggregate is a field of Beacon Block Body.
|
||||
type SyncAggregate struct {
|
||||
*eth.SyncAggregate
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of SyncAggregate.
|
||||
func (s *SyncAggregate) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
SyncCommitteeBits hexutil.Bytes `json:"sync_committee_bits"`
|
||||
@@ -842,10 +913,12 @@ func (s *SyncAggregate) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Eth1Data is a field of Beacon Block Body.
|
||||
type Eth1Data struct {
|
||||
*eth.Eth1Data
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of Eth1Data.
|
||||
func (e *Eth1Data) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
DepositRoot hexutil.Bytes `json:"deposit_root"`
|
||||
@@ -858,6 +931,7 @@ func (e *Eth1Data) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockBodyBellatrix.
|
||||
func (b *BlindedBeaconBlockBodyBellatrix) MarshalJSON() ([]byte, error) {
|
||||
sve := make([]*SignedVoluntaryExit, len(b.BlindedBeaconBlockBodyBellatrix.VoluntaryExits))
|
||||
for i := range b.BlindedBeaconBlockBodyBellatrix.VoluntaryExits {
|
||||
@@ -904,10 +978,12 @@ func (b *BlindedBeaconBlockBodyBellatrix) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// SignedBLSToExecutionChange is a field in Beacon Block Body for capella and above.
|
||||
type SignedBLSToExecutionChange struct {
|
||||
*eth.SignedBLSToExecutionChange
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of SignedBLSToExecutionChange.
|
||||
func (ch *SignedBLSToExecutionChange) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Message *BLSToExecutionChange `json:"message"`
|
||||
@@ -918,10 +994,12 @@ func (ch *SignedBLSToExecutionChange) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// BLSToExecutionChange is a field in SignedBLSToExecutionChange.
|
||||
type BLSToExecutionChange struct {
|
||||
*eth.BLSToExecutionChange
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of BLSToExecutionChange.
|
||||
func (ch *BLSToExecutionChange) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
@@ -934,18 +1012,22 @@ func (ch *BLSToExecutionChange) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// SignedBlindedBeaconBlockCapella is part of the request object sent to builder API /eth/v1/builder/blinded_blocks for Capella.
|
||||
type SignedBlindedBeaconBlockCapella struct {
|
||||
*eth.SignedBlindedBeaconBlockCapella
|
||||
}
|
||||
|
||||
// BlindedBeaconBlockCapella is a field in SignedBlindedBeaconBlockCapella.
|
||||
type BlindedBeaconBlockCapella struct {
|
||||
*eth.BlindedBeaconBlockCapella
|
||||
}
|
||||
|
||||
// BlindedBeaconBlockBodyCapella is a field in BlindedBeaconBlockCapella.
|
||||
type BlindedBeaconBlockBodyCapella struct {
|
||||
*eth.BlindedBeaconBlockBodyCapella
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of SignedBlindedBeaconBlockCapella.
|
||||
func (b *SignedBlindedBeaconBlockCapella) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Message *BlindedBeaconBlockCapella `json:"message"`
|
||||
@@ -956,6 +1038,7 @@ func (b *SignedBlindedBeaconBlockCapella) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockCapella
|
||||
func (b *BlindedBeaconBlockCapella) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
Slot string `json:"slot"`
|
||||
@@ -972,6 +1055,7 @@ func (b *BlindedBeaconBlockCapella) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockBodyCapella
|
||||
func (b *BlindedBeaconBlockBodyCapella) MarshalJSON() ([]byte, error) {
|
||||
sve := make([]*SignedVoluntaryExit, len(b.VoluntaryExits))
|
||||
for i := range b.VoluntaryExits {
|
||||
@@ -1024,6 +1108,7 @@ func (b *BlindedBeaconBlockBodyCapella) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// ErrorMessage is a JSON representation of the builder API's returned error message.
|
||||
type ErrorMessage struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
|
||||
97
api/client/client.go
Normal file
97
api/client/client.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Client is a wrapper object around the HTTP client.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
baseURL *url.URL
|
||||
token string
|
||||
}
|
||||
|
||||
// NewClient constructs a new client with the provided options (ex WithTimeout).
|
||||
// `host` is the base host + port used to construct request urls. This value can be
|
||||
// a URL string, or NewClient will assume an http endpoint if just `host:port` is used.
|
||||
func NewClient(host string, opts ...ClientOpt) (*Client, error) {
|
||||
u, err := urlForHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
hc: &http.Client{},
|
||||
baseURL: u,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Token returns the bearer token used for jwt authentication
|
||||
func (c *Client) Token() string {
|
||||
return c.token
|
||||
}
|
||||
|
||||
// BaseURL returns the base url of the client
|
||||
func (c *Client) BaseURL() *url.URL {
|
||||
return c.baseURL
|
||||
}
|
||||
|
||||
// Do execute the request against the http client
|
||||
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
||||
return c.hc.Do(req)
|
||||
}
|
||||
|
||||
func urlForHost(h string) (*url.URL, error) {
|
||||
// try to parse as url (being permissive)
|
||||
u, err := url.Parse(h)
|
||||
if err == nil && u.Host != "" {
|
||||
return u, nil
|
||||
}
|
||||
// try to parse as host:port
|
||||
host, port, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
return nil, ErrMalformedHostname
|
||||
}
|
||||
return &url.URL{Host: net.JoinHostPort(host, port), Scheme: "http"}, nil
|
||||
}
|
||||
|
||||
// NodeURL returns a human-readable string representation of the beacon node base url.
|
||||
func (c *Client) NodeURL() string {
|
||||
return c.baseURL.String()
|
||||
}
|
||||
|
||||
// Get is a generic, opinionated GET function to reduce boilerplate amongst the getters in this package.
|
||||
func (c *Client) Get(ctx context.Context, path string, opts ...ReqOption) ([]byte, error) {
|
||||
u := c.baseURL.ResolveReference(&url.URL{Path: path})
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(req)
|
||||
}
|
||||
r, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
err = r.Body.Close()
|
||||
}()
|
||||
if r.StatusCode != http.StatusOK {
|
||||
return nil, Non200Err(r)
|
||||
}
|
||||
b, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error reading http response body")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
48
api/client/client_test.go
Normal file
48
api/client/client_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
func TestValidHostname(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
hostArg string
|
||||
path string
|
||||
joined string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "hostname without port",
|
||||
hostArg: "mydomain.org",
|
||||
err: ErrMalformedHostname,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
cl, err := NewClient(c.hostArg)
|
||||
if c.err != nil {
|
||||
require.ErrorIs(t, err, c.err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.joined, cl.BaseURL().ResolveReference(&url.URL{Path: c.path}).String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithAuthenticationToken(t *testing.T) {
|
||||
cl, err := NewClient("https://www.offchainlabs.com:3500", WithAuthenticationToken("my token"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cl.Token(), "my token")
|
||||
}
|
||||
|
||||
func TestBaseURL(t *testing.T) {
|
||||
cl, err := NewClient("https://www.offchainlabs.com:3500")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "www.offchainlabs.com", cl.BaseURL().Hostname())
|
||||
require.Equal(t, "3500", cl.BaseURL().Port())
|
||||
}
|
||||
40
api/client/errors.go
Normal file
40
api/client/errors.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrMalformedHostname is used to indicate if a host name's format is incorrect.
|
||||
var ErrMalformedHostname = errors.New("hostname must include port, separated by one colon, like example.com:3500")
|
||||
|
||||
// ErrNotOK is used to indicate when an HTTP request to the API failed with any non-2xx response code.
|
||||
// More specific errors may be returned, but an error in reaction to a non-2xx response will always wrap ErrNotOK.
|
||||
var ErrNotOK = errors.New("did not receive 2xx response from API")
|
||||
|
||||
// ErrNotFound specifically means that a '404 - NOT FOUND' response was received from the API.
|
||||
var ErrNotFound = errors.Wrap(ErrNotOK, "recv 404 NotFound response from API")
|
||||
|
||||
// ErrInvalidNodeVersion indicates that the /eth/v1/node/version API response format was not recognized.
|
||||
var ErrInvalidNodeVersion = errors.New("invalid node version response")
|
||||
|
||||
// Non200Err is a function that parses an HTTP response to handle responses that are not 200 with a formatted error.
|
||||
func Non200Err(response *http.Response) error {
|
||||
bodyBytes, err := io.ReadAll(response.Body)
|
||||
var body string
|
||||
if err != nil {
|
||||
body = "(Unable to read response body.)"
|
||||
} else {
|
||||
body = "response body:\n" + string(bodyBytes)
|
||||
}
|
||||
msg := fmt.Sprintf("code=%d, url=%s, body=%s", response.StatusCode, response.Request.URL, body)
|
||||
switch response.StatusCode {
|
||||
case 404:
|
||||
return errors.Wrap(ErrNotFound, msg)
|
||||
default:
|
||||
return errors.Wrap(ErrNotOK, msg)
|
||||
}
|
||||
}
|
||||
48
api/client/options.go
Normal file
48
api/client/options.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReqOption is a request functional option.
|
||||
type ReqOption func(*http.Request)
|
||||
|
||||
// WithSSZEncoding is a request functional option that adds SSZ encoding header.
|
||||
func WithSSZEncoding() ReqOption {
|
||||
return func(req *http.Request) {
|
||||
req.Header.Set("Accept", "application/octet-stream")
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthorizationToken is a request functional option that adds header for authorization token.
|
||||
func WithAuthorizationToken(token string) ReqOption {
|
||||
return func(req *http.Request) {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
}
|
||||
|
||||
// ClientOpt is a functional option for the Client type (http.Client wrapper)
|
||||
type ClientOpt func(*Client)
|
||||
|
||||
// WithTimeout sets the .Timeout attribute of the wrapped http.Client.
|
||||
func WithTimeout(timeout time.Duration) ClientOpt {
|
||||
return func(c *Client) {
|
||||
c.hc.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithRoundTripper replaces the underlying HTTP's transport with a custom one.
|
||||
func WithRoundTripper(t http.RoundTripper) ClientOpt {
|
||||
return func(c *Client) {
|
||||
c.hc.Transport = t
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthenticationToken sets an oauth token to be used.
|
||||
func WithAuthenticationToken(token string) ClientOpt {
|
||||
return func(c *Client) {
|
||||
c.token = token
|
||||
}
|
||||
}
|
||||
13
api/client/validator/BUILD.bazel
Normal file
13
api/client/validator/BUILD.bazel
Normal file
@@ -0,0 +1,13 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["client.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/api/client/validator",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/client:go_default_library",
|
||||
"//validator/rpc/apimiddleware:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
121
api/client/validator/client.go
Normal file
121
api/client/validator/client.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/validator/rpc/apimiddleware"
|
||||
)
|
||||
|
||||
const (
|
||||
localKeysPath = "/eth/v1/keystores"
|
||||
remoteKeysPath = "/eth/v1/remotekeys"
|
||||
feeRecipientPath = "/eth/v1/validator/{pubkey}/feerecipient"
|
||||
)
|
||||
|
||||
// Client provides a collection of helper methods for calling the Keymanager API endpoints.
|
||||
type Client struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// NewClient returns a new Client that includes functions for REST calls to keymanager APIs.
|
||||
func NewClient(host string, opts ...client.ClientOpt) (*Client, error) {
|
||||
c, err := client.NewClient(host, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{c}, nil
|
||||
}
|
||||
|
||||
// GetValidatorPubKeys gets the current list of web3signer or the local validator public keys in hex format.
|
||||
func (c *Client) GetValidatorPubKeys(ctx context.Context) ([]string, error) {
|
||||
jsonlocal, err := c.GetLocalValidatorKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonremote, err := c.GetRemoteValidatorKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(jsonlocal.Keystores) == 0 && len(jsonremote.Keystores) == 0 {
|
||||
return nil, errors.New("there are no local keys or remote keys on the validator")
|
||||
}
|
||||
|
||||
hexKeys := make(map[string]bool)
|
||||
|
||||
for index := range jsonlocal.Keystores {
|
||||
hexKeys[jsonlocal.Keystores[index].ValidatingPubkey] = true
|
||||
}
|
||||
for index := range jsonremote.Keystores {
|
||||
hexKeys[jsonremote.Keystores[index].Pubkey] = true
|
||||
}
|
||||
keys := make([]string, 0)
|
||||
for k := range hexKeys {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// GetLocalValidatorKeys calls the keymanager APIs for local validator keys
|
||||
func (c *Client) GetLocalValidatorKeys(ctx context.Context) (*apimiddleware.ListKeystoresResponseJson, error) {
|
||||
localBytes, err := c.Get(ctx, localKeysPath, client.WithAuthorizationToken(c.Token()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsonlocal := &apimiddleware.ListKeystoresResponseJson{}
|
||||
if err := json.Unmarshal(localBytes, jsonlocal); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse local keystore list")
|
||||
}
|
||||
return jsonlocal, nil
|
||||
}
|
||||
|
||||
// GetRemoteValidatorKeys calls the keymanager APIs for web3signer validator keys
|
||||
func (c *Client) GetRemoteValidatorKeys(ctx context.Context) (*apimiddleware.ListRemoteKeysResponseJson, error) {
|
||||
remoteBytes, err := c.Get(ctx, remoteKeysPath, client.WithAuthorizationToken(c.Token()))
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "Prysm Wallet is not of type Web3Signer") {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
jsonremote := &apimiddleware.ListRemoteKeysResponseJson{}
|
||||
if len(remoteBytes) != 0 {
|
||||
if err := json.Unmarshal(remoteBytes, jsonremote); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse remote keystore list")
|
||||
}
|
||||
}
|
||||
return jsonremote, nil
|
||||
}
|
||||
|
||||
// GetFeeRecipientAddresses takes a list of validators in hex format and returns an equal length list of fee recipients in hex format.
|
||||
func (c *Client) GetFeeRecipientAddresses(ctx context.Context, validators []string) ([]string, error) {
|
||||
feeRecipients := make([]string, len(validators))
|
||||
for index, validator := range validators {
|
||||
feejson, err := c.GetFeeRecipientAddress(ctx, validator)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("keymanager API failed to retrieve fee recipient for validator %s", validators[index]))
|
||||
}
|
||||
if feejson.Data == nil {
|
||||
continue
|
||||
}
|
||||
feeRecipients[index] = feejson.Data.Ethaddress
|
||||
}
|
||||
return feeRecipients, nil
|
||||
}
|
||||
|
||||
// GetFeeRecipientAddress takes a public key and calls the keymanager API to return its fee recipient.
|
||||
func (c *Client) GetFeeRecipientAddress(ctx context.Context, pubkey string) (*apimiddleware.GetFeeRecipientByPubkeyResponseJson, error) {
|
||||
path := strings.Replace(feeRecipientPath, "{pubkey}", pubkey, 1)
|
||||
b, err := c.Get(ctx, path, client.WithAuthorizationToken(c.Token()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
feejson := &apimiddleware.GetFeeRecipientByPubkeyResponseJson{}
|
||||
if err := json.Unmarshal(b, feejson); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse fee recipient")
|
||||
}
|
||||
return feejson, nil
|
||||
}
|
||||
@@ -144,6 +144,7 @@ func (f *Feed) Send(value interface{}) (nsent int) {
|
||||
|
||||
if !f.typecheck(rvalue.Type()) {
|
||||
f.sendLock <- struct{}{}
|
||||
f.mu.Unlock()
|
||||
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
@@ -32,6 +32,8 @@ func TestFeedPanics(t *testing.T) {
|
||||
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
|
||||
|
||||
@@ -119,6 +119,7 @@ go_test(
|
||||
"process_block_test.go",
|
||||
"receive_attestation_test.go",
|
||||
"receive_block_test.go",
|
||||
"scenarios_test.go",
|
||||
"service_test.go",
|
||||
"setup_test.go",
|
||||
"weak_subjectivity_checks_test.go",
|
||||
|
||||
@@ -157,7 +157,11 @@ func (s *Service) getSyncCommitteeHeadState(ctx context.Context, slot primitives
|
||||
if headState == nil || headState.IsNil() {
|
||||
return nil, errors.New("nil state")
|
||||
}
|
||||
headState, err = transition.ProcessSlotsIfPossible(ctx, headState, slot)
|
||||
headRoot, err := s.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headState, err = transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
|
||||
func TestService_HeadSyncCommitteeIndices(t *testing.T) {
|
||||
s, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize)
|
||||
c := &Service{}
|
||||
c := &Service{cfg: &config{BeaconDB: dbTest.SetupDB(t)}}
|
||||
c.head = &head{state: s}
|
||||
|
||||
// Current period
|
||||
@@ -38,7 +39,7 @@ func TestService_HeadSyncCommitteeIndices(t *testing.T) {
|
||||
|
||||
func TestService_headCurrentSyncCommitteeIndices(t *testing.T) {
|
||||
s, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize)
|
||||
c := &Service{}
|
||||
c := &Service{cfg: &config{BeaconDB: dbTest.SetupDB(t)}}
|
||||
c.head = &head{state: s}
|
||||
|
||||
// Process slot up to `EpochsPerSyncCommitteePeriod` so it can `ProcessSyncCommitteeUpdates`.
|
||||
@@ -66,7 +67,7 @@ func TestService_headNextSyncCommitteeIndices(t *testing.T) {
|
||||
|
||||
func TestService_HeadSyncCommitteePubKeys(t *testing.T) {
|
||||
s, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize)
|
||||
c := &Service{}
|
||||
c := &Service{cfg: &config{BeaconDB: dbTest.SetupDB(t)}}
|
||||
c.head = &head{state: s}
|
||||
|
||||
// Process slot up to 2 * `EpochsPerSyncCommitteePeriod` so it can run `ProcessSyncCommitteeUpdates` twice.
|
||||
@@ -81,7 +82,7 @@ func TestService_HeadSyncCommitteePubKeys(t *testing.T) {
|
||||
|
||||
func TestService_HeadSyncCommitteeDomain(t *testing.T) {
|
||||
s, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().TargetCommitteeSize)
|
||||
c := &Service{}
|
||||
c := &Service{cfg: &config{BeaconDB: dbTest.SetupDB(t)}}
|
||||
c.head = &head{state: s}
|
||||
|
||||
wanted, err := signing.Domain(s.Fork(), slots.ToEpoch(s.Slot()), params.BeaconConfig().DomainSyncCommittee, s.GenesisValidatorsRoot())
|
||||
|
||||
@@ -53,7 +53,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
log = log.WithField("payloadHash", fmt.Sprintf("%#x", bytesutil.Trunc(p.BlockHash())))
|
||||
txs, err := p.Transactions()
|
||||
switch {
|
||||
case errors.Is(err, consensus_types.ErrUnsupportedGetter):
|
||||
case errors.Is(err, consensus_types.ErrUnsupportedField):
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
|
||||
@@ -20,6 +20,16 @@ import (
|
||||
|
||||
// getAttPreState retrieves the att pre state by either from the cache or the DB.
|
||||
func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (state.ReadOnlyBeaconState, error) {
|
||||
// If the attestation is recent and canonical we can use the head state to compute the shuffling.
|
||||
headEpoch := slots.ToEpoch(s.HeadSlot())
|
||||
if c.Epoch == headEpoch {
|
||||
targetSlot, err := s.cfg.ForkChoiceStore.Slot([32]byte(c.Root))
|
||||
if err == nil && slots.ToEpoch(targetSlot)+1 >= headEpoch {
|
||||
if s.cfg.ForkChoiceStore.IsCanonical([32]byte(c.Root)) {
|
||||
return s.HeadStateReadOnly(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use a multilock to allow scoped holding of a mutex by a checkpoint root + epoch
|
||||
// allowing us to behave smarter in terms of how this function is used concurrently.
|
||||
epochKey := strconv.FormatUint(uint64(c.Epoch), 10 /* base 10 */)
|
||||
@@ -33,17 +43,6 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (stat
|
||||
if cachedState != nil && !cachedState.IsNil() {
|
||||
return cachedState, nil
|
||||
}
|
||||
// If the attestation is recent and canonical we can use the head state to compute the shuffling.
|
||||
headEpoch := slots.ToEpoch(s.HeadSlot())
|
||||
if c.Epoch == headEpoch {
|
||||
targetSlot, err := s.cfg.ForkChoiceStore.Slot([32]byte(c.Root))
|
||||
if err == nil && slots.ToEpoch(targetSlot)+1 >= headEpoch {
|
||||
if s.cfg.ForkChoiceStore.IsCanonical([32]byte(c.Root)) {
|
||||
return s.HeadStateReadOnly(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try the next slot cache for the early epoch calls, this should mostly have been covered already
|
||||
// but is cheap
|
||||
slot, err := slots.EpochStart(c.Epoch)
|
||||
@@ -52,12 +51,14 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (stat
|
||||
}
|
||||
cachedState = transition.NextSlotState(c.Root, slot)
|
||||
if cachedState != nil && !cachedState.IsNil() {
|
||||
if cachedState.Slot() == slot {
|
||||
return cachedState, nil
|
||||
if cachedState.Slot() != slot {
|
||||
cachedState, err = transition.ProcessSlots(ctx, cachedState, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process slots")
|
||||
}
|
||||
}
|
||||
cachedState, err = transition.ProcessSlots(ctx, cachedState, slot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process slots")
|
||||
if err := s.checkpointStateCache.AddCheckpointState(c, cachedState); err != nil {
|
||||
return nil, errors.Wrap(err, "could not save checkpoint state to cache")
|
||||
}
|
||||
return cachedState, nil
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (stat
|
||||
return nil, errors.Wrap(err, "could not check checkpoint condition in forkchoice")
|
||||
}
|
||||
if !ok {
|
||||
return nil, ErrNotCheckpoint
|
||||
return nil, errors.Wrap(ErrNotCheckpoint, fmt.Sprintf("epoch %d root %#x", c.Epoch, c.Root))
|
||||
}
|
||||
|
||||
// Fallback to state regeneration.
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestStore_SaveCheckpointState(t *testing.T) {
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: bytesutil.PadTo([]byte{'B'}, fieldparams.RootLength)}))
|
||||
|
||||
s2, err := service.getAttPreState(ctx, cp2)
|
||||
require.ErrorIs(t, ErrNotCheckpoint, err)
|
||||
require.ErrorContains(t, "epoch 2 root 0x4200000000000000000000000000000000000000000000000000000000000000: not a checkpoint in forkchoice", err)
|
||||
|
||||
st, root, err = prepareForkchoiceState(ctx, 33, [32]byte(cp2.Root), [32]byte(cp1.Root), [32]byte{'R'}, cp2, cp2)
|
||||
require.NoError(t, err)
|
||||
@@ -328,3 +328,22 @@ func TestVerifyBeaconBlock_OK(t *testing.T) {
|
||||
|
||||
assert.NoError(t, service.verifyBeaconBlock(ctx, d), "Did not receive the wanted error")
|
||||
}
|
||||
|
||||
func TestGetAttPreState_HeadState(t *testing.T) {
|
||||
service, tr := minimalTestService(t)
|
||||
ctx := tr.ctx
|
||||
baseState, _ := util.DeterministicGenesisState(t, 1)
|
||||
|
||||
epoch := primitives.Epoch(1)
|
||||
blk := util.NewBeaconBlock()
|
||||
r1, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
checkpoint := ðpb.Checkpoint{Epoch: epoch, Root: r1[:]}
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, baseState, bytesutil.ToBytes32(checkpoint.Root)))
|
||||
require.NoError(t, transition.UpdateNextSlotCache(ctx, checkpoint.Root, baseState))
|
||||
_, err = service.getAttPreState(ctx, checkpoint)
|
||||
require.NoError(t, err)
|
||||
st, err := service.checkpointStateCache.StateByCheckpoint(checkpoint)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().SlotsPerEpoch, st.Slot())
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ func (s *Service) onBlock(ctx context.Context, signed interfaces.ReadOnlySignedB
|
||||
}()
|
||||
}
|
||||
defer reportAttestationInclusion(b)
|
||||
if err := s.handleEpochBoundary(ctx, postState); err != nil {
|
||||
if err := s.handleEpochBoundary(ctx, postState, blockRoot[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
onBlockProcessingTime.Observe(float64(time.Since(startTime).Milliseconds()))
|
||||
@@ -483,14 +483,14 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []interfaces.ReadOnlySi
|
||||
}
|
||||
|
||||
// Epoch boundary bookkeeping such as logging epoch summaries.
|
||||
func (s *Service) handleEpochBoundary(ctx context.Context, postState state.BeaconState) error {
|
||||
func (s *Service) handleEpochBoundary(ctx context.Context, postState state.BeaconState, blockRoot []byte) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.handleEpochBoundary")
|
||||
defer span.End()
|
||||
|
||||
var err error
|
||||
if postState.Slot()+1 == s.nextEpochBoundarySlot {
|
||||
copied := postState.Copy()
|
||||
copied, err := transition.ProcessSlots(ctx, copied, copied.Slot()+1)
|
||||
copied, err := transition.ProcessSlotsUsingNextSlotCache(ctx, copied, blockRoot, copied.Slot()+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -651,26 +651,24 @@ func (s *Service) validateMergeTransitionBlock(ctx context.Context, stateVersion
|
||||
|
||||
// This routine checks if there is a cached proposer payload ID available for the next slot proposer.
|
||||
// If there is not, it will call forkchoice updated with the correct payload attribute then cache the payload ID.
|
||||
func (s *Service) spawnLateBlockTasksLoop() {
|
||||
go func() {
|
||||
_, err := s.clockWaiter.WaitForClock(s.ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("spawnLateBlockTasksLoop encountered an error waiting for initialization")
|
||||
func (s *Service) runLateBlockTasks() {
|
||||
_, err := s.clockWaiter.WaitForClock(s.ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("runLateBlockTasks encountered an error waiting for initialization")
|
||||
return
|
||||
}
|
||||
attThreshold := params.BeaconConfig().SecondsPerSlot / 3
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, time.Duration(attThreshold)*time.Second, params.BeaconConfig().SecondsPerSlot)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C():
|
||||
s.lateBlockTasks(s.ctx)
|
||||
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting routine")
|
||||
return
|
||||
}
|
||||
attThreshold := params.BeaconConfig().SecondsPerSlot / 3
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, time.Duration(attThreshold)*time.Second, params.BeaconConfig().SecondsPerSlot)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C():
|
||||
s.lateBlockTasks(s.ctx)
|
||||
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting routine")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// lateBlockTasks is called 4 seconds into the slot and performs tasks
|
||||
@@ -685,12 +683,26 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
Type: statefeed.MissedSlot,
|
||||
})
|
||||
|
||||
headRoot := s.headRoot()
|
||||
headState := s.headState(ctx)
|
||||
lastRoot, lastState := transition.LastCachedState()
|
||||
if lastState == nil {
|
||||
lastRoot, lastState = headRoot[:], headState
|
||||
}
|
||||
// Copy all the field tries in our cached state in the event of late
|
||||
// blocks.
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("could not update next slot state cache")
|
||||
}
|
||||
|
||||
// Head root should be empty when retrieving proposer index for the next slot.
|
||||
_, id, has := s.cfg.ProposerSlotIndexCache.GetProposerPayloadIDs(s.CurrentSlot()+1, [32]byte{} /* head root */)
|
||||
// There exists proposer for next slot, but we haven't called fcu w/ payload attribute yet.
|
||||
if (!has && !features.Get().PrepareAllPayloads) || id != [8]byte{} {
|
||||
return
|
||||
}
|
||||
|
||||
s.headLock.RLock()
|
||||
headBlock, err := s.headBlock()
|
||||
if err != nil {
|
||||
@@ -698,8 +710,6 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve head block")
|
||||
return
|
||||
}
|
||||
headRoot := s.headRoot()
|
||||
headState := s.headState(ctx)
|
||||
s.headLock.RUnlock()
|
||||
_, err = s.notifyForkchoiceUpdate(ctx, ¬ifyForkchoiceUpdateArg{
|
||||
headState: headState,
|
||||
@@ -709,11 +719,4 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
}
|
||||
lastRoot, lastState := transition.LastCachedState()
|
||||
if lastState == nil {
|
||||
lastRoot, lastState = headRoot[:], headState
|
||||
}
|
||||
if err = transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("could not update next slot state cache")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ func TestHandleEpochBoundary_UpdateFirstSlot(t *testing.T) {
|
||||
s, _ := util.DeterministicGenesisState(t, 1024)
|
||||
service.head = &head{state: s}
|
||||
require.NoError(t, s.SetSlot(2*params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, service.handleEpochBoundary(ctx, s))
|
||||
require.NoError(t, service.handleEpochBoundary(ctx, s, []byte{}))
|
||||
require.Equal(t, 3*params.BeaconConfig().SlotsPerEpoch, service.nextEpochBoundarySlot)
|
||||
}
|
||||
|
||||
|
||||
363
beacon-chain/blockchain/scenarios_test.go
Normal file
363
beacon-chain/blockchain/scenarios_test.go
Normal file
@@ -0,0 +1,363 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
mockExecution "github.com/prysmaticlabs/prysm/v4/beacon-chain/execution/testing"
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/startup"
|
||||
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
type Unmarshaler interface {
|
||||
UnmarshalSSZ(buf []byte) error
|
||||
}
|
||||
|
||||
// dataFetcher fetches and unmarshals data from file to provided data structure.
|
||||
func dataFetcher(fPath string, data Unmarshaler) error {
|
||||
rawFile, err := os.ReadFile(fPath) // #nosec G304
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return data.UnmarshalSSZ(rawFile)
|
||||
}
|
||||
|
||||
// Test setup:
|
||||
// Start with a head at slot 6653310
|
||||
// We import a timely block 6653311 this should warm up the caches
|
||||
// We import a late block 6653312 (epoch boundary). This should advance the NCS.
|
||||
// We import an early block 6653313 (slot 1). This should cause a reorg.
|
||||
//
|
||||
// This test does not run a full node, but rather runs the full blockchain
|
||||
// package without execution engine deadlocks. If you suspect that a delay comes
|
||||
// from the EL you should not use this template. This test also does not fully
|
||||
// cover the DB paths, it tricks the DB in thinking that we are doing a
|
||||
// checkpoint sync from the justified state so that it will fail to migrate old
|
||||
// states to cold.
|
||||
func TestLateSlot1(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
slot32 := primitives.Slot(6653312)
|
||||
baseDir := "/home/heluani/Documents/code/ethereum/prysm/beacon-chain/blockchain/testing/scenario/"
|
||||
// finalizedState is the current finalized state at slot 30.
|
||||
finalizedStateData := ðpb.BeaconStateCapella{}
|
||||
sszPathFinalizedState := baseDir + "state_finalized.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathFinalizedState, finalizedStateData))
|
||||
finalizedState, err := state_native.InitializeFromProtoCapella(finalizedStateData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, finalizedState.Slot()+96)
|
||||
|
||||
// finalizedBlock is the beacon block with the root that was finalized
|
||||
// checkpoint root.
|
||||
finalizedBlockData := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathFinalizedBlock := baseDir + "block_finalized.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathFinalizedBlock, finalizedBlockData))
|
||||
finalizedBlock, err := consensus_blocks.NewSignedBeaconBlock(finalizedBlockData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, finalizedBlock.Block().Slot()+96)
|
||||
|
||||
// justifiedState is the current justified state at slot 30, we will
|
||||
// consider this to be our genesis. It is at slot32 - 64.
|
||||
justifiedStateData := ðpb.BeaconStateCapella{}
|
||||
sszPathJustifiedState := baseDir + "state_justified.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathJustifiedState, justifiedStateData))
|
||||
justifiedState, err := state_native.InitializeFromProtoCapella(justifiedStateData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, justifiedState.Slot()+64)
|
||||
|
||||
// justifiedBlock is the beacon block with the root that was justified
|
||||
// checkpoint root.
|
||||
justifiedBlockData := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathJustifiedBlock := baseDir + "block_justified.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathJustifiedBlock, justifiedBlockData))
|
||||
justifiedBlock, err := consensus_blocks.NewSignedBeaconBlock(justifiedBlockData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, justifiedBlock.Block().Slot()+64)
|
||||
|
||||
// finalizedJustifiedState is the beacon state at the justification
|
||||
// checkpoint of the finalized checkpoint of the state at slot 30,
|
||||
// It is needed to obtain the right balances when starting
|
||||
finalizedJustifiedStateData := ðpb.BeaconStateCapella{}
|
||||
sszPathFinalizedJustifiedState := baseDir + "state_finalized_justified.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathFinalizedJustifiedState, finalizedJustifiedStateData))
|
||||
finalizedJustifiedState, err := state_native.InitializeFromProtoCapella(finalizedJustifiedStateData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, finalizedJustifiedState.Slot()+128)
|
||||
|
||||
// finalizedJustifiedBlock is the beacon block with the root that was
|
||||
// the finalizedJustified checkpoint root.
|
||||
finalizedJustifiedBlockData := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathFinalizedJustifiedBlock := baseDir + "block_finalized_justified.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathFinalizedJustifiedBlock, finalizedJustifiedBlockData))
|
||||
finalizedJustifiedBlock, err := consensus_blocks.NewSignedBeaconBlock(finalizedJustifiedBlockData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, finalizedJustifiedBlock.Block().Slot()+128)
|
||||
|
||||
// postState30 is the state at slot 30 right after applying the block at
|
||||
// slot 30.
|
||||
postState30Data := ðpb.BeaconStateCapella{}
|
||||
sszPathPostState30 := baseDir + "post_state_30.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathPostState30, postState30Data))
|
||||
postState30, err := state_native.InitializeFromProtoCapella(postState30Data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, postState30.Slot()+2)
|
||||
|
||||
// block30 is the beacon block that arrived at slot 30. It's the
|
||||
// starting headRoot for the test
|
||||
beaconBlock30Data := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathBlock30 := baseDir + "block30.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathBlock30, beaconBlock30Data))
|
||||
block30, err := consensus_blocks.NewSignedBeaconBlock(beaconBlock30Data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, block30.Block().Slot()+2)
|
||||
|
||||
// block31 is the beacon block that arrived at slot 31.
|
||||
beaconBlock31Data := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathBlock31 := baseDir + "block31.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathBlock31, beaconBlock31Data))
|
||||
block31, err := consensus_blocks.NewSignedBeaconBlock(beaconBlock31Data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, block31.Block().Slot()+1)
|
||||
|
||||
// block32 is the beacon block that arrived at slot 32.
|
||||
beaconBlock32Data := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathBlock32 := baseDir + "block32.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathBlock32, beaconBlock32Data))
|
||||
block32, err := consensus_blocks.NewSignedBeaconBlock(beaconBlock32Data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, block32.Block().Slot())
|
||||
|
||||
// block33 is the beacon block that arrived at slot 33, this block is
|
||||
// reorged
|
||||
beaconBlock33Data := ðpb.SignedBeaconBlockCapella{}
|
||||
sszPathBlock33 := baseDir + "block33.ssz"
|
||||
require.NoError(t, dataFetcher(sszPathBlock33, beaconBlock33Data))
|
||||
block33, err := consensus_blocks.NewSignedBeaconBlock(beaconBlock33Data)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32+1, block33.Block().Slot())
|
||||
require.Equal(t, block32.Block().ParentRoot(), block33.Block().ParentRoot())
|
||||
|
||||
// newJustifiedBlock is the beacon block that arrived at slot 0, it's the
|
||||
// block that becomes justified at the epoch boundary.
|
||||
newJustifiedBlockData := ðpb.SignedBeaconBlockCapella{}
|
||||
newJustifiedBlockPath := baseDir + "new_justified_block.ssz"
|
||||
require.NoError(t, dataFetcher(newJustifiedBlockPath, newJustifiedBlockData))
|
||||
newJustifiedBlock, err := consensus_blocks.NewSignedBeaconBlock(newJustifiedBlockData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, newJustifiedBlock.Block().Slot()+32)
|
||||
|
||||
// newJustifiedState is the beacon State at slot 0. It is the state that
|
||||
// gets justified at the epoch boundary.
|
||||
newJustifiedStateData := ðpb.BeaconStateCapella{}
|
||||
newJustifiedStatePath := baseDir + "new_justified_state.ssz"
|
||||
require.NoError(t, dataFetcher(newJustifiedStatePath, newJustifiedStateData))
|
||||
newJustifiedState, err := state_native.InitializeFromProtoCapella(newJustifiedStateData)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, slot32, newJustifiedState.Slot()+32)
|
||||
|
||||
// Setup the service
|
||||
service, tr := minimalTestService(t)
|
||||
ctx, beaconDB, fcs := tr.ctx, tr.db, tr.fcs
|
||||
|
||||
// Save the justified and finalized states and blocks.
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, finalizedBlock))
|
||||
finalizedRoot, err := finalizedBlock.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, finalizedState.Copy(), finalizedRoot))
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, justifiedBlock))
|
||||
justifiedRoot, err := justifiedBlock.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, justifiedState.Copy(), justifiedRoot))
|
||||
fcp := postState30.FinalizedCheckpoint()
|
||||
require.Equal(t, [32]byte(fcp.Root), finalizedRoot)
|
||||
jcp := postState30.CurrentJustifiedCheckpoint()
|
||||
require.Equal(t, [32]byte(jcp.Root), justifiedRoot)
|
||||
finalizedSummary := ðpb.StateSummary{Slot: finalizedState.Slot(), Root: finalizedRoot[:]}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, finalizedSummary))
|
||||
justifiedSummary := ðpb.StateSummary{Slot: justifiedState.Slot(), Root: justifiedRoot[:]}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, justifiedSummary))
|
||||
|
||||
// Forkchoice requires justified balances,
|
||||
// For this we need to save the justified checkpoint of the
|
||||
// corresponding finalized state.
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, finalizedJustifiedBlock))
|
||||
finalizedJustifiedRoot, err := finalizedJustifiedBlock.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, finalizedJustifiedState.Copy(), finalizedJustifiedRoot))
|
||||
fjcs := finalizedState.CurrentJustifiedCheckpoint()
|
||||
require.Equal(t, [32]byte(fjcs.Root), finalizedJustifiedRoot)
|
||||
finalizedJustifiedSummary := ðpb.StateSummary{Slot: finalizedJustifiedState.Slot(), Root: finalizedJustifiedRoot[:]}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, finalizedJustifiedSummary))
|
||||
|
||||
// Setup forkchoice to have the two checkpoints
|
||||
execution, err := finalizedBlock.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
payloadHash := [32]byte(execution.BlockHash())
|
||||
// We use a fake finalized checkpoint for the finalized checkpoint node
|
||||
// it's self-referencing as if it was genesis
|
||||
st, _, err := prepareForkchoiceState(ctx, slot32-96, finalizedRoot, [32]byte{}, payloadHash, fjcs, fcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, finalizedRoot))
|
||||
|
||||
// We fake the justified checkpoint to have as parent the finalized
|
||||
// checkpoint
|
||||
execution, err = justifiedBlock.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
payloadHash = [32]byte(execution.BlockHash())
|
||||
st, _, err = prepareForkchoiceState(ctx, slot32-64, justifiedRoot, finalizedRoot, payloadHash, fcp, fcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, justifiedRoot))
|
||||
|
||||
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: fcp.Epoch, Root: [32]byte(fcp.Root)}))
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{Epoch: jcp.Epoch, Root: [32]byte(jcp.Root)}))
|
||||
|
||||
// Save the new justified state/block to DB since it will be justified
|
||||
// in the epoch pre-compute, we need to insert this block as well to
|
||||
// forkchoice
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, newJustifiedBlock))
|
||||
newJustifiedRoot, err := newJustifiedBlock.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, newJustifiedState.Copy(), newJustifiedRoot))
|
||||
newJustifiedSummary := ðpb.StateSummary{Slot: newJustifiedState.Slot(), Root: newJustifiedRoot[:]}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, newJustifiedSummary))
|
||||
st, _, err = prepareForkchoiceState(ctx, slot32-32, newJustifiedRoot, justifiedRoot, [32]byte{}, jcp, fcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, newJustifiedRoot))
|
||||
|
||||
// We fake the forkchoice node at 30 since we won't be able to import
|
||||
// the block 31 otherwise. We need to use a full state to be able to
|
||||
// import later block 31. When importing this block we need to set
|
||||
// genesis time so that it is timely
|
||||
secondsSinceGenesis, err := (slot32 - 2).SafeMul(params.BeaconConfig().SecondsPerSlot)
|
||||
require.NoError(t, err)
|
||||
genesisTime := time.Now().Add(-time.Duration(uint64(secondsSinceGenesis)) * time.Second)
|
||||
service.SetGenesisTime(genesisTime)
|
||||
fcs.SetGenesisTime(uint64(genesisTime.Unix()))
|
||||
|
||||
// Start the service that will run the late block tasks and advance
|
||||
// slots
|
||||
var vr [32]byte
|
||||
require.NoError(t, service.clockSetter.SetClock(startup.NewClock(time.Now(), vr)))
|
||||
go service.runLateBlockTasks()
|
||||
go service.spawnProcessAttestationsRoutine()
|
||||
|
||||
block30Root := block31.Block().ParentRoot()
|
||||
fakeState30 := postState30.Copy()
|
||||
bh := fakeState30.LatestBlockHeader()
|
||||
copy(bh.ParentRoot, newJustifiedRoot[:])
|
||||
require.NoError(t, fakeState30.SetLatestBlockHeader(bh))
|
||||
require.NoError(t, fcs.InsertNode(ctx, fakeState30, block30Root))
|
||||
fcHead, err := fcs.Head(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, block30Root, fcHead)
|
||||
|
||||
// Check blockchain package's checkpoints
|
||||
bfcp := service.FinalizedCheckpt()
|
||||
require.Equal(t, fcp.Epoch, bfcp.Epoch)
|
||||
require.Equal(t, [32]byte(bfcp.Root), [32]byte(fcp.Root))
|
||||
bjcp := service.CurrentJustifiedCheckpt()
|
||||
require.DeepEqual(t, jcp, bjcp)
|
||||
|
||||
// Set up the node's head
|
||||
require.NoError(t, beaconDB.SaveBlock(ctx, block30))
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, postState30.Copy(), block30Root))
|
||||
block30Summary := ðpb.StateSummary{Slot: slot32 - 2, Root: block30Root[:]}
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, block30Summary))
|
||||
require.NoError(t, service.setHead(block30Root, block30, postState30))
|
||||
require.NoError(t, beaconDB.SaveHeadBlockRoot(ctx, block30Root))
|
||||
headRoot, err := service.HeadRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, block30Root, [32]byte(headRoot))
|
||||
headState, err := service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, headState.Slot(), slot32-2)
|
||||
|
||||
// Update the node's next slot cache
|
||||
require.NoError(t, transition.UpdateNextSlotCache(ctx, block30Root[:], postState30))
|
||||
preState31 := transition.NextSlotState(block30Root[:], slot32-1)
|
||||
require.NotNil(t, preState31)
|
||||
|
||||
// Mock the execution engine to accept any block as valid
|
||||
executionEngine := &mockExecution.EngineClient{}
|
||||
service.cfg.ExecutionEngineCaller = executionEngine
|
||||
|
||||
// Save the origin checkpoint root to prevent the db from walking up the
|
||||
// ancestry. We are asserting that the justified checkpoint root (that
|
||||
// is at slot32 - 64 is the init checkpoint
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveOriginCheckpointBlockRoot(ctx, justifiedRoot))
|
||||
|
||||
// Wait until slot 31 to import it. We wait so that the late blocks
|
||||
// tasks are run.
|
||||
slot31Start := slots.StartTime(uint64(service.genesisTime.Unix()), slot32-1)
|
||||
time.Sleep(slot31Start.Add(time.Second).Sub(time.Now()))
|
||||
|
||||
// Import block 31 check that headRoot and NSC are right
|
||||
block31Root := block32.Block().ParentRoot()
|
||||
require.NoError(t, service.ReceiveBlock(ctx, block31, block31Root))
|
||||
headRoot, err = service.HeadRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, block30Root, block31.Block().ParentRoot())
|
||||
require.Equal(t, block31Root, [32]byte(headRoot))
|
||||
headState, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, headState.Slot(), slot32-1)
|
||||
|
||||
// Why do we need to sleep here to let the NSC to be updated? state copies take long it seems
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
preState32 := transition.NextSlotState(block31Root[:], slot32)
|
||||
require.NotNil(t, preState32)
|
||||
require.Equal(t, slot32, preState32.Slot())
|
||||
|
||||
// Wait until the start of slot 32 + 5 seconds so that slot 32 is late
|
||||
// Import block 32
|
||||
slot32Start := slots.StartTime(uint64(service.genesisTime.Unix()), slot32)
|
||||
time.Sleep(slot32Start.Add(5 * time.Second).Sub(time.Now()))
|
||||
|
||||
block32Root, err := block32.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.ReceiveBlock(ctx, block32, block32Root))
|
||||
headRoot, err = service.HeadRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, block32Root, [32]byte(headRoot))
|
||||
headState, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, headState.Slot(), slot32)
|
||||
|
||||
preState33 := transition.NextSlotState(block32Root[:], slot32+1)
|
||||
require.NotNil(t, preState33)
|
||||
require.Equal(t, slot32+1, preState33.Slot())
|
||||
|
||||
// We should also have advanced the NSC for the blockroot of 31
|
||||
preState33Alt := transition.NextSlotState(block31Root[:], slot32+1)
|
||||
require.NotNil(t, preState33Alt)
|
||||
require.Equal(t, slot32+1, preState33Alt.Slot())
|
||||
|
||||
// Wait until the start of slot 33 and import it early
|
||||
slot33Start := slots.StartTime(uint64(service.genesisTime.Unix()), slot32+1)
|
||||
time.Sleep(slot33Start.Add(time.Second).Sub(time.Now()))
|
||||
|
||||
block33Root, err := block33.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.ReceiveBlock(ctx, block33, block33Root))
|
||||
headRoot, err = service.HeadRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, block33Root, [32]byte(headRoot))
|
||||
headState, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, headState.Slot(), slot32+1)
|
||||
|
||||
preState34 := transition.NextSlotState(block33Root[:], slot32+2)
|
||||
require.NotNil(t, preState34)
|
||||
require.Equal(t, slot32+2, preState34.Slot())
|
||||
|
||||
require.LogsContain(t, hook, "pingo")
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (s *Service) Start() {
|
||||
}
|
||||
}
|
||||
s.spawnProcessAttestationsRoutine()
|
||||
s.spawnLateBlockTasksLoop()
|
||||
go s.runLateBlockTasks()
|
||||
}
|
||||
|
||||
// Stop the blockchain service's main event loop and associated goroutines.
|
||||
|
||||
@@ -3,6 +3,7 @@ package builder
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
buildertesting "github.com/prysmaticlabs/prysm/v4/api/client/builder/testing"
|
||||
blockchainTesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
@@ -38,6 +39,21 @@ func Test_RegisterValidator(t *testing.T) {
|
||||
assert.Equal(t, true, builder.RegisteredVals[pubkey])
|
||||
}
|
||||
|
||||
func Test_RegisterValidator_WithCache(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
headFetcher := &blockchainTesting.ChainService{}
|
||||
builder := buildertesting.NewClient()
|
||||
s, err := NewService(ctx, WithRegistrationCache(), WithHeadFetcher(headFetcher), WithBuilderClient(&builder))
|
||||
require.NoError(t, err)
|
||||
pubkey := bytesutil.ToBytes48([]byte("pubkey"))
|
||||
var feeRecipient [20]byte
|
||||
reg := ð.ValidatorRegistrationV1{Pubkey: pubkey[:], Timestamp: uint64(time.Now().UTC().Unix()), FeeRecipient: feeRecipient[:]}
|
||||
require.NoError(t, s.RegisterValidator(ctx, []*eth.SignedValidatorRegistrationV1{{Message: reg}}))
|
||||
registration, err := s.registrationCache.RegistrationByIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, reg, registration)
|
||||
}
|
||||
|
||||
func Test_BuilderMethodsWithouClient(t *testing.T) {
|
||||
s, err := NewService(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
2
beacon-chain/cache/BUILD.bazel
vendored
2
beacon-chain/cache/BUILD.bazel
vendored
@@ -34,6 +34,7 @@ go_library(
|
||||
deps = [
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/slice:go_default_library",
|
||||
@@ -87,7 +88,6 @@ go_test(
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_google_gofuzz//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
33
beacon-chain/cache/payload_id.go
vendored
33
beacon-chain/cache/payload_id.go
vendored
@@ -4,35 +4,41 @@ import (
|
||||
"bytes"
|
||||
"sync"
|
||||
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
)
|
||||
|
||||
const keyLength = 40
|
||||
const vIdLength = 8
|
||||
const pIdLength = 8
|
||||
const vpIdsLength = vIdLength + pIdLength
|
||||
|
||||
// ProposerPayloadIDsCache is a cache of proposer payload IDs.
|
||||
// The key is the slot. The value is the concatenation of the proposer and payload IDs. 8 bytes each.
|
||||
// The key is the concatenation of the slot and the block root.
|
||||
// The value is the concatenation of the proposer and payload IDs, 8 bytes each.
|
||||
type ProposerPayloadIDsCache struct {
|
||||
slotToProposerAndPayloadIDs map[[40]byte][vpIdsLength]byte
|
||||
slotToProposerAndPayloadIDs map[[keyLength]byte][vpIdsLength]byte
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewProposerPayloadIDsCache creates a new proposer payload IDs cache.
|
||||
func NewProposerPayloadIDsCache() *ProposerPayloadIDsCache {
|
||||
return &ProposerPayloadIDsCache{
|
||||
slotToProposerAndPayloadIDs: make(map[[40]byte][vpIdsLength]byte),
|
||||
slotToProposerAndPayloadIDs: make(map[[keyLength]byte][vpIdsLength]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// GetProposerPayloadIDs returns the proposer and payload IDs for the given slot.
|
||||
func (f *ProposerPayloadIDsCache) GetProposerPayloadIDs(slot primitives.Slot, r [32]byte) (primitives.ValidatorIndex, [8]byte, bool) {
|
||||
// GetProposerPayloadIDs returns the proposer and payload IDs for the given slot and head root to build the block.
|
||||
func (f *ProposerPayloadIDsCache) GetProposerPayloadIDs(
|
||||
slot primitives.Slot,
|
||||
r [fieldparams.RootLength]byte,
|
||||
) (primitives.ValidatorIndex, [pIdLength]byte, bool) {
|
||||
f.RLock()
|
||||
defer f.RUnlock()
|
||||
ids, ok := f.slotToProposerAndPayloadIDs[idKey(slot, r)]
|
||||
if !ok {
|
||||
return 0, [8]byte{}, false
|
||||
return 0, [pIdLength]byte{}, false
|
||||
}
|
||||
vId := ids[:vIdLength]
|
||||
|
||||
@@ -43,8 +49,13 @@ func (f *ProposerPayloadIDsCache) GetProposerPayloadIDs(slot primitives.Slot, r
|
||||
return primitives.ValidatorIndex(bytesutil.BytesToUint64BigEndian(vId)), pId, true
|
||||
}
|
||||
|
||||
// SetProposerAndPayloadIDs sets the proposer and payload IDs for the given slot.
|
||||
func (f *ProposerPayloadIDsCache) SetProposerAndPayloadIDs(slot primitives.Slot, vId primitives.ValidatorIndex, pId [8]byte, r [32]byte) {
|
||||
// SetProposerAndPayloadIDs sets the proposer and payload IDs for the given slot and head root to build block.
|
||||
func (f *ProposerPayloadIDsCache) SetProposerAndPayloadIDs(
|
||||
slot primitives.Slot,
|
||||
vId primitives.ValidatorIndex,
|
||||
pId [pIdLength]byte,
|
||||
r [fieldparams.RootLength]byte,
|
||||
) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
var vIdBytes [vIdLength]byte
|
||||
@@ -63,7 +74,7 @@ func (f *ProposerPayloadIDsCache) SetProposerAndPayloadIDs(slot primitives.Slot,
|
||||
}
|
||||
}
|
||||
|
||||
// PrunePayloadIDs removes the payload id entries that's current than input slot.
|
||||
// PrunePayloadIDs removes the payload ID entries older than input slot.
|
||||
func (f *ProposerPayloadIDsCache) PrunePayloadIDs(slot primitives.Slot) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
@@ -76,8 +87,8 @@ func (f *ProposerPayloadIDsCache) PrunePayloadIDs(slot primitives.Slot) {
|
||||
}
|
||||
}
|
||||
|
||||
func idKey(slot primitives.Slot, r [32]byte) [40]byte {
|
||||
var k [40]byte
|
||||
func idKey(slot primitives.Slot, r [fieldparams.RootLength]byte) [keyLength]byte {
|
||||
var k [keyLength]byte
|
||||
copy(k[:], append(bytesutil.Uint64ToBytesBigEndian(uint64(slot)), r[:]...))
|
||||
return k
|
||||
}
|
||||
|
||||
27
beacon-chain/cache/registration.go
vendored
27
beacon-chain/cache/registration.go
vendored
@@ -3,15 +3,11 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/math"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -38,33 +34,10 @@ func (regCache *RegistrationCache) RegistrationByIndex(id primitives.ValidatorIn
|
||||
regCache.lock.RUnlock()
|
||||
return nil, errors.Wrapf(ErrNotFoundRegistration, "validator id %d", id)
|
||||
}
|
||||
isExpired, err := RegistrationTimeStampExpired(v.Timestamp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to check registration expiration")
|
||||
}
|
||||
if isExpired {
|
||||
regCache.lock.RUnlock()
|
||||
regCache.lock.Lock()
|
||||
defer regCache.lock.Unlock()
|
||||
delete(regCache.indexToRegistration, id)
|
||||
log.Warnf("registration for validator index %d expired at unix time %d", id, v.Timestamp)
|
||||
return nil, errors.Wrapf(ErrNotFoundRegistration, "validator id %d", id)
|
||||
}
|
||||
regCache.lock.RUnlock()
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func RegistrationTimeStampExpired(ts uint64) (bool, error) {
|
||||
// safely convert unint64 to int64
|
||||
i, err := math.Int(ts)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
expiryDuration := params.BeaconConfig().RegistrationDuration
|
||||
// registered time + expiration duration < current time = expired
|
||||
return time.Unix(int64(i), 0).Add(expiryDuration).Before(time.Now()), nil
|
||||
}
|
||||
|
||||
// UpdateIndexToRegisteredMap adds or updates values in the cache based on the argument.
|
||||
func (regCache *RegistrationCache) UpdateIndexToRegisteredMap(ctx context.Context, m map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1) {
|
||||
_, span := trace.StartSpan(ctx, "RegistrationCache.UpdateIndexToRegisteredMap")
|
||||
|
||||
40
beacon-chain/cache/registration_test.go
vendored
40
beacon-chain/cache/registration_test.go
vendored
@@ -6,15 +6,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
func TestRegistrationCache(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
pubkey, err := hexutil.Decode("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
|
||||
require.NoError(t, err)
|
||||
validatorIndex := primitives.ValidatorIndex(1)
|
||||
@@ -31,29 +28,14 @@ func TestRegistrationCache(t *testing.T) {
|
||||
reg, err := cache.RegistrationByIndex(validatorIndex)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(reg.Pubkey), string(pubkey))
|
||||
t.Run("Registration expired", func(t *testing.T) {
|
||||
validatorIndex2 := primitives.ValidatorIndex(2)
|
||||
overExpirationPadTime := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*4) // 4 epochs
|
||||
m[validatorIndex2] = ðpb.ValidatorRegistrationV1{
|
||||
FeeRecipient: []byte{},
|
||||
GasLimit: 100,
|
||||
Timestamp: uint64(time.Now().Add(-1 * overExpirationPadTime).Unix()),
|
||||
Pubkey: pubkey,
|
||||
}
|
||||
cache.UpdateIndexToRegisteredMap(context.Background(), m)
|
||||
_, err := cache.RegistrationByIndex(validatorIndex2)
|
||||
require.ErrorContains(t, "no validator registered", err)
|
||||
require.LogsContain(t, hook, "expired")
|
||||
})
|
||||
t.Run("Registration close to expiration still passes", func(t *testing.T) {
|
||||
t.Run("successfully updates", func(t *testing.T) {
|
||||
pubkey, err := hexutil.Decode("0x88247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a")
|
||||
require.NoError(t, err)
|
||||
validatorIndex2 := primitives.ValidatorIndex(2)
|
||||
overExpirationPadTime := time.Second * time.Duration((params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*3)-5) // 3 epochs - 5 seconds
|
||||
m[validatorIndex2] = ðpb.ValidatorRegistrationV1{
|
||||
FeeRecipient: []byte{},
|
||||
GasLimit: 100,
|
||||
Timestamp: uint64(time.Now().Add(-1 * overExpirationPadTime).Unix()),
|
||||
Timestamp: uint64(time.Now().Unix()),
|
||||
Pubkey: pubkey,
|
||||
}
|
||||
cache.UpdateIndexToRegisteredMap(context.Background(), m)
|
||||
@@ -62,21 +44,3 @@ func TestRegistrationCache(t *testing.T) {
|
||||
require.Equal(t, string(reg.Pubkey), string(pubkey))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RegistrationTimeStampExpired(t *testing.T) {
|
||||
// expiration set at 3 epochs
|
||||
t.Run("expired registration", func(t *testing.T) {
|
||||
overExpirationPadTime := time.Second * time.Duration(params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*4) // 4 epochs
|
||||
ts := uint64(time.Now().Add(-1 * overExpirationPadTime).Unix())
|
||||
isExpired, err := RegistrationTimeStampExpired(ts)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isExpired)
|
||||
})
|
||||
t.Run("is not expired registration", func(t *testing.T) {
|
||||
overExpirationPadTime := time.Second * time.Duration((params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch)*3)-5) // 3 epochs -5 seconds
|
||||
ts := uint64(time.Now().Add(-1 * overExpirationPadTime).Unix())
|
||||
isExpired, err := RegistrationTimeStampExpired(ts)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, isExpired)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ func IsExecutionBlock(body interfaces.ReadOnlyBeaconBlockBody) (bool, error) {
|
||||
}
|
||||
payload, err := body.Execution()
|
||||
switch {
|
||||
case errors.Is(err, consensus_types.ErrUnsupportedGetter):
|
||||
case errors.Is(err, consensus_types.ErrUnsupportedField):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, err
|
||||
|
||||
@@ -68,7 +68,6 @@ go_test(
|
||||
"//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",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v4/time"
|
||||
@@ -66,25 +65,6 @@ func IsAggregator(committeeCount uint64, slotSig []byte) (bool, error) {
|
||||
return binary.LittleEndian.Uint64(b[:8])%modulo == 0, nil
|
||||
}
|
||||
|
||||
// AggregateSignature returns the aggregated signature of the input attestations.
|
||||
//
|
||||
// Spec pseudocode definition:
|
||||
//
|
||||
// def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature:
|
||||
// signatures = [attestation.signature for attestation in attestations]
|
||||
// return bls.Aggregate(signatures)
|
||||
func AggregateSignature(attestations []*ethpb.Attestation) (bls.Signature, error) {
|
||||
sigs := make([]bls.Signature, len(attestations))
|
||||
var err error
|
||||
for i := 0; i < len(sigs); i++ {
|
||||
sigs[i], err = bls.SignatureFromBytes(attestations[i].Signature)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bls.AggregateSignatures(sigs), nil
|
||||
}
|
||||
|
||||
// IsAggregated returns true if the attestation is an aggregated attestation,
|
||||
// false otherwise.
|
||||
func IsAggregated(attestation *ethpb.Attestation) bool {
|
||||
@@ -183,11 +163,11 @@ func ValidateAttestationTime(attSlot primitives.Slot, genesisTime time.Time, clo
|
||||
currentSlot,
|
||||
)
|
||||
if attTime.Before(lowerBounds) {
|
||||
attReceivedTooEarlyCount.Inc()
|
||||
attReceivedTooLateCount.Inc()
|
||||
return attError
|
||||
}
|
||||
if attTime.After(upperBounds) {
|
||||
attReceivedTooLateCount.Inc()
|
||||
attReceivedTooEarlyCount.Inc()
|
||||
return attError
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
state_native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
@@ -45,44 +43,6 @@ func TestAttestation_IsAggregator(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAttestation_AggregateSignature(t *testing.T) {
|
||||
t.Run("verified", func(t *testing.T) {
|
||||
pubkeys := make([]bls.PublicKey, 0, 100)
|
||||
atts := make([]*ethpb.Attestation, 0, 100)
|
||||
msg := bytesutil.ToBytes32([]byte("hello"))
|
||||
for i := 0; i < 100; i++ {
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pub := priv.PublicKey()
|
||||
sig := priv.Sign(msg[:])
|
||||
pubkeys = append(pubkeys, pub)
|
||||
att := ðpb.Attestation{Signature: sig.Marshal()}
|
||||
atts = append(atts, att)
|
||||
}
|
||||
aggSig, err := helpers.AggregateSignature(atts)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, aggSig.FastAggregateVerify(pubkeys, msg), "Signature did not verify")
|
||||
})
|
||||
|
||||
t.Run("not verified", func(t *testing.T) {
|
||||
pubkeys := make([]bls.PublicKey, 0, 100)
|
||||
atts := make([]*ethpb.Attestation, 0, 100)
|
||||
msg := []byte("hello")
|
||||
for i := 0; i < 100; i++ {
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pub := priv.PublicKey()
|
||||
sig := priv.Sign(msg)
|
||||
pubkeys = append(pubkeys, pub)
|
||||
att := ðpb.Attestation{Signature: sig.Marshal()}
|
||||
atts = append(atts, att)
|
||||
}
|
||||
aggSig, err := helpers.AggregateSignature(atts[0 : len(atts)-2])
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, false, aggSig.FastAggregateVerify(pubkeys, bytesutil.ToBytes32(msg)), "Signature not suppose to verify")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAttestation_ComputeSubnetForAttestation(t *testing.T) {
|
||||
// Create 10 committees
|
||||
committeeCount := uint64(10)
|
||||
|
||||
@@ -107,6 +107,7 @@ type HeadAccessDatabase interface {
|
||||
|
||||
// initialization method needed for origin checkpoint sync
|
||||
SaveOrigin(ctx context.Context, serState, serBlock []byte) error
|
||||
SaveOriginCheckpointBlockRoot(ctx context.Context, blockRoot [32]byte) error
|
||||
SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error
|
||||
}
|
||||
|
||||
|
||||
@@ -130,9 +130,15 @@ func performValidatorStateMigration(ctx context.Context, bar *progressbar.Progre
|
||||
return err
|
||||
}
|
||||
item := enc
|
||||
if hasAltairKey(item) {
|
||||
switch {
|
||||
case hasAltairKey(enc):
|
||||
item = item[len(altairKey):]
|
||||
case hasBellatrixKey(enc):
|
||||
item = item[len(bellatrixKey):]
|
||||
case hasCapellaKey(enc):
|
||||
item = item[len(capellaKey):]
|
||||
}
|
||||
|
||||
detector, err := detect.FromState(item)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -165,9 +171,14 @@ func performValidatorStateMigration(ctx context.Context, bar *progressbar.Progre
|
||||
return err
|
||||
}
|
||||
var stateBytes []byte
|
||||
if hasAltairKey(enc) {
|
||||
switch {
|
||||
case hasAltairKey(enc):
|
||||
stateBytes = snappy.Encode(nil, append(altairKey, rawObj...))
|
||||
} else {
|
||||
case hasBellatrixKey(enc):
|
||||
stateBytes = snappy.Encode(nil, append(bellatrixKey, rawObj...))
|
||||
case hasCapellaKey(enc):
|
||||
stateBytes = snappy.Encode(nil, append(capellaKey, rawObj...))
|
||||
default:
|
||||
stateBytes = snappy.Encode(nil, rawObj)
|
||||
}
|
||||
if stateErr := stateBkt.Put(keys[index], stateBytes); stateErr != nil {
|
||||
|
||||
@@ -313,3 +313,217 @@ func Test_migrateAltairStateValidators(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_migrateBellatrixStateValidators(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator)
|
||||
eval func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator)
|
||||
}{
|
||||
{
|
||||
name: "migrates validators and adds them to new buckets",
|
||||
setup: func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator) {
|
||||
// create some new buckets that should be present for this migration
|
||||
err := dbStore.db.Update(func(tx *bbolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists(stateValidatorsBucket)
|
||||
assert.NoError(t, err)
|
||||
_, err = tx.CreateBucketIfNotExists(blockRootValidatorHashesBucket)
|
||||
assert.NoError(t, err)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
eval: func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator) {
|
||||
// check whether the new buckets are present
|
||||
err := dbStore.db.View(func(tx *bbolt.Tx) error {
|
||||
valBkt := tx.Bucket(stateValidatorsBucket)
|
||||
assert.NotNil(t, valBkt)
|
||||
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
||||
assert.NotNil(t, idxBkt)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check if the migration worked
|
||||
blockRoot := [32]byte{'A'}
|
||||
rcvdState, err := dbStore.State(context.Background(), blockRoot)
|
||||
assert.NoError(t, err)
|
||||
require.DeepSSZEqual(t, rcvdState.ToProtoUnsafe(), state.ToProtoUnsafe(), "saved state with validators and retrieved state are not matching")
|
||||
|
||||
// find hashes of the validators that are set as part of the state
|
||||
var hashes []byte
|
||||
var individualHashes [][]byte
|
||||
for _, val := range vals {
|
||||
hash, hashErr := val.HashTreeRoot()
|
||||
assert.NoError(t, hashErr)
|
||||
hashes = append(hashes, hash[:]...)
|
||||
individualHashes = append(individualHashes, hash[:])
|
||||
}
|
||||
|
||||
// check if all the validators that were in the state, are stored properly in the validator bucket
|
||||
pbState, err := state_native.ProtobufBeaconStateBellatrix(rcvdState.ToProtoUnsafe())
|
||||
assert.NoError(t, err)
|
||||
validatorsFoundCount := 0
|
||||
for _, val := range pbState.Validators {
|
||||
hash, hashErr := val.HashTreeRoot()
|
||||
assert.NoError(t, hashErr)
|
||||
found := false
|
||||
for _, h := range individualHashes {
|
||||
if bytes.Equal(hash[:], h) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.Equal(t, true, found)
|
||||
validatorsFoundCount++
|
||||
}
|
||||
require.Equal(t, len(vals), validatorsFoundCount)
|
||||
|
||||
// check if the state validator indexes are stored properly
|
||||
err = dbStore.db.View(func(tx *bbolt.Tx) error {
|
||||
rcvdValhashBytes := tx.Bucket(blockRootValidatorHashesBucket).Get(blockRoot[:])
|
||||
rcvdValHashes, sErr := snappy.Decode(nil, rcvdValhashBytes)
|
||||
assert.NoError(t, sErr)
|
||||
require.DeepEqual(t, hashes, rcvdValHashes)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dbStore := setupDB(t)
|
||||
|
||||
// add a state with the given validators
|
||||
vals := validators(10)
|
||||
blockRoot := [32]byte{'A'}
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 20)
|
||||
err := st.SetFork(&v1alpha1.Fork{
|
||||
PreviousVersion: params.BeaconConfig().AltairForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().BellatrixForkVersion,
|
||||
Epoch: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, st.SetSlot(100))
|
||||
assert.NoError(t, st.SetValidators(vals))
|
||||
assert.NoError(t, dbStore.SaveState(context.Background(), st, blockRoot))
|
||||
|
||||
// enable historical state representation flag to test this
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnableHistoricalSpaceRepresentation: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
tt.setup(t, dbStore, st, vals)
|
||||
assert.NoError(t, migrateStateValidators(context.Background(), dbStore.db), "migrateArchivedIndex(tx) error")
|
||||
tt.eval(t, dbStore, st, vals)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_migrateCapellaStateValidators(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator)
|
||||
eval func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator)
|
||||
}{
|
||||
{
|
||||
name: "migrates validators and adds them to new buckets",
|
||||
setup: func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator) {
|
||||
// create some new buckets that should be present for this migration
|
||||
err := dbStore.db.Update(func(tx *bbolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists(stateValidatorsBucket)
|
||||
assert.NoError(t, err)
|
||||
_, err = tx.CreateBucketIfNotExists(blockRootValidatorHashesBucket)
|
||||
assert.NoError(t, err)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
eval: func(t *testing.T, dbStore *Store, state state.BeaconState, vals []*v1alpha1.Validator) {
|
||||
// check whether the new buckets are present
|
||||
err := dbStore.db.View(func(tx *bbolt.Tx) error {
|
||||
valBkt := tx.Bucket(stateValidatorsBucket)
|
||||
assert.NotNil(t, valBkt)
|
||||
idxBkt := tx.Bucket(blockRootValidatorHashesBucket)
|
||||
assert.NotNil(t, idxBkt)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check if the migration worked
|
||||
blockRoot := [32]byte{'A'}
|
||||
rcvdState, err := dbStore.State(context.Background(), blockRoot)
|
||||
assert.NoError(t, err)
|
||||
require.DeepSSZEqual(t, rcvdState.ToProtoUnsafe(), state.ToProtoUnsafe(), "saved state with validators and retrieved state are not matching")
|
||||
|
||||
// find hashes of the validators that are set as part of the state
|
||||
var hashes []byte
|
||||
var individualHashes [][]byte
|
||||
for _, val := range vals {
|
||||
hash, hashErr := val.HashTreeRoot()
|
||||
assert.NoError(t, hashErr)
|
||||
hashes = append(hashes, hash[:]...)
|
||||
individualHashes = append(individualHashes, hash[:])
|
||||
}
|
||||
|
||||
// check if all the validators that were in the state, are stored properly in the validator bucket
|
||||
pbState, err := state_native.ProtobufBeaconStateCapella(rcvdState.ToProtoUnsafe())
|
||||
assert.NoError(t, err)
|
||||
validatorsFoundCount := 0
|
||||
for _, val := range pbState.Validators {
|
||||
hash, hashErr := val.HashTreeRoot()
|
||||
assert.NoError(t, hashErr)
|
||||
found := false
|
||||
for _, h := range individualHashes {
|
||||
if bytes.Equal(hash[:], h) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.Equal(t, true, found)
|
||||
validatorsFoundCount++
|
||||
}
|
||||
require.Equal(t, len(vals), validatorsFoundCount)
|
||||
|
||||
// check if the state validator indexes are stored properly
|
||||
err = dbStore.db.View(func(tx *bbolt.Tx) error {
|
||||
rcvdValhashBytes := tx.Bucket(blockRootValidatorHashesBucket).Get(blockRoot[:])
|
||||
rcvdValHashes, sErr := snappy.Decode(nil, rcvdValhashBytes)
|
||||
assert.NoError(t, sErr)
|
||||
require.DeepEqual(t, hashes, rcvdValHashes)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dbStore := setupDB(t)
|
||||
|
||||
// add a state with the given validators
|
||||
vals := validators(10)
|
||||
blockRoot := [32]byte{'A'}
|
||||
st, _ := util.DeterministicGenesisStateCapella(t, 20)
|
||||
err := st.SetFork(&v1alpha1.Fork{
|
||||
PreviousVersion: params.BeaconConfig().BellatrixForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
Epoch: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, st.SetSlot(100))
|
||||
assert.NoError(t, st.SetValidators(vals))
|
||||
assert.NoError(t, dbStore.SaveState(context.Background(), st, blockRoot))
|
||||
|
||||
// enable historical state representation flag to test this
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnableHistoricalSpaceRepresentation: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
tt.setup(t, dbStore, st, vals)
|
||||
assert.NoError(t, migrateStateValidators(context.Background(), dbStore.db), "migrateArchivedIndex(tx) error")
|
||||
tt.eval(t, dbStore, st, vals)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,7 @@ func (f *ForkChoice) IsViableForCheckpoint(cp *forkchoicetypes.Checkpoint) (bool
|
||||
// validators' latest votes.
|
||||
func (f *ForkChoice) updateBalances() error {
|
||||
newBalances := f.justifiedBalances
|
||||
zHash := params.BeaconConfig().ZeroHash
|
||||
|
||||
for index, vote := range f.votes {
|
||||
// Skip if validator has been slashed
|
||||
@@ -273,7 +274,7 @@ func (f *ForkChoice) updateBalances() error {
|
||||
}
|
||||
// Skip if validator has never voted for current root and next root (i.e. if the
|
||||
// votes are zero hash aka genesis block), there's nothing to compute.
|
||||
if vote.currentRoot == params.BeaconConfig().ZeroHash && vote.nextRoot == params.BeaconConfig().ZeroHash {
|
||||
if vote.currentRoot == zHash && vote.nextRoot == zHash {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -293,7 +294,7 @@ func (f *ForkChoice) updateBalances() error {
|
||||
// Ignore the vote if the root is not in fork choice
|
||||
// store, that means we have not seen the block before.
|
||||
nextNode, ok := f.store.nodeByRoot[vote.nextRoot]
|
||||
if ok && vote.nextRoot != params.BeaconConfig().ZeroHash {
|
||||
if ok && vote.nextRoot != zHash {
|
||||
// Protection against nil node
|
||||
if nextNode == nil {
|
||||
return errors.Wrap(ErrNilNode, "could not update balances")
|
||||
@@ -302,7 +303,7 @@ func (f *ForkChoice) updateBalances() error {
|
||||
}
|
||||
|
||||
currentNode, ok := f.store.nodeByRoot[vote.currentRoot]
|
||||
if ok && vote.currentRoot != params.BeaconConfig().ZeroHash {
|
||||
if ok && vote.currentRoot != zHash {
|
||||
// Protection against nil node
|
||||
if currentNode == nil {
|
||||
return errors.Wrap(ErrNilNode, "could not update balances")
|
||||
|
||||
@@ -31,9 +31,7 @@ import (
|
||||
// store.justified_checkpoint = store.best_justified_checkpoint
|
||||
func (f *ForkChoice) NewSlot(ctx context.Context, slot primitives.Slot) error {
|
||||
// Reset proposer boost root
|
||||
if err := f.resetBoostedProposerRoot(ctx); err != nil {
|
||||
return errors.Wrap(err, "could not reset boosted proposer root in fork choice")
|
||||
}
|
||||
f.store.proposerBoostRoot = [32]byte{}
|
||||
|
||||
// Return if it's not a new epoch.
|
||||
if !slots.IsEpochStart(slot) {
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package doublylinkedtree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
)
|
||||
|
||||
// resetBoostedProposerRoot sets the value of the proposer boosted root to zeros.
|
||||
func (f *ForkChoice) resetBoostedProposerRoot(_ context.Context) error {
|
||||
f.store.proposerBoostRoot = [32]byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyProposerBoostScore applies the current proposer boost scores to the
|
||||
// relevant nodes.
|
||||
func (f *ForkChoice) applyProposerBoostScore() error {
|
||||
|
||||
@@ -82,6 +82,11 @@ func (f *ForkChoice) ShouldOverrideFCU() (override bool) {
|
||||
if head.weight*100 > f.store.committeeWeight*params.BeaconConfig().ReorgWeightThreshold {
|
||||
return
|
||||
}
|
||||
|
||||
// Only orphan a block if the parent LMD vote is strong
|
||||
if parent.weight*100 < f.store.committeeWeight*params.BeaconConfig().ReorgParentWeightThreshold {
|
||||
return
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -137,6 +142,11 @@ func (f *ForkChoice) GetProposerHead() [32]byte {
|
||||
return head.root
|
||||
}
|
||||
|
||||
// Only orphan a block if the parent LMD vote is strong
|
||||
if parent.weight*100 < f.store.committeeWeight*params.BeaconConfig().ReorgParentWeightThreshold {
|
||||
return head.root
|
||||
}
|
||||
|
||||
// Only reorg if we are proposing early
|
||||
secs, err := slots.SecondsSinceSlotStart(head.slot+1, f.store.genesisTime, uint64(time.Now().Unix()))
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,11 @@ func TestForkChoice_ShouldOverrideFCU(t *testing.T) {
|
||||
st, root, err := prepareForkchoiceState(ctx, 1, [32]byte{'a'}, [32]byte{}, [32]byte{'A'}, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, root))
|
||||
f.ProcessAttestation(ctx, []uint64{0, 1, 2}, root, 0)
|
||||
attesters := make([]uint64, f.numActiveValidators-64)
|
||||
for i := range attesters {
|
||||
attesters[i] = uint64(i + 64)
|
||||
}
|
||||
f.ProcessAttestation(ctx, attesters, root, 0)
|
||||
|
||||
driftGenesisTime(f, 2, orphanLateBlockFirstThreshold+1)
|
||||
st, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 0, 0)
|
||||
@@ -80,6 +84,12 @@ func TestForkChoice_ShouldOverrideFCU(t *testing.T) {
|
||||
require.Equal(t, false, f.ShouldOverrideFCU())
|
||||
f.store.headNode.parent = saved
|
||||
})
|
||||
t.Run("parent is weak", func(t *testing.T) {
|
||||
saved := f.store.headNode.parent.weight
|
||||
f.store.headNode.parent.weight = 0
|
||||
require.Equal(t, false, f.ShouldOverrideFCU())
|
||||
f.store.headNode.parent.weight = saved
|
||||
})
|
||||
t.Run("Head is strong", func(t *testing.T) {
|
||||
f.store.headNode.weight = f.store.committeeWeight
|
||||
require.Equal(t, false, f.ShouldOverrideFCU())
|
||||
@@ -101,7 +111,11 @@ func TestForkChoice_GetProposerHead(t *testing.T) {
|
||||
st, root, err := prepareForkchoiceState(ctx, 1, parentRoot, [32]byte{}, [32]byte{'A'}, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, root))
|
||||
f.ProcessAttestation(ctx, []uint64{0, 1, 2}, root, 0)
|
||||
attesters := make([]uint64, f.numActiveValidators-64)
|
||||
for i := range attesters {
|
||||
attesters[i] = uint64(i + 64)
|
||||
}
|
||||
f.ProcessAttestation(ctx, attesters, root, 0)
|
||||
|
||||
driftGenesisTime(f, 3, 1)
|
||||
childRoot := [32]byte{'b'}
|
||||
@@ -161,6 +175,12 @@ func TestForkChoice_GetProposerHead(t *testing.T) {
|
||||
require.Equal(t, childRoot, f.GetProposerHead())
|
||||
f.store.headNode.parent = saved
|
||||
})
|
||||
t.Run("parent is weak", func(t *testing.T) {
|
||||
saved := f.store.headNode.parent.weight
|
||||
f.store.headNode.parent.weight = 0
|
||||
require.Equal(t, false, f.ShouldOverrideFCU())
|
||||
f.store.headNode.parent.weight = saved
|
||||
})
|
||||
t.Run("Head is strong", func(t *testing.T) {
|
||||
f.store.headNode.weight = f.store.committeeWeight
|
||||
require.Equal(t, childRoot, f.GetProposerHead())
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -29,6 +30,9 @@ func (s *Service) processSyncAggregate(state state.BeaconState, blk interfaces.R
|
||||
if blk == nil || blk.Body() == nil {
|
||||
return
|
||||
}
|
||||
if blk.Version() == version.Phase0 {
|
||||
return
|
||||
}
|
||||
bits, err := blk.Body().SyncAggregate()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get SyncAggregate")
|
||||
|
||||
@@ -114,19 +114,11 @@ func (s *Service) Start() {
|
||||
"ValidatorIndices": tracked,
|
||||
}).Info("Starting service")
|
||||
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.config.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
|
||||
go s.run(stateChannel, stateSub)
|
||||
go s.run()
|
||||
}
|
||||
|
||||
// run waits until the beacon is synced and starts the monitoring system.
|
||||
func (s *Service) run(stateChannel chan *feed.Event, stateSub event.Subscription) {
|
||||
if stateChannel == nil {
|
||||
log.Error("State state is nil")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) run() {
|
||||
if err := s.waitForSync(s.config.InitialSyncComplete); err != nil {
|
||||
log.WithError(err)
|
||||
return
|
||||
@@ -154,6 +146,8 @@ func (s *Service) run(stateChannel chan *feed.Event, stateSub event.Subscription
|
||||
s.isLogging = true
|
||||
s.Unlock()
|
||||
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.config.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
s.monitorRoutine(stateChannel, stateSub)
|
||||
}
|
||||
|
||||
|
||||
@@ -271,11 +271,9 @@ func TestWaitForSyncCanceled(t *testing.T) {
|
||||
func TestRun(t *testing.T) {
|
||||
hook := logTest.NewGlobal()
|
||||
s := setupService(t)
|
||||
stateChannel := make(chan *feed.Event, 1)
|
||||
stateSub := s.config.StateNotifier.StateFeed().Subscribe(stateChannel)
|
||||
|
||||
go func() {
|
||||
s.run(stateChannel, stateSub)
|
||||
s.run()
|
||||
}()
|
||||
close(s.config.InitialSyncComplete)
|
||||
|
||||
|
||||
@@ -581,7 +581,8 @@ func (b *BeaconNode) fetchBuilderService() *builder.Service {
|
||||
|
||||
func (b *BeaconNode) registerAttestationPool() error {
|
||||
s, err := attestations.NewService(b.ctx, &attestations.Config{
|
||||
Pool: b.attestationPool,
|
||||
Pool: b.attestationPool,
|
||||
InitialSyncComplete: b.initialSyncComplete,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not register atts pool service")
|
||||
@@ -742,6 +743,7 @@ func (b *BeaconNode) registerSlasherService() error {
|
||||
SlashingPoolInserter: b.slashingsPool,
|
||||
SyncChecker: syncService,
|
||||
HeadStateFetcher: chainService,
|
||||
ClockWaiter: b.clockWaiter,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,6 +18,7 @@ go_library(
|
||||
deps = [
|
||||
"//beacon-chain/operations/attestations/kv:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
|
||||
@@ -53,27 +53,25 @@ func (c *AttCaches) aggregateUnaggregatedAttestations(ctx context.Context, unagg
|
||||
// Track the unaggregated attestations that aren't able to aggregate.
|
||||
leftOverUnaggregatedAtt := make(map[[32]byte]bool)
|
||||
for _, atts := range attsByDataRoot {
|
||||
aggregatedAtts := make([]*ethpb.Attestation, 0, len(atts))
|
||||
processedAtts, err := attaggregation.Aggregate(atts)
|
||||
aggregated, err := attaggregation.AggregateDisjointOneBitAtts(atts)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "could not aggregate unaggregated attestations")
|
||||
}
|
||||
for _, att := range processedAtts {
|
||||
if helpers.IsAggregated(att) {
|
||||
aggregatedAtts = append(aggregatedAtts, att)
|
||||
} else {
|
||||
h, err := hashFn(att)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
leftOverUnaggregatedAtt[h] = true
|
||||
if aggregated == nil {
|
||||
return errors.New("could not aggregate unaggregated attestations")
|
||||
}
|
||||
if helpers.IsAggregated(aggregated) {
|
||||
if err := c.SaveAggregatedAttestations([]*ethpb.Attestation{aggregated}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.SaveAggregatedAttestations(aggregatedAtts); err != nil {
|
||||
return err
|
||||
} else {
|
||||
h, err := hashFn(aggregated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
leftOverUnaggregatedAtt[h] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the unaggregated attestations from the pool that were successfully aggregated.
|
||||
for _, att := range unaggregatedAtts {
|
||||
h, err := hashFn(att)
|
||||
@@ -87,7 +85,6 @@ func (c *AttCaches) aggregateUnaggregatedAttestations(ctx context.Context, unagg
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,20 @@ var (
|
||||
Name: "expired_block_atts_total",
|
||||
Help: "The number of expired and deleted block attestations in the pool.",
|
||||
})
|
||||
batchForkChoiceAttsT1 = promauto.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "aggregate_attestations_t1",
|
||||
Help: "Captures times of attestation aggregation in milliseconds during the first interval per slot",
|
||||
Buckets: []float64{100, 200, 500, 1000, 1500, 2000, 2500, 3500},
|
||||
},
|
||||
)
|
||||
batchForkChoiceAttsT2 = promauto.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "aggregate_attestations_t2",
|
||||
Help: "Captures times of attestation aggregation in milliseconds during the second interval per slot",
|
||||
Buckets: []float64{10, 40, 100, 200, 600},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func (s *Service) updateMetrics() {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
attaggregation "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/attestation/aggregation/attestations"
|
||||
@@ -14,20 +16,34 @@ import (
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Prepare attestations for fork choice three times per slot.
|
||||
var prepareForkChoiceAttsPeriod = slots.DivideSlotBy(3 /* times-per-slot */)
|
||||
|
||||
// This prepares fork choice attestations by running batchForkChoiceAtts
|
||||
// every prepareForkChoiceAttsPeriod.
|
||||
func (s *Service) prepareForkChoiceAtts() {
|
||||
ticker := time.NewTicker(prepareForkChoiceAttsPeriod)
|
||||
defer ticker.Stop()
|
||||
intervals := features.Get().AggregateIntervals
|
||||
slotDuration := time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second
|
||||
// Adjust intervals for networks with a lower slot duration (Hive, e2e, etc)
|
||||
for {
|
||||
if intervals[len(intervals)-1] >= slotDuration {
|
||||
for i, offset := range intervals {
|
||||
intervals[i] = offset / 2
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
ticker := slots.NewSlotTickerWithIntervals(time.Unix(int64(s.genesisTime), 0), intervals[:])
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-ticker.C():
|
||||
t := time.Now()
|
||||
if err := s.batchForkChoiceAtts(s.ctx); err != nil {
|
||||
log.WithError(err).Error("Could not prepare attestations for fork choice")
|
||||
}
|
||||
if slots.TimeIntoSlot(s.genesisTime) < intervals[1] {
|
||||
batchForkChoiceAttsT1.Observe(float64(time.Since(t).Milliseconds()))
|
||||
} else if slots.TimeIntoSlot(s.genesisTime) < intervals[2] {
|
||||
batchForkChoiceAttsT2.Observe(float64(time.Since(t).Milliseconds()))
|
||||
}
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting routine")
|
||||
return
|
||||
|
||||
@@ -5,6 +5,7 @@ package attestations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
@@ -26,8 +27,9 @@ type Service struct {
|
||||
|
||||
// Config options for the service.
|
||||
type Config struct {
|
||||
Pool Pool
|
||||
pruneInterval time.Duration
|
||||
Pool Pool
|
||||
pruneInterval time.Duration
|
||||
InitialSyncComplete chan struct{}
|
||||
}
|
||||
|
||||
// NewService instantiates a new attestation pool service instance that will
|
||||
@@ -51,10 +53,24 @@ func NewService(ctx context.Context, cfg *Config) (*Service, error) {
|
||||
|
||||
// Start an attestation pool service's main event loop.
|
||||
func (s *Service) Start() {
|
||||
if err := s.waitForSync(s.cfg.InitialSyncComplete); err != nil {
|
||||
log.WithError(err).Error("failed to wait for initial sync")
|
||||
return
|
||||
}
|
||||
go s.prepareForkChoiceAtts()
|
||||
go s.pruneAttsPool()
|
||||
}
|
||||
|
||||
// waitForSync waits until the beacon node is synced to the latest head.
|
||||
func (s *Service) waitForSync(syncChan chan struct{}) error {
|
||||
select {
|
||||
case <-syncChan:
|
||||
return nil
|
||||
case <-s.ctx.Done():
|
||||
return errors.New("context closed, exiting goroutine")
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the beacon block attestation pool service's main event loop
|
||||
// and associated goroutines.
|
||||
func (s *Service) Stop() error {
|
||||
|
||||
@@ -30,8 +30,22 @@ func (s *Store) SaveSyncCommitteeMessage(msg *ethpb.SyncCommitteeMessage) error
|
||||
return errors.New("not typed []ethpb.SyncCommitteeMessage")
|
||||
}
|
||||
|
||||
messages = append(messages, copied)
|
||||
savedSyncCommitteeMessageTotal.Inc()
|
||||
idx := -1
|
||||
for i, msg := range messages {
|
||||
if msg.ValidatorIndex == copied.ValidatorIndex {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx >= 0 {
|
||||
// Override the existing messages with a new one
|
||||
messages[idx] = copied
|
||||
} else {
|
||||
// Append the new message
|
||||
messages = append(messages, copied)
|
||||
savedSyncCommitteeMessageTotal.Inc()
|
||||
}
|
||||
|
||||
return s.messageCache.Push(&queue.Item{
|
||||
Key: syncCommitteeKey(msg.Slot),
|
||||
Value: messages,
|
||||
|
||||
@@ -55,6 +55,7 @@ go_library(
|
||||
"//beacon-chain/p2p/types:go_default_library",
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/wrapper:go_default_library",
|
||||
|
||||
@@ -460,6 +460,19 @@ func convertToUdpMultiAddr(node *enode.Node) ([]ma.Multiaddr, error) {
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
func peerIdsFromMultiAddrs(addrs []ma.Multiaddr) []peer.ID {
|
||||
peers := []peer.ID{}
|
||||
for _, a := range addrs {
|
||||
info, err := peer.AddrInfoFromP2pAddr(a)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Could not derive peer info from multiaddress %s", a.String())
|
||||
continue
|
||||
}
|
||||
peers = append(peers, info.ID)
|
||||
}
|
||||
return peers
|
||||
}
|
||||
|
||||
func multiAddrFromString(address string) (ma.Multiaddr, error) {
|
||||
return ma.NewMultiaddr(address)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/p2p/muxer/mplex"
|
||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
ecdsaprysm "github.com/prysmaticlabs/prysm/v4/crypto/ecdsa"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
)
|
||||
@@ -60,8 +62,8 @@ func (s *Service) buildOptions(ip net.IP, priKey *ecdsa.PrivateKey) []libp2p.Opt
|
||||
libp2p.UserAgent(version.BuildData()),
|
||||
libp2p.ConnectionGater(s),
|
||||
libp2p.Transport(tcp.NewTCPTransport),
|
||||
libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport),
|
||||
libp2p.DefaultMuxers,
|
||||
libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport),
|
||||
}
|
||||
|
||||
options = append(options, libp2p.Security(noise.ID, noise.New))
|
||||
@@ -99,6 +101,9 @@ func (s *Service) buildOptions(ip net.IP, priKey *ecdsa.PrivateKey) []libp2p.Opt
|
||||
}
|
||||
// Disable Ping Service.
|
||||
options = append(options, libp2p.Ping(false))
|
||||
if features.Get().DisableResourceManager {
|
||||
options = append(options, libp2p.ResourceManager(&network.NullResourceManager{}))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestDefaultMultiplexers(t *testing.T) {
|
||||
err = cfg.Apply(append(opts, libp2p.FallbackDefaults)...)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, protocol.ID("/mplex/6.7.0"), cfg.Muxers[0].ID)
|
||||
assert.Equal(t, protocol.ID("/yamux/1.0.0"), cfg.Muxers[1].ID)
|
||||
assert.Equal(t, protocol.ID("/yamux/1.0.0"), cfg.Muxers[0].ID)
|
||||
assert.Equal(t, protocol.ID("/mplex/6.7.0"), cfg.Muxers[1].ID)
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ go_library(
|
||||
deps = [
|
||||
"//beacon-chain/p2p/peers/peerdata:go_default_library",
|
||||
"//beacon-chain/p2p/peers/scorers:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
@@ -44,6 +45,7 @@ go_test(
|
||||
"//beacon-chain/p2p/peers/peerdata:go_default_library",
|
||||
"//beacon-chain/p2p/peers/scorers:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/wrapper:go_default_library",
|
||||
|
||||
@@ -38,9 +38,10 @@ type StoreConfig struct {
|
||||
// the mutex when accessing data.
|
||||
type Store struct {
|
||||
sync.RWMutex
|
||||
ctx context.Context
|
||||
config *StoreConfig
|
||||
peers map[peer.ID]*PeerData
|
||||
ctx context.Context
|
||||
config *StoreConfig
|
||||
peers map[peer.ID]*PeerData
|
||||
trustedPeers map[peer.ID]bool
|
||||
}
|
||||
|
||||
// PeerData aggregates protocol and application level info about a single peer.
|
||||
@@ -69,9 +70,10 @@ type PeerData struct {
|
||||
// NewStore creates new peer data store.
|
||||
func NewStore(ctx context.Context, config *StoreConfig) *Store {
|
||||
return &Store{
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
peers: make(map[peer.ID]*PeerData),
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
peers: make(map[peer.ID]*PeerData),
|
||||
trustedPeers: make(map[peer.ID]bool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,12 +107,25 @@ func (s *Store) DeletePeerData(pid peer.ID) {
|
||||
delete(s.peers, pid)
|
||||
}
|
||||
|
||||
// SetTrustedPeers sets our desired trusted peer set.
|
||||
func (s *Store) SetTrustedPeers(peers []peer.ID) {
|
||||
for _, p := range peers {
|
||||
s.trustedPeers[p] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Peers returns map of peer data objects.
|
||||
// Important: it is assumed that store mutex is locked when calling this method.
|
||||
func (s *Store) Peers() map[peer.ID]*PeerData {
|
||||
return s.peers
|
||||
}
|
||||
|
||||
// IsTrustedPeer checks that the provided peer
|
||||
// is in our trusted peer set.
|
||||
func (s *Store) IsTrustedPeer(p peer.ID) bool {
|
||||
return s.trustedPeers[p]
|
||||
}
|
||||
|
||||
// Config exposes store configuration params.
|
||||
func (s *Store) Config() *StoreConfig {
|
||||
return s.config
|
||||
|
||||
@@ -80,3 +80,20 @@ func TestStore_PeerDataGetOrCreate(t *testing.T) {
|
||||
assert.Equal(t, uint64(0), peerData.ProcessedBlocks)
|
||||
require.Equal(t, 1, len(store.Peers()))
|
||||
}
|
||||
|
||||
func TestStore_TrustedPeers(t *testing.T) {
|
||||
store := peerdata.NewStore(context.Background(), &peerdata.StoreConfig{
|
||||
MaxPeers: 12,
|
||||
})
|
||||
|
||||
pid1 := peer.ID("00001")
|
||||
pid2 := peer.ID("00002")
|
||||
pid3 := peer.ID("00003")
|
||||
|
||||
tPeers := []peer.ID{pid1, pid2, pid3}
|
||||
store.SetTrustedPeers(tPeers)
|
||||
|
||||
assert.Equal(t, true, store.IsTrustedPeer(pid1))
|
||||
assert.Equal(t, true, store.IsTrustedPeer(pid2))
|
||||
assert.Equal(t, true, store.IsTrustedPeer(pid3))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -12,6 +13,11 @@ func TestMain(m *testing.M) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetOutput(io.Discard)
|
||||
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnablePeerScorer: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
resetFlags := flags.Get()
|
||||
flags.Init(&flags.GlobalFlags{
|
||||
BlockBatchLimit: 64,
|
||||
|
||||
@@ -15,6 +15,7 @@ go_library(
|
||||
"//beacon-chain/p2p/peers/peerdata:go_default_library",
|
||||
"//beacon-chain/p2p/types:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
@@ -39,6 +40,7 @@ go_test(
|
||||
"//beacon-chain/p2p/peers/peerdata:go_default_library",
|
||||
"//beacon-chain/p2p/types:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/rand"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v4/time"
|
||||
)
|
||||
@@ -290,6 +291,9 @@ func (s *BlockProviderScorer) mapScoresAndPeers(
|
||||
func (s *BlockProviderScorer) FormatScorePretty(pid peer.ID) string {
|
||||
s.store.RLock()
|
||||
defer s.store.RUnlock()
|
||||
if !features.Get().EnablePeerScorer {
|
||||
return "disabled"
|
||||
}
|
||||
score := s.score(pid)
|
||||
return fmt.Sprintf("[%0.1f%%, raw: %0.2f, blocks: %d/%d]",
|
||||
(score/s.MaxScore())*100, score, s.processedBlocks(pid), s.config.ProcessedBlocksCap)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/scorers"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/time"
|
||||
@@ -459,6 +460,16 @@ func TestScorers_BlockProvider_FormatScorePretty(t *testing.T) {
|
||||
tt.check(scorer)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("peer scorer disabled", func(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnablePeerScorer: false,
|
||||
})
|
||||
defer resetCfg()
|
||||
peerStatuses := peerStatusGen()
|
||||
scorer := peerStatuses.Scorers().BlockProviderScorer()
|
||||
assert.Equal(t, "disabled", scorer.FormatScorePretty("peer1"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestScorers_BlockProvider_BadPeerMarking(t *testing.T) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/scorers"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -14,6 +15,11 @@ func TestMain(m *testing.M) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetOutput(io.Discard)
|
||||
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnablePeerScorer: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
resetFlags := flags.Get()
|
||||
flags.Init(&flags.GlobalFlags{
|
||||
BlockBatchLimit: 64,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
)
|
||||
|
||||
var _ Scorer = (*Service)(nil)
|
||||
@@ -137,8 +138,10 @@ func (s *Service) IsBadPeerNoLock(pid peer.ID) bool {
|
||||
if s.scorers.peerStatusScorer.isBadPeer(pid) {
|
||||
return true
|
||||
}
|
||||
if s.scorers.gossipScorer.isBadPeer(pid) {
|
||||
return true
|
||||
if features.Get().EnablePeerScorer {
|
||||
if s.scorers.gossipScorer.isBadPeer(pid) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/scorers"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/rand"
|
||||
@@ -334,6 +335,10 @@ func (p *Status) IsBad(pid peer.ID) bool {
|
||||
|
||||
// isBad is the lock-free version of IsBad.
|
||||
func (p *Status) isBad(pid peer.ID) bool {
|
||||
// Do not disconnect from trusted peers.
|
||||
if p.store.IsTrustedPeer(pid) {
|
||||
return false
|
||||
}
|
||||
return p.isfromBadIP(pid) || p.scorers.IsBadPeerNoLock(pid)
|
||||
}
|
||||
|
||||
@@ -543,6 +548,11 @@ func (p *Status) Prune() {
|
||||
p.store.Lock()
|
||||
defer p.store.Unlock()
|
||||
|
||||
// Default to old method if flag isnt enabled.
|
||||
if !features.Get().EnablePeerScorer {
|
||||
p.deprecatedPrune()
|
||||
return
|
||||
}
|
||||
// Exit early if there is nothing to prune.
|
||||
if len(p.store.Peers()) <= p.store.Config().MaxPeers {
|
||||
return
|
||||
@@ -587,6 +597,52 @@ func (p *Status) Prune() {
|
||||
p.tallyIPTracker()
|
||||
}
|
||||
|
||||
// Deprecated: This is the old peer pruning method based on
|
||||
// bad response counts.
|
||||
func (p *Status) deprecatedPrune() {
|
||||
// Exit early if there is nothing to prune.
|
||||
if len(p.store.Peers()) <= p.store.Config().MaxPeers {
|
||||
return
|
||||
}
|
||||
|
||||
notBadPeer := func(peerData *peerdata.PeerData) bool {
|
||||
return peerData.BadResponses < p.scorers.BadResponsesScorer().Params().Threshold
|
||||
}
|
||||
type peerResp struct {
|
||||
pid peer.ID
|
||||
badResp int
|
||||
}
|
||||
peersToPrune := make([]*peerResp, 0)
|
||||
// Select disconnected peers with a smaller bad response count.
|
||||
for pid, peerData := range p.store.Peers() {
|
||||
if peerData.ConnState == PeerDisconnected && notBadPeer(peerData) {
|
||||
peersToPrune = append(peersToPrune, &peerResp{
|
||||
pid: pid,
|
||||
badResp: peerData.BadResponses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort peers in ascending order, so the peers with the
|
||||
// least amount of bad responses are pruned first. This
|
||||
// is to protect the node from malicious/lousy peers so
|
||||
// that their memory is still kept.
|
||||
sort.Slice(peersToPrune, func(i, j int) bool {
|
||||
return peersToPrune[i].badResp < peersToPrune[j].badResp
|
||||
})
|
||||
|
||||
limitDiff := len(p.store.Peers()) - p.store.Config().MaxPeers
|
||||
if limitDiff > len(peersToPrune) {
|
||||
limitDiff = len(peersToPrune)
|
||||
}
|
||||
peersToPrune = peersToPrune[:limitDiff]
|
||||
// Delete peers from map.
|
||||
for _, peerData := range peersToPrune {
|
||||
p.store.DeletePeerData(peerData.pid)
|
||||
}
|
||||
p.tallyIPTracker()
|
||||
}
|
||||
|
||||
// BestFinalized returns the highest finalized epoch equal to or higher than ours that is agreed
|
||||
// upon by the majority of peers. This method may not return the absolute highest finalized, but
|
||||
// the finalized epoch in which most peers can serve blocks (plurality voting).
|
||||
@@ -694,6 +750,9 @@ func (p *Status) BestNonFinalized(minPeers int, ourHeadEpoch primitives.Epoch) (
|
||||
// bad response count. In the future scoring will be used
|
||||
// to determine the most suitable peers to take out.
|
||||
func (p *Status) PeersToPrune() []peer.ID {
|
||||
if !features.Get().EnablePeerScorer {
|
||||
return p.deprecatedPeersToPrune()
|
||||
}
|
||||
connLimit := p.ConnectedPeerLimit()
|
||||
inBoundLimit := uint64(p.InboundLimit())
|
||||
activePeers := p.Active()
|
||||
@@ -714,7 +773,7 @@ func (p *Status) PeersToPrune() []peer.ID {
|
||||
// Select connected and inbound peers to prune.
|
||||
for pid, peerData := range p.store.Peers() {
|
||||
if peerData.ConnState == PeerConnected &&
|
||||
peerData.Direction == network.DirInbound {
|
||||
peerData.Direction == network.DirInbound && !p.store.IsTrustedPeer(pid) {
|
||||
peersToPrune = append(peersToPrune, &peerResp{
|
||||
pid: pid,
|
||||
score: p.scorers.ScoreNoLock(pid),
|
||||
@@ -757,6 +816,71 @@ func (p *Status) PeersToPrune() []peer.ID {
|
||||
return ids
|
||||
}
|
||||
|
||||
// Deprecated: Is used to represent the older method
|
||||
// of pruning which utilized bad response counts.
|
||||
func (p *Status) deprecatedPeersToPrune() []peer.ID {
|
||||
connLimit := p.ConnectedPeerLimit()
|
||||
inBoundLimit := p.InboundLimit()
|
||||
activePeers := p.Active()
|
||||
numInboundPeers := len(p.InboundConnected())
|
||||
// Exit early if we are still below our max
|
||||
// limit.
|
||||
if uint64(len(activePeers)) <= connLimit {
|
||||
return []peer.ID{}
|
||||
}
|
||||
p.store.Lock()
|
||||
defer p.store.Unlock()
|
||||
|
||||
type peerResp struct {
|
||||
pid peer.ID
|
||||
badResp int
|
||||
}
|
||||
peersToPrune := make([]*peerResp, 0)
|
||||
// Select connected and inbound peers to prune.
|
||||
for pid, peerData := range p.store.Peers() {
|
||||
if peerData.ConnState == PeerConnected &&
|
||||
peerData.Direction == network.DirInbound && !p.store.IsTrustedPeer(pid) {
|
||||
peersToPrune = append(peersToPrune, &peerResp{
|
||||
pid: pid,
|
||||
badResp: peerData.BadResponses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort in descending order to favour pruning peers with a
|
||||
// higher bad response count.
|
||||
sort.Slice(peersToPrune, func(i, j int) bool {
|
||||
return peersToPrune[i].badResp > peersToPrune[j].badResp
|
||||
})
|
||||
|
||||
// Determine amount of peers to prune using our
|
||||
// max connection limit.
|
||||
amountToPrune, err := pmath.Sub64(uint64(len(activePeers)), connLimit)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
log.WithError(err).Error("Failed to determine amount of peers to prune")
|
||||
return []peer.ID{}
|
||||
}
|
||||
// Also check for inbound peers above our limit.
|
||||
excessInbound := uint64(0)
|
||||
if numInboundPeers > inBoundLimit {
|
||||
excessInbound = uint64(numInboundPeers - inBoundLimit)
|
||||
}
|
||||
// Prune the largest amount between excess peers and
|
||||
// excess inbound peers.
|
||||
if excessInbound > amountToPrune {
|
||||
amountToPrune = excessInbound
|
||||
}
|
||||
if amountToPrune < uint64(len(peersToPrune)) {
|
||||
peersToPrune = peersToPrune[:amountToPrune]
|
||||
}
|
||||
ids := make([]peer.ID, 0, len(peersToPrune))
|
||||
for _, pr := range peersToPrune {
|
||||
ids = append(ids, pr.pid)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// HighestEpoch returns the highest epoch reported epoch amongst peers.
|
||||
func (p *Status) HighestEpoch() primitives.Epoch {
|
||||
p.store.RLock()
|
||||
@@ -780,6 +904,14 @@ func (p *Status) ConnectedPeerLimit() uint64 {
|
||||
return uint64(maxLim) - maxLimitBuffer
|
||||
}
|
||||
|
||||
// SetTrustedPeers sets our trusted peer set into
|
||||
// our peerstore.
|
||||
func (p *Status) SetTrustedPeers(peers []peer.ID) {
|
||||
p.store.Lock()
|
||||
defer p.store.Unlock()
|
||||
p.store.SetTrustedPeers(peers)
|
||||
}
|
||||
|
||||
// this method assumes the store lock is acquired before
|
||||
// executing the method.
|
||||
func (p *Status) isfromBadIP(pid peer.ID) bool {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/scorers"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/wrapper"
|
||||
@@ -548,6 +549,10 @@ func TestPrune(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPeerIPTracker(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnablePeerScorer: false,
|
||||
})
|
||||
defer resetCfg()
|
||||
maxBadResponses := 2
|
||||
p := peers.NewStatus(context.Background(), &peers.StatusConfig{
|
||||
PeerLimit: 30,
|
||||
@@ -582,7 +587,7 @@ func TestPeerIPTracker(t *testing.T) {
|
||||
p.Prune()
|
||||
|
||||
for _, pr := range badPeers {
|
||||
assert.Equal(t, true, p.IsBad(pr), "peer with good ip is regarded as bad")
|
||||
assert.Equal(t, false, p.IsBad(pr), "peer with good ip is regarded as bad")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,6 +691,10 @@ func TestAtInboundPeerLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrunePeers(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
EnablePeerScorer: false,
|
||||
})
|
||||
defer resetCfg()
|
||||
p := peers.NewStatus(context.Background(), &peers.StatusConfig{
|
||||
PeerLimit: 30,
|
||||
ScorerParams: &scorers.Config{
|
||||
@@ -735,6 +744,80 @@ func TestPrunePeers(t *testing.T) {
|
||||
assert.Equal(t, network.DirInbound, dir)
|
||||
}
|
||||
|
||||
// Ensure it is in the descending order.
|
||||
currCount, err := p.Scorers().BadResponsesScorer().Count(peersToPrune[0])
|
||||
require.NoError(t, err)
|
||||
for _, pid := range peersToPrune {
|
||||
count, err := p.Scorers().BadResponsesScorer().Count(pid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, currCount >= count)
|
||||
currCount = count
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrunePeers_TrustedPeers(t *testing.T) {
|
||||
p := peers.NewStatus(context.Background(), &peers.StatusConfig{
|
||||
PeerLimit: 30,
|
||||
ScorerParams: &scorers.Config{
|
||||
BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
|
||||
Threshold: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
// Peer added to peer handler.
|
||||
createPeer(t, p, nil, network.DirOutbound, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
|
||||
}
|
||||
// Assert there are no prunable peers.
|
||||
peersToPrune := p.PeersToPrune()
|
||||
assert.Equal(t, 0, len(peersToPrune))
|
||||
|
||||
for i := 0; i < 18; i++ {
|
||||
// Peer added to peer handler.
|
||||
createPeer(t, p, nil, network.DirInbound, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
|
||||
}
|
||||
|
||||
// Assert there are the correct prunable peers.
|
||||
peersToPrune = p.PeersToPrune()
|
||||
assert.Equal(t, 3, len(peersToPrune))
|
||||
|
||||
// Add in more peers.
|
||||
for i := 0; i < 13; i++ {
|
||||
// Peer added to peer handler.
|
||||
createPeer(t, p, nil, network.DirInbound, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
|
||||
}
|
||||
|
||||
trustedPeers := []peer.ID{}
|
||||
// Set up bad scores for inbound peers.
|
||||
inboundPeers := p.InboundConnected()
|
||||
for i, pid := range inboundPeers {
|
||||
modulo := i % 5
|
||||
// Increment bad scores for peers.
|
||||
for j := 0; j < modulo; j++ {
|
||||
p.Scorers().BadResponsesScorer().Increment(pid)
|
||||
}
|
||||
if modulo == 4 {
|
||||
trustedPeers = append(trustedPeers, pid)
|
||||
}
|
||||
}
|
||||
p.SetTrustedPeers(trustedPeers)
|
||||
// Assert all peers more than max are prunable.
|
||||
peersToPrune = p.PeersToPrune()
|
||||
assert.Equal(t, 16, len(peersToPrune))
|
||||
|
||||
// Check that trusted peers are not pruned.
|
||||
for _, pid := range peersToPrune {
|
||||
for _, tPid := range trustedPeers {
|
||||
assert.NotEqual(t, pid.String(), tPid.String())
|
||||
}
|
||||
}
|
||||
for _, pid := range peersToPrune {
|
||||
dir, err := p.Direction(pid)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, network.DirInbound, dir)
|
||||
}
|
||||
|
||||
// Ensure it is in the descending order.
|
||||
currScore := p.Scorers().Score(peersToPrune[0])
|
||||
for _, pid := range peersToPrune {
|
||||
|
||||
@@ -210,6 +210,10 @@ func (s *Service) Start() {
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not connect to static peer")
|
||||
}
|
||||
// Set trusted peers for those that are provided as static addresses.
|
||||
pids := peerIdsFromMultiAddrs(addrs)
|
||||
s.peers.SetTrustedPeers(pids)
|
||||
peersToWatch = append(peersToWatch, s.cfg.StaticPeers...)
|
||||
s.connectWithAllPeers(addrs)
|
||||
}
|
||||
// Initialize metadata according to the
|
||||
|
||||
@@ -62,13 +62,13 @@ func (s *Service) FindPeersWithSubnet(ctx context.Context, topic string,
|
||||
currNum := len(s.pubsub.ListPeers(topic))
|
||||
wg := new(sync.WaitGroup)
|
||||
for {
|
||||
if currNum >= threshold {
|
||||
break
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return false, errors.Errorf("unable to find requisite number of peers for topic %s - "+
|
||||
"only %d out of %d peers were able to be found", topic, currNum, threshold)
|
||||
}
|
||||
if currNum >= threshold {
|
||||
break
|
||||
}
|
||||
nodes := enode.ReadNodes(iterator, int(params.BeaconNetworkConfig().MinimumPeersInSubnetSearch))
|
||||
for _, node := range nodes {
|
||||
info, _, err := convertToAddrInfo(node)
|
||||
|
||||
@@ -6,10 +6,12 @@ go_library(
|
||||
"blinded_blocks.go",
|
||||
"blocks.go",
|
||||
"config.go",
|
||||
"handlers.go",
|
||||
"log.go",
|
||||
"pool.go",
|
||||
"server.go",
|
||||
"state.go",
|
||||
"structs.go",
|
||||
"sync_committee.go",
|
||||
"validator.go",
|
||||
],
|
||||
@@ -49,7 +51,9 @@ go_library(
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//encoding/ssz/detect:go_default_library",
|
||||
"//network:go_default_library",
|
||||
"//network/forks: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",
|
||||
@@ -57,8 +61,10 @@ go_library(
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_go_playground_validator_v10//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_wealdtech_go_bytesutil//:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
@@ -75,6 +81,7 @@ go_test(
|
||||
"blinded_blocks_test.go",
|
||||
"blocks_test.go",
|
||||
"config_test.go",
|
||||
"handlers_test.go",
|
||||
"init_test.go",
|
||||
"pool_test.go",
|
||||
"server_test.go",
|
||||
@@ -129,6 +136,7 @@ go_test(
|
||||
"@com_github_golang_mock//gomock:go_default_library",
|
||||
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@com_github_stretchr_testify//mock:go_default_library",
|
||||
"@com_github_wealdtech_go_bytesutil//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
|
||||
@@ -44,8 +44,8 @@ func (bs *Server) GetBlindedBlock(ctx context.Context, req *ethpbv1.BlockRequest
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get blinded block: %v", err)
|
||||
}
|
||||
result, err = getBlindedBlockAltair(blk)
|
||||
@@ -53,8 +53,8 @@ func (bs *Server) GetBlindedBlock(ctx context.Context, req *ethpbv1.BlockRequest
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get blinded block: %v", err)
|
||||
}
|
||||
result, err = bs.getBlindedBlockBellatrix(ctx, blk)
|
||||
@@ -62,8 +62,8 @@ func (bs *Server) GetBlindedBlock(ctx context.Context, req *ethpbv1.BlockRequest
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get blinded block: %v", err)
|
||||
}
|
||||
result, err = bs.getBlindedBlockCapella(ctx, blk)
|
||||
@@ -71,8 +71,8 @@ func (bs *Server) GetBlindedBlock(ctx context.Context, req *ethpbv1.BlockRequest
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get blinded block: %v", err)
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ func (bs *Server) GetBlindedBlockSSZ(ctx context.Context, req *ethpbv1.BlockRequ
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = getSSZBlockAltair(blk)
|
||||
@@ -108,8 +108,8 @@ func (bs *Server) GetBlindedBlockSSZ(ctx context.Context, req *ethpbv1.BlockRequ
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = bs.getBlindedSSZBlockBellatrix(ctx, blk)
|
||||
@@ -117,8 +117,8 @@ func (bs *Server) GetBlindedBlockSSZ(ctx context.Context, req *ethpbv1.BlockRequ
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = bs.getBlindedSSZBlockCapella(ctx, blk)
|
||||
@@ -126,8 +126,8 @@ func (bs *Server) GetBlindedBlockSSZ(ctx context.Context, req *ethpbv1.BlockRequ
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ func (bs *Server) SubmitBlindedBlock(ctx context.Context, req *ethpbv2.SignedBli
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlindedBlock")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func (bs *Server) SubmitBlindedBlockSSZ(ctx context.Context, req *ethpbv2.SSZCon
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlindedBlockSSZ")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -344,8 +344,8 @@ func getBlindedBlockAltair(blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.B
|
||||
func (bs *Server) getBlindedBlockBellatrix(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.BlindedBlockResponse, error) {
|
||||
bellatrixBlk, err := blk.PbBellatrixBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedBellatrixBlk, err := blk.PbBlindedBellatrixBlock(); err == nil {
|
||||
if blindedBellatrixBlk == nil {
|
||||
return nil, errNilBlock
|
||||
@@ -414,8 +414,8 @@ func (bs *Server) getBlindedBlockBellatrix(ctx context.Context, blk interfaces.R
|
||||
func (bs *Server) getBlindedBlockCapella(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.BlindedBlockResponse, error) {
|
||||
capellaBlk, err := blk.PbCapellaBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedCapellaBlk, err := blk.PbBlindedCapellaBlock(); err == nil {
|
||||
if blindedCapellaBlk == nil {
|
||||
return nil, errNilBlock
|
||||
@@ -484,8 +484,8 @@ func (bs *Server) getBlindedBlockCapella(ctx context.Context, blk interfaces.Rea
|
||||
func (bs *Server) getBlindedSSZBlockBellatrix(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.SSZContainer, error) {
|
||||
bellatrixBlk, err := blk.PbBellatrixBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedBellatrixBlk, err := blk.PbBlindedBellatrixBlock(); err == nil {
|
||||
if blindedBellatrixBlk == nil {
|
||||
return nil, errNilBlock
|
||||
@@ -559,8 +559,8 @@ func (bs *Server) getBlindedSSZBlockBellatrix(ctx context.Context, blk interface
|
||||
func (bs *Server) getBlindedSSZBlockCapella(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.SSZContainer, error) {
|
||||
capellaBlk, err := blk.PbCapellaBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedCapellaBlk, err := blk.PbBlindedCapellaBlock(); err == nil {
|
||||
if blindedCapellaBlk == nil {
|
||||
return nil, errNilBlock
|
||||
|
||||
@@ -43,7 +43,7 @@ var (
|
||||
// determines the best block root and state root to use for a Checkpoint Sync starting from that point.
|
||||
// DEPRECATED: GetWeakSubjectivity endpoint will no longer be supported
|
||||
func (bs *Server) GetWeakSubjectivity(ctx context.Context, _ *empty.Empty) (*ethpbv1.WeakSubjectivityResponse, error) {
|
||||
if err := rpchelpers.ValidateSync(ctx, bs.SyncChecker, bs.HeadFetcher, bs.GenesisTimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.GenesisTimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
// This is already a grpc error, so we can't wrap it any further
|
||||
return nil, err
|
||||
}
|
||||
@@ -206,7 +206,7 @@ func (bs *Server) SubmitBlock(ctx context.Context, req *ethpbv2.SignedBeaconBloc
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlock")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -246,7 +246,7 @@ func (bs *Server) SubmitBlockSSZ(ctx context.Context, req *ethpbv2.SSZContainer)
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlockSSZ")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -420,8 +420,8 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = getBlockAltair(blk)
|
||||
@@ -429,8 +429,8 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = bs.getBlockBellatrix(ctx, blk)
|
||||
@@ -438,8 +438,8 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = bs.getBlockCapella(ctx, blk)
|
||||
@@ -447,8 +447,8 @@ func (bs *Server) GetBlockV2(ctx context.Context, req *ethpbv2.BlockRequestV2) (
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "Unknown block type %T", blk)
|
||||
@@ -474,8 +474,8 @@ func (bs *Server) GetBlockSSZV2(ctx context.Context, req *ethpbv2.BlockRequestV2
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = getSSZBlockAltair(blk)
|
||||
@@ -483,8 +483,8 @@ func (bs *Server) GetBlockSSZV2(ctx context.Context, req *ethpbv2.BlockRequestV2
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = bs.getSSZBlockBellatrix(ctx, blk)
|
||||
@@ -492,8 +492,8 @@ func (bs *Server) GetBlockSSZV2(ctx context.Context, req *ethpbv2.BlockRequestV2
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
result, err = bs.getSSZBlockCapella(ctx, blk)
|
||||
@@ -501,8 +501,8 @@ func (bs *Server) GetBlockSSZV2(ctx context.Context, req *ethpbv2.BlockRequestV2
|
||||
result.Finalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoot)
|
||||
return result, nil
|
||||
}
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get signed beacon block: %v", err)
|
||||
}
|
||||
|
||||
@@ -689,8 +689,8 @@ func getBlockAltair(blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.BlockRes
|
||||
func (bs *Server) getBlockBellatrix(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.BlockResponseV2, error) {
|
||||
bellatrixBlk, err := blk.PbBellatrixBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedBellatrixBlk, err := blk.PbBlindedBellatrixBlock(); err == nil {
|
||||
if blindedBellatrixBlk == nil {
|
||||
return nil, errNilBlock
|
||||
@@ -759,8 +759,8 @@ func (bs *Server) getBlockBellatrix(ctx context.Context, blk interfaces.ReadOnly
|
||||
func (bs *Server) getBlockCapella(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.BlockResponseV2, error) {
|
||||
capellaBlk, err := blk.PbCapellaBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedCapellaBlk, err := blk.PbBlindedCapellaBlock(); err == nil {
|
||||
if blindedCapellaBlk == nil {
|
||||
return nil, errNilBlock
|
||||
@@ -872,8 +872,8 @@ func getSSZBlockAltair(blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.SSZCo
|
||||
func (bs *Server) getSSZBlockBellatrix(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.SSZContainer, error) {
|
||||
bellatrixBlk, err := blk.PbBellatrixBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedBellatrixBlk, err := blk.PbBlindedBellatrixBlock(); err == nil {
|
||||
if blindedBellatrixBlk == nil {
|
||||
return nil, errNilBlock
|
||||
@@ -948,8 +948,8 @@ func (bs *Server) getSSZBlockBellatrix(ctx context.Context, blk interfaces.ReadO
|
||||
func (bs *Server) getSSZBlockCapella(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.SSZContainer, error) {
|
||||
capellaBlk, err := blk.PbCapellaBlock()
|
||||
if err != nil {
|
||||
// ErrUnsupportedGetter means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedGetter) {
|
||||
// ErrUnsupportedField means that we have another block type
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
if blindedCapellaBlk, err := blk.PbBlindedCapellaBlock(); err == nil {
|
||||
if blindedCapellaBlk == nil {
|
||||
return nil, errNilBlock
|
||||
|
||||
@@ -136,7 +136,7 @@ func TestGetSpec(t *testing.T) {
|
||||
resp, err := server.GetSpec(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 105, len(resp.Data))
|
||||
assert.Equal(t, 108, len(resp.Data))
|
||||
for k, v := range resp.Data {
|
||||
switch k {
|
||||
case "CONFIG_NAME":
|
||||
@@ -331,6 +331,10 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "0x08000000", v)
|
||||
case "DOMAIN_CONTRIBUTION_AND_PROOF":
|
||||
assert.Equal(t, "0x09000000", v)
|
||||
case "DOMAIN_BLS_TO_EXECUTION_CHANGE":
|
||||
assert.Equal(t, "0x0a000000", v)
|
||||
case "DOMAIN_APPLICATION_BUILDER":
|
||||
assert.Equal(t, "0x00000001", v)
|
||||
case "TRANSITION_TOTAL_DIFFICULTY":
|
||||
assert.Equal(t, "0", v)
|
||||
case "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH":
|
||||
@@ -361,6 +365,8 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "2", v)
|
||||
case "REORG_WEIGHT_THRESHOLD":
|
||||
assert.Equal(t, "20", v)
|
||||
case "REORG_PARENT_WEIGHT_THRESHOLD":
|
||||
assert.Equal(t, "160", v)
|
||||
case "SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY":
|
||||
default:
|
||||
t.Errorf("Incorrect key: %s", k)
|
||||
|
||||
375
beacon-chain/rpc/eth/beacon/handlers.go
Normal file
375
beacon-chain/rpc/eth/beacon/handlers.go
Normal file
@@ -0,0 +1,375 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/network"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
broadcastValidationQueryParam = "broadcast_validation"
|
||||
broadcastValidationConsensus = "consensus"
|
||||
broadcastValidationConsensusAndEquivocation = "consensus_and_equivocation"
|
||||
)
|
||||
|
||||
// PublishBlindedBlockV2 instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct and publish a
|
||||
// `SignedBeaconBlock` by swapping out the `transactions_root` for the corresponding full list of `transactions`.
|
||||
// The beacon node should broadcast a newly constructed `SignedBeaconBlock` to the beacon network,
|
||||
// to be included in the beacon chain. The beacon node is not required to validate the signed
|
||||
// `BeaconBlock`, and a successful response (20X) only indicates that the broadcast has been
|
||||
// successful. The beacon node is expected to integrate the new block into its state, and
|
||||
// therefore validate the block internally, however blocks which fail the validation are still
|
||||
// broadcast but a different status code is returned (202). Pre-Bellatrix, this endpoint will accept
|
||||
// a `SignedBeaconBlock`. The broadcast behaviour may be adjusted via the `broadcast_validation`
|
||||
// query parameter.
|
||||
func (bs *Server) PublishBlindedBlockV2(w http.ResponseWriter, r *http.Request) {
|
||||
if ok := bs.checkSync(r.Context(), w); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not read request body",
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
|
||||
var capellaBlock *SignedBlindedBeaconBlockCapella
|
||||
if err = unmarshalStrict(body, &capellaBlock); err == nil {
|
||||
if err = validate.Struct(capellaBlock); err == nil {
|
||||
consensusBlock, err := capellaBlock.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var bellatrixBlock *SignedBlindedBeaconBlockBellatrix
|
||||
if err = unmarshalStrict(body, &bellatrixBlock); err == nil {
|
||||
if err = validate.Struct(bellatrixBlock); err == nil {
|
||||
consensusBlock, err := bellatrixBlock.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
var altairBlock *SignedBeaconBlockAltair
|
||||
if err = unmarshalStrict(body, &altairBlock); err == nil {
|
||||
if err = validate.Struct(altairBlock); err == nil {
|
||||
consensusBlock, err := altairBlock.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
var phase0Block *SignedBeaconBlock
|
||||
if err = unmarshalStrict(body, &phase0Block); err == nil {
|
||||
if err = validate.Struct(phase0Block); err == nil {
|
||||
consensusBlock, err := phase0Block.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Body does not represent a valid block type",
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
}
|
||||
|
||||
// PublishBlockV2 instructs the beacon node to broadcast a newly signed beacon block to the beacon network,
|
||||
// to be included in the beacon chain. A success response (20x) indicates that the block
|
||||
// passed gossip validation and was successfully broadcast onto the network.
|
||||
// The beacon node is also expected to integrate the block into the state, but may broadcast it
|
||||
// before doing so, so as to aid timely delivery of the block. Should the block fail full
|
||||
// validation, a separate success response code (202) is used to indicate that the block was
|
||||
// successfully broadcast but failed integration. The broadcast behaviour may be adjusted via the
|
||||
// `broadcast_validation` query parameter.
|
||||
func (bs *Server) PublishBlockV2(w http.ResponseWriter, r *http.Request) {
|
||||
if ok := bs.checkSync(r.Context(), w); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not read request body",
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
|
||||
var capellaBlock *SignedBeaconBlockCapella
|
||||
if err = unmarshalStrict(body, &capellaBlock); err == nil {
|
||||
if err = validate.Struct(capellaBlock); err == nil {
|
||||
consensusBlock, err := capellaBlock.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
var bellatrixBlock *SignedBeaconBlockBellatrix
|
||||
if err = unmarshalStrict(body, &bellatrixBlock); err == nil {
|
||||
if err = validate.Struct(bellatrixBlock); err == nil {
|
||||
consensusBlock, err := bellatrixBlock.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
var altairBlock *SignedBeaconBlockAltair
|
||||
if err = unmarshalStrict(body, &altairBlock); err == nil {
|
||||
if err = validate.Struct(altairBlock); err == nil {
|
||||
consensusBlock, err := altairBlock.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
var phase0Block *SignedBeaconBlock
|
||||
if err = unmarshalStrict(body, &phase0Block); err == nil {
|
||||
if err = validate.Struct(phase0Block); err == nil {
|
||||
consensusBlock, err := phase0Block.ToGeneric()
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not decode request body into consensus block: " + err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
if err = bs.validateBroadcast(r, consensusBlock); err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
bs.proposeBlock(r.Context(), w, consensusBlock)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Body does not represent a valid block type",
|
||||
Code: http.StatusBadRequest,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
}
|
||||
|
||||
func (bs *Server) proposeBlock(ctx context.Context, w http.ResponseWriter, blk *eth.GenericSignedBeaconBlock) {
|
||||
_, err := bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, blk)
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalStrict(data []byte, v interface{}) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
dec.DisallowUnknownFields()
|
||||
return dec.Decode(v)
|
||||
}
|
||||
|
||||
func (bs *Server) validateBroadcast(r *http.Request, blk *eth.GenericSignedBeaconBlock) error {
|
||||
switch r.URL.Query().Get(broadcastValidationQueryParam) {
|
||||
case broadcastValidationConsensus:
|
||||
b, err := blocks.NewSignedBeaconBlock(blk.Block)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not create signed beacon block")
|
||||
}
|
||||
if err = bs.validateConsensus(r.Context(), b); err != nil {
|
||||
return errors.Wrap(err, "consensus validation failed")
|
||||
}
|
||||
case broadcastValidationConsensusAndEquivocation:
|
||||
b, err := blocks.NewSignedBeaconBlock(blk.Block)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not create signed beacon block")
|
||||
}
|
||||
if err = bs.validateConsensus(r.Context(), b); err != nil {
|
||||
return errors.Wrap(err, "consensus validation failed")
|
||||
}
|
||||
if err = bs.validateEquivocation(b.Block()); err != nil {
|
||||
return errors.Wrap(err, "equivocation validation failed")
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *Server) validateConsensus(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) error {
|
||||
parentRoot := blk.Block().ParentRoot()
|
||||
parentState, err := bs.Stater.State(ctx, parentRoot[:])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get parent state")
|
||||
}
|
||||
_, err = transition.ExecuteStateTransition(ctx, parentState, blk)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not execute state transition")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *Server) validateEquivocation(blk interfaces.ReadOnlyBeaconBlock) error {
|
||||
if bs.ForkchoiceFetcher.HighestReceivedBlockSlot() == blk.Slot() {
|
||||
return fmt.Errorf("block for slot %d already exists in fork choice", blk.Slot())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bs *Server) checkSync(ctx context.Context, w http.ResponseWriter) bool {
|
||||
isSyncing, syncDetails, err := helpers.ValidateSyncHTTP(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher)
|
||||
if err != nil {
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: "Could not check if node is syncing: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return false
|
||||
}
|
||||
if isSyncing {
|
||||
msg := "Beacon node is currently syncing and not serving request on that endpoint"
|
||||
details, err := json.Marshal(syncDetails)
|
||||
if err == nil {
|
||||
msg += " Details: " + string(details)
|
||||
}
|
||||
errJson := &network.DefaultErrorJson{
|
||||
Message: msg,
|
||||
Code: http.StatusServiceUnavailable,
|
||||
}
|
||||
network.WriteError(w, errJson)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
1275
beacon-chain/rpc/eth/beacon/handlers_test.go
Normal file
1275
beacon-chain/rpc/eth/beacon/handlers_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1183,7 +1183,7 @@ func TestServer_SubmitAttestations_InvalidAttestationGRPCHeader(t *testing.T) {
|
||||
require.Equal(t, true, ok, "could not retrieve custom error metadata value")
|
||||
assert.DeepEqual(
|
||||
t,
|
||||
[]string{"{\"failures\":[{\"index\":0,\"message\":\"Incorrect attestation signature: signature must be 96 bytes\"}]}"},
|
||||
[]string{"{\"failures\":[{\"index\":0,\"message\":\"Incorrect attestation signature: could not create signature from byte slice: signature must be 96 bytes\"}]}"},
|
||||
v,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,4 +45,5 @@ type Server struct {
|
||||
ExecutionPayloadReconstructor execution.ExecutionPayloadReconstructor
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
BLSChangesPool blstoexec.PoolManager
|
||||
ForkchoiceFetcher blockchain.ForkchoiceFetcher
|
||||
}
|
||||
|
||||
1650
beacon-chain/rpc/eth/beacon/structs.go
Normal file
1650
beacon-chain/rpc/eth/beacon/structs.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -20,9 +20,9 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ValidateSync checks whether the node is currently syncing and returns an error if it is.
|
||||
// ValidateSyncGRPC checks whether the node is currently syncing and returns an error if it is.
|
||||
// It also appends syncing info to gRPC headers.
|
||||
func ValidateSync(
|
||||
func ValidateSyncGRPC(
|
||||
ctx context.Context,
|
||||
syncChecker sync.Checker,
|
||||
headFetcher blockchain.HeadFetcher,
|
||||
@@ -38,8 +38,8 @@ func ValidateSync(
|
||||
return status.Errorf(codes.Internal, "Could not check optimistic status: %v", err)
|
||||
}
|
||||
|
||||
syncDetailsContainer := &syncDetailsContainer{
|
||||
SyncDetails: &SyncDetailsJson{
|
||||
syncDetailsContainer := &SyncDetailsContainer{
|
||||
Data: &SyncDetailsJson{
|
||||
HeadSlot: strconv.FormatUint(uint64(headSlot), 10),
|
||||
SyncDistance: strconv.FormatUint(uint64(timeFetcher.CurrentSlot()-headSlot), 10),
|
||||
IsSyncing: true,
|
||||
@@ -58,6 +58,35 @@ func ValidateSync(
|
||||
return status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
|
||||
}
|
||||
|
||||
// ValidateSyncHTTP checks whether the node is currently syncing and returns sync information.
|
||||
// It returns information whether the node is currently syncing along with sync details.
|
||||
func ValidateSyncHTTP(
|
||||
ctx context.Context,
|
||||
syncChecker sync.Checker,
|
||||
headFetcher blockchain.HeadFetcher,
|
||||
timeFetcher blockchain.TimeFetcher,
|
||||
optimisticModeFetcher blockchain.OptimisticModeFetcher,
|
||||
) (bool, *SyncDetailsContainer, error) {
|
||||
if !syncChecker.Syncing() {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
headSlot := headFetcher.HeadSlot()
|
||||
isOptimistic, err := optimisticModeFetcher.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
return true, nil, errors.Wrap(err, "could not check optimistic status")
|
||||
}
|
||||
syncDetails := &SyncDetailsContainer{
|
||||
Data: &SyncDetailsJson{
|
||||
HeadSlot: strconv.FormatUint(uint64(headSlot), 10),
|
||||
SyncDistance: strconv.FormatUint(uint64(timeFetcher.CurrentSlot()-headSlot), 10),
|
||||
IsSyncing: true,
|
||||
IsOptimistic: isOptimistic,
|
||||
},
|
||||
}
|
||||
return true, syncDetails, nil
|
||||
}
|
||||
|
||||
// IsOptimistic checks whether the beacon state's block is optimistic.
|
||||
func IsOptimistic(
|
||||
ctx context.Context,
|
||||
@@ -197,7 +226,7 @@ type SyncDetailsJson struct {
|
||||
ElOffline bool `json:"el_offline"`
|
||||
}
|
||||
|
||||
// SyncDetailsContainer is a wrapper for SyncDetails.
|
||||
type syncDetailsContainer struct {
|
||||
SyncDetails *SyncDetailsJson `json:"sync_details"`
|
||||
// SyncDetailsContainer is a wrapper for Data.
|
||||
type SyncDetailsContainer struct {
|
||||
Data *SyncDetailsJson `json:"data"`
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestValidateSync(t *testing.T) {
|
||||
Slot: &headSlot,
|
||||
State: st,
|
||||
}
|
||||
err = ValidateSync(ctx, syncChecker, chainService, chainService, chainService)
|
||||
err = ValidateSyncGRPC(ctx, syncChecker, chainService, chainService, chainService)
|
||||
require.NotNil(t, err)
|
||||
sts, ok := grpc.ServerTransportStreamFromContext(ctx).(*runtime.ServerTransportStream)
|
||||
require.Equal(t, true, ok, "type assertion failed")
|
||||
@@ -51,7 +51,7 @@ func TestValidateSync(t *testing.T) {
|
||||
require.Equal(t, true, ok, "could not retrieve custom error metadata value")
|
||||
assert.DeepEqual(
|
||||
t,
|
||||
[]string{`{"sync_details":{"head_slot":"50","sync_distance":"50","is_syncing":true,"is_optimistic":false,"el_offline":false}}`},
|
||||
[]string{`{"data":{"head_slot":"50","sync_distance":"50","is_syncing":true,"is_optimistic":false,"el_offline":false}}`},
|
||||
v,
|
||||
)
|
||||
})
|
||||
@@ -67,7 +67,7 @@ func TestValidateSync(t *testing.T) {
|
||||
Slot: &headSlot,
|
||||
State: st,
|
||||
}
|
||||
err = ValidateSync(ctx, syncChecker, nil, nil, chainService)
|
||||
err = ValidateSyncGRPC(ctx, syncChecker, nil, nil, chainService)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -337,6 +337,9 @@ func handleEmptyFilters(req *ethpb.PeersRequest) (emptyState, emptyDirection boo
|
||||
func peerInfo(peerStatus *peers.Status, id peer.ID) (*ethpb.Peer, error) {
|
||||
enr, err := peerStatus.ENR(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, peerdata.ErrPeerUnknown) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "could not obtain ENR")
|
||||
}
|
||||
var serializedEnr string
|
||||
@@ -348,14 +351,23 @@ func peerInfo(peerStatus *peers.Status, id peer.ID) (*ethpb.Peer, error) {
|
||||
}
|
||||
address, err := peerStatus.Address(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, peerdata.ErrPeerUnknown) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "could not obtain address")
|
||||
}
|
||||
connectionState, err := peerStatus.ConnectionState(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, peerdata.ErrPeerUnknown) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "could not obtain connection state")
|
||||
}
|
||||
direction, err := peerStatus.Direction(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, peerdata.ErrPeerUnknown) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "could not obtain direction")
|
||||
}
|
||||
if eth.PeerDirection(direction) == eth.PeerDirection_UNKNOWN {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (vs *Server) GetAttesterDuties(ctx context.Context, req *ethpbv1.AttesterDu
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetAttesterDuties")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (vs *Server) GetProposerDuties(ctx context.Context, req *ethpbv1.ProposerDu
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetProposerDuties")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -224,7 +224,7 @@ func (vs *Server) GetSyncCommitteeDuties(ctx context.Context, req *ethpbv2.SyncC
|
||||
ctx, span := trace.StartSpan(ctx, "validator.GetSyncCommitteeDuties")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -307,7 +307,7 @@ func (vs *Server) ProduceBlockV2(ctx context.Context, req *ethpbv1.ProduceBlockR
|
||||
ctx, span := trace.StartSpan(ctx, "validator.ProduceBlockV2")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -406,7 +406,7 @@ func (vs *Server) ProduceBlockV2SSZ(ctx context.Context, req *ethpbv1.ProduceBlo
|
||||
ctx, span := trace.StartSpan(ctx, "validator.ProduceBlockV2SSZ")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -515,7 +515,7 @@ func (vs *Server) ProduceBlindedBlock(ctx context.Context, req *ethpbv1.ProduceB
|
||||
if !vs.BlockBuilder.Configured() {
|
||||
return nil, status.Error(codes.Internal, "Block builder not configured")
|
||||
}
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -614,7 +614,7 @@ func (vs *Server) ProduceBlindedBlockSSZ(ctx context.Context, req *ethpbv1.Produ
|
||||
if !vs.BlockBuilder.Configured() {
|
||||
return nil, status.Error(codes.Internal, "Block builder not configured")
|
||||
}
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -891,7 +891,7 @@ func (vs *Server) SubmitBeaconCommitteeSubscription(ctx context.Context, req *et
|
||||
ctx, span := trace.StartSpan(ctx, "validator.SubmitBeaconCommitteeSubscription")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
@@ -972,7 +972,7 @@ func (vs *Server) SubmitSyncCommitteeSubscription(ctx context.Context, req *ethp
|
||||
ctx, span := trace.StartSpan(ctx, "validator.SubmitSyncCommitteeSubscription")
|
||||
defer span.End()
|
||||
|
||||
if err := rpchelpers.ValidateSync(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
if err := rpchelpers.ValidateSyncGRPC(ctx, vs.SyncChecker, vs.HeadFetcher, vs.TimeFetcher, vs.OptimisticModeFetcher); err != nil {
|
||||
// We simply return the error because it's already a gRPC error.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ go_library(
|
||||
"server.go",
|
||||
"status.go",
|
||||
"sync_committee.go",
|
||||
"unblinder.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/v1alpha1/validator",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
@@ -55,13 +56,14 @@ go_library(
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/operations/voluntaryexits:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/payload-attribute:go_default_library",
|
||||
@@ -96,6 +98,7 @@ go_library(
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
"@org_golang_x_sync//errgroup:go_default_library",
|
||||
],
|
||||
@@ -181,7 +184,6 @@ go_test(
|
||||
"proposer_attestations_test.go",
|
||||
"proposer_bellatrix_test.go",
|
||||
"proposer_builder_test.go",
|
||||
"proposer_capella_test.go",
|
||||
"proposer_deposits_test.go",
|
||||
"proposer_empty_block_test.go",
|
||||
"proposer_execution_payload_test.go",
|
||||
@@ -194,6 +196,7 @@ go_test(
|
||||
"status_mainnet_test.go",
|
||||
"status_test.go",
|
||||
"sync_committee_test.go",
|
||||
"unblinder_test.go",
|
||||
"validator_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -50,6 +49,15 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
defer span.End()
|
||||
span.AddAttributes(trace.Int64Attribute("slot", int64(req.Slot)))
|
||||
|
||||
t, err := slots.ToTime(uint64(vs.TimeFetcher.GenesisTime().Unix()), req.Slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not convert slot to time")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": req.Slot,
|
||||
"sinceSlotStartTime": time.Since(t),
|
||||
}).Info("Begin building block")
|
||||
|
||||
// A syncing validator should not produce a block.
|
||||
if vs.SyncChecker.Syncing() {
|
||||
return nil, status.Error(codes.Unavailable, "Syncing to latest head, not ready to respond")
|
||||
@@ -131,8 +139,17 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
// Set sync aggregate. New in Altair.
|
||||
vs.setSyncAggregate(ctx, sBlk)
|
||||
|
||||
// Set execution data. New in Bellatrix.
|
||||
if err := vs.setExecutionData(ctx, sBlk, head); err != nil {
|
||||
// Get local and builder (if enabled) payloads. Set execution data. New in Bellatrix.
|
||||
localPayload, err := vs.getLocalPayload(ctx, sBlk.Block(), head)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get local payload: %v", err)
|
||||
}
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex())
|
||||
if err != nil {
|
||||
builderGetPayloadMissCount.Inc()
|
||||
log.WithError(err).Error("Could not get builder payload")
|
||||
}
|
||||
if err := setExecutionData(ctx, sBlk, localPayload, builderPayload); err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not set execution data: %v", err)
|
||||
}
|
||||
|
||||
@@ -146,6 +163,12 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
|
||||
}
|
||||
sBlk.SetStateRoot(sr)
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": req.Slot,
|
||||
"sinceSlotStartTime": time.Since(t),
|
||||
"validator": sBlk.Block().ProposerIndex(),
|
||||
}).Info("Finished building block")
|
||||
|
||||
pb, err := sBlk.Block().Proto()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not convert block to proto: %v", err)
|
||||
@@ -209,7 +232,18 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
|
||||
vs.setBlsToExecData(sBlk, head)
|
||||
}()
|
||||
|
||||
if err := vs.setExecutionData(ctx, sBlk, head); err != nil {
|
||||
localPayload, err := vs.getLocalPayload(ctx, sBlk.Block(), head)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not get local payload: %v", err)
|
||||
}
|
||||
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, sBlk.Block().Slot(), sBlk.Block().ProposerIndex())
|
||||
if err != nil {
|
||||
builderGetPayloadMissCount.Inc()
|
||||
log.WithError(err).Error("Could not get builder payload")
|
||||
}
|
||||
|
||||
if err := setExecutionData(ctx, sBlk, localPayload, builderPayload); err != nil {
|
||||
return status.Errorf(codes.Internal, "Could not set execution data: %v", err)
|
||||
}
|
||||
|
||||
@@ -309,29 +343,13 @@ func (vs *Server) GetFeeRecipientByPubKey(ctx context.Context, request *ethpb.Fe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (vs *Server) proposeGenericBeaconBlock(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*ethpb.ProposeResponse, error) {
|
||||
func (vs *Server) proposeGenericBeaconBlock(ctx context.Context, blk interfaces.SignedBeaconBlock) (*ethpb.ProposeResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.proposeGenericBeaconBlock")
|
||||
defer span.End()
|
||||
root, err := blk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not tree hash block: %v", err)
|
||||
}
|
||||
|
||||
if slots.ToEpoch(blk.Block().Slot()) >= params.BeaconConfig().CapellaForkEpoch {
|
||||
blk, err = vs.unblindBuilderBlockCapella(ctx, blk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
blk, err = vs.unblindBuilderBlock(ctx, blk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Do not block proposal critical path with debug logging or block feed updates.
|
||||
defer func() {
|
||||
log.WithField("blockRoot", fmt.Sprintf("%#x", bytesutil.Trunc(root[:]))).Debugf(
|
||||
log.WithField("slot", blk.Block().Slot()).Debugf(
|
||||
"Block proposal received via RPC")
|
||||
vs.BlockNotifier.BlockFeed().Send(&feed.Event{
|
||||
Type: blockfeed.ReceivedBlock,
|
||||
@@ -339,6 +357,15 @@ func (vs *Server) proposeGenericBeaconBlock(ctx context.Context, blk interfaces.
|
||||
})
|
||||
}()
|
||||
|
||||
unblinder, err := newUnblinder(blk, vs.BlockBuilder)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create unblinder")
|
||||
}
|
||||
blk, err = unblinder.unblindBuilderBlock(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not unblind builder block")
|
||||
}
|
||||
|
||||
// Broadcast the new block to the network.
|
||||
blkPb, err := blk.Proto()
|
||||
if err != nil {
|
||||
@@ -347,6 +374,11 @@ func (vs *Server) proposeGenericBeaconBlock(ctx context.Context, blk interfaces.
|
||||
if err := vs.P2P.Broadcast(ctx, blkPb); err != nil {
|
||||
return nil, fmt.Errorf("could not broadcast block: %v", err)
|
||||
}
|
||||
root, err := blk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not tree hash block: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockRoot": hex.EncodeToString(root[:]),
|
||||
}).Debug("Broadcasting block")
|
||||
|
||||
@@ -12,18 +12,14 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client/builder"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
"github.com/prysmaticlabs/prysm/v4/monitoring/tracing"
|
||||
"github.com/prysmaticlabs/prysm/v4/network/forks"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -45,93 +41,84 @@ var emptyTransactionsRoot = [32]byte{127, 254, 36, 30, 166, 1, 135, 253, 176, 24
|
||||
const blockBuilderTimeout = 1 * time.Second
|
||||
|
||||
// Sets the execution data for the block. Execution data can come from local EL client or remote builder depends on validator registration and circuit breaker conditions.
|
||||
func (vs *Server) setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, headState state.BeaconState) error {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.setExecutionData")
|
||||
func setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, localPayload, builderPayload interfaces.ExecutionData) error {
|
||||
_, span := trace.StartSpan(ctx, "ProposerServer.setExecutionData")
|
||||
defer span.End()
|
||||
|
||||
idx := blk.Block().ProposerIndex()
|
||||
slot := blk.Block().Slot()
|
||||
if slots.ToEpoch(slot) < params.BeaconConfig().BellatrixForkEpoch {
|
||||
return nil
|
||||
}
|
||||
|
||||
canUseBuilder, err := vs.canUseBuilder(ctx, slot, idx)
|
||||
span.AddAttributes(trace.BoolAttribute("canUseBuilder", canUseBuilder))
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to check if builder can be used")
|
||||
} else if canUseBuilder {
|
||||
builderPayload, err := vs.getPayloadHeaderFromBuilder(ctx, slot, idx)
|
||||
if localPayload == nil {
|
||||
return errors.New("local payload is nil")
|
||||
}
|
||||
|
||||
// Use local payload if builder payload is nil.
|
||||
if builderPayload == nil {
|
||||
return blk.SetExecution(localPayload)
|
||||
}
|
||||
|
||||
switch {
|
||||
case blk.Version() >= version.Capella:
|
||||
// Compare payload values between local and builder. Default to the local value if it is higher.
|
||||
localValueGwei, err := localPayload.ValueInGwei()
|
||||
if err != nil {
|
||||
builderGetPayloadMissCount.Inc()
|
||||
log.WithError(err).Warn("Proposer: failed to get payload header from builder")
|
||||
} else {
|
||||
switch {
|
||||
case blk.Version() >= version.Capella:
|
||||
localPayload, err := vs.getExecutionPayload(ctx, slot, idx, blk.Block().ParentRoot(), headState)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get execution payload")
|
||||
}
|
||||
// Compare payload values between local and builder. Default to the local value if it is higher.
|
||||
localValueGwei, err := localPayload.ValueInGwei()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get local payload value")
|
||||
}
|
||||
builderValueGwei, err := builderPayload.ValueInGwei()
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to get builder payload value") // Default to local if can't get builder value.
|
||||
}
|
||||
return errors.Wrap(err, "failed to get local payload value")
|
||||
}
|
||||
builderValueGwei, err := builderPayload.ValueInGwei()
|
||||
if err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to get builder payload value") // Default to local if can't get builder value.
|
||||
return blk.SetExecution(localPayload)
|
||||
}
|
||||
|
||||
withdrawalsMatched, err := matchingWithdrawalsRoot(localPayload, builderPayload)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return errors.Wrap(err, "failed to match withdrawals root")
|
||||
}
|
||||
withdrawalsMatched, err := matchingWithdrawalsRoot(localPayload, builderPayload)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
log.WithError(err).Warn("Proposer: failed to match withdrawals root")
|
||||
return blk.SetExecution(localPayload)
|
||||
}
|
||||
|
||||
// Use builder payload if the following in true:
|
||||
// builder_bid_value * 100 > local_block_value * (local-block-value-boost + 100)
|
||||
boost := params.BeaconConfig().LocalBlockValueBoost
|
||||
higherValueBuilder := builderValueGwei*100 > localValueGwei*(100+boost)
|
||||
// Use builder payload if the following in true:
|
||||
// builder_bid_value * 100 > local_block_value * (local-block-value-boost + 100)
|
||||
boost := params.BeaconConfig().LocalBlockValueBoost
|
||||
higherValueBuilder := builderValueGwei*100 > localValueGwei*(100+boost)
|
||||
|
||||
// If we can't get the builder value, just use local block.
|
||||
if higherValueBuilder && withdrawalsMatched { // Builder value is higher and withdrawals match.
|
||||
blk.SetBlinded(true)
|
||||
if err := blk.SetExecution(builderPayload); err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to set builder payload")
|
||||
blk.SetBlinded(false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if !higherValueBuilder {
|
||||
log.WithFields(logrus.Fields{
|
||||
"localGweiValue": localValueGwei,
|
||||
"localBoostPercentage": boost,
|
||||
"builderGweiValue": builderValueGwei,
|
||||
}).Warn("Proposer: using local execution payload because higher value")
|
||||
}
|
||||
span.AddAttributes(
|
||||
trace.BoolAttribute("higherValueBuilder", higherValueBuilder),
|
||||
trace.Int64Attribute("localGweiValue", int64(localValueGwei)), // lint:ignore uintcast -- This is OK for tracing.
|
||||
trace.Int64Attribute("localBoostPercentage", int64(boost)), // lint:ignore uintcast -- This is OK for tracing.
|
||||
trace.Int64Attribute("builderGweiValue", int64(builderValueGwei)), // lint:ignore uintcast -- This is OK for tracing.
|
||||
)
|
||||
// If we can't get the builder value, just use local block.
|
||||
if higherValueBuilder && withdrawalsMatched { // Builder value is higher and withdrawals match.
|
||||
blk.SetBlinded(true)
|
||||
if err := blk.SetExecution(builderPayload); err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to set builder payload")
|
||||
blk.SetBlinded(false)
|
||||
return blk.SetExecution(localPayload)
|
||||
default: // Bellatrix case.
|
||||
blk.SetBlinded(true)
|
||||
if err := blk.SetExecution(builderPayload); err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to set builder payload")
|
||||
blk.SetBlinded(false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if !higherValueBuilder {
|
||||
log.WithFields(logrus.Fields{
|
||||
"localGweiValue": localValueGwei,
|
||||
"localBoostPercentage": boost,
|
||||
"builderGweiValue": builderValueGwei,
|
||||
}).Warn("Proposer: using local execution payload because higher value")
|
||||
}
|
||||
span.AddAttributes(
|
||||
trace.BoolAttribute("higherValueBuilder", higherValueBuilder),
|
||||
trace.Int64Attribute("localGweiValue", int64(localValueGwei)), // lint:ignore uintcast -- This is OK for tracing.
|
||||
trace.Int64Attribute("localBoostPercentage", int64(boost)), // lint:ignore uintcast -- This is OK for tracing.
|
||||
trace.Int64Attribute("builderGweiValue", int64(builderValueGwei)), // lint:ignore uintcast -- This is OK for tracing.
|
||||
)
|
||||
return blk.SetExecution(localPayload)
|
||||
default: // Bellatrix case.
|
||||
blk.SetBlinded(true)
|
||||
if err := blk.SetExecution(builderPayload); err != nil {
|
||||
log.WithError(err).Warn("Proposer: failed to set builder payload")
|
||||
blk.SetBlinded(false)
|
||||
return blk.SetExecution(localPayload)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
executionData, err := vs.getExecutionPayload(ctx, slot, idx, blk.Block().ParentRoot(), headState)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get execution payload")
|
||||
}
|
||||
return blk.SetExecution(executionData)
|
||||
}
|
||||
|
||||
// This function retrieves the payload header given the slot number and the validator index.
|
||||
@@ -222,9 +209,12 @@ func (vs *Server) getPayloadHeaderFromBuilder(ctx context.Context, slot primitiv
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"value": v.String(),
|
||||
"builderPubKey": fmt.Sprintf("%#x", bid.Pubkey()),
|
||||
"blockHash": fmt.Sprintf("%#x", header.BlockHash()),
|
||||
"value": v.String(),
|
||||
"builderPubKey": fmt.Sprintf("%#x", bid.Pubkey()),
|
||||
"blockHash": fmt.Sprintf("%#x", header.BlockHash()),
|
||||
"slot": slot,
|
||||
"validator": idx,
|
||||
"sinceSlotStartTime": time.Since(t),
|
||||
}).Info("Received header with bid")
|
||||
|
||||
span.AddAttributes(
|
||||
@@ -236,129 +226,6 @@ func (vs *Server) getPayloadHeaderFromBuilder(ctx context.Context, slot primitiv
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// This function retrieves the full payload block using the input blind block. This input must be versioned as
|
||||
// bellatrix blind block. The output block will contain the full payload. The original header block
|
||||
// will be returned the block builder is not configured.
|
||||
func (vs *Server) unblindBuilderBlock(ctx context.Context, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ReadOnlySignedBeaconBlock, error) {
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No-op if the input block is not version blind and bellatrix.
|
||||
if b.Version() != version.Bellatrix || !b.IsBlinded() {
|
||||
return b, nil
|
||||
}
|
||||
// No-op nothing if the builder has not been configured.
|
||||
if !vs.BlockBuilder.Configured() {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
agg, err := b.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, err := b.Block().Body().Execution()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header, ok := h.Proto().(*enginev1.ExecutionPayloadHeader)
|
||||
if !ok {
|
||||
return nil, errors.New("execution data must be execution payload header")
|
||||
}
|
||||
parentRoot := b.Block().ParentRoot()
|
||||
stateRoot := b.Block().StateRoot()
|
||||
randaoReveal := b.Block().Body().RandaoReveal()
|
||||
graffiti := b.Block().Body().Graffiti()
|
||||
sig := b.Signature()
|
||||
psb := ðpb.SignedBlindedBeaconBlockBellatrix{
|
||||
Block: ðpb.BlindedBeaconBlockBellatrix{
|
||||
Slot: b.Block().Slot(),
|
||||
ProposerIndex: b.Block().ProposerIndex(),
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: stateRoot[:],
|
||||
Body: ðpb.BlindedBeaconBlockBodyBellatrix{
|
||||
RandaoReveal: randaoReveal[:],
|
||||
Eth1Data: b.Block().Body().Eth1Data(),
|
||||
Graffiti: graffiti[:],
|
||||
ProposerSlashings: b.Block().Body().ProposerSlashings(),
|
||||
AttesterSlashings: b.Block().Body().AttesterSlashings(),
|
||||
Attestations: b.Block().Body().Attestations(),
|
||||
Deposits: b.Block().Body().Deposits(),
|
||||
VoluntaryExits: b.Block().Body().VoluntaryExits(),
|
||||
SyncAggregate: agg,
|
||||
ExecutionPayloadHeader: header,
|
||||
},
|
||||
},
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(psb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create signed block")
|
||||
}
|
||||
payload, err := vs.BlockBuilder.SubmitBlindedBlock(ctx, sb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headerRoot, err := header.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payloadRoot, err := payload.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if headerRoot != payloadRoot {
|
||||
return nil, fmt.Errorf("header and payload root do not match, consider disconnect from relay to avoid further issues, "+
|
||||
"%#x != %#x", headerRoot, payloadRoot)
|
||||
}
|
||||
|
||||
pbPayload, err := payload.PbBellatrix()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get payload")
|
||||
}
|
||||
bb := ðpb.SignedBeaconBlockBellatrix{
|
||||
Block: ðpb.BeaconBlockBellatrix{
|
||||
Slot: psb.Block.Slot,
|
||||
ProposerIndex: psb.Block.ProposerIndex,
|
||||
ParentRoot: psb.Block.ParentRoot,
|
||||
StateRoot: psb.Block.StateRoot,
|
||||
Body: ðpb.BeaconBlockBodyBellatrix{
|
||||
RandaoReveal: psb.Block.Body.RandaoReveal,
|
||||
Eth1Data: psb.Block.Body.Eth1Data,
|
||||
Graffiti: psb.Block.Body.Graffiti,
|
||||
ProposerSlashings: psb.Block.Body.ProposerSlashings,
|
||||
AttesterSlashings: psb.Block.Body.AttesterSlashings,
|
||||
Attestations: psb.Block.Body.Attestations,
|
||||
Deposits: psb.Block.Body.Deposits,
|
||||
VoluntaryExits: psb.Block.Body.VoluntaryExits,
|
||||
SyncAggregate: agg,
|
||||
ExecutionPayload: pbPayload,
|
||||
},
|
||||
},
|
||||
Signature: psb.Signature,
|
||||
}
|
||||
wb, err := consensusblocks.NewSignedBeaconBlock(bb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txs, err := payload.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions from payload")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockHash": fmt.Sprintf("%#x", h.BlockHash()),
|
||||
"feeRecipient": fmt.Sprintf("%#x", h.FeeRecipient()),
|
||||
"gasUsed": h.GasUsed,
|
||||
"slot": b.Block().Slot(),
|
||||
"txs": len(txs),
|
||||
}).Info("Retrieved full payload from builder")
|
||||
|
||||
return wb, nil
|
||||
}
|
||||
|
||||
// Validates builder signature and returns an error if the signature is invalid.
|
||||
func validateBuilderSignature(signedBid builder.SignedBid) error {
|
||||
d, err := signing.ComputeDomain(params.BeaconConfig().DomainApplicationBuilder,
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
consensus_types "github.com/prysmaticlabs/prysm/v4/consensus-types"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
@@ -70,12 +71,18 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
BeaconDB: beaconDB,
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true, Cfg: &builderTest.Config{BeaconDB: beaconDB}},
|
||||
ForkchoiceFetcher: &blockchainTest.ChainService{},
|
||||
}
|
||||
|
||||
t.Run("No builder configured. Use local block", func(t *testing.T) {
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState))
|
||||
b := blk.Block()
|
||||
localPayload, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload))
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), e.BlockNumber()) // Local block
|
||||
@@ -128,7 +135,13 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
vs.ForkchoiceFetcher.SetForkChoiceGenesisTime(uint64(time.Now().Unix()))
|
||||
vs.TimeFetcher = chain
|
||||
vs.HeadFetcher = chain
|
||||
require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState))
|
||||
b := blk.Block()
|
||||
|
||||
localPayload, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload))
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), e.BlockNumber()) // Local block because incorrect withdrawals
|
||||
@@ -184,7 +197,13 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
vs.ForkchoiceFetcher.SetForkChoiceGenesisTime(uint64(time.Now().Unix()))
|
||||
vs.TimeFetcher = chain
|
||||
vs.HeadFetcher = chain
|
||||
require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState))
|
||||
|
||||
b := blk.Block()
|
||||
localPayload, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload))
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(2), e.BlockNumber()) // Builder block
|
||||
@@ -193,7 +212,12 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
|
||||
require.NoError(t, err)
|
||||
vs.ExecutionEngineCaller = &powtesting.EngineClient{PayloadIDBytes: id, ExecutionPayloadCapella: &v1.ExecutionPayloadCapella{BlockNumber: 3}, BlockValue: 2}
|
||||
require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState))
|
||||
b := blk.Block()
|
||||
localPayload, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload))
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(3), e.BlockNumber()) // Local block
|
||||
@@ -208,7 +232,12 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella())
|
||||
require.NoError(t, err)
|
||||
vs.ExecutionEngineCaller = &powtesting.EngineClient{PayloadIDBytes: id, ExecutionPayloadCapella: &v1.ExecutionPayloadCapella{BlockNumber: 3}, BlockValue: 1}
|
||||
require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState))
|
||||
b := blk.Block()
|
||||
localPayload, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload))
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(3), e.BlockNumber()) // Local block
|
||||
@@ -224,7 +253,12 @@ func TestServer_setExecutionData(t *testing.T) {
|
||||
Cfg: &builderTest.Config{BeaconDB: beaconDB},
|
||||
}
|
||||
vs.ExecutionEngineCaller = &powtesting.EngineClient{PayloadIDBytes: id, ExecutionPayloadCapella: &v1.ExecutionPayloadCapella{BlockNumber: 4}, BlockValue: 0}
|
||||
require.NoError(t, vs.setExecutionData(context.Background(), blk, capellaTransitionState))
|
||||
b := blk.Block()
|
||||
localPayload, err := vs.getLocalPayload(ctx, b, capellaTransitionState)
|
||||
require.NoError(t, err)
|
||||
builderPayload, err := vs.getBuilderPayload(ctx, b.Slot(), b.ProposerIndex())
|
||||
require.ErrorIs(t, consensus_types.ErrNilObjectWrapped, err) // Builder returns fault. Use local block
|
||||
require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload))
|
||||
e, err := blk.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(4), e.BlockNumber()) // Local block
|
||||
@@ -465,146 +499,6 @@ func TestServer_getPayloadHeader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_getBuilderBlock(t *testing.T) {
|
||||
p := emptyPayload()
|
||||
p.GasLimit = 123
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
blk interfaces.ReadOnlySignedBeaconBlock
|
||||
mock *builderTest.MockBuilderService
|
||||
err string
|
||||
returnedBlk interfaces.ReadOnlySignedBeaconBlock
|
||||
}{
|
||||
{
|
||||
name: "nil block",
|
||||
blk: nil,
|
||||
err: "signed beacon block can't be nil",
|
||||
},
|
||||
{
|
||||
name: "old block version",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "not configured",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockBellatrix())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: false,
|
||||
},
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockBellatrix())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "submit blind block error",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
Payload: &v1.ExecutionPayload{},
|
||||
HasConfigured: true,
|
||||
ErrSubmitBlindedBlock: errors.New("can't submit"),
|
||||
},
|
||||
err: "can't submit",
|
||||
},
|
||||
{
|
||||
name: "head and payload root mismatch",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: true,
|
||||
Payload: p,
|
||||
},
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = p
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
err: "header and payload root do not match",
|
||||
},
|
||||
{
|
||||
name: "can get payload",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
txRoot, err := ssz.TransactionsRoot([][]byte{})
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.ExecutionPayloadHeader = &v1.ExecutionPayloadHeader{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
TransactionsRoot: txRoot[:],
|
||||
GasLimit: 123,
|
||||
}
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: true,
|
||||
Payload: p,
|
||||
},
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = p
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
vs := &Server{BlockBuilder: tc.mock}
|
||||
gotBlk, err := vs.unblindBuilderBlock(context.Background(), tc.blk)
|
||||
if tc.err != "" {
|
||||
require.ErrorContains(t, tc.err, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, tc.returnedBlk, gotBlk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_validateBuilderSignature(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Sets the bls to exec data for a block.
|
||||
@@ -34,130 +27,3 @@ func (vs *Server) setBlsToExecData(blk interfaces.SignedBeaconBlock, headState s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (vs *Server) unblindBuilderBlockCapella(ctx context.Context, b interfaces.ReadOnlySignedBeaconBlock) (interfaces.ReadOnlySignedBeaconBlock, error) {
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return nil, errors.Wrap(err, "block is nil")
|
||||
}
|
||||
|
||||
// No-op if the input block is not version blind and capella.
|
||||
if b.Version() != version.Capella || !b.IsBlinded() {
|
||||
return b, nil
|
||||
}
|
||||
// No-op nothing if the builder has not been configured.
|
||||
if !vs.BlockBuilder.Configured() {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
agg, err := b.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get sync aggregate")
|
||||
}
|
||||
h, err := b.Block().Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution header")
|
||||
}
|
||||
header, ok := h.Proto().(*enginev1.ExecutionPayloadHeaderCapella)
|
||||
if !ok {
|
||||
return nil, errors.New("execution data must be execution payload header capella")
|
||||
}
|
||||
parentRoot := b.Block().ParentRoot()
|
||||
stateRoot := b.Block().StateRoot()
|
||||
randaoReveal := b.Block().Body().RandaoReveal()
|
||||
graffiti := b.Block().Body().Graffiti()
|
||||
sig := b.Signature()
|
||||
blsToExecChange, err := b.Block().Body().BLSToExecutionChanges()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get bls to execution changes")
|
||||
}
|
||||
sb := ðpb.SignedBlindedBeaconBlockCapella{
|
||||
Block: ðpb.BlindedBeaconBlockCapella{
|
||||
Slot: b.Block().Slot(),
|
||||
ProposerIndex: b.Block().ProposerIndex(),
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: stateRoot[:],
|
||||
Body: ðpb.BlindedBeaconBlockBodyCapella{
|
||||
RandaoReveal: randaoReveal[:],
|
||||
Eth1Data: b.Block().Body().Eth1Data(),
|
||||
Graffiti: graffiti[:],
|
||||
ProposerSlashings: b.Block().Body().ProposerSlashings(),
|
||||
AttesterSlashings: b.Block().Body().AttesterSlashings(),
|
||||
Attestations: b.Block().Body().Attestations(),
|
||||
Deposits: b.Block().Body().Deposits(),
|
||||
VoluntaryExits: b.Block().Body().VoluntaryExits(),
|
||||
SyncAggregate: agg,
|
||||
ExecutionPayloadHeader: header,
|
||||
BlsToExecutionChanges: blsToExecChange,
|
||||
},
|
||||
},
|
||||
Signature: sig[:],
|
||||
}
|
||||
|
||||
wrappedSb, err := consensusblocks.NewSignedBeaconBlock(sb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create signed block")
|
||||
}
|
||||
|
||||
payload, err := vs.BlockBuilder.SubmitBlindedBlock(ctx, wrappedSb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not submit blinded block")
|
||||
}
|
||||
|
||||
payloadHtr, err := payload.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get payload hash tree root")
|
||||
}
|
||||
headerHtr, err := header.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get header hash tree root")
|
||||
}
|
||||
if payloadHtr != headerHtr {
|
||||
return nil, fmt.Errorf("payload hash tree root %x does not match header hash tree root %x", payloadHtr, headerHtr)
|
||||
}
|
||||
|
||||
capellaPayload, err := payload.PbCapella()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get payload")
|
||||
}
|
||||
|
||||
bb := ðpb.SignedBeaconBlockCapella{
|
||||
Block: ðpb.BeaconBlockCapella{
|
||||
Slot: sb.Block.Slot,
|
||||
ProposerIndex: sb.Block.ProposerIndex,
|
||||
ParentRoot: sb.Block.ParentRoot,
|
||||
StateRoot: sb.Block.StateRoot,
|
||||
Body: ðpb.BeaconBlockBodyCapella{
|
||||
RandaoReveal: sb.Block.Body.RandaoReveal,
|
||||
Eth1Data: sb.Block.Body.Eth1Data,
|
||||
Graffiti: sb.Block.Body.Graffiti,
|
||||
ProposerSlashings: sb.Block.Body.ProposerSlashings,
|
||||
AttesterSlashings: sb.Block.Body.AttesterSlashings,
|
||||
Attestations: sb.Block.Body.Attestations,
|
||||
Deposits: sb.Block.Body.Deposits,
|
||||
VoluntaryExits: sb.Block.Body.VoluntaryExits,
|
||||
SyncAggregate: agg,
|
||||
ExecutionPayload: capellaPayload,
|
||||
BlsToExecutionChanges: blsToExecChange,
|
||||
},
|
||||
},
|
||||
Signature: sb.Signature,
|
||||
}
|
||||
wb, err := consensusblocks.NewSignedBeaconBlock(bb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create signed block")
|
||||
}
|
||||
|
||||
txs, err := payload.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions from payload")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockHash": fmt.Sprintf("%#x", h.BlockHash()),
|
||||
"feeRecipient": fmt.Sprintf("%#x", h.FeeRecipient()),
|
||||
"gasUsed": h.GasUsed,
|
||||
"slot": b.Block().Slot(),
|
||||
"txs": len(txs),
|
||||
}).Info("Retrieved full capella payload from builder")
|
||||
|
||||
return wb, nil
|
||||
}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
builderTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/builder/testing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
v1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
func TestServer_unblindBuilderCapellaBlock(t *testing.T) {
|
||||
p := emptyPayloadCapella()
|
||||
p.GasLimit = 123
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
blk interfaces.ReadOnlySignedBeaconBlock
|
||||
mock *builderTest.MockBuilderService
|
||||
err string
|
||||
returnedBlk interfaces.ReadOnlySignedBeaconBlock
|
||||
}{
|
||||
{
|
||||
name: "nil block",
|
||||
blk: nil,
|
||||
err: "signed beacon block can't be nil",
|
||||
},
|
||||
{
|
||||
name: "old block version",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "not configured",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockBellatrix())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: false,
|
||||
},
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockBellatrix())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "submit blind block error",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockCapella()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
PayloadCapella: &v1.ExecutionPayloadCapella{},
|
||||
HasConfigured: true,
|
||||
ErrSubmitBlindedBlock: errors.New("can't submit"),
|
||||
},
|
||||
err: "can't submit",
|
||||
},
|
||||
{
|
||||
name: "can get payload",
|
||||
blk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockCapella()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
txRoot, err := ssz.TransactionsRoot(make([][]byte, 0))
|
||||
require.NoError(t, err)
|
||||
wdRoot, err := ssz.WithdrawalSliceRoot([]*v1.Withdrawal{}, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.ExecutionPayloadHeader = &v1.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
TransactionsRoot: txRoot[:],
|
||||
GasLimit: 123,
|
||||
WithdrawalsRoot: wdRoot[:],
|
||||
}
|
||||
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 1, FromBlsPubkey: []byte{'a'}}},
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 2, FromBlsPubkey: []byte{'b'}}},
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 3, FromBlsPubkey: []byte{'c'}}},
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 4, FromBlsPubkey: []byte{'d'}}},
|
||||
}
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: true,
|
||||
PayloadCapella: p,
|
||||
},
|
||||
returnedBlk: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockCapella()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = p
|
||||
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 1, FromBlsPubkey: []byte{'a'}}},
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 2, FromBlsPubkey: []byte{'b'}}},
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 3, FromBlsPubkey: []byte{'c'}}},
|
||||
{Message: ð.BLSToExecutionChange{ValidatorIndex: 4, FromBlsPubkey: []byte{'d'}}},
|
||||
}
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
vs := &Server{BlockBuilder: tc.mock}
|
||||
gotBlk, err := vs.unblindBuilderBlockCapella(context.Background(), tc.blk)
|
||||
if tc.err != "" {
|
||||
require.ErrorContains(t, tc.err, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, tc.returnedBlk, gotBlk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -43,10 +43,17 @@ var (
|
||||
|
||||
// This returns the execution payload of a given slot. The function has full awareness of pre and post merge.
|
||||
// The payload is computed given the respected time of merge.
|
||||
func (vs *Server) getExecutionPayload(ctx context.Context, slot primitives.Slot, vIdx primitives.ValidatorIndex, headRoot [32]byte, st state.BeaconState) (interfaces.ExecutionData, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.getExecutionPayload")
|
||||
func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock, st state.BeaconState) (interfaces.ExecutionData, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.getLocalPayload")
|
||||
defer span.End()
|
||||
|
||||
if blk.Version() < version.Bellatrix {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
slot := blk.Slot()
|
||||
vIdx := blk.ProposerIndex()
|
||||
headRoot := blk.ParentRoot()
|
||||
proposerID, payloadId, ok := vs.ProposerSlotIndexCache.GetProposerPayloadIDs(slot, headRoot)
|
||||
feeRecipient := params.BeaconConfig().DefaultFeeRecipient
|
||||
recipient, err := vs.BeaconDB.FeeRecipientByValidatorID(ctx, vIdx)
|
||||
@@ -221,6 +228,27 @@ func (vs *Server) getTerminalBlockHashIfExists(ctx context.Context, transitionTi
|
||||
return vs.ExecutionEngineCaller.GetTerminalBlockHash(ctx, transitionTime)
|
||||
}
|
||||
|
||||
func (vs *Server) getBuilderPayload(ctx context.Context,
|
||||
slot primitives.Slot,
|
||||
vIdx primitives.ValidatorIndex) (interfaces.ExecutionData, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.getBuilderPayload")
|
||||
defer span.End()
|
||||
|
||||
if slots.ToEpoch(slot) < params.BeaconConfig().BellatrixForkEpoch {
|
||||
return nil, nil
|
||||
}
|
||||
canUseBuilder, err := vs.canUseBuilder(ctx, slot, vIdx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to check if we can use the builder")
|
||||
}
|
||||
span.AddAttributes(trace.BoolAttribute("canUseBuilder", canUseBuilder))
|
||||
if !canUseBuilder {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return vs.getPayloadHeaderFromBuilder(ctx, slot, vIdx)
|
||||
}
|
||||
|
||||
// activationEpochNotReached returns true if activation epoch has not been reach.
|
||||
// Which satisfy the following conditions in spec:
|
||||
//
|
||||
|
||||
@@ -143,7 +143,13 @@ func TestServer_getExecutionPayload(t *testing.T) {
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(tt.st.Slot(), 100, [8]byte{100}, [32]byte{'a'})
|
||||
_, err := vs.getExecutionPayload(context.Background(), tt.st.Slot(), tt.validatorIndx, [32]byte{'a'}, tt.st)
|
||||
blk := util.NewBeaconBlockBellatrix()
|
||||
blk.Block.Slot = tt.st.Slot()
|
||||
blk.Block.ProposerIndex = tt.validatorIndx
|
||||
blk.Block.ParentRoot = bytesutil.PadTo([]byte{'a'}, 32)
|
||||
b, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
_, err = vs.getLocalPayload(context.Background(), b.Block(), tt.st)
|
||||
if tt.errString != "" {
|
||||
require.ErrorContains(t, tt.errString, err)
|
||||
} else {
|
||||
@@ -179,7 +185,13 @@ func TestServer_getExecutionPayloadContextTimeout(t *testing.T) {
|
||||
}
|
||||
vs.ProposerSlotIndexCache.SetProposerAndPayloadIDs(nonTransitionSt.Slot(), 100, [8]byte{100}, [32]byte{'a'})
|
||||
|
||||
_, err = vs.getExecutionPayload(context.Background(), nonTransitionSt.Slot(), 100, [32]byte{'a'}, nonTransitionSt)
|
||||
blk := util.NewBeaconBlockBellatrix()
|
||||
blk.Block.Slot = nonTransitionSt.Slot()
|
||||
blk.Block.ProposerIndex = 100
|
||||
blk.Block.ParentRoot = bytesutil.PadTo([]byte{'a'}, 32)
|
||||
b, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
_, err = vs.getLocalPayload(context.Background(), b.Block(), nonTransitionSt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -225,7 +237,13 @@ func TestServer_getExecutionPayload_UnexpectedFeeRecipient(t *testing.T) {
|
||||
BeaconDB: beaconDB,
|
||||
ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(),
|
||||
}
|
||||
gotPayload, err := vs.getExecutionPayload(context.Background(), transitionSt.Slot(), 0, [32]byte{}, transitionSt)
|
||||
|
||||
blk := util.NewBeaconBlockBellatrix()
|
||||
blk.Block.Slot = transitionSt.Slot()
|
||||
blk.Block.ParentRoot = bytesutil.PadTo([]byte{}, 32)
|
||||
b, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
gotPayload, err := vs.getLocalPayload(context.Background(), b.Block(), transitionSt)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, gotPayload)
|
||||
|
||||
@@ -237,7 +255,7 @@ func TestServer_getExecutionPayload_UnexpectedFeeRecipient(t *testing.T) {
|
||||
payload.FeeRecipient = evilRecipientAddress[:]
|
||||
vs.ProposerSlotIndexCache = cache.NewProposerPayloadIDsCache()
|
||||
|
||||
gotPayload, err = vs.getExecutionPayload(context.Background(), transitionSt.Slot(), 0, [32]byte{}, transitionSt)
|
||||
gotPayload, err = vs.getLocalPayload(context.Background(), b.Block(), transitionSt)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, gotPayload)
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/attestation"
|
||||
@@ -547,6 +548,12 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
|
||||
blockToPropose := util.NewBlindedBeaconBlockCapella()
|
||||
blockToPropose.Block.Slot = 5
|
||||
blockToPropose.Block.ParentRoot = parent[:]
|
||||
txRoot, err := ssz.TransactionsRoot([][]byte{})
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot, err := ssz.WithdrawalSliceRoot([]*enginev1.Withdrawal{}, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
blockToPropose.Block.Body.ExecutionPayloadHeader.TransactionsRoot = txRoot[:]
|
||||
blockToPropose.Block.Body.ExecutionPayloadHeader.WithdrawalsRoot = withdrawalsRoot[:]
|
||||
blk := ðpb.GenericSignedBeaconBlock_BlindedCapella{BlindedCapella: blockToPropose}
|
||||
return ðpb.GenericSignedBeaconBlock{Block: blk}
|
||||
},
|
||||
@@ -555,30 +562,19 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := dbutil.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
genesis := util.NewBeaconBlock()
|
||||
util.SaveBlock(t, ctx, db, genesis)
|
||||
|
||||
numDeposits := uint64(64)
|
||||
beaconState, _ := util.DeterministicGenesisState(t, numDeposits)
|
||||
bsRoot, err := beaconState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
genesisRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveState(ctx, beaconState, genesisRoot), "Could not save genesis state")
|
||||
|
||||
c := &mock.ChainService{Root: bsRoot[:], State: beaconState}
|
||||
proposerServer := &Server{
|
||||
ChainStartFetcher: &mockExecution.Chain{},
|
||||
Eth1InfoFetcher: &mockExecution.Chain{},
|
||||
Eth1BlockFetcher: &mockExecution.Chain{},
|
||||
BlockReceiver: c,
|
||||
HeadFetcher: c,
|
||||
BlockNotifier: c.BlockNotifier(),
|
||||
P2P: mockp2p.NewTestP2P(t),
|
||||
BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true, PayloadCapella: emptyPayloadCapella()},
|
||||
BlockReceiver: c,
|
||||
BlockNotifier: c.BlockNotifier(),
|
||||
P2P: mockp2p.NewTestP2P(t),
|
||||
BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true, PayloadCapella: emptyPayloadCapella()},
|
||||
}
|
||||
blockToPropose := tt.block(bsRoot)
|
||||
res, err := proposerServer.ProposeBeaconBlock(context.Background(), blockToPropose)
|
||||
|
||||
184
beacon-chain/rpc/prysm/v1alpha1/validator/unblinder.go
Normal file
184
beacon-chain/rpc/prysm/v1alpha1/validator/unblinder.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/builder"
|
||||
consensus_types "github.com/prysmaticlabs/prysm/v4/consensus-types"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type unblinder struct {
|
||||
b interfaces.SignedBeaconBlock
|
||||
builder builder.BlockBuilder
|
||||
}
|
||||
|
||||
func newUnblinder(b interfaces.SignedBeaconBlock, builder builder.BlockBuilder) (*unblinder, error) {
|
||||
if err := consensusblocks.BeaconBlockIsNil(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if builder == nil {
|
||||
return nil, errors.New("nil builder provided")
|
||||
}
|
||||
return &unblinder{
|
||||
b: b,
|
||||
builder: builder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *unblinder) unblindBuilderBlock(ctx context.Context) (interfaces.SignedBeaconBlock, error) {
|
||||
if !u.b.IsBlinded() || u.b.Version() < version.Bellatrix {
|
||||
return u.b, nil
|
||||
}
|
||||
if u.b.IsBlinded() && !u.builder.Configured() {
|
||||
return nil, errors.New("builder not configured")
|
||||
}
|
||||
|
||||
psb, err := u.blindedProtoBlock()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get blinded proto block")
|
||||
}
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(psb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create signed block")
|
||||
}
|
||||
if err = copyBlockData(u.b, sb); err != nil {
|
||||
return nil, errors.Wrap(err, "could not copy block data")
|
||||
}
|
||||
h, err := u.b.Block().Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution")
|
||||
}
|
||||
if err = sb.SetExecution(h); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set execution")
|
||||
}
|
||||
|
||||
payload, err := u.builder.SubmitBlindedBlock(ctx, sb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not submit blinded block")
|
||||
}
|
||||
headerRoot, err := h.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get header root")
|
||||
}
|
||||
payloadRoot, err := payload.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get payload root")
|
||||
}
|
||||
if headerRoot != payloadRoot {
|
||||
return nil, fmt.Errorf("header and payload root do not match, consider disconnect from relay to avoid further issues, "+
|
||||
"%#x != %#x", headerRoot, payloadRoot)
|
||||
}
|
||||
|
||||
bb, err := u.protoBlock()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get proto block")
|
||||
}
|
||||
wb, err := consensusblocks.NewSignedBeaconBlock(bb)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create signed block")
|
||||
}
|
||||
if err = copyBlockData(sb, wb); err != nil {
|
||||
return nil, errors.Wrap(err, "could not copy block data")
|
||||
}
|
||||
if err = wb.SetExecution(payload); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set execution")
|
||||
}
|
||||
|
||||
txs, err := payload.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions from payload")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockHash": fmt.Sprintf("%#x", h.BlockHash()),
|
||||
"feeRecipient": fmt.Sprintf("%#x", h.FeeRecipient()),
|
||||
"gasUsed": h.GasUsed(),
|
||||
"slot": u.b.Block().Slot(),
|
||||
"txs": len(txs),
|
||||
}).Info("Retrieved full payload from builder")
|
||||
|
||||
return wb, nil
|
||||
}
|
||||
|
||||
func copyBlockData(src interfaces.SignedBeaconBlock, dst interfaces.SignedBeaconBlock) error {
|
||||
agg, err := src.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get sync aggregate")
|
||||
}
|
||||
parentRoot := src.Block().ParentRoot()
|
||||
stateRoot := src.Block().StateRoot()
|
||||
randaoReveal := src.Block().Body().RandaoReveal()
|
||||
graffiti := src.Block().Body().Graffiti()
|
||||
sig := src.Signature()
|
||||
blsToExecChanges, err := src.Block().Body().BLSToExecutionChanges()
|
||||
if err != nil && !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return errors.Wrap(err, "could not get bls to execution changes")
|
||||
}
|
||||
|
||||
dst.SetSlot(src.Block().Slot())
|
||||
dst.SetProposerIndex(src.Block().ProposerIndex())
|
||||
dst.SetParentRoot(parentRoot[:])
|
||||
dst.SetStateRoot(stateRoot[:])
|
||||
dst.SetRandaoReveal(randaoReveal[:])
|
||||
dst.SetEth1Data(src.Block().Body().Eth1Data())
|
||||
dst.SetGraffiti(graffiti[:])
|
||||
dst.SetProposerSlashings(src.Block().Body().ProposerSlashings())
|
||||
dst.SetAttesterSlashings(src.Block().Body().AttesterSlashings())
|
||||
dst.SetAttestations(src.Block().Body().Attestations())
|
||||
dst.SetDeposits(src.Block().Body().Deposits())
|
||||
dst.SetVoluntaryExits(src.Block().Body().VoluntaryExits())
|
||||
if err = dst.SetSyncAggregate(agg); err != nil {
|
||||
return errors.Wrap(err, "could not set sync aggregate")
|
||||
}
|
||||
dst.SetSignature(sig[:])
|
||||
if err = dst.SetBLSToExecutionChanges(blsToExecChanges); err != nil && !errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
return errors.Wrap(err, "could not set bls to execution changes")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *unblinder) blindedProtoBlock() (proto.Message, error) {
|
||||
switch u.b.Version() {
|
||||
case version.Bellatrix:
|
||||
return ðpb.SignedBlindedBeaconBlockBellatrix{
|
||||
Block: ðpb.BlindedBeaconBlockBellatrix{
|
||||
Body: ðpb.BlindedBeaconBlockBodyBellatrix{},
|
||||
},
|
||||
}, nil
|
||||
case version.Capella:
|
||||
return ðpb.SignedBlindedBeaconBlockCapella{
|
||||
Block: ðpb.BlindedBeaconBlockCapella{
|
||||
Body: ðpb.BlindedBeaconBlockBodyCapella{},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid version %s", version.String(u.b.Version()))
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unblinder) protoBlock() (proto.Message, error) {
|
||||
switch u.b.Version() {
|
||||
case version.Bellatrix:
|
||||
return ðpb.SignedBeaconBlockBellatrix{
|
||||
Block: ðpb.BeaconBlockBellatrix{
|
||||
Body: ðpb.BeaconBlockBodyBellatrix{},
|
||||
},
|
||||
}, nil
|
||||
case version.Capella:
|
||||
return ðpb.SignedBeaconBlockCapella{
|
||||
Block: ðpb.BeaconBlockCapella{
|
||||
Body: ðpb.BeaconBlockBodyCapella{},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid version %s", version.String(u.b.Version()))
|
||||
}
|
||||
}
|
||||
264
beacon-chain/rpc/prysm/v1alpha1/validator/unblinder_test.go
Normal file
264
beacon-chain/rpc/prysm/v1alpha1/validator/unblinder_test.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
builderTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/builder/testing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
v1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
func Test_unblindBuilderBlock(t *testing.T) {
|
||||
p := emptyPayload()
|
||||
p.GasLimit = 123
|
||||
pCapella := emptyPayloadCapella()
|
||||
pCapella.GasLimit = 123
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
blk interfaces.SignedBeaconBlock
|
||||
mock *builderTest.MockBuilderService
|
||||
err string
|
||||
returnedBlk interfaces.SignedBeaconBlock
|
||||
}{
|
||||
{
|
||||
name: "old block version",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
returnedBlk: func() interfaces.SignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "blinded without configured builder",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
wb, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockBellatrix())
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: false,
|
||||
},
|
||||
err: "builder not configured",
|
||||
},
|
||||
{
|
||||
name: "non-blinded without configured builder",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = &v1.ExecutionPayload{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
Transactions: make([][]byte, 0),
|
||||
GasLimit: 123,
|
||||
}
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: false,
|
||||
Payload: p,
|
||||
},
|
||||
returnedBlk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = p
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "submit blind block error",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
Payload: &v1.ExecutionPayload{},
|
||||
HasConfigured: true,
|
||||
ErrSubmitBlindedBlock: errors.New("can't submit"),
|
||||
},
|
||||
err: "can't submit",
|
||||
},
|
||||
{
|
||||
name: "head and payload root mismatch",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: true,
|
||||
Payload: p,
|
||||
},
|
||||
returnedBlk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = p
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
err: "header and payload root do not match",
|
||||
},
|
||||
{
|
||||
name: "can get payload Bellatrix",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
txRoot, err := ssz.TransactionsRoot([][]byte{})
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.ExecutionPayloadHeader = &v1.ExecutionPayloadHeader{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
TransactionsRoot: txRoot[:],
|
||||
GasLimit: 123,
|
||||
}
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: true,
|
||||
Payload: p,
|
||||
},
|
||||
returnedBlk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.ExecutionPayload = p
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "can get payload Capella",
|
||||
blk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBlindedBeaconBlockCapella()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
|
||||
{
|
||||
Message: ð.BLSToExecutionChange{
|
||||
ValidatorIndex: 123,
|
||||
FromBlsPubkey: []byte{'a'},
|
||||
ToExecutionAddress: []byte{'a'},
|
||||
},
|
||||
Signature: []byte("sig123"),
|
||||
},
|
||||
{
|
||||
Message: ð.BLSToExecutionChange{
|
||||
ValidatorIndex: 456,
|
||||
FromBlsPubkey: []byte{'b'},
|
||||
ToExecutionAddress: []byte{'b'},
|
||||
},
|
||||
Signature: []byte("sig456"),
|
||||
},
|
||||
}
|
||||
txRoot, err := ssz.TransactionsRoot([][]byte{})
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot, err := ssz.WithdrawalSliceRoot([]*v1.Withdrawal{}, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.ExecutionPayloadHeader = &v1.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
TransactionsRoot: txRoot[:],
|
||||
WithdrawalsRoot: withdrawalsRoot[:],
|
||||
GasLimit: 123,
|
||||
}
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
mock: &builderTest.MockBuilderService{
|
||||
HasConfigured: true,
|
||||
PayloadCapella: pCapella,
|
||||
},
|
||||
returnedBlk: func() interfaces.SignedBeaconBlock {
|
||||
b := util.NewBeaconBlockCapella()
|
||||
b.Block.Slot = 1
|
||||
b.Block.ProposerIndex = 2
|
||||
b.Block.Body.BlsToExecutionChanges = []*eth.SignedBLSToExecutionChange{
|
||||
{
|
||||
Message: ð.BLSToExecutionChange{
|
||||
ValidatorIndex: 123,
|
||||
FromBlsPubkey: []byte{'a'},
|
||||
ToExecutionAddress: []byte{'a'},
|
||||
},
|
||||
Signature: []byte("sig123"),
|
||||
},
|
||||
{
|
||||
Message: ð.BLSToExecutionChange{
|
||||
ValidatorIndex: 456,
|
||||
FromBlsPubkey: []byte{'b'},
|
||||
ToExecutionAddress: []byte{'b'},
|
||||
},
|
||||
Signature: []byte("sig456"),
|
||||
},
|
||||
}
|
||||
b.Block.Body.ExecutionPayload = pCapella
|
||||
wb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return wb
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
unblinder, err := newUnblinder(tc.blk, tc.mock)
|
||||
require.NoError(t, err)
|
||||
gotBlk, err := unblinder.unblindBuilderBlock(context.Background())
|
||||
if tc.err != "" {
|
||||
require.ErrorContains(t, tc.err, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, tc.returnedBlk, gotBlk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -340,7 +340,10 @@ func (s *Service) Start() {
|
||||
ExecutionPayloadReconstructor: s.cfg.ExecutionPayloadReconstructor,
|
||||
BLSChangesPool: s.cfg.BLSChangesPool,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
ForkchoiceFetcher: s.cfg.ForkchoiceFetcher,
|
||||
}
|
||||
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks", beaconChainServerV1.PublishBlockV2)
|
||||
s.cfg.Router.HandleFunc("/eth/v2/beacon/blinded_blocks", beaconChainServerV1.PublishBlindedBlockV2)
|
||||
ethpbv1alpha1.RegisterNodeServer(s.grpcServer, nodeServer)
|
||||
ethpbservice.RegisterBeaconNodeServer(s.grpcServer, nodeServerV1)
|
||||
ethpbv1alpha1.RegisterHealthServer(s.grpcServer, nodeServer)
|
||||
|
||||
@@ -20,6 +20,7 @@ type BeaconState interface {
|
||||
ReadOnlyBeaconState
|
||||
WriteOnlyBeaconState
|
||||
Copy() BeaconState
|
||||
CopyAllTries()
|
||||
HashTreeRoot(ctx context.Context) ([32]byte, error)
|
||||
StateProver
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user