mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Compare commits
26 Commits
v4.1.1
...
initsync-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5a2873376 | ||
|
|
4d4753ebf3 | ||
|
|
d4fd310615 | ||
|
|
58cdb29ef3 | ||
|
|
cc2b4db582 | ||
|
|
be9b6ea837 | ||
|
|
806a394c89 | ||
|
|
97a99874e8 | ||
|
|
945b087ca9 | ||
|
|
b57effd096 | ||
|
|
867db1aeee | ||
|
|
99843688cd | ||
|
|
a536612c39 | ||
|
|
c5501f8775 | ||
|
|
55e4c6e1db | ||
|
|
2806326155 | ||
|
|
d7318ea485 | ||
|
|
e183d1dff4 | ||
|
|
a3868e7fc6 | ||
|
|
af70677778 | ||
|
|
8eb82dd378 | ||
|
|
0bd232667b | ||
|
|
39072e1b74 | ||
|
|
66011d5d9c | ||
|
|
419dbd57f7 | ||
|
|
da6ae3c204 |
28
WORKSPACE
28
WORKSPACE
@@ -65,10 +65,10 @@ bazel_skylib_workspace()
|
||||
|
||||
http_archive(
|
||||
name = "bazel_gazelle",
|
||||
sha256 = "29d5dafc2a5582995488c6735115d1d366fcd6a0fc2e2a153f02988706349825",
|
||||
sha256 = "d3fa66a39028e97d76f9e2db8f1b0c11c099e8e01bf363a923074784e451f809",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.31.0/bazel-gazelle-v0.31.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.31.0/bazel-gazelle-v0.31.0.tar.gz",
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.33.0/bazel-gazelle-v0.33.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.33.0/bazel-gazelle-v0.33.0.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -110,10 +110,10 @@ http_archive(
|
||||
# Expose internals of go_test for custom build transitions.
|
||||
"//third_party:io_bazel_rules_go_test.patch",
|
||||
],
|
||||
sha256 = "bfc5ce70b9d1634ae54f4e7b495657a18a04e0d596785f672d35d5f505ab491a",
|
||||
sha256 = "91585017debb61982f7054c9688857a2ad1fd823fc3f9cb05048b0025c47d023",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.40.0/rules_go-v0.40.0.zip",
|
||||
"https://github.com/bazelbuild/rules_go/releases/download/v0.40.0/rules_go-v0.40.0.zip",
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.42.0/rules_go-v0.42.0.zip",
|
||||
"https://github.com/bazelbuild/rules_go/releases/download/v0.42.0/rules_go-v0.42.0.zip",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -374,6 +374,22 @@ http_archive(
|
||||
)
|
||||
|
||||
# External dependencies
|
||||
http_archive(
|
||||
name = "googleapis",
|
||||
sha256 = "9d1a930e767c93c825398b8f8692eca3fe353b9aaadedfbcf1fca2282c85df88",
|
||||
strip_prefix = "googleapis-64926d52febbf298cb82a8f472ade4a3969ba922",
|
||||
urls = [
|
||||
"https://github.com/googleapis/googleapis/archive/64926d52febbf298cb82a8f472ade4a3969ba922.zip",
|
||||
],
|
||||
)
|
||||
|
||||
load("@googleapis//:repository_rules.bzl", "switched_rules_by_language")
|
||||
|
||||
switched_rules_by_language(
|
||||
name = "com_google_googleapis_imports",
|
||||
go = True,
|
||||
)
|
||||
|
||||
load("//:deps.bzl", "prysm_deps")
|
||||
|
||||
# gazelle:repository_macro deps.bzl%prysm_deps
|
||||
|
||||
@@ -13,6 +13,7 @@ go_library(
|
||||
"//api/client:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//beacon-chain/rpc/eth/beacon:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v4/network/forks"
|
||||
v1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
@@ -284,7 +285,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 {
|
||||
func (c *Client) SubmitChangeBLStoExecution(ctx context.Context, request []*shared.SignedBLSToExecutionChange) error {
|
||||
u := c.BaseURL().ResolveReference(&url.URL{Path: changeBLStoExecutionPath})
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
@@ -323,12 +324,12 @@ 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) {
|
||||
func (c *Client) GetBLStoExecutionChanges(ctx context.Context) (*beacon.BLSToExecutionChangesPoolResponse, error) {
|
||||
body, err := c.Get(ctx, changeBLStoExecutionPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
poolResponse := &apimiddleware.BLSToExecutionChangesPoolResponseJson{}
|
||||
poolResponse := &beacon.BLSToExecutionChangesPoolResponse{}
|
||||
err = json.Unmarshal(body, poolResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -4,6 +4,7 @@ const (
|
||||
VersionHeader = "Eth-Consensus-Version"
|
||||
ExecutionPayloadBlindedHeader = "Eth-Execution-Payload-Blinded"
|
||||
ExecutionPayloadValueHeader = "Eth-Execution-Payload-Value"
|
||||
ConsensusBlockValueHeader = "Eth-Consensus-Block-Value"
|
||||
JsonMediaType = "application/json"
|
||||
OctetStreamMediaType = "application/octet-stream"
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ go_library(
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/das:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/filters:go_default_library",
|
||||
"//beacon-chain/db/kv:go_default_library",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice"
|
||||
@@ -178,3 +179,10 @@ func WithSyncComplete(c chan struct{}) Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithAvailabilityStore(vs das.AvailabilityStore) Option {
|
||||
return func(s *Service) error {
|
||||
s.avs = vs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.databaseDACheck(ctx, b); err != nil {
|
||||
if err := s.avs.VerifyAvailability(ctx, s.CurrentSlot(), b); err != nil {
|
||||
return errors.Wrap(err, "could not validate blob data availability")
|
||||
}
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b.Block(),
|
||||
@@ -333,33 +333,6 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return s.saveHeadNoDB(ctx, lastB, lastBR, preState, !isValidPayload)
|
||||
}
|
||||
|
||||
func commitmentsToCheck(b consensusblocks.ROBlock, current primitives.Slot) [][]byte {
|
||||
if b.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
// We are only required to check within MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS
|
||||
if !params.WithinDAPeriod(slots.ToEpoch(b.Block().Slot()), slots.ToEpoch(current)) {
|
||||
return nil
|
||||
}
|
||||
kzgCommitments, err := b.Block().Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return kzgCommitments
|
||||
}
|
||||
|
||||
func (s *Service) databaseDACheck(ctx context.Context, b consensusblocks.ROBlock) error {
|
||||
commitments := commitmentsToCheck(b, s.CurrentSlot())
|
||||
if len(commitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, b.Root())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get blob sidecars")
|
||||
}
|
||||
return kzg.IsDataAvailable(commitments, sidecars)
|
||||
}
|
||||
|
||||
func (s *Service) updateEpochBoundaryCaches(ctx context.Context, st state.BeaconState) error {
|
||||
e := coreTime.CurrentEpoch(st)
|
||||
if err := helpers.UpdateCommitteeCache(ctx, st, e); err != nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -39,7 +38,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
prysmTime "github.com/prysmaticlabs/prysm/v4/time"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
@@ -2038,71 +2036,3 @@ func driftGenesisTime(s *Service, slot, delay int64) {
|
||||
offset := slot*int64(params.BeaconConfig().SecondsPerSlot) - delay
|
||||
s.SetGenesisTime(time.Unix(time.Now().Unix()-offset, 0))
|
||||
}
|
||||
|
||||
func Test_commitmentsToCheck(t *testing.T) {
|
||||
windowSlots, err := slots.EpochEnd(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)
|
||||
require.NoError(t, err)
|
||||
commits := [][]byte{
|
||||
bytesutil.PadTo([]byte("a"), 48),
|
||||
bytesutil.PadTo([]byte("b"), 48),
|
||||
bytesutil.PadTo([]byte("c"), 48),
|
||||
bytesutil.PadTo([]byte("d"), 48),
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
commits [][]byte
|
||||
block func(*testing.T) consensusblocks.ROBlock
|
||||
slot primitives.Slot
|
||||
}{
|
||||
{
|
||||
name: "pre deneb",
|
||||
block: func(t *testing.T) consensusblocks.ROBlock {
|
||||
bb := util.NewBeaconBlockBellatrix()
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(bb)
|
||||
require.NoError(t, err)
|
||||
rb, err := consensusblocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "commitments within da",
|
||||
block: func(t *testing.T) consensusblocks.ROBlock {
|
||||
d := util.NewBeaconBlockDeneb()
|
||||
d.Block.Body.BlobKzgCommitments = commits
|
||||
d.Block.Slot = 100
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(d)
|
||||
require.NoError(t, err)
|
||||
rb, err := consensusblocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
commits: commits,
|
||||
slot: 100,
|
||||
},
|
||||
{
|
||||
name: "commitments outside da",
|
||||
block: func(t *testing.T) consensusblocks.ROBlock {
|
||||
d := util.NewBeaconBlockDeneb()
|
||||
// block is from slot 0, "current slot" is window size +1 (so outside the window)
|
||||
d.Block.Body.BlobKzgCommitments = commits
|
||||
sb, err := consensusblocks.NewSignedBeaconBlock(d)
|
||||
require.NoError(t, err)
|
||||
rb, err := consensusblocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
slot: windowSlots + 1,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
b := c.block(t)
|
||||
co := commitmentsToCheck(b, c.slot)
|
||||
require.Equal(t, len(c.commits), len(co))
|
||||
for i := 0; i < len(c.commits); i++ {
|
||||
require.Equal(t, true, bytes.Equal(c.commits[i], co[i]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
|
||||
coreTime "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
|
||||
f "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice"
|
||||
@@ -63,6 +64,7 @@ type Service struct {
|
||||
syncComplete chan struct{}
|
||||
blobNotifiers *blobNotifierMap
|
||||
blockBeingSynced *currentlySyncingBlock
|
||||
avs das.AvailabilityStore
|
||||
}
|
||||
|
||||
// config options for the service.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/async/event"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
testDB "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice"
|
||||
@@ -78,6 +79,7 @@ type testServiceRequirements struct {
|
||||
func minimalTestService(t *testing.T, opts ...Option) (*Service, *testServiceRequirements) {
|
||||
ctx := context.Background()
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
avs := das.NewCachingDBVerifiedStore(beaconDB)
|
||||
fcs := doublylinkedtree.New()
|
||||
sg := stategen.New(beaconDB, fcs)
|
||||
notif := &mockBeaconNode{}
|
||||
@@ -110,6 +112,7 @@ func minimalTestService(t *testing.T, opts ...Option) (*Service, *testServiceReq
|
||||
WithAttestationService(req.attSrv),
|
||||
WithBLSToExecPool(req.blsPool),
|
||||
WithDepositCache(dc),
|
||||
WithAvailabilityStore(avs),
|
||||
}
|
||||
// append the variadic opts so they override the defaults by being processed afterwards
|
||||
opts = append(defOpts, opts...)
|
||||
|
||||
@@ -42,7 +42,6 @@ go_library(
|
||||
"//math:go_default_library",
|
||||
"//network/forks:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//proto/prysm/v1alpha1/slashings:go_default_library",
|
||||
@@ -100,7 +99,6 @@ go_test(
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//encoding/ssz:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/migration:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation/aggregation:go_default_library",
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
)
|
||||
@@ -251,7 +250,7 @@ func BLSChangesSignatureBatch(
|
||||
// is from a previous fork.
|
||||
func VerifyBLSChangeSignature(
|
||||
st state.ReadOnlyBeaconState,
|
||||
change *ethpbv2.SignedBLSToExecutionChange,
|
||||
change *ethpb.SignedBLSToExecutionChange,
|
||||
) error {
|
||||
c := params.BeaconConfig()
|
||||
domain, err := signing.ComputeDomain(c.DomainBLSToExecutionChange, c.GenesisForkVersion, st.GenesisValidatorsRoot())
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
@@ -1209,8 +1208,7 @@ func TestBLSChangesSignatureBatch(t *testing.T) {
|
||||
require.Equal(t, true, verify)
|
||||
|
||||
// Verify a single change
|
||||
change := migration.V1Alpha1SignedBLSToExecChangeToV2(signedChanges[0])
|
||||
require.NoError(t, blocks.VerifyBLSChangeSignature(st, change))
|
||||
require.NoError(t, blocks.VerifyBLSChangeSignature(st, signedChanges[0]))
|
||||
}
|
||||
|
||||
func TestBLSChangesSignatureBatchWrongFork(t *testing.T) {
|
||||
@@ -1274,8 +1272,7 @@ func TestBLSChangesSignatureBatchWrongFork(t *testing.T) {
|
||||
require.Equal(t, false, verify)
|
||||
|
||||
// Verify a single change
|
||||
change := migration.V1Alpha1SignedBLSToExecChangeToV2(signedChanges[0])
|
||||
require.ErrorIs(t, signing.ErrSigFailedToVerify, blocks.VerifyBLSChangeSignature(st, change))
|
||||
require.ErrorIs(t, signing.ErrSigFailedToVerify, blocks.VerifyBLSChangeSignature(st, signedChanges[0]))
|
||||
}
|
||||
|
||||
func TestBLSChangesSignatureBatchFromBellatrix(t *testing.T) {
|
||||
@@ -1362,7 +1359,6 @@ func TestBLSChangesSignatureBatchFromBellatrix(t *testing.T) {
|
||||
require.Equal(t, true, verify)
|
||||
|
||||
// Verify a single change
|
||||
change := migration.V1Alpha1SignedBLSToExecChangeToV2(signedChanges[0])
|
||||
require.NoError(t, blocks.VerifyBLSChangeSignature(st, change))
|
||||
require.NoError(t, blocks.VerifyBLSChangeSignature(st, signedChanges[0]))
|
||||
params.OverrideBeaconConfig(savedConfig)
|
||||
}
|
||||
|
||||
35
beacon-chain/das/BUILD.bazel
Normal file
35
beacon-chain/das/BUILD.bazel
Normal file
@@ -0,0 +1,35 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["availability.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/das",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/blockchain/kzg:go_default_library",
|
||||
"//cache/nonblocking:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["availability_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
],
|
||||
)
|
||||
127
beacon-chain/das/availability.go
Normal file
127
beacon-chain/das/availability.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package das
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
errors "github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/kzg"
|
||||
"github.com/prysmaticlabs/prysm/v4/cache/nonblocking"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"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/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
errCachedCommitmentMismatch = errors.New("previously verified commitments do not match those in block")
|
||||
// concurrentBlockFetchers * blocks per request is used to size the LRU so that we can have one cache
|
||||
// for many worker goroutines without them evicting each others results.
|
||||
// TODO: figure out how to determine the max number of init sync workers.
|
||||
concurrentBlockFetchers = 10
|
||||
)
|
||||
|
||||
// AvailabilityStore describes a component that can verify and save sidecars for a given block, and confirm previously
|
||||
// verified and saved sidecars.
|
||||
type AvailabilityStore interface {
|
||||
VerifyAvailability(ctx context.Context, current primitives.Slot, b blocks.ROBlock) error
|
||||
SaveIfAvailable(ctx context.Context, current primitives.Slot, b blocks.BlockWithVerifiedBlobs) error
|
||||
}
|
||||
|
||||
type CachingDBVerifiedStore struct {
|
||||
db BlobsDB
|
||||
cache *nonblocking.LRU[[32]byte, [][]byte]
|
||||
}
|
||||
|
||||
var _ AvailabilityStore = &CachingDBVerifiedStore{}
|
||||
|
||||
func NewCachingDBVerifiedStore(db BlobsDB) *CachingDBVerifiedStore {
|
||||
discardev := func([32]byte, [][]byte) {}
|
||||
size := int(params.BeaconNetworkConfig().MaxRequestBlocks) * concurrentBlockFetchers
|
||||
cache, err := nonblocking.NewLRU[[32]byte, [][]byte](size, discardev)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &CachingDBVerifiedStore{
|
||||
db: db,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CachingDBVerifiedStore) VerifyAvailability(ctx context.Context, current primitives.Slot, b blocks.ROBlock) error {
|
||||
c := commitmentsToCheck(b, current)
|
||||
if len(c) == 0 {
|
||||
return nil
|
||||
}
|
||||
// get the previously verified commitments
|
||||
vc, ok := s.cache.Get(b.Root())
|
||||
if !ok {
|
||||
return s.databaseDACheck(ctx, current, b.Root(), c)
|
||||
}
|
||||
// check commitments to make sure that they match
|
||||
if len(c) != len(vc) {
|
||||
return errors.Wrapf(errCachedCommitmentMismatch, "cache=%d, block=%d", len(vc), len(c))
|
||||
}
|
||||
for i := range vc {
|
||||
// check each commitment in the block against value previously validated and saved.
|
||||
if !bytes.Equal(vc[i], c[i]) {
|
||||
return errors.Wrapf(errCachedCommitmentMismatch, "commitment %#x at index %d does not match cache %#x", c[i], i, vc[i])
|
||||
}
|
||||
}
|
||||
// all commitments match
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CachingDBVerifiedStore) SaveIfAvailable(ctx context.Context, current primitives.Slot, bwb blocks.BlockWithVerifiedBlobs) error {
|
||||
b := bwb.Block
|
||||
c := commitmentsToCheck(b, current)
|
||||
if len(c) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := kzg.IsDataAvailable(c, bwb.Blobs); err != nil {
|
||||
return errors.Wrapf(err, "kzg.IsDataAvailable check failed for %#x", b.Root())
|
||||
}
|
||||
if err := s.db.SaveBlobSidecar(ctx, bwb.Blobs); err != nil {
|
||||
return errors.Wrapf(err, "error while trying to save verified blob sidecars for root %#x", b.Root())
|
||||
}
|
||||
// cache the commitments that matched so that we can confirm them cheaply while they remain in cache.
|
||||
s.cache.Add(b.Root(), c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CachingDBVerifiedStore) databaseDACheck(ctx context.Context, current primitives.Slot, root [32]byte, cmts [][]byte) error {
|
||||
log.WithField("root", fmt.Sprintf("%#x", root)).Warn("Falling back to database DA check")
|
||||
sidecars, err := s.db.BlobSidecarsByRoot(ctx, root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get blob sidecars")
|
||||
}
|
||||
if err := kzg.IsDataAvailable(cmts, sidecars); err != nil {
|
||||
return err
|
||||
}
|
||||
s.cache.Add(root, cmts)
|
||||
return nil
|
||||
}
|
||||
|
||||
type BlobsDB interface {
|
||||
BlobSidecarsByRoot(ctx context.Context, beaconBlockRoot [32]byte, indices ...uint64) ([]*ethpb.BlobSidecar, error)
|
||||
SaveBlobSidecar(ctx context.Context, sidecars []*ethpb.BlobSidecar) error
|
||||
}
|
||||
|
||||
func commitmentsToCheck(b blocks.ROBlock, current primitives.Slot) [][]byte {
|
||||
if b.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
// We are only required to check within MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS
|
||||
if !params.WithinDAPeriod(slots.ToEpoch(b.Block().Slot()), slots.ToEpoch(current)) {
|
||||
return nil
|
||||
}
|
||||
kzgCommitments, err := b.Block().Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return kzgCommitments
|
||||
}
|
||||
82
beacon-chain/das/availability_test.go
Normal file
82
beacon-chain/das/availability_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package das
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
)
|
||||
|
||||
func Test_commitmentsToCheck(t *testing.T) {
|
||||
windowSlots, err := slots.EpochEnd(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)
|
||||
require.NoError(t, err)
|
||||
commits := [][]byte{
|
||||
bytesutil.PadTo([]byte("a"), 48),
|
||||
bytesutil.PadTo([]byte("b"), 48),
|
||||
bytesutil.PadTo([]byte("c"), 48),
|
||||
bytesutil.PadTo([]byte("d"), 48),
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
commits [][]byte
|
||||
block func(*testing.T) blocks.ROBlock
|
||||
slot primitives.Slot
|
||||
}{
|
||||
{
|
||||
name: "pre deneb",
|
||||
block: func(t *testing.T) blocks.ROBlock {
|
||||
bb := util.NewBeaconBlockBellatrix()
|
||||
sb, err := blocks.NewSignedBeaconBlock(bb)
|
||||
require.NoError(t, err)
|
||||
rb, err := blocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "commitments within da",
|
||||
block: func(t *testing.T) blocks.ROBlock {
|
||||
d := util.NewBeaconBlockDeneb()
|
||||
d.Block.Body.BlobKzgCommitments = commits
|
||||
d.Block.Slot = 100
|
||||
sb, err := blocks.NewSignedBeaconBlock(d)
|
||||
require.NoError(t, err)
|
||||
rb, err := blocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
commits: commits,
|
||||
slot: 100,
|
||||
},
|
||||
{
|
||||
name: "commitments outside da",
|
||||
block: func(t *testing.T) blocks.ROBlock {
|
||||
d := util.NewBeaconBlockDeneb()
|
||||
// block is from slot 0, "current slot" is window size +1 (so outside the window)
|
||||
d.Block.Body.BlobKzgCommitments = commits
|
||||
sb, err := blocks.NewSignedBeaconBlock(d)
|
||||
require.NoError(t, err)
|
||||
rb, err := blocks.NewROBlock(sb)
|
||||
require.NoError(t, err)
|
||||
return rb
|
||||
},
|
||||
slot: windowSlots + 1,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
b := c.block(t)
|
||||
co := commitmentsToCheck(b, c.slot)
|
||||
require.Equal(t, len(c.commits), len(co))
|
||||
for i := 0; i < len(c.commits); i++ {
|
||||
require.Equal(t, true, bytes.Equal(c.commits[i], co[i]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,7 @@ func filterForIndices(sc *ethpb.BlobSidecars, indices ...uint64) ([]*ethpb.BlobS
|
||||
}
|
||||
|
||||
// BlobSidecarsBySlot retrieves BlobSidecars for the given slot.
|
||||
// If the `indices` argument is omitted, all blobs for the root will be returned.
|
||||
// If the `indices` argument is omitted, all blobs for the slot will be returned.
|
||||
// Otherwise, the result will be filtered to only include the specified indices.
|
||||
// An error will result if an invalid index is specified.
|
||||
// The bucket size is bounded by 131072 entries. That's the most blobs a node will keep before rotating it out.
|
||||
|
||||
@@ -48,6 +48,8 @@ func TestLatestMainchainInfo_OK(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
testAcc.Backend.Commit()
|
||||
|
||||
tickerChan := make(chan time.Time)
|
||||
web3Service.eth1HeadTicker = &time.Ticker{C: tickerChan}
|
||||
exitRoutine := make(chan bool)
|
||||
|
||||
go func() {
|
||||
@@ -58,8 +60,6 @@ func TestLatestMainchainInfo_OK(t *testing.T) {
|
||||
header, err := web3Service.HeaderByNumber(web3Service.ctx, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
tickerChan := make(chan time.Time)
|
||||
web3Service.eth1HeadTicker = &time.Ticker{C: tickerChan}
|
||||
tickerChan <- time.Now()
|
||||
web3Service.cancel()
|
||||
exitRoutine <- true
|
||||
|
||||
@@ -208,13 +208,9 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.ensureValidPowchainData(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to validate powchain data")
|
||||
}
|
||||
|
||||
eth1Data, err := s.cfg.beaconDB.ExecutionChainData(ctx)
|
||||
eth1Data, err := s.validPowchainData(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to retrieve eth1 data")
|
||||
return nil, errors.Wrap(err, "unable to validate powchain data")
|
||||
}
|
||||
if err := s.initializeEth1Data(ctx, eth1Data); err != nil {
|
||||
return nil, err
|
||||
@@ -822,23 +818,22 @@ func validateDepositContainers(ctrs []*ethpb.DepositContainer) bool {
|
||||
|
||||
// Validates the current powchain data is saved and makes sure that any
|
||||
// embedded genesis state is correctly accounted for.
|
||||
func (s *Service) ensureValidPowchainData(ctx context.Context) error {
|
||||
func (s *Service) validPowchainData(ctx context.Context) (*ethpb.ETH1ChainData, error) {
|
||||
genState, err := s.cfg.beaconDB.GenesisState(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Exit early if no genesis state is saved.
|
||||
if genState == nil || genState.IsNil() {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
eth1Data, err := s.cfg.beaconDB.ExecutionChainData(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to retrieve eth1 data")
|
||||
return nil, errors.Wrap(err, "unable to retrieve eth1 data")
|
||||
}
|
||||
if genState == nil || genState.IsNil() {
|
||||
return eth1Data, nil
|
||||
}
|
||||
if eth1Data == nil || !eth1Data.ChainstartData.Chainstarted || !validateDepositContainers(eth1Data.DepositContainers) {
|
||||
pbState, err := native.ProtobufBeaconStatePhase0(s.preGenesisState.ToProtoUnsafe())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
s.chainStartData = ðpb.ChainStartData{
|
||||
Chainstarted: true,
|
||||
@@ -856,22 +851,24 @@ func (s *Service) ensureValidPowchainData(ctx context.Context) error {
|
||||
if features.Get().EnableEIP4881 {
|
||||
trie, ok := s.depositTrie.(*depositsnapshot.DepositTree)
|
||||
if !ok {
|
||||
return errors.New("deposit trie was not EIP4881 DepositTree")
|
||||
return nil, errors.New("deposit trie was not EIP4881 DepositTree")
|
||||
}
|
||||
eth1Data.DepositSnapshot, err = trie.ToProto()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
trie, ok := s.depositTrie.(*trie.SparseMerkleTrie)
|
||||
if !ok {
|
||||
return errors.New("deposit trie was not SparseMerkleTrie")
|
||||
return nil, errors.New("deposit trie was not SparseMerkleTrie")
|
||||
}
|
||||
eth1Data.Trie = trie.ToProto()
|
||||
}
|
||||
return s.cfg.beaconDB.SaveExecutionChainData(ctx, eth1Data)
|
||||
if err := s.cfg.beaconDB.SaveExecutionChainData(ctx, eth1Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return eth1Data, nil
|
||||
}
|
||||
|
||||
func dedupEndpoints(endpoints []string) []string {
|
||||
|
||||
@@ -571,7 +571,8 @@ func TestService_EnsureConsistentPowchainData(t *testing.T) {
|
||||
assert.NoError(t, genState.SetSlot(1000))
|
||||
|
||||
require.NoError(t, s1.cfg.beaconDB.SaveGenesisData(context.Background(), genState))
|
||||
require.NoError(t, s1.ensureValidPowchainData(context.Background()))
|
||||
_, err = s1.validPowchainData(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
eth1Data, err := s1.cfg.beaconDB.ExecutionChainData(context.Background())
|
||||
assert.NoError(t, err)
|
||||
@@ -601,7 +602,8 @@ func TestService_InitializeCorrectly(t *testing.T) {
|
||||
assert.NoError(t, genState.SetSlot(1000))
|
||||
|
||||
require.NoError(t, s1.cfg.beaconDB.SaveGenesisData(context.Background(), genState))
|
||||
require.NoError(t, s1.ensureValidPowchainData(context.Background()))
|
||||
_, err = s1.validPowchainData(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
eth1Data, err := s1.cfg.beaconDB.ExecutionChainData(context.Background())
|
||||
assert.NoError(t, err)
|
||||
@@ -636,7 +638,8 @@ func TestService_EnsureValidPowchainData(t *testing.T) {
|
||||
DepositContainers: []*ethpb.DepositContainer{{Index: 1}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s1.ensureValidPowchainData(context.Background()))
|
||||
_, err = s1.validPowchainData(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
eth1Data, err := s1.cfg.beaconDB.ExecutionChainData(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -52,7 +52,6 @@ func DefaultConfig(enableDebugRPCEndpoints bool, httpModules string) MuxConfig {
|
||||
}
|
||||
if flags.EnableHTTPEthAPI(httpModules) {
|
||||
ethRegistrations := []gateway.PbHandlerRegistration{
|
||||
ethpbservice.RegisterBeaconNodeHandler,
|
||||
ethpbservice.RegisterBeaconChainHandler,
|
||||
ethpbservice.RegisterBeaconValidatorHandler,
|
||||
ethpbservice.RegisterEventsHandler,
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestDefaultConfig(t *testing.T) {
|
||||
require.Equal(t, 2, len(cfg.EthPbMux.Patterns))
|
||||
assert.Equal(t, "/internal/eth/v1/", cfg.EthPbMux.Patterns[0])
|
||||
assert.Equal(t, "/internal/eth/v2/", cfg.EthPbMux.Patterns[1])
|
||||
assert.Equal(t, 4, len(cfg.EthPbMux.Registrations))
|
||||
assert.Equal(t, 3, len(cfg.EthPbMux.Registrations))
|
||||
assert.NotNil(t, cfg.V1AlphaPbMux.Mux)
|
||||
require.Equal(t, 2, len(cfg.V1AlphaPbMux.Patterns))
|
||||
assert.Equal(t, "/eth/v1alpha1/", cfg.V1AlphaPbMux.Patterns[0])
|
||||
@@ -29,7 +29,7 @@ func TestDefaultConfig(t *testing.T) {
|
||||
require.Equal(t, 2, len(cfg.EthPbMux.Patterns))
|
||||
assert.Equal(t, "/internal/eth/v1/", cfg.EthPbMux.Patterns[0])
|
||||
assert.Equal(t, "/internal/eth/v2/", cfg.EthPbMux.Patterns[1])
|
||||
assert.Equal(t, 5, len(cfg.EthPbMux.Registrations))
|
||||
assert.Equal(t, 4, len(cfg.EthPbMux.Registrations))
|
||||
assert.NotNil(t, cfg.V1AlphaPbMux.Mux)
|
||||
require.Equal(t, 2, len(cfg.V1AlphaPbMux.Patterns))
|
||||
assert.Equal(t, "/eth/v1alpha1/", cfg.V1AlphaPbMux.Patterns[0])
|
||||
@@ -41,7 +41,7 @@ func TestDefaultConfig(t *testing.T) {
|
||||
assert.NotNil(t, cfg.EthPbMux.Mux)
|
||||
require.Equal(t, 2, len(cfg.EthPbMux.Patterns))
|
||||
assert.Equal(t, "/internal/eth/v1/", cfg.EthPbMux.Patterns[0])
|
||||
assert.Equal(t, 5, len(cfg.EthPbMux.Registrations))
|
||||
assert.Equal(t, 4, len(cfg.EthPbMux.Registrations))
|
||||
assert.Equal(t, (*gateway.PbMux)(nil), cfg.V1AlphaPbMux)
|
||||
})
|
||||
t.Run("Without Eth API", func(t *testing.T) {
|
||||
|
||||
@@ -23,6 +23,7 @@ go_library(
|
||||
"//beacon-chain/cache:go_default_library",
|
||||
"//beacon-chain/cache/depositcache:go_default_library",
|
||||
"//beacon-chain/cache/depositsnapshot:go_default_library",
|
||||
"//beacon-chain/das:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/db/kv:go_default_library",
|
||||
"//beacon-chain/db/slasherkv:go_default_library",
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/kv"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/slasherkv"
|
||||
@@ -244,13 +245,19 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("Registering Blockchain Service")
|
||||
if err := beacon.registerBlockchainService(beacon.forkChoicer, synchronizer, beacon.initialSyncComplete); err != nil {
|
||||
avs := das.NewCachingDBVerifiedStore(beacon.db)
|
||||
bcOpts := []blockchain.Option{
|
||||
blockchain.WithForkChoiceStore(beacon.forkChoicer),
|
||||
blockchain.WithClockSynchronizer(synchronizer),
|
||||
blockchain.WithSyncComplete(beacon.initialSyncComplete),
|
||||
blockchain.WithAvailabilityStore(avs),
|
||||
}
|
||||
if err := beacon.registerBlockchainService(bcOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugln("Registering Initial Sync Service")
|
||||
if err := beacon.registerInitialSyncService(beacon.initialSyncComplete); err != nil {
|
||||
if err := beacon.registerInitialSyncService(beacon.initialSyncComplete, avs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -606,7 +613,8 @@ func (b *BeaconNode) registerAttestationPool() error {
|
||||
return b.services.RegisterService(s)
|
||||
}
|
||||
|
||||
func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *startup.ClockSynchronizer, syncComplete chan struct{}) error {
|
||||
func (b *BeaconNode) registerBlockchainService(required []blockchain.Option) error {
|
||||
log.Debugln("Registering Blockchain Service")
|
||||
var web3Service *execution.Service
|
||||
if err := b.services.FetchService(&web3Service); err != nil {
|
||||
return err
|
||||
@@ -617,10 +625,9 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st
|
||||
return err
|
||||
}
|
||||
|
||||
opts := append(b.serviceFlagOpts.blockchainFlagOpts, required...)
|
||||
// skipcq: CRT-D0001
|
||||
opts := append(
|
||||
b.serviceFlagOpts.blockchainFlagOpts,
|
||||
blockchain.WithForkChoiceStore(fc),
|
||||
opts = append(opts,
|
||||
blockchain.WithDatabase(b.db),
|
||||
blockchain.WithDepositCache(b.depositCache),
|
||||
blockchain.WithChainStartFetcher(web3Service),
|
||||
@@ -636,8 +643,6 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st
|
||||
blockchain.WithSlasherAttestationsFeed(b.slasherAttestationsFeed),
|
||||
blockchain.WithFinalizedStateAtStartUp(b.finalizedStateAtStartUp),
|
||||
blockchain.WithProposerIdsCache(b.proposerIdsCache),
|
||||
blockchain.WithClockSynchronizer(gs),
|
||||
blockchain.WithSyncComplete(syncComplete),
|
||||
)
|
||||
|
||||
blockchainService, err := blockchain.NewService(b.ctx, opts...)
|
||||
@@ -719,7 +724,7 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}) erro
|
||||
return b.services.RegisterService(rs)
|
||||
}
|
||||
|
||||
func (b *BeaconNode) registerInitialSyncService(complete chan struct{}) error {
|
||||
func (b *BeaconNode) registerInitialSyncService(complete chan struct{}, avs das.AvailabilityStore) error {
|
||||
var chainService *blockchain.Service
|
||||
if err := b.services.FetchService(&chainService); err != nil {
|
||||
return err
|
||||
@@ -733,6 +738,7 @@ func (b *BeaconNode) registerInitialSyncService(complete chan struct{}) error {
|
||||
BlockNotifier: b,
|
||||
ClockWaiter: b.clockWaiter,
|
||||
InitialSyncComplete: complete,
|
||||
AVS: avs,
|
||||
})
|
||||
return b.services.RegisterService(is)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ go_library(
|
||||
"//api/gateway/apimiddleware:go_default_library",
|
||||
"//api/grpc:go_default_library",
|
||||
"//beacon-chain/rpc/eth/events:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
|
||||
@@ -313,6 +313,8 @@ func receiveEvents(eventChan <-chan *sse.Event, w http.ResponseWriter, req *http
|
||||
default:
|
||||
return apimiddleware.InternalServerError(errors.New("payload version unsupported"))
|
||||
}
|
||||
case events.BlobSidecarTopic:
|
||||
data = &BlobSidecarJson{}
|
||||
case "error":
|
||||
data = &EventErrorJson{}
|
||||
default:
|
||||
|
||||
@@ -19,26 +19,6 @@ import (
|
||||
|
||||
// https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/submitPoolBLSToExecutionChange
|
||||
// expects posting a top-level array. We make it more proto-friendly by wrapping it in a struct.
|
||||
func wrapBLSChangesArray(
|
||||
endpoint *apimiddleware.Endpoint,
|
||||
_ http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) (apimiddleware.RunDefault, apimiddleware.ErrorJson) {
|
||||
if _, ok := endpoint.PostRequest.(*SubmitBLSToExecutionChangesRequest); !ok {
|
||||
return true, nil
|
||||
}
|
||||
changes := make([]*SignedBLSToExecutionChangeJson, 0)
|
||||
if err := json.NewDecoder(req.Body).Decode(&changes); err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not decode body")
|
||||
}
|
||||
j := &SubmitBLSToExecutionChangesRequest{Changes: changes}
|
||||
b, err := json.Marshal(j)
|
||||
if err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not marshal wrapped body")
|
||||
}
|
||||
req.Body = io.NopCloser(bytes.NewReader(b))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type v1alpha1SignedPhase0Block struct {
|
||||
Block *BeaconBlockJson `json:"block"` // tech debt on phase 0 called this block instead of "message"
|
||||
@@ -299,43 +279,6 @@ func preparePublishedBlindedBlock(endpoint *apimiddleware.Endpoint, _ http.Respo
|
||||
return apimiddleware.InternalServerError(errors.New("unsupported block type"))
|
||||
}
|
||||
|
||||
type tempSyncCommitteesResponseJson struct {
|
||||
Data *tempSyncCommitteeValidatorsJson `json:"data"`
|
||||
}
|
||||
|
||||
type tempSyncCommitteeValidatorsJson struct {
|
||||
Validators []string `json:"validators"`
|
||||
ValidatorAggregates []*tempSyncSubcommitteeValidatorsJson `json:"validator_aggregates"`
|
||||
}
|
||||
|
||||
type tempSyncSubcommitteeValidatorsJson struct {
|
||||
Validators []string `json:"validators"`
|
||||
}
|
||||
|
||||
// https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.0.0#/Beacon/getEpochSyncCommittees returns validator_aggregates as a nested array.
|
||||
// grpc-gateway returns a struct with nested fields which we have to transform into a plain 2D array.
|
||||
func prepareValidatorAggregates(body []byte, responseContainer interface{}) (apimiddleware.RunDefault, apimiddleware.ErrorJson) {
|
||||
tempContainer := &tempSyncCommitteesResponseJson{}
|
||||
if err := json.Unmarshal(body, tempContainer); err != nil {
|
||||
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not unmarshal response into temp container")
|
||||
}
|
||||
container, ok := responseContainer.(*SyncCommitteesResponseJson)
|
||||
if !ok {
|
||||
return false, apimiddleware.InternalServerError(errors.New("container is not of the correct type"))
|
||||
}
|
||||
|
||||
container.Data = &SyncCommitteeValidatorsJson{}
|
||||
container.Data.Validators = tempContainer.Data.Validators
|
||||
container.Data.ValidatorAggregates = make([][]string, len(tempContainer.Data.ValidatorAggregates))
|
||||
for i, srcValAgg := range tempContainer.Data.ValidatorAggregates {
|
||||
dstValAgg := make([]string, len(srcValAgg.Validators))
|
||||
copy(dstValAgg, tempContainer.Data.ValidatorAggregates[i].Validators)
|
||||
container.Data.ValidatorAggregates[i] = dstValAgg
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type phase0BlockResponseJson struct {
|
||||
Version string `json:"version" enum:"true"`
|
||||
Data *SignedBeaconBlockJson `json:"data"`
|
||||
|
||||
@@ -3,7 +3,6 @@ package apimiddleware
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -19,46 +18,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
)
|
||||
|
||||
func TestWrapBLSChangesArray(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
PostRequest: &SubmitBLSToExecutionChangesRequest{},
|
||||
}
|
||||
unwrappedChanges := []*SignedBLSToExecutionChangeJson{{Signature: "sig"}}
|
||||
unwrappedChangesJson, err := json.Marshal(unwrappedChanges)
|
||||
require.NoError(t, err)
|
||||
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(unwrappedChangesJson)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest("POST", "http://foo.example", &body)
|
||||
|
||||
runDefault, errJson := wrapBLSChangesArray(endpoint, nil, request)
|
||||
require.Equal(t, true, errJson == nil)
|
||||
assert.Equal(t, apimiddleware.RunDefault(true), runDefault)
|
||||
wrappedChanges := &SubmitBLSToExecutionChangesRequest{}
|
||||
require.NoError(t, json.NewDecoder(request.Body).Decode(wrappedChanges))
|
||||
require.Equal(t, 1, len(wrappedChanges.Changes), "wrong number of wrapped items")
|
||||
assert.Equal(t, "sig", wrappedChanges.Changes[0].Signature)
|
||||
})
|
||||
|
||||
t.Run("invalid_body", func(t *testing.T) {
|
||||
endpoint := &apimiddleware.Endpoint{
|
||||
PostRequest: &SubmitBLSToExecutionChangesRequest{},
|
||||
}
|
||||
var body bytes.Buffer
|
||||
_, err := body.Write([]byte("invalid"))
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest("POST", "http://foo.example", &body)
|
||||
|
||||
runDefault, errJson := wrapBLSChangesArray(endpoint, nil, request)
|
||||
require.Equal(t, false, errJson == nil)
|
||||
assert.Equal(t, apimiddleware.RunDefault(false), runDefault)
|
||||
assert.Equal(t, true, strings.Contains(errJson.Msg(), "could not decode body"))
|
||||
assert.Equal(t, http.StatusInternalServerError, errJson.StatusCode())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetInitialPublishBlockPostRequest(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
@@ -401,31 +360,6 @@ func TestPreparePublishedBlindedBlock(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrepareValidatorAggregates(t *testing.T) {
|
||||
body := &tempSyncCommitteesResponseJson{
|
||||
Data: &tempSyncCommitteeValidatorsJson{
|
||||
Validators: []string{"1", "2"},
|
||||
ValidatorAggregates: []*tempSyncSubcommitteeValidatorsJson{
|
||||
{
|
||||
Validators: []string{"3", "4"},
|
||||
},
|
||||
{
|
||||
Validators: []string{"5"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
bodyJson, err := json.Marshal(body)
|
||||
require.NoError(t, err)
|
||||
|
||||
container := &SyncCommitteesResponseJson{}
|
||||
runDefault, errJson := prepareValidatorAggregates(bodyJson, container)
|
||||
require.Equal(t, nil, errJson)
|
||||
require.Equal(t, apimiddleware.RunDefault(false), runDefault)
|
||||
assert.DeepEqual(t, []string{"1", "2"}, container.Data.Validators)
|
||||
require.DeepEqual(t, [][]string{{"3", "4"}, {"5"}}, container.Data.ValidatorAggregates)
|
||||
}
|
||||
|
||||
func TestSerializeV2Block(t *testing.T) {
|
||||
t.Run("Phase 0", func(t *testing.T) {
|
||||
response := &BlockV2ResponseJson{
|
||||
|
||||
@@ -16,9 +16,6 @@ func (f *BeaconEndpointFactory) IsNil() bool {
|
||||
// Paths is a collection of all valid beacon chain API paths.
|
||||
func (_ *BeaconEndpointFactory) Paths() []string {
|
||||
return []string{
|
||||
"/eth/v1/beacon/states/{state_id}/root",
|
||||
"/eth/v1/beacon/states/{state_id}/sync_committees",
|
||||
"/eth/v1/beacon/states/{state_id}/randao",
|
||||
"/eth/v1/beacon/blinded_blocks",
|
||||
"/eth/v1/beacon/blocks/{block_id}",
|
||||
"/eth/v2/beacon/blocks/{block_id}",
|
||||
@@ -26,15 +23,7 @@ func (_ *BeaconEndpointFactory) Paths() []string {
|
||||
"/eth/v1/beacon/blinded_blocks/{block_id}",
|
||||
"/eth/v1/beacon/pool/attester_slashings",
|
||||
"/eth/v1/beacon/pool/proposer_slashings",
|
||||
"/eth/v1/beacon/pool/bls_to_execution_changes",
|
||||
"/eth/v1/beacon/pool/bls_to_execution_changes",
|
||||
"/eth/v1/beacon/weak_subjectivity",
|
||||
"/eth/v1/node/identity",
|
||||
"/eth/v1/node/peers",
|
||||
"/eth/v1/node/peers/{peer_id}",
|
||||
"/eth/v1/node/peer_count",
|
||||
"/eth/v1/node/version",
|
||||
"/eth/v1/node/health",
|
||||
"/eth/v1/debug/beacon/states/{state_id}",
|
||||
"/eth/v2/debug/beacon/states/{state_id}",
|
||||
"/eth/v1/debug/beacon/heads",
|
||||
@@ -53,17 +42,6 @@ func (_ *BeaconEndpointFactory) Paths() []string {
|
||||
func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, error) {
|
||||
endpoint := apimiddleware.DefaultEndpoint()
|
||||
switch path {
|
||||
case "/eth/v1/beacon/states/{state_id}/root":
|
||||
endpoint.GetResponse = &StateRootResponseJson{}
|
||||
case "/eth/v1/beacon/states/{state_id}/sync_committees":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}}
|
||||
endpoint.GetResponse = &SyncCommitteesResponseJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeGrpcResponseBodyIntoContainer: prepareValidatorAggregates,
|
||||
}
|
||||
case "/eth/v1/beacon/states/{state_id}/randao":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}}
|
||||
endpoint.GetResponse = &RandaoResponseJson{}
|
||||
case "/eth/v1/beacon/blocks/{block_id}":
|
||||
endpoint.GetResponse = &BlockResponseJson{}
|
||||
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleGetBeaconBlockSSZ}
|
||||
@@ -87,29 +65,8 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
|
||||
case "/eth/v1/beacon/pool/proposer_slashings":
|
||||
endpoint.PostRequest = &ProposerSlashingJson{}
|
||||
endpoint.GetResponse = &ProposerSlashingsPoolResponseJson{}
|
||||
case "/eth/v1/beacon/pool/bls_to_execution_changes":
|
||||
endpoint.PostRequest = &SubmitBLSToExecutionChangesRequest{}
|
||||
endpoint.GetResponse = &BLSToExecutionChangesPoolResponseJson{}
|
||||
endpoint.Err = &IndexedVerificationFailureErrorJson{}
|
||||
endpoint.Hooks = apimiddleware.HookCollection{
|
||||
OnPreDeserializeRequestBodyIntoContainer: wrapBLSChangesArray,
|
||||
}
|
||||
case "/eth/v1/beacon/weak_subjectivity":
|
||||
endpoint.GetResponse = &WeakSubjectivityResponse{}
|
||||
case "/eth/v1/node/identity":
|
||||
endpoint.GetResponse = &IdentityResponseJson{}
|
||||
case "/eth/v1/node/peers":
|
||||
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "state", Enum: true}, {Name: "direction", Enum: true}}
|
||||
endpoint.GetResponse = &PeersResponseJson{}
|
||||
case "/eth/v1/node/peers/{peer_id}":
|
||||
endpoint.RequestURLLiterals = []string{"peer_id"}
|
||||
endpoint.GetResponse = &PeerResponseJson{}
|
||||
case "/eth/v1/node/peer_count":
|
||||
endpoint.GetResponse = &PeerCountResponseJson{}
|
||||
case "/eth/v1/node/version":
|
||||
endpoint.GetResponse = &VersionResponseJson{}
|
||||
case "/eth/v1/node/health":
|
||||
// Use default endpoint
|
||||
case "/eth/v1/debug/beacon/states/{state_id}":
|
||||
endpoint.GetResponse = &BeaconStateResponseJson{}
|
||||
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleGetBeaconStateSSZ}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/gateway/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
)
|
||||
|
||||
@@ -21,30 +20,6 @@ type WeakSubjectivityResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type StateRootResponseJson struct {
|
||||
Data *StateRootResponse_StateRootJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type StateRootResponse_StateRootJson struct {
|
||||
StateRoot string `json:"root" hex:"true"`
|
||||
}
|
||||
|
||||
type SyncCommitteesResponseJson struct {
|
||||
Data *SyncCommitteeValidatorsJson `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type RandaoResponseJson struct {
|
||||
Data *struct {
|
||||
Randao string `json:"randao" hex:"true"`
|
||||
} `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type BlockResponseJson struct {
|
||||
Data *SignedBeaconBlockJson `json:"data"`
|
||||
}
|
||||
@@ -83,41 +58,6 @@ type ProposerSlashingsPoolResponseJson struct {
|
||||
Data []*ProposerSlashingJson `json:"data"`
|
||||
}
|
||||
|
||||
type BLSToExecutionChangesPoolResponseJson struct {
|
||||
Data []*SignedBLSToExecutionChangeJson `json:"data"`
|
||||
}
|
||||
|
||||
type IdentityResponseJson struct {
|
||||
Data *IdentityJson `json:"data"`
|
||||
}
|
||||
|
||||
type PeersResponseJson struct {
|
||||
Data []*PeerJson `json:"data"`
|
||||
}
|
||||
|
||||
type PeerResponseJson struct {
|
||||
Data *PeerJson `json:"data"`
|
||||
}
|
||||
|
||||
type PeerCountResponseJson struct {
|
||||
Data PeerCountResponse_PeerCountJson `json:"data"`
|
||||
}
|
||||
|
||||
type PeerCountResponse_PeerCountJson struct {
|
||||
Disconnected string `json:"disconnected"`
|
||||
Connecting string `json:"connecting"`
|
||||
Connected string `json:"connected"`
|
||||
Disconnecting string `json:"disconnecting"`
|
||||
}
|
||||
|
||||
type VersionResponseJson struct {
|
||||
Data *VersionJson `json:"data"`
|
||||
}
|
||||
|
||||
type SyncingResponseJson struct {
|
||||
Data *shared.SyncDetails `json:"data"`
|
||||
}
|
||||
|
||||
type BeaconStateResponseJson struct {
|
||||
Data *BeaconStateJson `json:"data"`
|
||||
}
|
||||
@@ -708,10 +648,6 @@ type BLSToExecutionChangeJson struct {
|
||||
ToExecutionAddress string `json:"to_execution_address" hex:"true"`
|
||||
}
|
||||
|
||||
type SubmitBLSToExecutionChangesRequest struct {
|
||||
Changes []*SignedBLSToExecutionChangeJson `json:"changes"`
|
||||
}
|
||||
|
||||
type DepositJson struct {
|
||||
Proof []string `json:"proof" hex:"true"`
|
||||
Data *Deposit_DataJson `json:"data"`
|
||||
@@ -734,31 +670,6 @@ type VoluntaryExitJson struct {
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
}
|
||||
|
||||
type IdentityJson struct {
|
||||
PeerId string `json:"peer_id"`
|
||||
Enr string `json:"enr"`
|
||||
P2PAddresses []string `json:"p2p_addresses"`
|
||||
DiscoveryAddresses []string `json:"discovery_addresses"`
|
||||
Metadata *MetadataJson `json:"metadata"`
|
||||
}
|
||||
|
||||
type MetadataJson struct {
|
||||
SeqNumber string `json:"seq_number"`
|
||||
Attnets string `json:"attnets" hex:"true"`
|
||||
}
|
||||
|
||||
type PeerJson struct {
|
||||
PeerId string `json:"peer_id"`
|
||||
Enr string `json:"enr"`
|
||||
Address string `json:"last_seen_p2p_address"`
|
||||
State string `json:"state" enum:"true"`
|
||||
Direction string `json:"direction" enum:"true"`
|
||||
}
|
||||
|
||||
type VersionJson struct {
|
||||
Version string `json:"version" enum:"true"`
|
||||
}
|
||||
|
||||
type WithdrawalJson struct {
|
||||
WithdrawalIndex string `json:"index"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
@@ -937,11 +848,6 @@ type SyncCommitteeJson struct {
|
||||
AggregatePubkey string `json:"aggregate_pubkey" hex:"true"`
|
||||
}
|
||||
|
||||
type SyncCommitteeValidatorsJson struct {
|
||||
Validators []string `json:"validators"`
|
||||
ValidatorAggregates [][]string `json:"validator_aggregates"`
|
||||
}
|
||||
|
||||
type PendingAttestationJson struct {
|
||||
AggregationBits string `json:"aggregation_bits" hex:"true"`
|
||||
Data *AttestationDataJson `json:"data"`
|
||||
|
||||
@@ -8,19 +8,17 @@ go_library(
|
||||
"config.go",
|
||||
"handlers.go",
|
||||
"handlers_pool.go",
|
||||
"handlers_state.go",
|
||||
"handlers_validator.go",
|
||||
"log.go",
|
||||
"pool.go",
|
||||
"server.go",
|
||||
"state.go",
|
||||
"structs.go",
|
||||
"sync_committee.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api:go_default_library",
|
||||
"//api/grpc:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
@@ -64,10 +62,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_golang_protobuf//ptypes/empty",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
@@ -84,13 +82,12 @@ go_test(
|
||||
"blocks_test.go",
|
||||
"config_test.go",
|
||||
"handlers_pool_test.go",
|
||||
"handlers_state_test.go",
|
||||
"handlers_test.go",
|
||||
"handlers_validators_test.go",
|
||||
"init_test.go",
|
||||
"pool_test.go",
|
||||
"server_test.go",
|
||||
"state_test.go",
|
||||
"sync_committee_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
@@ -150,8 +147,6 @@ go_test(
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@com_github_stretchr_testify//mock:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -664,13 +664,13 @@ func (s *Server) GetStateFork(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
st, err := s.Stater.State(ctx, []byte(stateId))
|
||||
if err != nil {
|
||||
http2.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return
|
||||
}
|
||||
fork := st.Fork()
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
|
||||
if err != nil {
|
||||
http2.HandleError(w, errors.Wrap(err, "Could not check if slot's block is optimistic").Error(), http.StatusInternalServerError)
|
||||
http2.HandleError(w, "Could not check optimistic status"+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
@@ -772,7 +772,7 @@ func (s *Server) GetCommittees(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check if slot's block is optimistic: "+err.Error(), http.StatusInternalServerError)
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -967,7 +967,7 @@ func (s *Server) GetFinalityCheckpoints(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check if slot's block is optimistic: "+err.Error(), http.StatusInternalServerError)
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
|
||||
@@ -20,7 +21,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
@@ -91,7 +93,7 @@ func (s *Server) SubmitAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var validAttestations []*ethpbalpha.Attestation
|
||||
var validAttestations []*eth.Attestation
|
||||
var attFailures []*shared.IndexedVerificationFailure
|
||||
for i, sourceAtt := range req.Data {
|
||||
att, err := sourceAtt.ToConsensus()
|
||||
@@ -137,7 +139,6 @@ func (s *Server) SubmitAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
subnet := corehelpers.ComputeSubnetFromCommitteeAndSlot(uint64(len(vals)), att.Data.CommitteeIndex, att.Data.Slot)
|
||||
|
||||
if err = s.Broadcaster.BroadcastAttestation(ctx, subnet, att); err != nil {
|
||||
failedBroadcasts = append(failedBroadcasts, strconv.Itoa(i))
|
||||
log.WithError(err).Errorf("could not broadcast attestation at index %d", i)
|
||||
}
|
||||
|
||||
@@ -268,7 +269,7 @@ func (s *Server) SubmitSyncCommitteeSignatures(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
var validMessages []*ethpbalpha.SyncCommitteeMessage
|
||||
var validMessages []*eth.SyncCommitteeMessage
|
||||
var msgFailures []*shared.IndexedVerificationFailure
|
||||
for i, sourceMsg := range req.Data {
|
||||
msg, err := sourceMsg.ToConsensus()
|
||||
@@ -298,3 +299,147 @@ func (s *Server) SubmitSyncCommitteeSignatures(w http.ResponseWriter, r *http.Re
|
||||
http2.WriteError(w, failuresErr)
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitBLSToExecutionChanges submits said object to the node's pool
|
||||
// if it passes validation the node must broadcast it to the network.
|
||||
func (s *Server) SubmitBLSToExecutionChanges(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitBLSToExecutionChanges")
|
||||
defer span.End()
|
||||
st, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get head state: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var failures []*shared.IndexedVerificationFailure
|
||||
var toBroadcast []*eth.SignedBLSToExecutionChange
|
||||
|
||||
var req []*shared.SignedBLSToExecutionChange
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if len(req) == 0 {
|
||||
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
for i, change := range req {
|
||||
sbls, err := change.ToConsensus()
|
||||
if err != nil {
|
||||
failures = append(failures, &shared.IndexedVerificationFailure{
|
||||
Index: i,
|
||||
Message: "Unable to decode SignedBLSToExecutionChange: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
_, err = blocks.ValidateBLSToExecutionChange(st, sbls)
|
||||
if err != nil {
|
||||
failures = append(failures, &shared.IndexedVerificationFailure{
|
||||
Index: i,
|
||||
Message: "Could not validate SignedBLSToExecutionChange: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
if err := blocks.VerifyBLSChangeSignature(st, sbls); err != nil {
|
||||
failures = append(failures, &shared.IndexedVerificationFailure{
|
||||
Index: i,
|
||||
Message: "Could not validate signature: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
Type: operation.BLSToExecutionChangeReceived,
|
||||
Data: &operation.BLSToExecutionChangeReceivedData{
|
||||
Change: sbls,
|
||||
},
|
||||
})
|
||||
s.BLSChangesPool.InsertBLSToExecChange(sbls)
|
||||
if st.Version() >= version.Capella {
|
||||
toBroadcast = append(toBroadcast, sbls)
|
||||
}
|
||||
}
|
||||
go s.broadcastBLSChanges(ctx, toBroadcast)
|
||||
if len(failures) > 0 {
|
||||
failuresErr := &shared.IndexedVerificationFailureError{
|
||||
Code: http.StatusBadRequest,
|
||||
Message: "One or more BLSToExecutionChange failed validation",
|
||||
Failures: failures,
|
||||
}
|
||||
http2.WriteError(w, failuresErr)
|
||||
}
|
||||
}
|
||||
|
||||
// broadcastBLSBatch broadcasts the first `broadcastBLSChangesRateLimit` messages from the slice pointed to by ptr.
|
||||
// It validates the messages again because they could have been invalidated by being included in blocks since the last validation.
|
||||
// It removes the messages from the slice and modifies it in place.
|
||||
func (s *Server) broadcastBLSBatch(ctx context.Context, ptr *[]*eth.SignedBLSToExecutionChange) {
|
||||
limit := broadcastBLSChangesRateLimit
|
||||
if len(*ptr) < broadcastBLSChangesRateLimit {
|
||||
limit = len(*ptr)
|
||||
}
|
||||
st, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not get head state")
|
||||
return
|
||||
}
|
||||
for _, ch := range (*ptr)[:limit] {
|
||||
if ch != nil {
|
||||
_, err := blocks.ValidateBLSToExecutionChange(st, ch)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not validate BLS to execution change")
|
||||
continue
|
||||
}
|
||||
if err := s.Broadcaster.Broadcast(ctx, ch); err != nil {
|
||||
log.WithError(err).Error("could not broadcast BLS to execution changes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
*ptr = (*ptr)[limit:]
|
||||
}
|
||||
|
||||
func (s *Server) broadcastBLSChanges(ctx context.Context, changes []*eth.SignedBLSToExecutionChange) {
|
||||
s.broadcastBLSBatch(ctx, &changes)
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
s.broadcastBLSBatch(ctx, &changes)
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListBLSToExecutionChanges retrieves BLS to execution changes known by the node but not necessarily incorporated into any block
|
||||
func (s *Server) ListBLSToExecutionChanges(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "beacon.ListBLSToExecutionChanges")
|
||||
defer span.End()
|
||||
|
||||
sourceChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get BLS to execution changes: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
changes, err := shared.SignedBlsToExecutionChangesFromConsensus(sourceChanges)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "failed to decode SignedBlsToExecutionChanges: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http2.WriteJson(w, &BLSToExecutionChangesPoolResponse{
|
||||
Data: changes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,25 +8,37 @@ import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
blockchainmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
prysmtime "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/blstoexec"
|
||||
blstoexecmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/blstoexec/mock"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/synccommittee"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/voluntaryexits/mock"
|
||||
p2pMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
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/crypto/bls/common"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
ethpbv1alpha1 "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
)
|
||||
|
||||
func TestListAttestations(t *testing.T) {
|
||||
@@ -618,6 +630,373 @@ func TestSubmitSyncCommitteeSignatures(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestListBLSToExecutionChanges(t *testing.T) {
|
||||
change1 := ðpbv1alpha1.SignedBLSToExecutionChange{
|
||||
Message: ðpbv1alpha1.BLSToExecutionChange{
|
||||
ValidatorIndex: 1,
|
||||
FromBlsPubkey: bytesutil.PadTo([]byte("pubkey1"), 48),
|
||||
ToExecutionAddress: bytesutil.PadTo([]byte("address1"), 20),
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature1"), 96),
|
||||
}
|
||||
change2 := ðpbv1alpha1.SignedBLSToExecutionChange{
|
||||
Message: ðpbv1alpha1.BLSToExecutionChange{
|
||||
ValidatorIndex: 2,
|
||||
FromBlsPubkey: bytesutil.PadTo([]byte("pubkey2"), 48),
|
||||
ToExecutionAddress: bytesutil.PadTo([]byte("address2"), 20),
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature2"), 96),
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
BLSChangesPool: &blstoexecmock.PoolMock{Changes: []*ethpbv1alpha1.SignedBLSToExecutionChange{change1, change2}},
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/pool/bls_to_execution_changes", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.ListBLSToExecutionChanges(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
json1, err := shared.SignedBlsToExecutionChangeFromConsensus(change1)
|
||||
require.NoError(t, err)
|
||||
json2, err := shared.SignedBlsToExecutionChangeFromConsensus(change2)
|
||||
require.NoError(t, err)
|
||||
resp := &BLSToExecutionChangesPoolResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.Equal(t, 2, len(resp.Data))
|
||||
assert.DeepEqual(t, json1, resp.Data[0])
|
||||
assert.DeepEqual(t, json2, resp.Data[1])
|
||||
}
|
||||
|
||||
func TestSubmitSignedBLSToExecutionChanges_Ok(t *testing.T) {
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
c := params.BeaconConfig().Copy()
|
||||
// Required for correct committee size calculation.
|
||||
c.CapellaForkEpoch = c.BellatrixForkEpoch.Add(2)
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
spb := ðpbv1alpha1.BeaconStateCapella{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
},
|
||||
}
|
||||
numValidators := 10
|
||||
validators := make([]*ethpbv1alpha1.Validator, numValidators)
|
||||
blsChanges := make([]*ethpbv1alpha1.BLSToExecutionChange, numValidators)
|
||||
spb.Balances = make([]uint64, numValidators)
|
||||
privKeys := make([]common.SecretKey, numValidators)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
executionAddress := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13}
|
||||
|
||||
for i := range validators {
|
||||
v := ðpbv1alpha1.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = params.BeaconConfig().FarFutureEpoch
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[i] = priv
|
||||
pubkey := priv.PublicKey().Marshal()
|
||||
|
||||
message := ðpbv1alpha1.BLSToExecutionChange{
|
||||
ToExecutionAddress: executionAddress,
|
||||
ValidatorIndex: primitives.ValidatorIndex(i),
|
||||
FromBlsPubkey: pubkey,
|
||||
}
|
||||
|
||||
hashFn := ssz.NewHasherFunc(hash.CustomSHA256Hasher())
|
||||
digest := hashFn.Hash(pubkey)
|
||||
digest[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
|
||||
copy(v.WithdrawalCredentials, digest[:])
|
||||
validators[i] = v
|
||||
blsChanges[i] = message
|
||||
}
|
||||
spb.Validators = validators
|
||||
slot, err := slots.EpochStart(params.BeaconConfig().CapellaForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spb.Slot = slot
|
||||
st, err := state_native.InitializeFromProtoCapella(spb)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges := make([]*shared.SignedBLSToExecutionChange, numValidators)
|
||||
for i, message := range blsChanges {
|
||||
signature, err := signing.ComputeDomainAndSign(st, prysmtime.CurrentEpoch(st), message, params.BeaconConfig().DomainBLSToExecutionChange, privKeys[i])
|
||||
require.NoError(t, err)
|
||||
m, err := shared.BlsToExecutionChangeFromConsensus(message)
|
||||
require.NoError(t, err)
|
||||
signed := &shared.SignedBLSToExecutionChange{
|
||||
Message: m,
|
||||
Signature: hexutil.Encode(signature),
|
||||
}
|
||||
signedChanges[i] = signed
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainService := &blockchainmock.ChainService{State: st}
|
||||
s := &Server{
|
||||
HeadFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
AttestationsPool: attestations.NewPool(),
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: &blockchainmock.MockOperationNotifier{},
|
||||
BLSChangesPool: blstoexec.NewPool(),
|
||||
}
|
||||
jsonBytes, err := json.Marshal(signedChanges)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example/eth/v1/beacon/pool/bls_to_execution_changes", bytes.NewReader(jsonBytes))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.SubmitBLSToExecutionChanges(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
time.Sleep(100 * time.Millisecond) // Delay to let the routine start
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled)
|
||||
assert.Equal(t, numValidators, len(broadcaster.BroadcastMessages))
|
||||
|
||||
poolChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
require.Equal(t, len(poolChanges), len(signedChanges))
|
||||
require.NoError(t, err)
|
||||
for i, v1alphaChange := range poolChanges {
|
||||
sc, err := signedChanges[i].ToConsensus()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, v1alphaChange, sc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitSignedBLSToExecutionChanges_Bellatrix(t *testing.T) {
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
c := params.BeaconConfig().Copy()
|
||||
// Required for correct committee size calculation.
|
||||
c.CapellaForkEpoch = c.BellatrixForkEpoch.Add(2)
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
spb := ðpbv1alpha1.BeaconStateBellatrix{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().BellatrixForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().AltairForkVersion,
|
||||
Epoch: params.BeaconConfig().BellatrixForkEpoch,
|
||||
},
|
||||
}
|
||||
numValidators := 10
|
||||
validators := make([]*ethpbv1alpha1.Validator, numValidators)
|
||||
blsChanges := make([]*ethpbv1alpha1.BLSToExecutionChange, numValidators)
|
||||
spb.Balances = make([]uint64, numValidators)
|
||||
privKeys := make([]common.SecretKey, numValidators)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
executionAddress := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13}
|
||||
|
||||
for i := range validators {
|
||||
v := ðpbv1alpha1.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = params.BeaconConfig().FarFutureEpoch
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[i] = priv
|
||||
pubkey := priv.PublicKey().Marshal()
|
||||
|
||||
message := ðpbv1alpha1.BLSToExecutionChange{
|
||||
ToExecutionAddress: executionAddress,
|
||||
ValidatorIndex: primitives.ValidatorIndex(i),
|
||||
FromBlsPubkey: pubkey,
|
||||
}
|
||||
|
||||
hashFn := ssz.NewHasherFunc(hash.CustomSHA256Hasher())
|
||||
digest := hashFn.Hash(pubkey)
|
||||
digest[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
|
||||
copy(v.WithdrawalCredentials, digest[:])
|
||||
validators[i] = v
|
||||
blsChanges[i] = message
|
||||
}
|
||||
spb.Validators = validators
|
||||
slot, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spb.Slot = slot
|
||||
st, err := state_native.InitializeFromProtoBellatrix(spb)
|
||||
require.NoError(t, err)
|
||||
|
||||
spc := ðpbv1alpha1.BeaconStateCapella{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
},
|
||||
}
|
||||
slot, err = slots.EpochStart(params.BeaconConfig().CapellaForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spc.Slot = slot
|
||||
|
||||
stc, err := state_native.InitializeFromProtoCapella(spc)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges := make([]*shared.SignedBLSToExecutionChange, numValidators)
|
||||
for i, message := range blsChanges {
|
||||
signature, err := signing.ComputeDomainAndSign(stc, prysmtime.CurrentEpoch(stc), message, params.BeaconConfig().DomainBLSToExecutionChange, privKeys[i])
|
||||
require.NoError(t, err)
|
||||
|
||||
bl, err := shared.BlsToExecutionChangeFromConsensus(message)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges[i] = &shared.SignedBLSToExecutionChange{
|
||||
Message: bl,
|
||||
Signature: hexutil.Encode(signature),
|
||||
}
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainService := &blockchainmock.ChainService{State: st}
|
||||
s := &Server{
|
||||
HeadFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
AttestationsPool: attestations.NewPool(),
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: &blockchainmock.MockOperationNotifier{},
|
||||
BLSChangesPool: blstoexec.NewPool(),
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(signedChanges)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example/eth/v1/beacon/pool/bls_to_execution_changes", bytes.NewReader(jsonBytes))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitBLSToExecutionChanges(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
// Check that we didn't broadcast the messages but did in fact fill in
|
||||
// the pool
|
||||
assert.Equal(t, false, broadcaster.BroadcastCalled)
|
||||
|
||||
poolChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
require.Equal(t, len(poolChanges), len(signedChanges))
|
||||
require.NoError(t, err)
|
||||
for i, v1alphaChange := range poolChanges {
|
||||
sc, err := signedChanges[i].ToConsensus()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, v1alphaChange, sc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitSignedBLSToExecutionChanges_Failures(t *testing.T) {
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
c := params.BeaconConfig().Copy()
|
||||
// Required for correct committee size calculation.
|
||||
c.CapellaForkEpoch = c.BellatrixForkEpoch.Add(2)
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
spb := ðpbv1alpha1.BeaconStateCapella{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
},
|
||||
}
|
||||
numValidators := 10
|
||||
validators := make([]*ethpbv1alpha1.Validator, numValidators)
|
||||
blsChanges := make([]*ethpbv1alpha1.BLSToExecutionChange, numValidators)
|
||||
spb.Balances = make([]uint64, numValidators)
|
||||
privKeys := make([]common.SecretKey, numValidators)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
executionAddress := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13}
|
||||
|
||||
for i := range validators {
|
||||
v := ðpbv1alpha1.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = params.BeaconConfig().FarFutureEpoch
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[i] = priv
|
||||
pubkey := priv.PublicKey().Marshal()
|
||||
|
||||
message := ðpbv1alpha1.BLSToExecutionChange{
|
||||
ToExecutionAddress: executionAddress,
|
||||
ValidatorIndex: primitives.ValidatorIndex(i),
|
||||
FromBlsPubkey: pubkey,
|
||||
}
|
||||
|
||||
hashFn := ssz.NewHasherFunc(hash.CustomSHA256Hasher())
|
||||
digest := hashFn.Hash(pubkey)
|
||||
digest[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
|
||||
copy(v.WithdrawalCredentials, digest[:])
|
||||
validators[i] = v
|
||||
blsChanges[i] = message
|
||||
}
|
||||
spb.Validators = validators
|
||||
slot, err := slots.EpochStart(params.BeaconConfig().CapellaForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spb.Slot = slot
|
||||
st, err := state_native.InitializeFromProtoCapella(spb)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges := make([]*shared.SignedBLSToExecutionChange, numValidators)
|
||||
for i, message := range blsChanges {
|
||||
signature, err := signing.ComputeDomainAndSign(st, prysmtime.CurrentEpoch(st), message, params.BeaconConfig().DomainBLSToExecutionChange, privKeys[i])
|
||||
require.NoError(t, err)
|
||||
|
||||
bl, err := shared.BlsToExecutionChangeFromConsensus(message)
|
||||
require.NoError(t, err)
|
||||
if i == 1 {
|
||||
signature[0] = 0x00
|
||||
}
|
||||
signedChanges[i] = &shared.SignedBLSToExecutionChange{
|
||||
Message: bl,
|
||||
Signature: hexutil.Encode(signature),
|
||||
}
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainService := &blockchainmock.ChainService{State: st}
|
||||
s := &Server{
|
||||
HeadFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
AttestationsPool: attestations.NewPool(),
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: &blockchainmock.MockOperationNotifier{},
|
||||
BLSChangesPool: blstoexec.NewPool(),
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(signedChanges)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example/eth/v1/beacon/pool/bls_to_execution_changes", bytes.NewReader(jsonBytes))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitBLSToExecutionChanges(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
time.Sleep(10 * time.Millisecond) // Delay to allow the routine to start
|
||||
require.StringContains(t, "One or more BLSToExecutionChange failed validation", writer.Body.String())
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled)
|
||||
assert.Equal(t, numValidators, len(broadcaster.BroadcastMessages)+1)
|
||||
|
||||
poolChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
require.Equal(t, len(poolChanges)+1, len(signedChanges))
|
||||
require.NoError(t, err)
|
||||
|
||||
v2Change, err := shared.SignedBlsToExecutionChangeFromConsensus(poolChanges[0])
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, v2Change, signedChanges[0])
|
||||
|
||||
for i := 2; i < numValidators; i++ {
|
||||
v2Change, err := shared.SignedBlsToExecutionChangeFromConsensus(poolChanges[i-1])
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, v2Change, signedChanges[i])
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
singleAtt = `[
|
||||
{
|
||||
|
||||
336
beacon-chain/rpc/eth/beacon/handlers_state.go
Normal file
336
beacon-chain/rpc/eth/beacon/handlers_state.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
type syncCommitteeStateRequest struct {
|
||||
epoch *primitives.Epoch
|
||||
stateId []byte
|
||||
}
|
||||
|
||||
// GetStateRoot calculates HashTreeRoot for state with given 'stateId'. If stateId is root, same value will be returned.
|
||||
func (s *Server) GetStateRoot(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetStateRoot")
|
||||
defer span.End()
|
||||
|
||||
stateId := mux.Vars(r)["state_id"]
|
||||
if stateId == "" {
|
||||
http2.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
stateRoot, err := s.Stater.StateRoot(ctx, []byte(stateId))
|
||||
if err != nil {
|
||||
if rootNotFoundErr, ok := err.(*lookup.StateRootNotFoundError); ok {
|
||||
http2.HandleError(w, "State root not found: "+rootNotFoundErr.Error(), http.StatusNotFound)
|
||||
return
|
||||
} else if parseErr, ok := err.(*lookup.StateIdParseError); ok {
|
||||
http2.HandleError(w, "Invalid state ID: "+parseErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
http2.HandleError(w, "Could not get state root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err := s.Stater.State(ctx, []byte(stateId))
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return
|
||||
}
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not calculate root of latest block header: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
resp := &GetStateRootResponse{
|
||||
Data: &StateRoot{
|
||||
Root: hexutil.Encode(stateRoot),
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetRandao fetches the RANDAO mix for the requested epoch from the state identified by state_id.
|
||||
// If an epoch is not specified then the RANDAO mix for the state's current epoch will be returned.
|
||||
// By adjusting the state_id parameter you can query for any historic value of the RANDAO mix.
|
||||
// Ordinarily states from the same epoch will mutate the RANDAO mix for that epoch as blocks are applied.
|
||||
func (s *Server) GetRandao(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetRandao")
|
||||
defer span.End()
|
||||
|
||||
stateId := mux.Vars(r)["state_id"]
|
||||
if stateId == "" {
|
||||
http2.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ok, rawEpoch, e := shared.UintFromQuery(w, r, "epoch")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
st, err := s.Stater.State(ctx, []byte(stateId))
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
stEpoch := slots.ToEpoch(st.Slot())
|
||||
epoch := stEpoch
|
||||
if rawEpoch != "" {
|
||||
epoch = primitives.Epoch(e)
|
||||
}
|
||||
|
||||
// future epochs and epochs too far back are not supported.
|
||||
randaoEpochLowerBound := uint64(0)
|
||||
// Lower bound should not underflow.
|
||||
if uint64(stEpoch) > uint64(st.RandaoMixesLength()) {
|
||||
randaoEpochLowerBound = uint64(stEpoch) - uint64(st.RandaoMixesLength())
|
||||
}
|
||||
if epoch > stEpoch || uint64(epoch) < randaoEpochLowerBound+1 {
|
||||
http2.HandleError(w, "Epoch is out of range for the randao mixes of the state", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
idx := epoch % params.BeaconConfig().EpochsPerHistoricalVector
|
||||
randao, err := st.RandaoMixAtIndex(uint64(idx))
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not get randao mix at index %d: %v", idx, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not calculate root of latest block header: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
resp := &GetRandaoResponse{
|
||||
Data: &Randao{Randao: hexutil.Encode(randao)},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetSyncCommittees retrieves the sync committees for the given epoch.
|
||||
// If the epoch is not passed in, then the sync committees for the epoch of the state will be obtained.
|
||||
func (s *Server) GetSyncCommittees(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetSyncCommittees")
|
||||
defer span.End()
|
||||
|
||||
stateId := mux.Vars(r)["state_id"]
|
||||
if stateId == "" {
|
||||
http2.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ok, rawEpoch, e := shared.UintFromQuery(w, r, "epoch")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
epoch := primitives.Epoch(e)
|
||||
|
||||
currentSlot := s.GenesisTimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
currentPeriodStartEpoch, err := slots.SyncCommitteePeriodStartEpoch(currentEpoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not calculate start period for slot %d: %v", currentSlot, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
requestNextCommittee := false
|
||||
if rawEpoch != "" {
|
||||
reqPeriodStartEpoch, err := slots.SyncCommitteePeriodStartEpoch(epoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not calculate start period for epoch %d: %v", e, err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if reqPeriodStartEpoch > currentPeriodStartEpoch+params.BeaconConfig().EpochsPerSyncCommitteePeriod {
|
||||
http2.HandleError(
|
||||
w,
|
||||
fmt.Sprintf("Could not fetch sync committee too far in the future (requested epoch %d, current epoch %d)", e, currentEpoch),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
}
|
||||
if reqPeriodStartEpoch > currentPeriodStartEpoch {
|
||||
requestNextCommittee = true
|
||||
epoch = currentPeriodStartEpoch
|
||||
}
|
||||
}
|
||||
|
||||
syncCommitteeReq := &syncCommitteeStateRequest{
|
||||
epoch: nil,
|
||||
stateId: []byte(stateId),
|
||||
}
|
||||
if rawEpoch != "" {
|
||||
syncCommitteeReq.epoch = &epoch
|
||||
}
|
||||
st, ok := s.stateForSyncCommittee(ctx, w, syncCommitteeReq)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var committeeIndices []string
|
||||
var committee *ethpbalpha.SyncCommittee
|
||||
if requestNextCommittee {
|
||||
// Get the next sync committee and sync committee indices from the state.
|
||||
committeeIndices, committee, err = nextCommitteeIndicesFromState(st)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get next sync committee indices: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Get the current sync committee and sync committee indices from the state.
|
||||
committeeIndices, committee, err = currentCommitteeIndicesFromState(st)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get current sync committee indices: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
subcommittees, err := extractSyncSubcommittees(st, committee)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not extract sync subcommittees: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not calculate root of latest block header: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
resp := GetSyncCommitteeResponse{
|
||||
Data: &SyncCommitteeValidators{
|
||||
Validators: committeeIndices,
|
||||
ValidatorAggregates: subcommittees,
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
func committeeIndicesFromState(st state.BeaconState, committee *ethpbalpha.SyncCommittee) ([]string, *ethpbalpha.SyncCommittee, error) {
|
||||
committeeIndices := make([]string, len(committee.Pubkeys))
|
||||
for i, key := range committee.Pubkeys {
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(key))
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"validator index not found for pubkey %#x",
|
||||
bytesutil.Trunc(key),
|
||||
)
|
||||
}
|
||||
committeeIndices[i] = strconv.FormatUint(uint64(index), 10)
|
||||
}
|
||||
return committeeIndices, committee, nil
|
||||
}
|
||||
|
||||
func currentCommitteeIndicesFromState(st state.BeaconState) ([]string, *ethpbalpha.SyncCommittee, error) {
|
||||
committee, err := st.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"could not get sync committee: %v", err,
|
||||
)
|
||||
}
|
||||
|
||||
return committeeIndicesFromState(st, committee)
|
||||
}
|
||||
|
||||
func nextCommitteeIndicesFromState(st state.BeaconState) ([]string, *ethpbalpha.SyncCommittee, error) {
|
||||
committee, err := st.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"could not get sync committee: %v", err,
|
||||
)
|
||||
}
|
||||
|
||||
return committeeIndicesFromState(st, committee)
|
||||
}
|
||||
|
||||
func extractSyncSubcommittees(st state.BeaconState, committee *ethpbalpha.SyncCommittee) ([][]string, error) {
|
||||
subcommitteeCount := params.BeaconConfig().SyncCommitteeSubnetCount
|
||||
subcommittees := make([][]string, subcommitteeCount)
|
||||
for i := uint64(0); i < subcommitteeCount; i++ {
|
||||
pubkeys, err := altair.SyncSubCommitteePubkeys(committee, primitives.CommitteeIndex(i))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to get subcommittee pubkeys: %v", err,
|
||||
)
|
||||
}
|
||||
subcommittee := make([]string, len(pubkeys))
|
||||
for j, key := range pubkeys {
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(key))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"validator index not found for pubkey %#x",
|
||||
bytesutil.Trunc(key),
|
||||
)
|
||||
}
|
||||
subcommittee[j] = strconv.FormatUint(uint64(index), 10)
|
||||
}
|
||||
subcommittees[i] = subcommittee
|
||||
}
|
||||
return subcommittees, nil
|
||||
}
|
||||
|
||||
func (s *Server) stateForSyncCommittee(ctx context.Context, w http.ResponseWriter, req *syncCommitteeStateRequest) (state.BeaconState, bool) {
|
||||
if req.epoch != nil {
|
||||
slot, err := slots.EpochStart(*req.epoch)
|
||||
if err != nil {
|
||||
http2.HandleError(w, fmt.Sprintf("Could not calculate start slot for epoch %d: %v", *req.epoch, err), http.StatusInternalServerError)
|
||||
return nil, false
|
||||
}
|
||||
st, err := s.Stater.State(ctx, []byte(strconv.FormatUint(uint64(slot), 10)))
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return nil, false
|
||||
}
|
||||
return st, true
|
||||
}
|
||||
st, err := s.Stater.State(ctx, req.stateId)
|
||||
if err != nil {
|
||||
shared.WriteStateFetchError(w, err)
|
||||
return nil, false
|
||||
}
|
||||
return st, true
|
||||
}
|
||||
649
beacon-chain/rpc/eth/beacon/handlers_state_test.go
Normal file
649
beacon-chain/rpc/eth/beacon/handlers_state_test.go
Normal file
@@ -0,0 +1,649 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/gorilla/mux"
|
||||
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
func TestGetStateRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fakeState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
stateRoot, err := fakeState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
db := dbTest.SetupDB(t)
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconStateRoot: stateRoot[:],
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/root", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetStateRoot(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetStateRootResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, hexutil.Encode(stateRoot[:]), resp.Data.Root)
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconStateRoot: stateRoot[:],
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/root", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetStateRoot(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetStateRootResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.DeepEqual(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
headerRoot, err := fakeState.LatestBlockHeader().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
chainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{
|
||||
headerRoot: true,
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconStateRoot: stateRoot[:],
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/root", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetStateRoot(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetStateRootResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.DeepEqual(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRandao(t *testing.T) {
|
||||
mixCurrent := bytesutil.ToBytes32([]byte("current"))
|
||||
mixOld := bytesutil.ToBytes32([]byte("old"))
|
||||
epochCurrent := primitives.Epoch(100000)
|
||||
epochOld := 100000 - params.BeaconConfig().EpochsPerHistoricalVector + 1
|
||||
|
||||
ctx := context.Background()
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
// Set slot to epoch 100000
|
||||
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch*100000))
|
||||
require.NoError(t, st.UpdateRandaoMixesAtIndex(uint64(epochCurrent%params.BeaconConfig().EpochsPerHistoricalVector), mixCurrent))
|
||||
require.NoError(t, st.UpdateRandaoMixesAtIndex(uint64(epochOld%params.BeaconConfig().EpochsPerHistoricalVector), mixOld))
|
||||
|
||||
headEpoch := primitives.Epoch(1)
|
||||
headSt, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headSt.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
headRandao := bytesutil.ToBytes32([]byte("head"))
|
||||
require.NoError(t, headSt.UpdateRandaoMixesAtIndex(uint64(headEpoch), headRandao))
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
t.Run("no epoch requested", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/randao", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetRandaoResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, hexutil.Encode(mixCurrent[:]), resp.Data.Randao)
|
||||
})
|
||||
t.Run("current epoch requested", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com//eth/v1/beacon/states/{state_id}/randao?epoch=%d", epochCurrent), nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetRandaoResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, hexutil.Encode(mixCurrent[:]), resp.Data.Randao)
|
||||
})
|
||||
t.Run("old epoch requested", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com//eth/v1/beacon/states/{state_id}/randao?epoch=%d", epochOld), nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetRandaoResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, hexutil.Encode(mixOld[:]), resp.Data.Randao)
|
||||
})
|
||||
t.Run("head state below `EpochsPerHistoricalVector`", func(t *testing.T) {
|
||||
s.Stater = &testutil.MockStater{
|
||||
BeaconState: headSt,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/randao", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetRandaoResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, hexutil.Encode(headRandao[:]), resp.Data.Randao)
|
||||
})
|
||||
t.Run("epoch too old", func(t *testing.T) {
|
||||
epochTooOld := primitives.Epoch(100000 - st.RandaoMixesLength())
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com//eth/v1/beacon/states/{state_id}/randao?epoch=%d", epochTooOld), nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
require.StringContains(t, "Epoch is out of range for the randao mixes of the state", e.Message)
|
||||
})
|
||||
t.Run("epoch in the future", func(t *testing.T) {
|
||||
futureEpoch := primitives.Epoch(100000 + 1)
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com//eth/v1/beacon/states/{state_id}/randao?epoch=%d", futureEpoch), nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
require.StringContains(t, "Epoch is out of range for the randao mixes of the state", e.Message)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
chainService := &chainMock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/randao", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetRandaoResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.DeepEqual(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
headerRoot, err := headSt.LatestBlockHeader().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
chainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{
|
||||
headerRoot: true,
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/randao", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetRandao(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetRandaoResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.DeepEqual(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_currentCommitteeIndicesFromState(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
wantedIndices := make([]string, len(wantedCommittee))
|
||||
for i := 0; i < len(wantedCommittee); i++ {
|
||||
wantedIndices[i] = strconv.FormatUint(uint64(i), 10)
|
||||
wantedCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
indices, committee, err := currentCommitteeIndicesFromState(st)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, wantedIndices, indices)
|
||||
require.DeepEqual(t, wantedCommittee, committee.Pubkeys)
|
||||
})
|
||||
t.Run("validator in committee not found in state", func(t *testing.T) {
|
||||
wantedCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48)
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
_, _, err := currentCommitteeIndicesFromState(st)
|
||||
require.ErrorContains(t, "index not found for pubkey", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_nextCommitteeIndicesFromState(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
wantedIndices := make([]string, len(wantedCommittee))
|
||||
for i := 0; i < len(wantedCommittee); i++ {
|
||||
wantedIndices[i] = strconv.FormatUint(uint64(i), 10)
|
||||
wantedCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
indices, committee, err := nextCommitteeIndicesFromState(st)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, wantedIndices, indices)
|
||||
require.DeepEqual(t, wantedCommittee, committee.Pubkeys)
|
||||
})
|
||||
t.Run("validator in committee not found in state", func(t *testing.T) {
|
||||
wantedCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48)
|
||||
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
_, _, err := nextCommitteeIndicesFromState(st)
|
||||
require.ErrorContains(t, "index not found for pubkey", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_extractSyncSubcommittees(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
for i := 0; i < len(syncCommittee); i++ {
|
||||
syncCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
|
||||
commSize := params.BeaconConfig().SyncCommitteeSize
|
||||
subCommSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
||||
wantedSubcommitteeValidators := make([][]string, 0)
|
||||
|
||||
for i := uint64(0); i < commSize; i += subCommSize {
|
||||
sub := make([]string, 0)
|
||||
start := i
|
||||
end := i + subCommSize
|
||||
if end > commSize {
|
||||
end = commSize
|
||||
}
|
||||
for j := start; j < end; j++ {
|
||||
sub = append(sub, strconv.FormatUint(j, 10))
|
||||
}
|
||||
wantedSubcommitteeValidators = append(wantedSubcommitteeValidators, sub)
|
||||
}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
committee, err := st.CurrentSyncCommittee()
|
||||
require.NoError(t, err)
|
||||
subcommittee, err := extractSyncSubcommittees(st, committee)
|
||||
require.NoError(t, err)
|
||||
for i, got := range subcommittee {
|
||||
want := wantedSubcommitteeValidators[i]
|
||||
require.DeepEqual(t, want, got)
|
||||
}
|
||||
})
|
||||
t.Run("validator in subcommittee not found in state", func(t *testing.T) {
|
||||
syncCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48)
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
committee, err := st.CurrentSyncCommittee()
|
||||
require.NoError(t, err)
|
||||
_, err = extractSyncSubcommittees(st, committee)
|
||||
require.ErrorContains(t, "index not found for pubkey", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSyncCommittees(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
for i := 0; i < len(syncCommittee); i++ {
|
||||
syncCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
stRoot, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
stSlot := st.Slot()
|
||||
chainService := &chainMock.ChainService{Slot: &stSlot}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/sync_committees", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": hexutil.Encode(stRoot[:])})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetSyncCommittees(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetSyncCommitteeResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
committeeVals := resp.Data.Validators
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(committeeVals)))
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ {
|
||||
assert.Equal(t, strconv.FormatUint(i, 10), committeeVals[i])
|
||||
}
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSubnetCount, uint64(len(resp.Data.ValidatorAggregates)))
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
||||
vStartIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount * i)
|
||||
vEndIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize/params.BeaconConfig().SyncCommitteeSubnetCount*(i+1) - 1)
|
||||
j := 0
|
||||
for vIndex := vStartIndex; vIndex <= vEndIndex; vIndex++ {
|
||||
assert.Equal(t, strconv.FormatUint(uint64(vIndex), 10), resp.Data.ValidatorAggregates[i][j])
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
stSlot := st.Slot()
|
||||
chainService := &chainMock.ChainService{Optimistic: true, Slot: &stSlot}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/sync_committees", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": hexutil.Encode(stRoot[:])})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetSyncCommittees(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetSyncCommitteeResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
headerRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
stSlot := st.Slot()
|
||||
chainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{
|
||||
headerRoot: true,
|
||||
},
|
||||
Slot: &stSlot,
|
||||
}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com//eth/v1/beacon/states/{state_id}/sync_committees", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": hexutil.Encode(stRoot[:])})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetSyncCommittees(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetSyncCommitteeResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
type futureSyncMockFetcher struct {
|
||||
BeaconState state.BeaconState
|
||||
BeaconStateRoot []byte
|
||||
}
|
||||
|
||||
func (m *futureSyncMockFetcher) State(_ context.Context, stateId []byte) (state.BeaconState, error) {
|
||||
expectedRequest := []byte(strconv.FormatUint(uint64(0), 10))
|
||||
res := bytes.Compare(stateId, expectedRequest)
|
||||
if res != 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"requested wrong epoch for next sync committee (expected %#x, received %#x)",
|
||||
expectedRequest,
|
||||
stateId,
|
||||
)
|
||||
}
|
||||
return m.BeaconState, nil
|
||||
}
|
||||
func (m *futureSyncMockFetcher) StateRoot(context.Context, []byte) ([]byte, error) {
|
||||
return m.BeaconStateRoot, nil
|
||||
}
|
||||
|
||||
func (m *futureSyncMockFetcher) StateBySlot(context.Context, primitives.Slot) (state.BeaconState, error) {
|
||||
return m.BeaconState, nil
|
||||
}
|
||||
|
||||
func TestGetSyncCommittees_Future(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
for i := 0; i < len(syncCommittee); i++ {
|
||||
syncCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &futureSyncMockFetcher{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
epoch := 2 * params.BeaconConfig().EpochsPerSyncCommitteePeriod
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com//eth/v1/beacon/states/{state_id}/sync_committees?epoch=%d", epoch), nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetSyncCommittees(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.StringContains(t, "Could not fetch sync committee too far in the future", e.Message)
|
||||
|
||||
epoch = 2*params.BeaconConfig().EpochsPerSyncCommitteePeriod - 1
|
||||
request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com//eth/v1/beacon/states/{state_id}/sync_committees?epoch=%d", epoch), nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
|
||||
writer = httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetSyncCommittees(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetSyncCommitteeResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
committeeVals := resp.Data.Validators
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(committeeVals)))
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ {
|
||||
assert.Equal(t, strconv.FormatUint(i, 10), committeeVals[i])
|
||||
}
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSubnetCount, uint64(len(resp.Data.ValidatorAggregates)))
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
||||
vStartIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount * i)
|
||||
vEndIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize/params.BeaconConfig().SyncCommitteeSubnetCount*(i+1) - 1)
|
||||
j := 0
|
||||
for vIndex := vStartIndex; vIndex <= vEndIndex; vIndex++ {
|
||||
assert.Equal(t, strconv.FormatUint(uint64(vIndex), 10), resp.Data.ValidatorAggregates[i][j])
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
rpctesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
|
||||
@@ -1999,10 +2000,18 @@ func TestGetFinalityCheckpoints(t *testing.T) {
|
||||
fakeState, err := util.NewBeaconState(fillCheckpoints)
|
||||
require.NoError(t, err)
|
||||
|
||||
stateProvider := func(ctx context.Context, stateId []byte) (state.BeaconState, error) {
|
||||
if bytes.Equal(stateId, []byte("foobar")) {
|
||||
return nil, &lookup.StateNotFoundError{}
|
||||
}
|
||||
return fakeState, nil
|
||||
}
|
||||
|
||||
chainService := &chainMock.ChainService{}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: fakeState,
|
||||
BeaconState: fakeState,
|
||||
StateProviderFunc: stateProvider,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
@@ -2039,6 +2048,19 @@ func TestGetFinalityCheckpoints(t *testing.T) {
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.StringContains(t, "state_id is required in URL params", e.Message)
|
||||
})
|
||||
t.Run("state not found", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "/eth/v1/beacon/states/{state_id}/finality_checkpoints", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"state_id": "foobar"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetFinalityCheckpoints(writer, request)
|
||||
assert.Equal(t, http.StatusNotFound, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusNotFound, e.Code)
|
||||
assert.StringContains(t, "State not found", e.Message)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
chainService := &chainMock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
|
||||
@@ -2,20 +2,12 @@ package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/api/grpc"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/operation"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/features"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -135,127 +127,3 @@ func (bs *Server) SubmitProposerSlashing(ctx context.Context, req *ethpbv1.Propo
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
// SubmitSignedBLSToExecutionChanges submits said object to the node's pool
|
||||
// if it passes validation the node must broadcast it to the network.
|
||||
func (bs *Server) SubmitSignedBLSToExecutionChanges(ctx context.Context, req *ethpbv2.SubmitBLSToExecutionChangesRequest) (*emptypb.Empty, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.SubmitSignedBLSToExecutionChanges")
|
||||
defer span.End()
|
||||
st, err := bs.ChainInfoFetcher.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get head state: %v", err)
|
||||
}
|
||||
var failures []*helpers.SingleIndexedVerificationFailure
|
||||
var toBroadcast []*ethpbalpha.SignedBLSToExecutionChange
|
||||
|
||||
for i, change := range req.GetChanges() {
|
||||
alphaChange := migration.V2SignedBLSToExecutionChangeToV1Alpha1(change)
|
||||
_, err = blocks.ValidateBLSToExecutionChange(st, alphaChange)
|
||||
if err != nil {
|
||||
failures = append(failures, &helpers.SingleIndexedVerificationFailure{
|
||||
Index: i,
|
||||
Message: "Could not validate SignedBLSToExecutionChange: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
if err := blocks.VerifyBLSChangeSignature(st, change); err != nil {
|
||||
failures = append(failures, &helpers.SingleIndexedVerificationFailure{
|
||||
Index: i,
|
||||
Message: "Could not validate signature: " + err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
bs.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
Type: operation.BLSToExecutionChangeReceived,
|
||||
Data: &operation.BLSToExecutionChangeReceivedData{
|
||||
Change: alphaChange,
|
||||
},
|
||||
})
|
||||
bs.BLSChangesPool.InsertBLSToExecChange(alphaChange)
|
||||
if st.Version() >= version.Capella {
|
||||
toBroadcast = append(toBroadcast, alphaChange)
|
||||
}
|
||||
}
|
||||
go bs.broadcastBLSChanges(ctx, toBroadcast)
|
||||
if len(failures) > 0 {
|
||||
failuresContainer := &helpers.IndexedVerificationFailure{Failures: failures}
|
||||
err := grpc.AppendCustomErrorHeader(ctx, failuresContainer)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.InvalidArgument,
|
||||
"One or more BLSToExecutionChange failed validation. Could not prepare BLSToExecutionChange failure information: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return nil, status.Errorf(codes.InvalidArgument, "One or more BLSToExecutionChange failed validation")
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
// broadcastBLSBatch broadcasts the first `broadcastBLSChangesRateLimit` messages from the slice pointed to by ptr.
|
||||
// It validates the messages again because they could have been invalidated by being included in blocks since the last validation.
|
||||
// It removes the messages from the slice and modifies it in place.
|
||||
func (bs *Server) broadcastBLSBatch(ctx context.Context, ptr *[]*ethpbalpha.SignedBLSToExecutionChange) {
|
||||
limit := broadcastBLSChangesRateLimit
|
||||
if len(*ptr) < broadcastBLSChangesRateLimit {
|
||||
limit = len(*ptr)
|
||||
}
|
||||
st, err := bs.ChainInfoFetcher.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not get head state")
|
||||
return
|
||||
}
|
||||
for _, ch := range (*ptr)[:limit] {
|
||||
if ch != nil {
|
||||
_, err := blocks.ValidateBLSToExecutionChange(st, ch)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not validate BLS to execution change")
|
||||
continue
|
||||
}
|
||||
if err := bs.Broadcaster.Broadcast(ctx, ch); err != nil {
|
||||
log.WithError(err).Error("could not broadcast BLS to execution changes.")
|
||||
}
|
||||
}
|
||||
}
|
||||
*ptr = (*ptr)[limit:]
|
||||
}
|
||||
|
||||
func (bs *Server) broadcastBLSChanges(ctx context.Context, changes []*ethpbalpha.SignedBLSToExecutionChange) {
|
||||
bs.broadcastBLSBatch(ctx, &changes)
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
bs.broadcastBLSBatch(ctx, &changes)
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListBLSToExecutionChanges retrieves BLS to execution changes known by the node but not necessarily incorporated into any block
|
||||
func (bs *Server) ListBLSToExecutionChanges(ctx context.Context, _ *emptypb.Empty) (*ethpbv2.BLSToExecutionChangesPoolResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.ListBLSToExecutionChanges")
|
||||
defer span.End()
|
||||
|
||||
sourceChanges, err := bs.BLSChangesPool.PendingBLSToExecChanges()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get BLS to execution changes: %v", err)
|
||||
}
|
||||
|
||||
changes := make([]*ethpbv2.SignedBLSToExecutionChange, len(sourceChanges))
|
||||
for i, ch := range sourceChanges {
|
||||
changes[i] = migration.V1Alpha1SignedBLSToExecChangeToV2(ch)
|
||||
}
|
||||
|
||||
return ðpbv2.BLSToExecutionChangesPoolResponse{
|
||||
Data: changes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -3,27 +3,17 @@ package beacon
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
blockchainmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
|
||||
prysmtime "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/attestations"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/blstoexec"
|
||||
blstoexecmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/blstoexec/mock"
|
||||
slashingsmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/slashings/mock"
|
||||
p2pMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/testing"
|
||||
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/crypto/bls/common"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
ethpbv1alpha1 "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
@@ -566,342 +556,3 @@ func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) {
|
||||
require.ErrorContains(t, "Invalid proposer slashing", err)
|
||||
assert.Equal(t, false, broadcaster.BroadcastCalled)
|
||||
}
|
||||
|
||||
func TestListBLSToExecutionChanges(t *testing.T) {
|
||||
change1 := ðpbv1alpha1.SignedBLSToExecutionChange{
|
||||
Message: ðpbv1alpha1.BLSToExecutionChange{
|
||||
ValidatorIndex: 1,
|
||||
FromBlsPubkey: bytesutil.PadTo([]byte("pubkey1"), 48),
|
||||
ToExecutionAddress: bytesutil.PadTo([]byte("address1"), 20),
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature1"), 96),
|
||||
}
|
||||
change2 := ðpbv1alpha1.SignedBLSToExecutionChange{
|
||||
Message: ðpbv1alpha1.BLSToExecutionChange{
|
||||
ValidatorIndex: 2,
|
||||
FromBlsPubkey: bytesutil.PadTo([]byte("pubkey2"), 48),
|
||||
ToExecutionAddress: bytesutil.PadTo([]byte("address2"), 20),
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature2"), 96),
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
BLSChangesPool: &blstoexecmock.PoolMock{Changes: []*ethpbv1alpha1.SignedBLSToExecutionChange{change1, change2}},
|
||||
}
|
||||
|
||||
resp, err := s.ListBLSToExecutionChanges(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(resp.Data))
|
||||
assert.DeepEqual(t, migration.V1Alpha1SignedBLSToExecChangeToV2(change1), resp.Data[0])
|
||||
assert.DeepEqual(t, migration.V1Alpha1SignedBLSToExecChangeToV2(change2), resp.Data[1])
|
||||
}
|
||||
|
||||
func TestSubmitSignedBLSToExecutionChanges_Ok(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
c := params.BeaconConfig().Copy()
|
||||
// Required for correct committee size calculation.
|
||||
c.CapellaForkEpoch = c.BellatrixForkEpoch.Add(2)
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
spb := ðpbv1alpha1.BeaconStateCapella{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
},
|
||||
}
|
||||
numValidators := 10
|
||||
validators := make([]*ethpbv1alpha1.Validator, numValidators)
|
||||
blsChanges := make([]*ethpbv2.BLSToExecutionChange, numValidators)
|
||||
spb.Balances = make([]uint64, numValidators)
|
||||
privKeys := make([]common.SecretKey, numValidators)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
executionAddress := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13}
|
||||
|
||||
for i := range validators {
|
||||
v := ðpbv1alpha1.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = params.BeaconConfig().FarFutureEpoch
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[i] = priv
|
||||
pubkey := priv.PublicKey().Marshal()
|
||||
|
||||
message := ðpbv2.BLSToExecutionChange{
|
||||
ToExecutionAddress: executionAddress,
|
||||
ValidatorIndex: primitives.ValidatorIndex(i),
|
||||
FromBlsPubkey: pubkey,
|
||||
}
|
||||
|
||||
hashFn := ssz.NewHasherFunc(hash.CustomSHA256Hasher())
|
||||
digest := hashFn.Hash(pubkey)
|
||||
digest[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
|
||||
copy(v.WithdrawalCredentials, digest[:])
|
||||
validators[i] = v
|
||||
blsChanges[i] = message
|
||||
}
|
||||
spb.Validators = validators
|
||||
slot, err := slots.EpochStart(params.BeaconConfig().CapellaForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spb.Slot = slot
|
||||
st, err := state_native.InitializeFromProtoCapella(spb)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges := make([]*ethpbv2.SignedBLSToExecutionChange, numValidators)
|
||||
for i, message := range blsChanges {
|
||||
signature, err := signing.ComputeDomainAndSign(st, prysmtime.CurrentEpoch(st), message, params.BeaconConfig().DomainBLSToExecutionChange, privKeys[i])
|
||||
require.NoError(t, err)
|
||||
|
||||
signed := ðpbv2.SignedBLSToExecutionChange{
|
||||
Message: message,
|
||||
Signature: signature,
|
||||
}
|
||||
signedChanges[i] = signed
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainService := &blockchainmock.ChainService{State: st}
|
||||
s := &Server{
|
||||
HeadFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
AttestationsPool: attestations.NewPool(),
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: &blockchainmock.MockOperationNotifier{},
|
||||
BLSChangesPool: blstoexec.NewPool(),
|
||||
}
|
||||
|
||||
_, err = s.SubmitSignedBLSToExecutionChanges(ctx, ðpbv2.SubmitBLSToExecutionChangesRequest{
|
||||
Changes: signedChanges,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
time.Sleep(100 * time.Millisecond) // Delay to let the routine start
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled)
|
||||
assert.Equal(t, numValidators, len(broadcaster.BroadcastMessages))
|
||||
|
||||
poolChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
require.Equal(t, len(poolChanges), len(signedChanges))
|
||||
require.NoError(t, err)
|
||||
for i, v1alphaChange := range poolChanges {
|
||||
v2Change := migration.V1Alpha1SignedBLSToExecChangeToV2(v1alphaChange)
|
||||
require.DeepEqual(t, v2Change, signedChanges[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitSignedBLSToExecutionChanges_Bellatrix(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
c := params.BeaconConfig().Copy()
|
||||
// Required for correct committee size calculation.
|
||||
c.CapellaForkEpoch = c.BellatrixForkEpoch.Add(2)
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
spb := ðpbv1alpha1.BeaconStateBellatrix{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().BellatrixForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().AltairForkVersion,
|
||||
Epoch: params.BeaconConfig().BellatrixForkEpoch,
|
||||
},
|
||||
}
|
||||
numValidators := 10
|
||||
validators := make([]*ethpbv1alpha1.Validator, numValidators)
|
||||
blsChanges := make([]*ethpbv2.BLSToExecutionChange, numValidators)
|
||||
spb.Balances = make([]uint64, numValidators)
|
||||
privKeys := make([]common.SecretKey, numValidators)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
executionAddress := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13}
|
||||
|
||||
for i := range validators {
|
||||
v := ðpbv1alpha1.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = params.BeaconConfig().FarFutureEpoch
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[i] = priv
|
||||
pubkey := priv.PublicKey().Marshal()
|
||||
|
||||
message := ðpbv2.BLSToExecutionChange{
|
||||
ToExecutionAddress: executionAddress,
|
||||
ValidatorIndex: primitives.ValidatorIndex(i),
|
||||
FromBlsPubkey: pubkey,
|
||||
}
|
||||
|
||||
hashFn := ssz.NewHasherFunc(hash.CustomSHA256Hasher())
|
||||
digest := hashFn.Hash(pubkey)
|
||||
digest[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
|
||||
copy(v.WithdrawalCredentials, digest[:])
|
||||
validators[i] = v
|
||||
blsChanges[i] = message
|
||||
}
|
||||
spb.Validators = validators
|
||||
slot, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spb.Slot = slot
|
||||
st, err := state_native.InitializeFromProtoBellatrix(spb)
|
||||
require.NoError(t, err)
|
||||
|
||||
spc := ðpbv1alpha1.BeaconStateCapella{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
},
|
||||
}
|
||||
slot, err = slots.EpochStart(params.BeaconConfig().CapellaForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spc.Slot = slot
|
||||
|
||||
stc, err := state_native.InitializeFromProtoCapella(spc)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges := make([]*ethpbv2.SignedBLSToExecutionChange, numValidators)
|
||||
for i, message := range blsChanges {
|
||||
signature, err := signing.ComputeDomainAndSign(stc, prysmtime.CurrentEpoch(stc), message, params.BeaconConfig().DomainBLSToExecutionChange, privKeys[i])
|
||||
require.NoError(t, err)
|
||||
|
||||
signed := ðpbv2.SignedBLSToExecutionChange{
|
||||
Message: message,
|
||||
Signature: signature,
|
||||
}
|
||||
signedChanges[i] = signed
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainService := &blockchainmock.ChainService{State: st}
|
||||
s := &Server{
|
||||
HeadFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
AttestationsPool: attestations.NewPool(),
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: &blockchainmock.MockOperationNotifier{},
|
||||
BLSChangesPool: blstoexec.NewPool(),
|
||||
}
|
||||
|
||||
_, err = s.SubmitSignedBLSToExecutionChanges(ctx, ðpbv2.SubmitBLSToExecutionChangesRequest{
|
||||
Changes: signedChanges,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that we didn't broadcast the messages but did in fact fill in
|
||||
// the pool
|
||||
assert.Equal(t, false, broadcaster.BroadcastCalled)
|
||||
|
||||
poolChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
require.Equal(t, len(poolChanges), len(signedChanges))
|
||||
require.NoError(t, err)
|
||||
for i, v1alphaChange := range poolChanges {
|
||||
v2Change := migration.V1Alpha1SignedBLSToExecChangeToV2(v1alphaChange)
|
||||
require.DeepEqual(t, v2Change, signedChanges[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitSignedBLSToExecutionChanges_Failures(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
c := params.BeaconConfig().Copy()
|
||||
// Required for correct committee size calculation.
|
||||
c.CapellaForkEpoch = c.BellatrixForkEpoch.Add(2)
|
||||
params.OverrideBeaconConfig(c)
|
||||
|
||||
spb := ðpbv1alpha1.BeaconStateCapella{
|
||||
Fork: ðpbv1alpha1.Fork{
|
||||
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
},
|
||||
}
|
||||
numValidators := 10
|
||||
validators := make([]*ethpbv1alpha1.Validator, numValidators)
|
||||
blsChanges := make([]*ethpbv2.BLSToExecutionChange, numValidators)
|
||||
spb.Balances = make([]uint64, numValidators)
|
||||
privKeys := make([]common.SecretKey, numValidators)
|
||||
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
|
||||
executionAddress := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13}
|
||||
|
||||
for i := range validators {
|
||||
v := ðpbv1alpha1.Validator{}
|
||||
v.EffectiveBalance = maxEffectiveBalance
|
||||
v.WithdrawableEpoch = params.BeaconConfig().FarFutureEpoch
|
||||
v.WithdrawalCredentials = make([]byte, 32)
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
privKeys[i] = priv
|
||||
pubkey := priv.PublicKey().Marshal()
|
||||
|
||||
message := ðpbv2.BLSToExecutionChange{
|
||||
ToExecutionAddress: executionAddress,
|
||||
ValidatorIndex: primitives.ValidatorIndex(i),
|
||||
FromBlsPubkey: pubkey,
|
||||
}
|
||||
|
||||
hashFn := ssz.NewHasherFunc(hash.CustomSHA256Hasher())
|
||||
digest := hashFn.Hash(pubkey)
|
||||
digest[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
|
||||
copy(v.WithdrawalCredentials, digest[:])
|
||||
validators[i] = v
|
||||
blsChanges[i] = message
|
||||
}
|
||||
spb.Validators = validators
|
||||
slot, err := slots.EpochStart(params.BeaconConfig().CapellaForkEpoch)
|
||||
require.NoError(t, err)
|
||||
spb.Slot = slot
|
||||
st, err := state_native.InitializeFromProtoCapella(spb)
|
||||
require.NoError(t, err)
|
||||
|
||||
signedChanges := make([]*ethpbv2.SignedBLSToExecutionChange, numValidators)
|
||||
for i, message := range blsChanges {
|
||||
signature, err := signing.ComputeDomainAndSign(st, prysmtime.CurrentEpoch(st), message, params.BeaconConfig().DomainBLSToExecutionChange, privKeys[i])
|
||||
require.NoError(t, err)
|
||||
|
||||
signed := ðpbv2.SignedBLSToExecutionChange{
|
||||
Message: message,
|
||||
Signature: signature,
|
||||
}
|
||||
signedChanges[i] = signed
|
||||
}
|
||||
signedChanges[1].Signature[0] = 0x00
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainService := &blockchainmock.ChainService{State: st}
|
||||
s := &Server{
|
||||
HeadFetcher: chainService,
|
||||
ChainInfoFetcher: chainService,
|
||||
AttestationsPool: attestations.NewPool(),
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: &blockchainmock.MockOperationNotifier{},
|
||||
BLSChangesPool: blstoexec.NewPool(),
|
||||
}
|
||||
|
||||
_, err = s.SubmitSignedBLSToExecutionChanges(ctx, ðpbv2.SubmitBLSToExecutionChangesRequest{
|
||||
Changes: signedChanges,
|
||||
})
|
||||
time.Sleep(10 * time.Millisecond) // Delay to allow the routine to start
|
||||
require.ErrorContains(t, "One or more BLSToExecutionChange failed validation", err)
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled)
|
||||
assert.Equal(t, numValidators, len(broadcaster.BroadcastMessages)+1)
|
||||
|
||||
poolChanges, err := s.BLSChangesPool.PendingBLSToExecChanges()
|
||||
require.Equal(t, len(poolChanges)+1, len(signedChanges))
|
||||
require.NoError(t, err)
|
||||
|
||||
v2Change := migration.V1Alpha1SignedBLSToExecChangeToV2(poolChanges[0])
|
||||
require.DeepEqual(t, v2Change, signedChanges[0])
|
||||
for i := 2; i < numValidators; i++ {
|
||||
v2Change := migration.V1Alpha1SignedBLSToExecChangeToV2(poolChanges[i-1])
|
||||
require.DeepEqual(t, v2Change, signedChanges[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
eth2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type stateRequest struct {
|
||||
epoch *primitives.Epoch
|
||||
stateId []byte
|
||||
}
|
||||
|
||||
// GetStateRoot calculates HashTreeRoot for state with given 'stateId'. If stateId is root, same value will be returned.
|
||||
func (bs *Server) GetStateRoot(ctx context.Context, req *ethpb.StateRequest) (*ethpb.StateRootResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetStateRoot")
|
||||
defer span.End()
|
||||
|
||||
stateRoot, err := bs.Stater.StateRoot(ctx, req.StateId)
|
||||
if err != nil {
|
||||
if rootNotFoundErr, ok := err.(*lookup.StateRootNotFoundError); ok {
|
||||
return nil, status.Errorf(codes.NotFound, "State root not found: %v", rootNotFoundErr)
|
||||
} else if parseErr, ok := err.(*lookup.StateIdParseError); ok {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid state ID: %v", parseErr)
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state root: %v", err)
|
||||
}
|
||||
st, err := bs.Stater.State(ctx, req.StateId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get state: %v", err)
|
||||
}
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.BeaconDB)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not calculate root of latest block header")
|
||||
}
|
||||
isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
return ðpb.StateRootResponse{
|
||||
Data: ðpb.StateRootResponse_StateRoot{
|
||||
Root: stateRoot,
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetRandao fetches the RANDAO mix for the requested epoch from the state identified by state_id.
|
||||
// If an epoch is not specified then the RANDAO mix for the state's current epoch will be returned.
|
||||
// By adjusting the state_id parameter you can query for any historic value of the RANDAO mix.
|
||||
// Ordinarily states from the same epoch will mutate the RANDAO mix for that epoch as blocks are applied.
|
||||
func (bs *Server) GetRandao(ctx context.Context, req *eth2.RandaoRequest) (*eth2.RandaoResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.GetRandao")
|
||||
defer span.End()
|
||||
|
||||
st, err := bs.Stater.State(ctx, req.StateId)
|
||||
if err != nil {
|
||||
return nil, helpers.PrepareStateFetchGRPCError(err)
|
||||
}
|
||||
|
||||
stEpoch := slots.ToEpoch(st.Slot())
|
||||
epoch := stEpoch
|
||||
if req.Epoch != nil {
|
||||
epoch = *req.Epoch
|
||||
}
|
||||
|
||||
// future epochs and epochs too far back are not supported.
|
||||
randaoEpochLowerBound := uint64(0)
|
||||
// Lower bound should not underflow.
|
||||
if uint64(stEpoch) > uint64(st.RandaoMixesLength()) {
|
||||
randaoEpochLowerBound = uint64(stEpoch) - uint64(st.RandaoMixesLength())
|
||||
}
|
||||
if epoch > stEpoch || uint64(epoch) < randaoEpochLowerBound+1 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Epoch is out of range for the randao mixes of the state")
|
||||
}
|
||||
idx := epoch % params.BeaconConfig().EpochsPerHistoricalVector
|
||||
randao, err := st.RandaoMixAtIndex(uint64(idx))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get randao mix at index %d", idx)
|
||||
}
|
||||
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.BeaconDB)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not calculate root of latest block header")
|
||||
}
|
||||
isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
return ð2.RandaoResponse{
|
||||
Data: ð2.RandaoResponse_Randao{Randao: randao},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bs *Server) stateFromRequest(ctx context.Context, req *stateRequest) (state.BeaconState, error) {
|
||||
if req.epoch != nil {
|
||||
slot, err := slots.EpochStart(*req.epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Could not calculate start slot for epoch %d: %v",
|
||||
*req.epoch,
|
||||
err,
|
||||
)
|
||||
}
|
||||
st, err := bs.Stater.State(ctx, []byte(strconv.FormatUint(uint64(slot), 10)))
|
||||
if err != nil {
|
||||
return nil, helpers.PrepareStateFetchGRPCError(err)
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
var err error
|
||||
st, err := bs.Stater.State(ctx, req.stateId)
|
||||
if err != nil {
|
||||
return nil, helpers.PrepareStateFetchGRPCError(err)
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
eth2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
func TestGetStateRoot(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fakeState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
stateRoot, err := fakeState.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
chainService := &chainMock.ChainService{}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconStateRoot: stateRoot[:],
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
resp, err := server.GetStateRoot(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, stateRoot[:], resp.Data.Root)
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
chainService := &chainMock.ChainService{Optimistic: true}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconStateRoot: stateRoot[:],
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
resp, err := server.GetStateRoot(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
headerRoot, err := fakeState.LatestBlockHeader().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
chainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{
|
||||
headerRoot: true,
|
||||
},
|
||||
}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconStateRoot: stateRoot[:],
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
resp, err := server.GetStateRoot(context.Background(), ð.StateRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRandao(t *testing.T) {
|
||||
mixCurrent := bytesutil.ToBytes32([]byte("current"))
|
||||
mixOld := bytesutil.ToBytes32([]byte("old"))
|
||||
epochCurrent := primitives.Epoch(100000)
|
||||
epochOld := 100000 - params.BeaconConfig().EpochsPerHistoricalVector + 1
|
||||
|
||||
ctx := context.Background()
|
||||
st, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
// Set slot to epoch 100000
|
||||
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch*100000))
|
||||
require.NoError(t, st.UpdateRandaoMixesAtIndex(uint64(epochCurrent%params.BeaconConfig().EpochsPerHistoricalVector), mixCurrent))
|
||||
require.NoError(t, st.UpdateRandaoMixesAtIndex(uint64(epochOld%params.BeaconConfig().EpochsPerHistoricalVector), mixOld))
|
||||
|
||||
headEpoch := primitives.Epoch(1)
|
||||
headSt, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, headSt.SetSlot(params.BeaconConfig().SlotsPerEpoch))
|
||||
headRandao := bytesutil.ToBytes32([]byte("head"))
|
||||
require.NoError(t, headSt.UpdateRandaoMixesAtIndex(uint64(headEpoch), headRandao))
|
||||
|
||||
db := dbTest.SetupDB(t)
|
||||
chainService := &chainMock.ChainService{}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
|
||||
t.Run("no epoch requested", func(t *testing.T) {
|
||||
resp, err := server.GetRandao(ctx, ð2.RandaoRequest{StateId: []byte("head")})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, mixCurrent[:], resp.Data.Randao)
|
||||
})
|
||||
t.Run("current epoch requested", func(t *testing.T) {
|
||||
resp, err := server.GetRandao(ctx, ð2.RandaoRequest{StateId: []byte("head"), Epoch: &epochCurrent})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, mixCurrent[:], resp.Data.Randao)
|
||||
})
|
||||
t.Run("old epoch requested", func(t *testing.T) {
|
||||
resp, err := server.GetRandao(ctx, ð2.RandaoRequest{StateId: []byte("head"), Epoch: &epochOld})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, mixOld[:], resp.Data.Randao)
|
||||
})
|
||||
t.Run("head state below `EpochsPerHistoricalVector`", func(t *testing.T) {
|
||||
server.Stater = &testutil.MockStater{
|
||||
BeaconState: headSt,
|
||||
}
|
||||
resp, err := server.GetRandao(ctx, ð2.RandaoRequest{StateId: []byte("head")})
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, headRandao[:], resp.Data.Randao)
|
||||
})
|
||||
t.Run("epoch too old", func(t *testing.T) {
|
||||
epochTooOld := primitives.Epoch(100000 - st.RandaoMixesLength())
|
||||
_, err := server.GetRandao(ctx, ð2.RandaoRequest{StateId: make([]byte, 0), Epoch: &epochTooOld})
|
||||
require.ErrorContains(t, "Epoch is out of range for the randao mixes of the state", err)
|
||||
})
|
||||
t.Run("epoch in the future", func(t *testing.T) {
|
||||
futureEpoch := primitives.Epoch(100000 + 1)
|
||||
_, err := server.GetRandao(ctx, ð2.RandaoRequest{StateId: make([]byte, 0), Epoch: &futureEpoch})
|
||||
require.ErrorContains(t, "Epoch is out of range for the randao mixes of the state", err)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
chainService := &chainMock.ChainService{Optimistic: true}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
resp, err := server.GetRandao(context.Background(), ð2.RandaoRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
headerRoot, err := headSt.LatestBlockHeader().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
chainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{
|
||||
headerRoot: true,
|
||||
},
|
||||
}
|
||||
server := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
resp, err := server.GetRandao(context.Background(), ð2.RandaoRequest{
|
||||
StateId: []byte("head"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
assert.DeepEqual(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
@@ -121,3 +121,38 @@ type ValidatorBalance struct {
|
||||
Index string `json:"index"`
|
||||
Balance string `json:"balance"`
|
||||
}
|
||||
|
||||
type GetStateRootResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *StateRoot `json:"data"`
|
||||
}
|
||||
|
||||
type StateRoot struct {
|
||||
Root string `json:"root"`
|
||||
}
|
||||
|
||||
type GetRandaoResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *Randao `json:"data"`
|
||||
}
|
||||
|
||||
type Randao struct {
|
||||
Randao string `json:"randao"`
|
||||
}
|
||||
|
||||
type GetSyncCommitteeResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *SyncCommitteeValidators `json:"data"`
|
||||
}
|
||||
|
||||
type SyncCommitteeValidators struct {
|
||||
Validators []string `json:"validators"`
|
||||
ValidatorAggregates [][]string `json:"validator_aggregates"`
|
||||
}
|
||||
|
||||
type BLSToExecutionChangesPoolResponse struct {
|
||||
Data []*shared.SignedBLSToExecutionChange `json:"data"`
|
||||
}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ListSyncCommittees retrieves the sync committees for the given epoch.
|
||||
// If the epoch is not passed in, then the sync committees for the epoch of the state will be obtained.
|
||||
func (bs *Server) ListSyncCommittees(ctx context.Context, req *ethpbv2.StateSyncCommitteesRequest) (*ethpbv2.StateSyncCommitteesResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "beacon.ListSyncCommittees")
|
||||
defer span.End()
|
||||
|
||||
currentSlot := bs.GenesisTimeFetcher.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
currentPeriodStartEpoch, err := slots.SyncCommitteePeriodStartEpoch(currentEpoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Could not calculate start period for slot %d: %v",
|
||||
currentSlot,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
requestNextCommittee := false
|
||||
if req.Epoch != nil {
|
||||
reqPeriodStartEpoch, err := slots.SyncCommitteePeriodStartEpoch(*req.Epoch)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Could not calculate start period for epoch %d: %v",
|
||||
*req.Epoch,
|
||||
err,
|
||||
)
|
||||
}
|
||||
if reqPeriodStartEpoch > currentPeriodStartEpoch+params.BeaconConfig().EpochsPerSyncCommitteePeriod {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Could not fetch sync committee too far in the future. Requested epoch: %d, current epoch: %d",
|
||||
*req.Epoch, currentEpoch,
|
||||
)
|
||||
}
|
||||
if reqPeriodStartEpoch > currentPeriodStartEpoch {
|
||||
requestNextCommittee = true
|
||||
req.Epoch = ¤tPeriodStartEpoch
|
||||
}
|
||||
}
|
||||
|
||||
st, err := bs.stateFromRequest(ctx, &stateRequest{
|
||||
epoch: req.Epoch,
|
||||
stateId: req.StateId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not fetch beacon state using request: %v", err)
|
||||
}
|
||||
|
||||
var committeeIndices []primitives.ValidatorIndex
|
||||
var committee *ethpbalpha.SyncCommittee
|
||||
if requestNextCommittee {
|
||||
// Get the next sync committee and sync committee indices from the state.
|
||||
committeeIndices, committee, err = nextCommitteeIndicesFromState(st)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get next sync committee indices: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Get the current sync committee and sync committee indices from the state.
|
||||
committeeIndices, committee, err = currentCommitteeIndicesFromState(st)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get current sync committee indices: %v", err)
|
||||
}
|
||||
}
|
||||
subcommittees, err := extractSyncSubcommittees(st, committee)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not extract sync subcommittees: %v", err)
|
||||
}
|
||||
|
||||
isOptimistic, err := helpers.IsOptimistic(ctx, req.StateId, bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.BeaconDB)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not check if slot's block is optimistic: %v", err)
|
||||
}
|
||||
|
||||
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not calculate root of latest block header")
|
||||
}
|
||||
isFinalized := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
|
||||
|
||||
return ðpbv2.StateSyncCommitteesResponse{
|
||||
Data: ðpbv2.SyncCommitteeValidators{
|
||||
Validators: committeeIndices,
|
||||
ValidatorAggregates: subcommittees,
|
||||
},
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: isFinalized,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func committeeIndicesFromState(st state.BeaconState, committee *ethpbalpha.SyncCommittee) ([]primitives.ValidatorIndex, *ethpbalpha.SyncCommittee, error) {
|
||||
committeeIndices := make([]primitives.ValidatorIndex, len(committee.Pubkeys))
|
||||
for i, key := range committee.Pubkeys {
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(key))
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"validator index not found for pubkey %#x",
|
||||
bytesutil.Trunc(key),
|
||||
)
|
||||
}
|
||||
committeeIndices[i] = index
|
||||
}
|
||||
return committeeIndices, committee, nil
|
||||
}
|
||||
|
||||
func currentCommitteeIndicesFromState(st state.BeaconState) ([]primitives.ValidatorIndex, *ethpbalpha.SyncCommittee, error) {
|
||||
committee, err := st.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"could not get sync committee: %v", err,
|
||||
)
|
||||
}
|
||||
|
||||
return committeeIndicesFromState(st, committee)
|
||||
}
|
||||
|
||||
func nextCommitteeIndicesFromState(st state.BeaconState) ([]primitives.ValidatorIndex, *ethpbalpha.SyncCommittee, error) {
|
||||
committee, err := st.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"could not get sync committee: %v", err,
|
||||
)
|
||||
}
|
||||
|
||||
return committeeIndicesFromState(st, committee)
|
||||
}
|
||||
|
||||
func extractSyncSubcommittees(st state.BeaconState, committee *ethpbalpha.SyncCommittee) ([]*ethpbv2.SyncSubcommitteeValidators, error) {
|
||||
subcommitteeCount := params.BeaconConfig().SyncCommitteeSubnetCount
|
||||
subcommittees := make([]*ethpbv2.SyncSubcommitteeValidators, subcommitteeCount)
|
||||
for i := uint64(0); i < subcommitteeCount; i++ {
|
||||
pubkeys, err := altair.SyncSubCommitteePubkeys(committee, primitives.CommitteeIndex(i))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to get subcommittee pubkeys: %v", err,
|
||||
)
|
||||
}
|
||||
subcommittee := ðpbv2.SyncSubcommitteeValidators{Validators: make([]primitives.ValidatorIndex, len(pubkeys))}
|
||||
for j, key := range pubkeys {
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(key))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"validator index not found for pubkey %#x",
|
||||
bytesutil.Trunc(key),
|
||||
)
|
||||
}
|
||||
subcommittee.Validators[j] = index
|
||||
}
|
||||
subcommittees[i] = subcommittee
|
||||
}
|
||||
return subcommittees, nil
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
package beacon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func Test_currentCommitteeIndicesFromState(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
wantedIndices := make([]primitives.ValidatorIndex, len(wantedCommittee))
|
||||
for i := 0; i < len(wantedCommittee); i++ {
|
||||
wantedIndices[i] = primitives.ValidatorIndex(i)
|
||||
wantedCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
indices, committee, err := currentCommitteeIndicesFromState(st)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, wantedIndices, indices)
|
||||
require.DeepEqual(t, wantedCommittee, committee.Pubkeys)
|
||||
})
|
||||
t.Run("validator in committee not found in state", func(t *testing.T) {
|
||||
wantedCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48)
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
_, _, err := currentCommitteeIndicesFromState(st)
|
||||
require.ErrorContains(t, "index not found for pubkey", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_nextCommitteeIndicesFromState(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
wantedIndices := make([]primitives.ValidatorIndex, len(wantedCommittee))
|
||||
for i := 0; i < len(wantedCommittee); i++ {
|
||||
wantedIndices[i] = primitives.ValidatorIndex(i)
|
||||
wantedCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
indices, committee, err := nextCommitteeIndicesFromState(st)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, wantedIndices, indices)
|
||||
require.DeepEqual(t, wantedCommittee, committee.Pubkeys)
|
||||
})
|
||||
t.Run("validator in committee not found in state", func(t *testing.T) {
|
||||
wantedCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48)
|
||||
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: wantedCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
_, _, err := nextCommitteeIndicesFromState(st)
|
||||
require.ErrorContains(t, "index not found for pubkey", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_extractSyncSubcommittees(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
for i := 0; i < len(syncCommittee); i++ {
|
||||
syncCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
|
||||
commSize := params.BeaconConfig().SyncCommitteeSize
|
||||
subCommSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
|
||||
wantedSubcommitteeValidators := make([][]primitives.ValidatorIndex, 0)
|
||||
|
||||
for i := uint64(0); i < commSize; i += subCommSize {
|
||||
sub := make([]primitives.ValidatorIndex, 0)
|
||||
start := i
|
||||
end := i + subCommSize
|
||||
if end > commSize {
|
||||
end = commSize
|
||||
}
|
||||
for j := start; j < end; j++ {
|
||||
sub = append(sub, primitives.ValidatorIndex(j))
|
||||
}
|
||||
wantedSubcommitteeValidators = append(wantedSubcommitteeValidators, sub)
|
||||
}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
committee, err := st.CurrentSyncCommittee()
|
||||
require.NoError(t, err)
|
||||
subcommittee, err := extractSyncSubcommittees(st, committee)
|
||||
require.NoError(t, err)
|
||||
for i, got := range subcommittee {
|
||||
want := wantedSubcommitteeValidators[i]
|
||||
require.DeepEqual(t, want, got.Validators)
|
||||
}
|
||||
})
|
||||
t.Run("validator in subcommittee not found in state", func(t *testing.T) {
|
||||
syncCommittee[0] = bytesutil.PadTo([]byte("fakepubkey"), 48)
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
committee, err := st.CurrentSyncCommittee()
|
||||
require.NoError(t, err)
|
||||
_, err = extractSyncSubcommittees(st, committee)
|
||||
require.ErrorContains(t, "index not found for pubkey", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListSyncCommittees(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
for i := 0; i < len(syncCommittee); i++ {
|
||||
syncCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
stRoot, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
stSlot := st.Slot()
|
||||
chainService := &mock.ChainService{Slot: &stSlot}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
req := ðpbv2.StateSyncCommitteesRequest{StateId: stRoot[:]}
|
||||
resp, err := s.ListSyncCommittees(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
committeeVals := resp.Data.Validators
|
||||
require.NotNil(t, committeeVals)
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(committeeVals)), "incorrect committee size")
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ {
|
||||
assert.Equal(t, primitives.ValidatorIndex(i), committeeVals[i])
|
||||
}
|
||||
require.NotNil(t, resp.Data.ValidatorAggregates)
|
||||
assert.Equal(t, params.BeaconConfig().SyncCommitteeSubnetCount, uint64(len(resp.Data.ValidatorAggregates)))
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
||||
vStartIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount * i)
|
||||
vEndIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize/params.BeaconConfig().SyncCommitteeSubnetCount*(i+1) - 1)
|
||||
j := 0
|
||||
for vIndex := vStartIndex; vIndex <= vEndIndex; vIndex++ {
|
||||
assert.Equal(t, vIndex, resp.Data.ValidatorAggregates[i].Validators[j])
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
stSlot := st.Slot()
|
||||
chainService := &mock.ChainService{Optimistic: true, Slot: &stSlot}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
resp, err := s.ListSyncCommittees(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
blk.Block.ParentRoot = parentRoot[:]
|
||||
root, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, db, blk)
|
||||
require.NoError(t, db.SaveGenesisBlockRoot(ctx, root))
|
||||
|
||||
headerRoot, err := st.LatestBlockHeader().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
stSlot := st.Slot()
|
||||
chainService := &mock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{
|
||||
headerRoot: true,
|
||||
},
|
||||
Slot: &stSlot,
|
||||
}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
ChainInfoFetcher: chainService,
|
||||
}
|
||||
resp, err := s.ListSyncCommittees(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
type futureSyncMockFetcher struct {
|
||||
BeaconState state.BeaconState
|
||||
BeaconStateRoot []byte
|
||||
}
|
||||
|
||||
func (m *futureSyncMockFetcher) State(_ context.Context, stateId []byte) (state.BeaconState, error) {
|
||||
expectedRequest := []byte(strconv.FormatUint(uint64(0), 10))
|
||||
res := bytes.Compare(stateId, expectedRequest)
|
||||
if res != 0 {
|
||||
return nil, status.Errorf(
|
||||
codes.Internal,
|
||||
"Requested wrong epoch for next sync committee, expected: %#x, received: %#x",
|
||||
expectedRequest,
|
||||
stateId,
|
||||
)
|
||||
}
|
||||
return m.BeaconState, nil
|
||||
}
|
||||
func (m *futureSyncMockFetcher) StateRoot(context.Context, []byte) ([]byte, error) {
|
||||
return m.BeaconStateRoot, nil
|
||||
}
|
||||
|
||||
func (m *futureSyncMockFetcher) StateBySlot(context.Context, primitives.Slot) (state.BeaconState, error) {
|
||||
return m.BeaconState, nil
|
||||
}
|
||||
|
||||
func TestListSyncCommitteesFuture(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
|
||||
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
|
||||
vals := st.Validators()
|
||||
for i := 0; i < len(syncCommittee); i++ {
|
||||
syncCommittee[i] = vals[i].PublicKey
|
||||
}
|
||||
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
|
||||
Pubkeys: syncCommittee,
|
||||
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
|
||||
}))
|
||||
db := dbTest.SetupDB(t)
|
||||
|
||||
chainService := &mock.ChainService{}
|
||||
s := &Server{
|
||||
GenesisTimeFetcher: &testutil.MockGenesisTimeFetcher{
|
||||
Genesis: time.Now(),
|
||||
},
|
||||
Stater: &futureSyncMockFetcher{
|
||||
BeaconState: st,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
BeaconDB: db,
|
||||
}
|
||||
req := ðpbv2.StateSyncCommitteesRequest{StateId: []byte("head")}
|
||||
epoch := 2 * params.BeaconConfig().EpochsPerSyncCommitteePeriod
|
||||
req.Epoch = &epoch
|
||||
_, err := s.ListSyncCommittees(ctx, req)
|
||||
require.ErrorContains(t, "Could not fetch sync committee too far in the future", err)
|
||||
|
||||
epoch = 2*params.BeaconConfig().EpochsPerSyncCommitteePeriod - 1
|
||||
resp, err := s.ListSyncCommittees(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, resp.Data)
|
||||
committeeVals := resp.Data.Validators
|
||||
require.NotNil(t, committeeVals)
|
||||
require.Equal(t, params.BeaconConfig().SyncCommitteeSize, uint64(len(committeeVals)), "incorrect committee size")
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSize; i++ {
|
||||
assert.Equal(t, primitives.ValidatorIndex(i), committeeVals[i])
|
||||
}
|
||||
require.NotNil(t, resp.Data.ValidatorAggregates)
|
||||
assert.Equal(t, params.BeaconConfig().SyncCommitteeSubnetCount, uint64(len(resp.Data.ValidatorAggregates)))
|
||||
for i := uint64(0); i < params.BeaconConfig().SyncCommitteeSubnetCount; i++ {
|
||||
vStartIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount * i)
|
||||
vEndIndex := primitives.ValidatorIndex(params.BeaconConfig().SyncCommitteeSize/params.BeaconConfig().SyncCommitteeSubnetCount*(i+1) - 1)
|
||||
j := 0
|
||||
for vIndex := vStartIndex; vIndex <= vEndIndex; vIndex++ {
|
||||
assert.Equal(t, vIndex, resp.Data.ValidatorAggregates[i].Validators[j])
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -4,47 +4,43 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"handlers.go",
|
||||
"node.go",
|
||||
"handlers_peers.go",
|
||||
"server.go",
|
||||
"structs.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/node",
|
||||
visibility = ["//beacon-chain:__subpackages__"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//api/grpc:go_default_library",
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/execution:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/p2p/peers:go_default_library",
|
||||
"//beacon-chain/p2p/peers/peerdata:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/migration:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//metadata:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"handlers_peers_test.go",
|
||||
"handlers_test.go",
|
||||
"node_test.go",
|
||||
"server_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//api/grpc:go_default_library",
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/p2p/peers:go_default_library",
|
||||
@@ -53,8 +49,7 @@ go_test(
|
||||
"//beacon-chain/sync/initial-sync/testing:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/wrapper:go_default_library",
|
||||
"//proto/eth/service:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
@@ -62,13 +57,11 @@ go_test(
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//p2p/enr:go_default_library",
|
||||
"@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:go_default_library",
|
||||
"@com_github_gorilla_mux//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/host/peerstore/test:go_default_library",
|
||||
"@com_github_multiformats_go_multiaddr//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
stateConnecting = ethpb.ConnectionState_CONNECTING.String()
|
||||
stateConnected = ethpb.ConnectionState_CONNECTED.String()
|
||||
stateDisconnecting = ethpb.ConnectionState_DISCONNECTING.String()
|
||||
stateDisconnected = ethpb.ConnectionState_DISCONNECTED.String()
|
||||
directionInbound = ethpb.PeerDirection_INBOUND.String()
|
||||
directionOutbound = ethpb.PeerDirection_OUTBOUND.String()
|
||||
)
|
||||
|
||||
// GetSyncStatus requests the beacon node to describe if it's currently syncing or not, and
|
||||
// if it is, what block it is up to.
|
||||
func (s *Server) GetSyncStatus(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -32,3 +48,86 @@ func (s *Server) GetSyncStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
http2.WriteJson(w, response)
|
||||
}
|
||||
|
||||
// GetIdentity retrieves data about the node's network presence.
|
||||
func (s *Server) GetIdentity(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.GetIdentity")
|
||||
defer span.End()
|
||||
|
||||
peerId := s.PeerManager.PeerID().String()
|
||||
sourcep2p := s.PeerManager.Host().Addrs()
|
||||
p2pAddresses := make([]string, len(sourcep2p))
|
||||
for i := range sourcep2p {
|
||||
p2pAddresses[i] = sourcep2p[i].String() + "/p2p/" + peerId
|
||||
}
|
||||
sourceDisc, err := s.PeerManager.DiscoveryAddresses()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not obtain discovery address: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
discoveryAddresses := make([]string, len(sourceDisc))
|
||||
for i := range sourceDisc {
|
||||
discoveryAddresses[i] = sourceDisc[i].String()
|
||||
}
|
||||
serializedEnr, err := p2p.SerializeENR(s.PeerManager.ENR())
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not obtain enr: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &GetIdentityResponse{
|
||||
Data: &Identity{
|
||||
PeerId: peerId,
|
||||
Enr: "enr:" + serializedEnr,
|
||||
P2PAddresses: p2pAddresses,
|
||||
DiscoveryAddresses: discoveryAddresses,
|
||||
Metadata: &Metadata{
|
||||
SeqNumber: strconv.FormatUint(s.MetadataProvider.MetadataSeq(), 10),
|
||||
Attnets: hexutil.Encode(s.MetadataProvider.Metadata().AttnetsBitfield()),
|
||||
},
|
||||
},
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetVersion requests that the beacon node identify information about its implementation in a
|
||||
// format similar to a HTTP User-Agent field.
|
||||
func (*Server) GetVersion(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.GetVersion")
|
||||
defer span.End()
|
||||
|
||||
v := fmt.Sprintf("Prysm/%s (%s %s)", version.SemanticVersion(), runtime.GOOS, runtime.GOARCH)
|
||||
resp := &GetVersionResponse{
|
||||
Data: &Version{
|
||||
Version: v,
|
||||
},
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetHealth returns node health status in http status codes. Useful for load balancers.
|
||||
func (s *Server) GetHealth(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.GetHealth")
|
||||
defer span.End()
|
||||
|
||||
ok, rawSyncingStatus, syncingStatus := shared.UintFromQuery(w, r, "syncing_status")
|
||||
// lint:ignore uintcast -- custom syncing status being outside of range is harmless
|
||||
intSyncingStatus := int(syncingStatus)
|
||||
if !ok || (rawSyncingStatus != "" && http.StatusText(intSyncingStatus) == "") {
|
||||
http2.HandleError(w, "syncing_status is not a valid HTTP status code", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if s.SyncChecker.Synced() {
|
||||
return
|
||||
}
|
||||
if s.SyncChecker.Syncing() || s.SyncChecker.Initialized() {
|
||||
if rawSyncingStatus != "" {
|
||||
w.WriteHeader(intSyncingStatus)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusPartialContent)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
|
||||
279
beacon-chain/rpc/eth/node/handlers_peers.go
Normal file
279
beacon-chain/rpc/eth/node/handlers_peers.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// GetPeer retrieves data about the given peer.
|
||||
func (s *Server) GetPeer(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.GetPeer")
|
||||
defer span.End()
|
||||
|
||||
rawId := mux.Vars(r)["peer_id"]
|
||||
if rawId == "" {
|
||||
http2.HandleError(w, "peer_id is required in URL params", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
peerStatus := s.PeersFetcher.Peers()
|
||||
id, err := peer.Decode(rawId)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Invalid peer ID: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
enr, err := peerStatus.ENR(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, peerdata.ErrPeerUnknown) {
|
||||
http2.HandleError(w, "Peer not found: "+err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http2.HandleError(w, "Could not obtain ENR: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
serializedEnr, err := p2p.SerializeENR(enr)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not obtain ENR: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
p2pAddress, err := peerStatus.Address(id)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not obtain address: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
state, err := peerStatus.ConnectionState(id)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not obtain connection state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
direction, err := peerStatus.Direction(id)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not obtain direction: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if eth.PeerDirection(direction) == eth.PeerDirection_UNKNOWN {
|
||||
http2.HandleError(w, "Peer not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
v1ConnState := migration.V1Alpha1ConnectionStateToV1(eth.ConnectionState(state))
|
||||
v1PeerDirection, err := migration.V1Alpha1PeerDirectionToV1(eth.PeerDirection(direction))
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not handle peer direction: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &GetPeerResponse{
|
||||
Data: &Peer{
|
||||
PeerId: rawId,
|
||||
Enr: "enr:" + serializedEnr,
|
||||
LastSeenP2PAddress: p2pAddress.String(),
|
||||
State: strings.ToLower(v1ConnState.String()),
|
||||
Direction: strings.ToLower(v1PeerDirection.String()),
|
||||
},
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetPeers retrieves data about the node's network peers.
|
||||
func (s *Server) GetPeers(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.GetPeers")
|
||||
defer span.End()
|
||||
|
||||
states := r.URL.Query()["state"]
|
||||
directions := r.URL.Query()["direction"]
|
||||
|
||||
peerStatus := s.PeersFetcher.Peers()
|
||||
emptyStateFilter, emptyDirectionFilter := handleEmptyFilters(states, directions)
|
||||
|
||||
if emptyStateFilter && emptyDirectionFilter {
|
||||
allIds := peerStatus.All()
|
||||
allPeers := make([]*Peer, 0, len(allIds))
|
||||
for _, id := range allIds {
|
||||
p, err := peerInfo(peerStatus, id)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get peer info: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
allPeers = append(allPeers, p)
|
||||
}
|
||||
resp := &GetPeersResponse{Data: allPeers}
|
||||
http2.WriteJson(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
var stateIds []peer.ID
|
||||
if emptyStateFilter {
|
||||
stateIds = peerStatus.All()
|
||||
} else {
|
||||
for _, stateFilter := range states {
|
||||
switch strings.ToUpper(stateFilter) {
|
||||
case stateConnecting:
|
||||
ids := peerStatus.Connecting()
|
||||
stateIds = append(stateIds, ids...)
|
||||
case stateConnected:
|
||||
ids := peerStatus.Connected()
|
||||
stateIds = append(stateIds, ids...)
|
||||
case stateDisconnecting:
|
||||
ids := peerStatus.Disconnecting()
|
||||
stateIds = append(stateIds, ids...)
|
||||
case stateDisconnected:
|
||||
ids := peerStatus.Disconnected()
|
||||
stateIds = append(stateIds, ids...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var directionIds []peer.ID
|
||||
if emptyDirectionFilter {
|
||||
directionIds = peerStatus.All()
|
||||
} else {
|
||||
for _, directionFilter := range directions {
|
||||
switch strings.ToUpper(directionFilter) {
|
||||
case directionInbound:
|
||||
ids := peerStatus.Inbound()
|
||||
directionIds = append(directionIds, ids...)
|
||||
case directionOutbound:
|
||||
ids := peerStatus.Outbound()
|
||||
directionIds = append(directionIds, ids...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var filteredIds []peer.ID
|
||||
for _, stateId := range stateIds {
|
||||
for _, directionId := range directionIds {
|
||||
if stateId.String() == directionId.String() {
|
||||
filteredIds = append(filteredIds, stateId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
filteredPeers := make([]*Peer, 0, len(filteredIds))
|
||||
for _, id := range filteredIds {
|
||||
p, err := peerInfo(peerStatus, id)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get peer info: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
filteredPeers = append(filteredPeers, p)
|
||||
}
|
||||
|
||||
resp := &GetPeersResponse{Data: filteredPeers}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetPeerCount retrieves number of known peers.
|
||||
func (s *Server) GetPeerCount(w http.ResponseWriter, r *http.Request) {
|
||||
_, span := trace.StartSpan(r.Context(), "node.PeerCount")
|
||||
defer span.End()
|
||||
|
||||
peerStatus := s.PeersFetcher.Peers()
|
||||
|
||||
resp := &GetPeerCountResponse{
|
||||
Data: &PeerCount{
|
||||
Disconnected: strconv.FormatInt(int64(len(peerStatus.Disconnected())), 10),
|
||||
Connecting: strconv.FormatInt(int64(len(peerStatus.Connecting())), 10),
|
||||
Connected: strconv.FormatInt(int64(len(peerStatus.Connected())), 10),
|
||||
Disconnecting: strconv.FormatInt(int64(len(peerStatus.Disconnecting())), 10),
|
||||
},
|
||||
}
|
||||
http2.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
func handleEmptyFilters(states []string, directions []string) (emptyState, emptyDirection bool) {
|
||||
emptyState = true
|
||||
for _, stateFilter := range states {
|
||||
normalized := strings.ToUpper(stateFilter)
|
||||
filterValid := normalized == stateConnecting || normalized == stateConnected ||
|
||||
normalized == stateDisconnecting || normalized == stateDisconnected
|
||||
if filterValid {
|
||||
emptyState = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
emptyDirection = true
|
||||
for _, directionFilter := range directions {
|
||||
normalized := strings.ToUpper(directionFilter)
|
||||
filterValid := normalized == directionInbound || normalized == directionOutbound
|
||||
if filterValid {
|
||||
emptyDirection = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return emptyState, emptyDirection
|
||||
}
|
||||
|
||||
func peerInfo(peerStatus *peers.Status, id peer.ID) (*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
|
||||
if enr != nil {
|
||||
serializedEnr, err = p2p.SerializeENR(enr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not serialize ENR")
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
p := &Peer{
|
||||
PeerId: id.String(),
|
||||
State: strings.ToLower(eth.ConnectionState(connectionState).String()),
|
||||
Direction: strings.ToLower(eth.PeerDirection(direction).String()),
|
||||
}
|
||||
if address != nil {
|
||||
p.LastSeenP2PAddress = address.String()
|
||||
}
|
||||
if serializedEnr != "" {
|
||||
p.Enr = "enr:" + serializedEnr
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
346
beacon-chain/rpc/eth/node/handlers_peers_test.go
Normal file
346
beacon-chain/rpc/eth/node/handlers_peers_test.go
Normal file
@@ -0,0 +1,346 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
libp2ptest "github.com/libp2p/go-libp2p/p2p/host/peerstore/test"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers"
|
||||
mockp2p "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/testing"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
func TestGetPeer(t *testing.T) {
|
||||
const rawId = "16Uiu2HAkvyYtoQXZNTsthjgLHjEnv7kvwzEmjvsJjWXpbhtqpSUN"
|
||||
decodedId, err := peer.Decode(rawId)
|
||||
require.NoError(t, err)
|
||||
enrRecord := &enr.Record{}
|
||||
err = enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{7, 7, 7, 7})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
const p2pAddr = "/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
peerFetcher.Peers().Add(enrRecord, decodedId, p2pMultiAddr, network.DirInbound)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers/{peer_id}", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"peer_id": rawId})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPeer(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetPeerResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, rawId, resp.Data.PeerId)
|
||||
assert.Equal(t, p2pAddr, resp.Data.LastSeenP2PAddress)
|
||||
assert.Equal(t, "enr:yoABgmlwhAcHBwc", resp.Data.Enr)
|
||||
assert.Equal(t, "disconnected", resp.Data.State)
|
||||
assert.Equal(t, "inbound", resp.Data.Direction)
|
||||
})
|
||||
|
||||
t.Run("Invalid ID", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers/{peer_id}", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"peer_id": "foo"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPeer(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.StringContains(t, "Invalid peer ID", e.Message)
|
||||
})
|
||||
|
||||
t.Run("Peer not found", func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers/{peer_id}", nil)
|
||||
request = mux.SetURLVars(request, map[string]string{"peer_id": "16Uiu2HAmQqFdEcHbSmQTQuLoAhnMUrgoWoraKK4cUJT6FuuqHqTU"})
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPeer(writer, request)
|
||||
require.Equal(t, http.StatusNotFound, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusNotFound, e.Code)
|
||||
assert.StringContains(t, "Peer not found", e.Message)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPeers(t *testing.T) {
|
||||
ids := libp2ptest.GeneratePeerIDs(9)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
peerFetcher.ClearPeers()
|
||||
peerStatus := peerFetcher.Peers()
|
||||
|
||||
for i, id := range ids {
|
||||
// Make last peer undiscovered
|
||||
if i == len(ids)-1 {
|
||||
peerStatus.Add(nil, id, nil, network.DirUnknown)
|
||||
} else {
|
||||
enrRecord := &enr.Record{}
|
||||
err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{127, 0, 0, byte(i)})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
var p2pAddr = "/ip4/127.0.0." + strconv.Itoa(i) + "/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
var direction network.Direction
|
||||
if i%2 == 0 {
|
||||
direction = network.DirInbound
|
||||
} else {
|
||||
direction = network.DirOutbound
|
||||
}
|
||||
peerStatus.Add(enrRecord, id, p2pMultiAddr, direction)
|
||||
|
||||
switch i {
|
||||
case 0, 1:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnecting)
|
||||
case 2, 3:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnected)
|
||||
case 4, 5:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnecting)
|
||||
case 6, 7:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnected)
|
||||
default:
|
||||
t.Fatalf("Failed to set connection state for peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
// We will check the first peer from the list.
|
||||
expectedId := ids[0]
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers?state=connecting&direction=inbound", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPeers(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetPeersResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
returnedPeer := resp.Data[0]
|
||||
assert.Equal(t, expectedId.Pretty(), returnedPeer.PeerId)
|
||||
expectedEnr, err := peerStatus.ENR(expectedId)
|
||||
require.NoError(t, err)
|
||||
serializedEnr, err := p2p.SerializeENR(expectedEnr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "enr:"+serializedEnr, returnedPeer.Enr)
|
||||
expectedP2PAddr, err := peerStatus.Address(expectedId)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedP2PAddr.String(), returnedPeer.LastSeenP2PAddress)
|
||||
assert.Equal(t, "connecting", returnedPeer.State)
|
||||
assert.Equal(t, "inbound", returnedPeer.Direction)
|
||||
})
|
||||
|
||||
filterTests := []struct {
|
||||
name string
|
||||
states []string
|
||||
directions []string
|
||||
wantIds []peer.ID
|
||||
}{
|
||||
{
|
||||
name: "No filters - return all peers",
|
||||
states: []string{},
|
||||
directions: []string{},
|
||||
wantIds: ids[:len(ids)-1], // Excluding last peer as it is not connected.
|
||||
},
|
||||
{
|
||||
name: "State filter empty - return peers for all states",
|
||||
states: []string{},
|
||||
directions: []string{"inbound"},
|
||||
wantIds: []peer.ID{ids[0], ids[2], ids[4], ids[6]},
|
||||
},
|
||||
{
|
||||
name: "Direction filter empty - return peers for all directions",
|
||||
states: []string{"connected"},
|
||||
directions: []string{},
|
||||
wantIds: []peer.ID{ids[2], ids[3]},
|
||||
},
|
||||
{
|
||||
name: "One state and direction",
|
||||
states: []string{"disconnected"},
|
||||
directions: []string{"inbound"},
|
||||
wantIds: []peer.ID{ids[6]},
|
||||
},
|
||||
{
|
||||
name: "Multiple states and directions",
|
||||
states: []string{"connecting", "disconnecting"},
|
||||
directions: []string{"inbound", "outbound"},
|
||||
wantIds: []peer.ID{ids[0], ids[1], ids[4], ids[5]},
|
||||
},
|
||||
{
|
||||
name: "Unknown filter is ignored",
|
||||
states: []string{"connected", "foo"},
|
||||
directions: []string{"outbound", "foo"},
|
||||
wantIds: []peer.ID{ids[3]},
|
||||
},
|
||||
{
|
||||
name: "Only unknown filters - return all peers",
|
||||
states: []string{"foo"},
|
||||
directions: []string{"foo"},
|
||||
wantIds: ids[:len(ids)-1], // Excluding last peer as it is not connected.
|
||||
},
|
||||
}
|
||||
for _, tt := range filterTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
states := strings.Join(tt.states, "&state=")
|
||||
statesQuery := ""
|
||||
if states != "" {
|
||||
statesQuery = "state=" + states
|
||||
}
|
||||
directions := strings.Join(tt.directions, "&direction=")
|
||||
directionsQuery := ""
|
||||
if directions != "" {
|
||||
directionsQuery = "direction=" + directions
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers?"+statesQuery+"&"+directionsQuery, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPeers(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetPeersResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, len(tt.wantIds), len(resp.Data), "Wrong number of peers returned")
|
||||
for _, id := range tt.wantIds {
|
||||
expectedId := id.Pretty()
|
||||
found := false
|
||||
for _, returnedPeer := range resp.Data {
|
||||
if returnedPeer.PeerId == expectedId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected ID '" + expectedId + "' not found")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPeers_NoPeersReturnsEmptyArray(t *testing.T) {
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
peerFetcher.ClearPeers()
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers?state=connecting&state=connected", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetPeers(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetPeersResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, 0, len(resp.Data))
|
||||
}
|
||||
|
||||
func TestGetPeerCount(t *testing.T) {
|
||||
ids := libp2ptest.GeneratePeerIDs(10)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
peerFetcher.ClearPeers()
|
||||
peerStatus := peerFetcher.Peers()
|
||||
|
||||
for i, id := range ids {
|
||||
enrRecord := &enr.Record{}
|
||||
err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{127, 0, 0, byte(i)})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
var p2pAddr = "/ip4/127.0.0." + strconv.Itoa(i) + "/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
var direction network.Direction
|
||||
if i%2 == 0 {
|
||||
direction = network.DirInbound
|
||||
} else {
|
||||
direction = network.DirOutbound
|
||||
}
|
||||
peerStatus.Add(enrRecord, id, p2pMultiAddr, direction)
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnecting)
|
||||
case 1, 2:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnected)
|
||||
case 3, 4, 5:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnecting)
|
||||
case 6, 7, 8, 9:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnected)
|
||||
default:
|
||||
t.Fatalf("Failed to set connection state for peer")
|
||||
}
|
||||
}
|
||||
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peer_count", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetPeerCount(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetPeerCountResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, "1", resp.Data.Connecting, "Wrong number of connecting peers")
|
||||
assert.Equal(t, "2", resp.Data.Connected, "Wrong number of connected peers")
|
||||
assert.Equal(t, "3", resp.Data.Disconnecting, "Wrong number of disconnecting peers")
|
||||
assert.Equal(t, "4", resp.Data.Disconnected, "Wrong number of disconnected peers")
|
||||
}
|
||||
|
||||
func BenchmarkGetPeers(b *testing.B) {
|
||||
// We simulate having a lot of peers.
|
||||
ids := libp2ptest.GeneratePeerIDs(2000)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
|
||||
for _, id := range ids {
|
||||
enrRecord := &enr.Record{}
|
||||
err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(b, err)
|
||||
enrRecord.Set(enr.IPv4{7, 7, 7, 7})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(b, err)
|
||||
const p2pAddr = "/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(b, err)
|
||||
peerFetcher.Peers().Add(enrRecord, id, p2pMultiAddr, network.DirInbound)
|
||||
}
|
||||
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/peers", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetPeers(writer, request)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.GetPeers(writer, request)
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,37 @@ package node
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
mockp2p "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
|
||||
syncmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/wrapper"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
type dummyIdentity enode.ID
|
||||
|
||||
func (_ dummyIdentity) Verify(_ *enr.Record, _ []byte) error { return nil }
|
||||
func (id dummyIdentity) NodeAddr(_ *enr.Record) []byte { return id[:] }
|
||||
|
||||
func TestSyncStatus(t *testing.T) {
|
||||
currentSlot := new(primitives.Slot)
|
||||
*currentSlot = 110
|
||||
@@ -50,3 +68,164 @@ func TestSyncStatus(t *testing.T) {
|
||||
assert.Equal(t, true, resp.Data.IsOptimistic)
|
||||
assert.Equal(t, false, resp.Data.ElOffline)
|
||||
}
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
semVer := version.SemanticVersion()
|
||||
os := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/version", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s := &Server{}
|
||||
s.GetVersion(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetVersionResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.StringContains(t, semVer, resp.Data.Version)
|
||||
assert.StringContains(t, os, resp.Data.Version)
|
||||
assert.StringContains(t, arch, resp.Data.Version)
|
||||
}
|
||||
|
||||
func TestGetHealth(t *testing.T) {
|
||||
checker := &syncmock.Sync{}
|
||||
s := &Server{
|
||||
SyncChecker: checker,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/health", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetHealth(writer, request)
|
||||
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
|
||||
|
||||
checker.IsInitialized = true
|
||||
request = httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/health", nil)
|
||||
writer = httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetHealth(writer, request)
|
||||
assert.Equal(t, http.StatusPartialContent, writer.Code)
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/node/health?syncing_status=%d", http.StatusPaymentRequired), nil)
|
||||
writer = httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetHealth(writer, request)
|
||||
assert.Equal(t, http.StatusPaymentRequired, writer.Code)
|
||||
|
||||
checker.IsSynced = true
|
||||
request = httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/health", nil)
|
||||
writer = httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
s.GetHealth(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
}
|
||||
|
||||
func TestGetIdentity(t *testing.T) {
|
||||
p2pAddr, err := ma.NewMultiaddr("/ip4/7.7.7.7/udp/30303")
|
||||
require.NoError(t, err)
|
||||
discAddr1, err := ma.NewMultiaddr("/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
|
||||
require.NoError(t, err)
|
||||
discAddr2, err := ma.NewMultiaddr("/ip6/1:2:3:4:5:6:7:8/udp/20202/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
|
||||
require.NoError(t, err)
|
||||
enrRecord := &enr.Record{}
|
||||
err = enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{7, 7, 7, 7})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
attnets := bitfield.NewBitvector64()
|
||||
attnets.SetBitAt(1, true)
|
||||
metadataProvider := &mockp2p.MockMetadataProvider{Data: wrapper.WrappedMetadataV0(&pb.MetaDataV0{SeqNumber: 1, Attnets: attnets})}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
peerManager := &mockp2p.MockPeerManager{
|
||||
Enr: enrRecord,
|
||||
PID: "foo",
|
||||
BHost: &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
|
||||
DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
|
||||
}
|
||||
s := &Server{
|
||||
PeerManager: peerManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/identity", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetIdentity(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &GetIdentityResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
expectedID := peer.ID("foo").Pretty()
|
||||
assert.Equal(t, expectedID, resp.Data.PeerId)
|
||||
expectedEnr, err := p2p.SerializeENR(enrRecord)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, fmt.Sprint("enr:", expectedEnr), resp.Data.Enr)
|
||||
require.Equal(t, 1, len(resp.Data.P2PAddresses))
|
||||
assert.Equal(t, p2pAddr.String()+"/p2p/"+expectedID, resp.Data.P2PAddresses[0])
|
||||
require.Equal(t, 2, len(resp.Data.DiscoveryAddresses))
|
||||
ipv4Found, ipv6Found := false, false
|
||||
for _, address := range resp.Data.DiscoveryAddresses {
|
||||
if address == discAddr1.String() {
|
||||
ipv4Found = true
|
||||
} else if address == discAddr2.String() {
|
||||
ipv6Found = true
|
||||
}
|
||||
}
|
||||
assert.Equal(t, true, ipv4Found, "IPv4 discovery address not found")
|
||||
assert.Equal(t, true, ipv6Found, "IPv6 discovery address not found")
|
||||
assert.Equal(t, discAddr1.String(), resp.Data.DiscoveryAddresses[0])
|
||||
assert.Equal(t, discAddr2.String(), resp.Data.DiscoveryAddresses[1])
|
||||
})
|
||||
|
||||
t.Run("ENR failure", func(t *testing.T) {
|
||||
peerManager := &mockp2p.MockPeerManager{
|
||||
Enr: &enr.Record{},
|
||||
PID: "foo",
|
||||
BHost: &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
|
||||
DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
|
||||
}
|
||||
s := &Server{
|
||||
PeerManager: peerManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/identity", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetIdentity(writer, request)
|
||||
require.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusInternalServerError, e.Code)
|
||||
assert.StringContains(t, "Could not obtain enr", e.Message)
|
||||
})
|
||||
|
||||
t.Run("Discovery addresses failure", func(t *testing.T) {
|
||||
peerManager := &mockp2p.MockPeerManager{
|
||||
Enr: enrRecord,
|
||||
PID: "foo",
|
||||
BHost: &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
|
||||
DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
|
||||
FailDiscoveryAddr: true,
|
||||
}
|
||||
s := &Server{
|
||||
PeerManager: peerManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/node/identity", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetIdentity(writer, request)
|
||||
require.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusInternalServerError, e.Code)
|
||||
assert.StringContains(t, "Could not obtain discovery address", e.Message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,371 +0,0 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/pkg/errors"
|
||||
grpcutil "github.com/prysmaticlabs/prysm/v4/api/grpc"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/migration"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
var (
|
||||
stateConnecting = ethpb.ConnectionState_CONNECTING.String()
|
||||
stateConnected = ethpb.ConnectionState_CONNECTED.String()
|
||||
stateDisconnecting = ethpb.ConnectionState_DISCONNECTING.String()
|
||||
stateDisconnected = ethpb.ConnectionState_DISCONNECTED.String()
|
||||
directionInbound = ethpb.PeerDirection_INBOUND.String()
|
||||
directionOutbound = ethpb.PeerDirection_OUTBOUND.String()
|
||||
)
|
||||
|
||||
// GetIdentity retrieves data about the node's network presence.
|
||||
func (ns *Server) GetIdentity(ctx context.Context, _ *emptypb.Empty) (*ethpb.IdentityResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "node.GetIdentity")
|
||||
defer span.End()
|
||||
|
||||
peerId := ns.PeerManager.PeerID().Pretty()
|
||||
|
||||
serializedEnr, err := p2p.SerializeENR(ns.PeerManager.ENR())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain enr: %v", err)
|
||||
}
|
||||
enr := "enr:" + serializedEnr
|
||||
|
||||
sourcep2p := ns.PeerManager.Host().Addrs()
|
||||
p2pAddresses := make([]string, len(sourcep2p))
|
||||
for i := range sourcep2p {
|
||||
p2pAddresses[i] = sourcep2p[i].String() + "/p2p/" + peerId
|
||||
}
|
||||
|
||||
sourceDisc, err := ns.PeerManager.DiscoveryAddresses()
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain discovery address: %v", err)
|
||||
}
|
||||
discoveryAddresses := make([]string, len(sourceDisc))
|
||||
for i := range sourceDisc {
|
||||
discoveryAddresses[i] = sourceDisc[i].String()
|
||||
}
|
||||
|
||||
meta := ðpb.Metadata{
|
||||
SeqNumber: ns.MetadataProvider.MetadataSeq(),
|
||||
Attnets: ns.MetadataProvider.Metadata().AttnetsBitfield(),
|
||||
}
|
||||
|
||||
return ðpb.IdentityResponse{
|
||||
Data: ðpb.Identity{
|
||||
PeerId: peerId,
|
||||
Enr: enr,
|
||||
P2PAddresses: p2pAddresses,
|
||||
DiscoveryAddresses: discoveryAddresses,
|
||||
Metadata: meta,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPeer retrieves data about the given peer.
|
||||
func (ns *Server) GetPeer(ctx context.Context, req *ethpb.PeerRequest) (*ethpb.PeerResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "node.GetPeer")
|
||||
defer span.End()
|
||||
|
||||
peerStatus := ns.PeersFetcher.Peers()
|
||||
id, err := peer.Decode(req.PeerId)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Invalid peer ID: %v", err)
|
||||
}
|
||||
enr, err := peerStatus.ENR(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, peerdata.ErrPeerUnknown) {
|
||||
return nil, status.Error(codes.NotFound, "Peer not found")
|
||||
}
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain ENR: %v", err)
|
||||
}
|
||||
serializedEnr, err := p2p.SerializeENR(enr)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain ENR: %v", err)
|
||||
}
|
||||
p2pAddress, err := peerStatus.Address(id)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain address: %v", err)
|
||||
}
|
||||
state, err := peerStatus.ConnectionState(id)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain connection state: %v", err)
|
||||
}
|
||||
direction, err := peerStatus.Direction(id)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not obtain direction: %v", err)
|
||||
}
|
||||
if eth.PeerDirection(direction) == eth.PeerDirection_UNKNOWN {
|
||||
return nil, status.Error(codes.NotFound, "Peer not found")
|
||||
}
|
||||
|
||||
v1ConnState := migration.V1Alpha1ConnectionStateToV1(eth.ConnectionState(state))
|
||||
v1PeerDirection, err := migration.V1Alpha1PeerDirectionToV1(eth.PeerDirection(direction))
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not handle peer direction: %v", err)
|
||||
}
|
||||
return ðpb.PeerResponse{
|
||||
Data: ðpb.Peer{
|
||||
PeerId: req.PeerId,
|
||||
Enr: "enr:" + serializedEnr,
|
||||
LastSeenP2PAddress: p2pAddress.String(),
|
||||
State: v1ConnState,
|
||||
Direction: v1PeerDirection,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListPeers retrieves data about the node's network peers.
|
||||
func (ns *Server) ListPeers(ctx context.Context, req *ethpb.PeersRequest) (*ethpb.PeersResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "node.ListPeers")
|
||||
defer span.End()
|
||||
|
||||
peerStatus := ns.PeersFetcher.Peers()
|
||||
emptyStateFilter, emptyDirectionFilter := handleEmptyFilters(req)
|
||||
|
||||
if emptyStateFilter && emptyDirectionFilter {
|
||||
allIds := peerStatus.All()
|
||||
allPeers := make([]*ethpb.Peer, 0, len(allIds))
|
||||
for _, id := range allIds {
|
||||
p, err := peerInfo(peerStatus, id)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get peer info: %v", err)
|
||||
}
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
allPeers = append(allPeers, p)
|
||||
}
|
||||
return ðpb.PeersResponse{Data: allPeers}, nil
|
||||
}
|
||||
|
||||
var stateIds []peer.ID
|
||||
if emptyStateFilter {
|
||||
stateIds = peerStatus.All()
|
||||
} else {
|
||||
for _, stateFilter := range req.State {
|
||||
normalized := strings.ToUpper(stateFilter.String())
|
||||
if normalized == stateConnecting {
|
||||
ids := peerStatus.Connecting()
|
||||
stateIds = append(stateIds, ids...)
|
||||
continue
|
||||
}
|
||||
if normalized == stateConnected {
|
||||
ids := peerStatus.Connected()
|
||||
stateIds = append(stateIds, ids...)
|
||||
continue
|
||||
}
|
||||
if normalized == stateDisconnecting {
|
||||
ids := peerStatus.Disconnecting()
|
||||
stateIds = append(stateIds, ids...)
|
||||
continue
|
||||
}
|
||||
if normalized == stateDisconnected {
|
||||
ids := peerStatus.Disconnected()
|
||||
stateIds = append(stateIds, ids...)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var directionIds []peer.ID
|
||||
if emptyDirectionFilter {
|
||||
directionIds = peerStatus.All()
|
||||
} else {
|
||||
for _, directionFilter := range req.Direction {
|
||||
normalized := strings.ToUpper(directionFilter.String())
|
||||
if normalized == directionInbound {
|
||||
ids := peerStatus.Inbound()
|
||||
directionIds = append(directionIds, ids...)
|
||||
continue
|
||||
}
|
||||
if normalized == directionOutbound {
|
||||
ids := peerStatus.Outbound()
|
||||
directionIds = append(directionIds, ids...)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var filteredIds []peer.ID
|
||||
for _, stateId := range stateIds {
|
||||
for _, directionId := range directionIds {
|
||||
if stateId.Pretty() == directionId.Pretty() {
|
||||
filteredIds = append(filteredIds, stateId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
filteredPeers := make([]*ethpb.Peer, 0, len(filteredIds))
|
||||
for _, id := range filteredIds {
|
||||
p, err := peerInfo(peerStatus, id)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "Could not get peer info: %v", err)
|
||||
}
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
filteredPeers = append(filteredPeers, p)
|
||||
}
|
||||
|
||||
return ðpb.PeersResponse{Data: filteredPeers}, nil
|
||||
}
|
||||
|
||||
// PeerCount retrieves number of known peers.
|
||||
func (ns *Server) PeerCount(ctx context.Context, _ *emptypb.Empty) (*ethpb.PeerCountResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "node.PeerCount")
|
||||
defer span.End()
|
||||
|
||||
peerStatus := ns.PeersFetcher.Peers()
|
||||
|
||||
return ðpb.PeerCountResponse{
|
||||
Data: ðpb.PeerCountResponse_PeerCount{
|
||||
Disconnected: uint64(len(peerStatus.Disconnected())),
|
||||
Connecting: uint64(len(peerStatus.Connecting())),
|
||||
Connected: uint64(len(peerStatus.Connected())),
|
||||
Disconnecting: uint64(len(peerStatus.Disconnecting())),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetVersion requests that the beacon node identify information about its implementation in a
|
||||
// format similar to a HTTP User-Agent field.
|
||||
func (_ *Server) GetVersion(ctx context.Context, _ *emptypb.Empty) (*ethpb.VersionResponse, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "node.GetVersion")
|
||||
defer span.End()
|
||||
|
||||
v := fmt.Sprintf("Prysm/%s (%s %s)", version.SemanticVersion(), runtime.GOOS, runtime.GOARCH)
|
||||
return ðpb.VersionResponse{
|
||||
Data: ðpb.Version{
|
||||
Version: v,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetHealth returns node health status in http status codes. Useful for load balancers.
|
||||
// Response Usage:
|
||||
//
|
||||
// "200":
|
||||
// description: Node is ready
|
||||
// "206":
|
||||
// description: Node is syncing but can serve incomplete data
|
||||
// "503":
|
||||
// description: Node not initialized or having issues
|
||||
func (ns *Server) GetHealth(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "node.GetHealth")
|
||||
defer span.End()
|
||||
|
||||
if ns.SyncChecker.Synced() {
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
if ns.SyncChecker.Syncing() || ns.SyncChecker.Initialized() {
|
||||
if err := grpc.SetHeader(ctx, metadata.Pairs(grpcutil.HttpCodeMetadataKey, strconv.Itoa(http.StatusPartialContent))); err != nil {
|
||||
// We return a positive result because failing to set a non-gRPC related header should not cause the gRPC call to fail.
|
||||
//nolint:nilerr
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
return &emptypb.Empty{}, status.Error(codes.Internal, "Node not initialized or having issues")
|
||||
}
|
||||
|
||||
func handleEmptyFilters(req *ethpb.PeersRequest) (emptyState, emptyDirection bool) {
|
||||
emptyState = true
|
||||
for _, stateFilter := range req.State {
|
||||
normalized := strings.ToUpper(stateFilter.String())
|
||||
filterValid := normalized == stateConnecting || normalized == stateConnected ||
|
||||
normalized == stateDisconnecting || normalized == stateDisconnected
|
||||
if filterValid {
|
||||
emptyState = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
emptyDirection = true
|
||||
for _, directionFilter := range req.Direction {
|
||||
normalized := strings.ToUpper(directionFilter.String())
|
||||
filterValid := normalized == directionInbound || normalized == directionOutbound
|
||||
if filterValid {
|
||||
emptyDirection = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return emptyState, emptyDirection
|
||||
}
|
||||
|
||||
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
|
||||
if enr != nil {
|
||||
serializedEnr, err = p2p.SerializeENR(enr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not serialize ENR")
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
v1ConnState := migration.V1Alpha1ConnectionStateToV1(eth.ConnectionState(connectionState))
|
||||
v1PeerDirection, err := migration.V1Alpha1PeerDirectionToV1(eth.PeerDirection(direction))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not handle peer direction")
|
||||
}
|
||||
p := ethpb.Peer{
|
||||
PeerId: id.Pretty(),
|
||||
State: v1ConnState,
|
||||
Direction: v1PeerDirection,
|
||||
}
|
||||
if address != nil {
|
||||
p.LastSeenP2PAddress = address.String()
|
||||
}
|
||||
if serializedEnr != "" {
|
||||
p.Enr = "enr:" + serializedEnr
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
grpcruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
libp2ptest "github.com/libp2p/go-libp2p/p2p/host/peerstore/test"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
grpcutil "github.com/prysmaticlabs/prysm/v4/api/grpc"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers"
|
||||
mockp2p "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/testing"
|
||||
syncmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/wrapper"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
|
||||
pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
type dummyIdentity enode.ID
|
||||
|
||||
func (_ dummyIdentity) Verify(_ *enr.Record, _ []byte) error { return nil }
|
||||
func (id dummyIdentity) NodeAddr(_ *enr.Record) []byte { return id[:] }
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
semVer := version.SemanticVersion()
|
||||
os := runtime.GOOS
|
||||
arch := runtime.GOARCH
|
||||
res, err := (&Server{}).GetVersion(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
v := res.Data.Version
|
||||
assert.Equal(t, true, strings.Contains(v, semVer))
|
||||
assert.Equal(t, true, strings.Contains(v, os))
|
||||
assert.Equal(t, true, strings.Contains(v, arch))
|
||||
}
|
||||
|
||||
func TestGetHealth(t *testing.T) {
|
||||
ctx := grpc.NewContextWithServerTransportStream(context.Background(), &grpcruntime.ServerTransportStream{})
|
||||
checker := &syncmock.Sync{}
|
||||
s := &Server{
|
||||
SyncChecker: checker,
|
||||
}
|
||||
|
||||
_, err := s.GetHealth(ctx, &emptypb.Empty{})
|
||||
require.ErrorContains(t, "Node not initialized or having issues", err)
|
||||
checker.IsInitialized = true
|
||||
_, err = s.GetHealth(ctx, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
stream, ok := grpc.ServerTransportStreamFromContext(ctx).(*grpcruntime.ServerTransportStream)
|
||||
require.Equal(t, true, ok, "type assertion failed")
|
||||
assert.Equal(t, stream.Header()[strings.ToLower(grpcutil.HttpCodeMetadataKey)][0], strconv.Itoa(http.StatusPartialContent))
|
||||
checker.IsSynced = true
|
||||
_, err = s.GetHealth(ctx, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetIdentity(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p2pAddr, err := ma.NewMultiaddr("/ip4/7.7.7.7/udp/30303")
|
||||
require.NoError(t, err)
|
||||
discAddr1, err := ma.NewMultiaddr("/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
|
||||
require.NoError(t, err)
|
||||
discAddr2, err := ma.NewMultiaddr("/ip6/1:2:3:4:5:6:7:8/udp/20202/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
|
||||
require.NoError(t, err)
|
||||
enrRecord := &enr.Record{}
|
||||
err = enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{7, 7, 7, 7})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
attnets := bitfield.NewBitvector64()
|
||||
attnets.SetBitAt(1, true)
|
||||
metadataProvider := &mockp2p.MockMetadataProvider{Data: wrapper.WrappedMetadataV0(&pb.MetaDataV0{SeqNumber: 1, Attnets: attnets})}
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
peerManager := &mockp2p.MockPeerManager{
|
||||
Enr: enrRecord,
|
||||
PID: "foo",
|
||||
BHost: &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
|
||||
DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
|
||||
}
|
||||
s := &Server{
|
||||
PeerManager: peerManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
}
|
||||
|
||||
resp, err := s.GetIdentity(ctx, &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
expectedID := peer.ID("foo").Pretty()
|
||||
assert.Equal(t, expectedID, resp.Data.PeerId)
|
||||
expectedEnr, err := p2p.SerializeENR(enrRecord)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, fmt.Sprint("enr:", expectedEnr), resp.Data.Enr)
|
||||
require.Equal(t, 1, len(resp.Data.P2PAddresses))
|
||||
assert.Equal(t, p2pAddr.String()+"/p2p/"+expectedID, resp.Data.P2PAddresses[0])
|
||||
require.Equal(t, 2, len(resp.Data.DiscoveryAddresses))
|
||||
ipv4Found, ipv6Found := false, false
|
||||
for _, address := range resp.Data.DiscoveryAddresses {
|
||||
if address == discAddr1.String() {
|
||||
ipv4Found = true
|
||||
} else if address == discAddr2.String() {
|
||||
ipv6Found = true
|
||||
}
|
||||
}
|
||||
assert.Equal(t, true, ipv4Found, "IPv4 discovery address not found")
|
||||
assert.Equal(t, true, ipv6Found, "IPv6 discovery address not found")
|
||||
assert.Equal(t, discAddr1.String(), resp.Data.DiscoveryAddresses[0])
|
||||
assert.Equal(t, discAddr2.String(), resp.Data.DiscoveryAddresses[1])
|
||||
})
|
||||
|
||||
t.Run("ENR failure", func(t *testing.T) {
|
||||
peerManager := &mockp2p.MockPeerManager{
|
||||
Enr: &enr.Record{},
|
||||
PID: "foo",
|
||||
BHost: &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
|
||||
DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
|
||||
}
|
||||
s := &Server{
|
||||
PeerManager: peerManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
}
|
||||
|
||||
_, err = s.GetIdentity(ctx, &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "Could not obtain enr", err)
|
||||
})
|
||||
|
||||
t.Run("Discovery addresses failure", func(t *testing.T) {
|
||||
peerManager := &mockp2p.MockPeerManager{
|
||||
Enr: enrRecord,
|
||||
PID: "foo",
|
||||
BHost: &mockp2p.MockHost{Addresses: []ma.Multiaddr{p2pAddr}},
|
||||
DiscoveryAddr: []ma.Multiaddr{discAddr1, discAddr2},
|
||||
FailDiscoveryAddr: true,
|
||||
}
|
||||
s := &Server{
|
||||
PeerManager: peerManager,
|
||||
MetadataProvider: metadataProvider,
|
||||
}
|
||||
|
||||
_, err = s.GetIdentity(ctx, &emptypb.Empty{})
|
||||
assert.ErrorContains(t, "Could not obtain discovery address", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPeer(t *testing.T) {
|
||||
const rawId = "16Uiu2HAkvyYtoQXZNTsthjgLHjEnv7kvwzEmjvsJjWXpbhtqpSUN"
|
||||
ctx := context.Background()
|
||||
decodedId, err := peer.Decode(rawId)
|
||||
require.NoError(t, err)
|
||||
enrRecord := &enr.Record{}
|
||||
err = enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{7, 7, 7, 7})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
const p2pAddr = "/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
peerFetcher.Peers().Add(enrRecord, decodedId, p2pMultiAddr, network.DirInbound)
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
resp, err := s.GetPeer(ctx, ðpb.PeerRequest{PeerId: rawId})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, rawId, resp.Data.PeerId)
|
||||
assert.Equal(t, p2pAddr, resp.Data.LastSeenP2PAddress)
|
||||
assert.Equal(t, "enr:yoABgmlwhAcHBwc", resp.Data.Enr)
|
||||
assert.Equal(t, ethpb.ConnectionState_DISCONNECTED, resp.Data.State)
|
||||
assert.Equal(t, ethpb.PeerDirection_INBOUND, resp.Data.Direction)
|
||||
})
|
||||
|
||||
t.Run("Invalid ID", func(t *testing.T) {
|
||||
_, err = s.GetPeer(ctx, ðpb.PeerRequest{PeerId: "foo"})
|
||||
assert.ErrorContains(t, "Invalid peer ID", err)
|
||||
})
|
||||
|
||||
t.Run("Peer not found", func(t *testing.T) {
|
||||
generatedId := "16Uiu2HAmQqFdEcHbSmQTQuLoAhnMUrgoWoraKK4cUJT6FuuqHqTU"
|
||||
_, err = s.GetPeer(ctx, ðpb.PeerRequest{PeerId: generatedId})
|
||||
assert.ErrorContains(t, "Peer not found", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListPeers(t *testing.T) {
|
||||
ids := libp2ptest.GeneratePeerIDs(9)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
peerFetcher.ClearPeers()
|
||||
peerStatus := peerFetcher.Peers()
|
||||
|
||||
for i, id := range ids {
|
||||
// Make last peer undiscovered
|
||||
if i == len(ids)-1 {
|
||||
peerStatus.Add(nil, id, nil, network.DirUnknown)
|
||||
} else {
|
||||
enrRecord := &enr.Record{}
|
||||
err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{127, 0, 0, byte(i)})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
var p2pAddr = "/ip4/127.0.0." + strconv.Itoa(i) + "/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
var direction network.Direction
|
||||
if i%2 == 0 {
|
||||
direction = network.DirInbound
|
||||
} else {
|
||||
direction = network.DirOutbound
|
||||
}
|
||||
peerStatus.Add(enrRecord, id, p2pMultiAddr, direction)
|
||||
|
||||
switch i {
|
||||
case 0, 1:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnecting)
|
||||
case 2, 3:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnected)
|
||||
case 4, 5:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnecting)
|
||||
case 6, 7:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnected)
|
||||
default:
|
||||
t.Fatalf("Failed to set connection state for peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
|
||||
t.Run("Peer data OK", func(t *testing.T) {
|
||||
// We will check the first peer from the list.
|
||||
expectedId := ids[0]
|
||||
|
||||
resp, err := s.ListPeers(context.Background(), ðpb.PeersRequest{
|
||||
State: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTING},
|
||||
Direction: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(resp.Data))
|
||||
returnedPeer := resp.Data[0]
|
||||
assert.Equal(t, expectedId.Pretty(), returnedPeer.PeerId)
|
||||
expectedEnr, err := peerStatus.ENR(expectedId)
|
||||
require.NoError(t, err)
|
||||
serializedEnr, err := p2p.SerializeENR(expectedEnr)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "enr:"+serializedEnr, returnedPeer.Enr)
|
||||
expectedP2PAddr, err := peerStatus.Address(expectedId)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedP2PAddr.String(), returnedPeer.LastSeenP2PAddress)
|
||||
assert.Equal(t, ethpb.ConnectionState_CONNECTING, returnedPeer.State)
|
||||
assert.Equal(t, ethpb.PeerDirection_INBOUND, returnedPeer.Direction)
|
||||
})
|
||||
|
||||
filterTests := []struct {
|
||||
name string
|
||||
states []ethpb.ConnectionState
|
||||
directions []ethpb.PeerDirection
|
||||
wantIds []peer.ID
|
||||
}{
|
||||
{
|
||||
name: "No filters - return all peers",
|
||||
states: []ethpb.ConnectionState{},
|
||||
directions: []ethpb.PeerDirection{},
|
||||
wantIds: ids[:len(ids)-1], // Excluding last peer as it is not connected.
|
||||
},
|
||||
{
|
||||
name: "State filter empty - return peers for all states",
|
||||
states: []ethpb.ConnectionState{},
|
||||
directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND},
|
||||
wantIds: []peer.ID{ids[0], ids[2], ids[4], ids[6]},
|
||||
},
|
||||
{
|
||||
name: "Direction filter empty - return peers for all directions",
|
||||
states: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED},
|
||||
directions: []ethpb.PeerDirection{},
|
||||
wantIds: []peer.ID{ids[2], ids[3]},
|
||||
},
|
||||
{
|
||||
name: "One state and direction",
|
||||
states: []ethpb.ConnectionState{ethpb.ConnectionState_DISCONNECTED},
|
||||
directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND},
|
||||
wantIds: []peer.ID{ids[6]},
|
||||
},
|
||||
{
|
||||
name: "Multiple states and directions",
|
||||
states: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTING, ethpb.ConnectionState_DISCONNECTING},
|
||||
directions: []ethpb.PeerDirection{ethpb.PeerDirection_INBOUND, ethpb.PeerDirection_OUTBOUND},
|
||||
wantIds: []peer.ID{ids[0], ids[1], ids[4], ids[5]},
|
||||
},
|
||||
{
|
||||
name: "Unknown filter is ignored",
|
||||
states: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED, 99},
|
||||
directions: []ethpb.PeerDirection{ethpb.PeerDirection_OUTBOUND, 99},
|
||||
wantIds: []peer.ID{ids[3]},
|
||||
},
|
||||
{
|
||||
name: "Only unknown filters - return all peers",
|
||||
states: []ethpb.ConnectionState{99},
|
||||
directions: []ethpb.PeerDirection{99},
|
||||
wantIds: ids[:len(ids)-1], // Excluding last peer as it is not connected.
|
||||
},
|
||||
}
|
||||
for _, tt := range filterTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resp, err := s.ListPeers(context.Background(), ðpb.PeersRequest{
|
||||
State: tt.states,
|
||||
Direction: tt.directions,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, len(tt.wantIds), len(resp.Data), "Wrong number of peers returned")
|
||||
for _, id := range tt.wantIds {
|
||||
expectedId := id.Pretty()
|
||||
found := false
|
||||
for _, returnedPeer := range resp.Data {
|
||||
if returnedPeer.PeerId == expectedId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Expected ID '" + expectedId + "' not found")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPeers_NoPeersReturnsEmptyArray(t *testing.T) {
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
peerFetcher.ClearPeers()
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
|
||||
resp, err := s.ListPeers(context.Background(), ðpb.PeersRequest{
|
||||
State: []ethpb.ConnectionState{ethpb.ConnectionState_CONNECTED},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Equal(t, 0, len(resp.Data))
|
||||
}
|
||||
|
||||
func TestPeerCount(t *testing.T) {
|
||||
ids := libp2ptest.GeneratePeerIDs(10)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
peerFetcher.ClearPeers()
|
||||
peerStatus := peerFetcher.Peers()
|
||||
|
||||
for i, id := range ids {
|
||||
enrRecord := &enr.Record{}
|
||||
err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(t, err)
|
||||
enrRecord.Set(enr.IPv4{127, 0, 0, byte(i)})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(t, err)
|
||||
var p2pAddr = "/ip4/127.0.0." + strconv.Itoa(i) + "/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
var direction network.Direction
|
||||
if i%2 == 0 {
|
||||
direction = network.DirInbound
|
||||
} else {
|
||||
direction = network.DirOutbound
|
||||
}
|
||||
peerStatus.Add(enrRecord, id, p2pMultiAddr, direction)
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnecting)
|
||||
case 1, 2:
|
||||
peerStatus.SetConnectionState(id, peers.PeerConnected)
|
||||
case 3, 4, 5:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnecting)
|
||||
case 6, 7, 8, 9:
|
||||
peerStatus.SetConnectionState(id, peers.PeerDisconnected)
|
||||
default:
|
||||
t.Fatalf("Failed to set connection state for peer")
|
||||
}
|
||||
}
|
||||
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
resp, err := s.PeerCount(context.Background(), &emptypb.Empty{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(1), resp.Data.Connecting, "Wrong number of connecting peers")
|
||||
assert.Equal(t, uint64(2), resp.Data.Connected, "Wrong number of connected peers")
|
||||
assert.Equal(t, uint64(3), resp.Data.Disconnecting, "Wrong number of disconnecting peers")
|
||||
assert.Equal(t, uint64(4), resp.Data.Disconnected, "Wrong number of disconnected peers")
|
||||
}
|
||||
|
||||
func BenchmarkListPeers(b *testing.B) {
|
||||
// We simulate having a lot of peers.
|
||||
ids := libp2ptest.GeneratePeerIDs(2000)
|
||||
peerFetcher := &mockp2p.MockPeersProvider{}
|
||||
|
||||
for _, id := range ids {
|
||||
enrRecord := &enr.Record{}
|
||||
err := enrRecord.SetSig(dummyIdentity{1}, []byte{42})
|
||||
require.NoError(b, err)
|
||||
enrRecord.Set(enr.IPv4{7, 7, 7, 7})
|
||||
err = enrRecord.SetSig(dummyIdentity{}, []byte{})
|
||||
require.NoError(b, err)
|
||||
const p2pAddr = "/ip4/7.7.7.7/udp/30303/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N"
|
||||
p2pMultiAddr, err := ma.NewMultiaddr(p2pAddr)
|
||||
require.NoError(b, err)
|
||||
peerFetcher.Peers().Add(enrRecord, id, p2pMultiAddr, network.DirInbound)
|
||||
}
|
||||
|
||||
s := Server{PeersFetcher: peerFetcher}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := s.ListPeers(context.Background(), ðpb.PeersRequest{
|
||||
State: []ethpb.ConnectionState{},
|
||||
Direction: []ethpb.PeerDirection{},
|
||||
})
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
|
||||
)
|
||||
|
||||
var _ ethpbservice.BeaconNodeServer = (*Server)(nil)
|
||||
@@ -11,3 +11,55 @@ type SyncStatusResponseData struct {
|
||||
IsOptimistic bool `json:"is_optimistic"`
|
||||
ElOffline bool `json:"el_offline"`
|
||||
}
|
||||
|
||||
type GetIdentityResponse struct {
|
||||
Data *Identity `json:"data"`
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
PeerId string `json:"peer_id"`
|
||||
Enr string `json:"enr"`
|
||||
P2PAddresses []string `json:"p2p_addresses"`
|
||||
DiscoveryAddresses []string `json:"discovery_addresses"`
|
||||
Metadata *Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
SeqNumber string `json:"seq_number"`
|
||||
Attnets string `json:"attnets"`
|
||||
}
|
||||
|
||||
type GetPeerResponse struct {
|
||||
Data *Peer `json:"data"`
|
||||
}
|
||||
|
||||
type GetPeersResponse struct {
|
||||
Data []*Peer `json:"data"`
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
PeerId string `json:"peer_id"`
|
||||
Enr string `json:"enr"`
|
||||
LastSeenP2PAddress string `json:"last_seen_p2p_address"`
|
||||
State string `json:"state"`
|
||||
Direction string `json:"direction"`
|
||||
}
|
||||
|
||||
type GetPeerCountResponse struct {
|
||||
Data *PeerCount `json:"data"`
|
||||
}
|
||||
|
||||
type PeerCount struct {
|
||||
Disconnected string `json:"disconnected"`
|
||||
Connecting string `json:"connecting"`
|
||||
Connected string `json:"connected"`
|
||||
Disconnecting string `json:"disconnecting"`
|
||||
}
|
||||
|
||||
type GetVersionResponse struct {
|
||||
Data *Version `json:"data"`
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"handlers.go",
|
||||
"server.go",
|
||||
"service.go",
|
||||
"structs.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards",
|
||||
@@ -21,11 +22,13 @@ go_library(
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_wealdtech_go_bytesutil//:go_default_library",
|
||||
"@io_opencensus_go//trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -54,6 +57,7 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -8,9 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair"
|
||||
coreblocks "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/epoch/precompute"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
@@ -20,10 +18,13 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
"github.com/wealdtech/go-bytesutil"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// BlockRewards is an HTTP handler for Beacon API getBlockRewards.
|
||||
func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.BlockRewards")
|
||||
defer span.End()
|
||||
segments := strings.Split(r.URL.Path, "/")
|
||||
blockId := segments[len(segments)-1]
|
||||
|
||||
@@ -36,63 +37,6 @@ func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// We want to run several block processing functions that update the proposer's balance.
|
||||
// This will allow us to calculate proposer rewards for each operation (atts, slashings etc).
|
||||
// To do this, we replay the state up to the block's slot, but before processing the block.
|
||||
st, err := s.ReplayerBuilder.ReplayerForSlot(blk.Block().Slot()-1).ReplayToSlot(r.Context(), blk.Block().Slot())
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
proposerIndex := blk.Block().ProposerIndex()
|
||||
initBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err = altair.ProcessAttestationsNoVerifySignature(r.Context(), st, blk)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get attestation rewards"+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
attBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err = coreblocks.ProcessAttesterSlashings(r.Context(), st, blk.Block().Body().AttesterSlashings(), validators.SlashValidator)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get attester slashing rewards: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
attSlashingsBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
st, err = coreblocks.ProcessProposerSlashings(r.Context(), st, blk.Block().Body().ProposerSlashings(), validators.SlashValidator)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get proposer slashing rewards"+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
proposerSlashingsBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get proposer's balance: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
sa, err := blk.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync aggregate: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var syncCommitteeReward uint64
|
||||
_, syncCommitteeReward, err = altair.ProcessSyncAggregate(r.Context(), st, sa)
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get sync aggregate rewards: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
optimistic, err := s.OptimisticModeFetcher.IsOptimistic(r.Context())
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get optimistic mode info: "+err.Error(), http.StatusInternalServerError)
|
||||
@@ -103,18 +47,15 @@ func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) {
|
||||
http2.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
blockRewards, httpError := s.BlockRewardFetcher.GetBlockRewardsData(ctx, blk)
|
||||
if httpError != nil {
|
||||
http2.WriteError(w, httpError)
|
||||
return
|
||||
}
|
||||
response := &BlockRewardsResponse{
|
||||
Data: BlockRewards{
|
||||
ProposerIndex: strconv.FormatUint(uint64(proposerIndex), 10),
|
||||
Total: strconv.FormatUint(proposerSlashingsBalance-initBalance+syncCommitteeReward, 10),
|
||||
Attestations: strconv.FormatUint(attBalance-initBalance, 10),
|
||||
SyncAggregate: strconv.FormatUint(syncCommitteeReward, 10),
|
||||
ProposerSlashings: strconv.FormatUint(proposerSlashingsBalance-attSlashingsBalance, 10),
|
||||
AttesterSlashings: strconv.FormatUint(attSlashingsBalance-attBalance, 10),
|
||||
},
|
||||
Data: blockRewards,
|
||||
ExecutionOptimistic: optimistic,
|
||||
Finalized: s.FinalizationFetcher.IsFinalized(r.Context(), blkRoot),
|
||||
Finalized: s.FinalizationFetcher.IsFinalized(ctx, blkRoot),
|
||||
}
|
||||
http2.WriteJson(w, response)
|
||||
}
|
||||
@@ -165,6 +106,8 @@ func (s *Server) AttestationRewards(w http.ResponseWriter, r *http.Request) {
|
||||
// SyncCommitteeRewards retrieves rewards info for sync committee members specified by array of public keys or validator index.
|
||||
// If no array is provided, return reward info for every committee member.
|
||||
func (s *Server) SyncCommitteeRewards(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.SyncCommitteeRewards")
|
||||
defer span.End()
|
||||
segments := strings.Split(r.URL.Path, "/")
|
||||
blockId := segments[len(segments)-1]
|
||||
|
||||
@@ -176,9 +119,10 @@ func (s *Server) SyncCommitteeRewards(w http.ResponseWriter, r *http.Request) {
|
||||
http2.HandleError(w, "Sync committee rewards are not supported for Phase 0", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
st, err := s.ReplayerBuilder.ReplayerForSlot(blk.Block().Slot()-1).ReplayToSlot(r.Context(), blk.Block().Slot())
|
||||
if err != nil {
|
||||
http2.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError)
|
||||
|
||||
st, httpErr := s.BlockRewardFetcher.GetStateForRewards(ctx, blk)
|
||||
if httpErr != nil {
|
||||
http2.WriteError(w, httpErr)
|
||||
return
|
||||
}
|
||||
sa, err := blk.Block().Body().SyncAggregate()
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair"
|
||||
@@ -34,12 +35,42 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
func TestBlockRewards(t *testing.T) {
|
||||
func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, interfaces.SignedBeaconBlock, error) {
|
||||
helpers.ClearCache()
|
||||
|
||||
var sbb interfaces.SignedBeaconBlock
|
||||
var st state.BeaconState
|
||||
var err error
|
||||
switch forkName {
|
||||
case "phase0":
|
||||
return nil, nil, errors.New("phase0 not supported")
|
||||
case "altair":
|
||||
st, err = util.NewBeaconStateAltair()
|
||||
require.NoError(t, err)
|
||||
b := util.HydrateSignedBeaconBlockAltair(util.NewBeaconBlockAltair())
|
||||
sbb, err = blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
case "bellatrix":
|
||||
st, err = util.NewBeaconStateBellatrix()
|
||||
require.NoError(t, err)
|
||||
b := util.HydrateSignedBeaconBlockBellatrix(util.NewBeaconBlockBellatrix())
|
||||
sbb, err = blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
case "capella":
|
||||
st, err = util.NewBeaconStateCapella()
|
||||
require.NoError(t, err)
|
||||
b := util.HydrateSignedBeaconBlockCapella(util.NewBeaconBlockCapella())
|
||||
sbb, err = blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
case "deneb":
|
||||
st, err = util.NewBeaconStateDeneb()
|
||||
require.NoError(t, err)
|
||||
b := util.HydrateSignedBeaconBlockDeneb(util.NewBeaconBlockDeneb())
|
||||
sbb, err = blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
return nil, nil, errors.New("fork is not supported")
|
||||
}
|
||||
valCount := 64
|
||||
|
||||
st, err := util.NewBeaconStateCapella()
|
||||
require.NoError(t, st.SetSlot(1))
|
||||
require.NoError(t, err)
|
||||
validators := make([]*eth.Validator, 0, valCount)
|
||||
@@ -68,11 +99,10 @@ func TestBlockRewards(t *testing.T) {
|
||||
bRoots[0] = slot0bRoot
|
||||
require.NoError(t, st.SetBlockRoots(bRoots))
|
||||
|
||||
b := util.HydrateSignedBeaconBlockCapella(util.NewBeaconBlockCapella())
|
||||
b.Block.Slot = 2
|
||||
sbb.SetSlot(2)
|
||||
// we have to set the proposer index to the value that will be randomly chosen (fortunately it's deterministic)
|
||||
b.Block.ProposerIndex = 12
|
||||
b.Block.Body.Attestations = []*eth.Attestation{
|
||||
sbb.SetProposerIndex(12)
|
||||
sbb.SetAttestations([]*eth.Attestation{
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0b00000111},
|
||||
Data: util.HydrateAttestationData(ð.AttestationData{}),
|
||||
@@ -83,7 +113,8 @@ func TestBlockRewards(t *testing.T) {
|
||||
Data: util.HydrateAttestationData(ð.AttestationData{}),
|
||||
Signature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
attData1 := util.HydrateAttestationData(ð.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32)})
|
||||
attData2 := util.HydrateAttestationData(ð.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32)})
|
||||
domain, err := signing.Domain(st.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, st.GenesisValidatorsRoot())
|
||||
@@ -92,7 +123,7 @@ func TestBlockRewards(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
sigRoot2, err := signing.ComputeSigningRoot(attData2, domain)
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.AttesterSlashings = []*eth.AttesterSlashing{
|
||||
sbb.SetAttesterSlashings([]*eth.AttesterSlashing{
|
||||
{
|
||||
Attestation_1: ð.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
@@ -105,7 +136,7 @@ func TestBlockRewards(t *testing.T) {
|
||||
Signature: secretKeys[0].Sign(sigRoot2[:]).Marshal(),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
header1 := ð.BeaconBlockHeader{
|
||||
Slot: 0,
|
||||
ProposerIndex: 1,
|
||||
@@ -126,7 +157,7 @@ func TestBlockRewards(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
sigRoot2, err = signing.ComputeSigningRoot(header2, domain)
|
||||
require.NoError(t, err)
|
||||
b.Block.Body.ProposerSlashings = []*eth.ProposerSlashing{
|
||||
sbb.SetProposerSlashings([]*eth.ProposerSlashing{
|
||||
{
|
||||
Header_1: ð.SignedBeaconBlockHeader{
|
||||
Header: header1,
|
||||
@@ -137,7 +168,7 @@ func TestBlockRewards(t *testing.T) {
|
||||
Signature: secretKeys[1].Sign(sigRoot2[:]).Marshal(),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
scBits := bitfield.NewBitvector512()
|
||||
scBits.SetBitAt(10, true)
|
||||
scBits.SetBitAt(100, true)
|
||||
@@ -153,24 +184,51 @@ func TestBlockRewards(t *testing.T) {
|
||||
sig2, err := blst.SignatureFromBytes(secretKeys[19].Sign(r[:]).Marshal())
|
||||
require.NoError(t, err)
|
||||
aggSig := bls.AggregateSignatures([]bls.Signature{sig1, sig2}).Marshal()
|
||||
b.Block.Body.SyncAggregate = ð.SyncAggregate{SyncCommitteeBits: scBits, SyncCommitteeSignature: aggSig}
|
||||
|
||||
sbb, err := blocks.NewSignedBeaconBlock(b)
|
||||
err = sbb.SetSyncAggregate(ð.SyncAggregate{SyncCommitteeBits: scBits, SyncCommitteeSignature: aggSig})
|
||||
require.NoError(t, err)
|
||||
|
||||
return st, sbb, nil
|
||||
}
|
||||
|
||||
func TestBlockRewards(t *testing.T) {
|
||||
phase0block, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock())
|
||||
require.NoError(t, err)
|
||||
mockChainService := &mock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
|
||||
0: phase0block,
|
||||
2: sbb,
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st)),
|
||||
}
|
||||
t.Run("phase 0", func(t *testing.T) {
|
||||
mockChainService := &mock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
|
||||
0: phase0block,
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
}
|
||||
url := "http://only.the.slot.number.at.the.end.is.important/0"
|
||||
request := httptest.NewRequest("GET", url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.BlockRewards(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.Equal(t, "Block rewards are not supported for Phase 0 blocks", e.Message)
|
||||
})
|
||||
t.Run("altair", func(t *testing.T) {
|
||||
st, sbb, err := BlockRewardTestSetup(t, "altair")
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainService := &mock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
|
||||
0: phase0block,
|
||||
2: sbb,
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))},
|
||||
}
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
url := "http://only.the.slot.number.at.the.end.is.important/2"
|
||||
request := httptest.NewRequest("GET", url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
@@ -189,18 +247,104 @@ func TestBlockRewards(t *testing.T) {
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, false, resp.Finalized)
|
||||
})
|
||||
t.Run("phase 0", func(t *testing.T) {
|
||||
url := "http://only.the.slot.number.at.the.end.is.important/0"
|
||||
t.Run("bellatrix", func(t *testing.T) {
|
||||
st, sbb, err := BlockRewardTestSetup(t, "bellatrix")
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainService := &mock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
|
||||
0: phase0block,
|
||||
2: sbb,
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))},
|
||||
}
|
||||
|
||||
url := "http://only.the.slot.number.at.the.end.is.important/2"
|
||||
request := httptest.NewRequest("GET", url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.BlockRewards(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &http2.DefaultErrorJson{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.Equal(t, "Block rewards are not supported for Phase 0 blocks", e.Message)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &BlockRewardsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, "12", resp.Data.ProposerIndex)
|
||||
assert.Equal(t, "125089490", resp.Data.Total)
|
||||
assert.Equal(t, "89442", resp.Data.Attestations)
|
||||
assert.Equal(t, "48", resp.Data.SyncAggregate)
|
||||
assert.Equal(t, "62500000", resp.Data.AttesterSlashings)
|
||||
assert.Equal(t, "62500000", resp.Data.ProposerSlashings)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, false, resp.Finalized)
|
||||
})
|
||||
t.Run("capella", func(t *testing.T) {
|
||||
st, sbb, err := BlockRewardTestSetup(t, "capella")
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainService := &mock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
|
||||
0: phase0block,
|
||||
2: sbb,
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))},
|
||||
}
|
||||
|
||||
url := "http://only.the.slot.number.at.the.end.is.important/2"
|
||||
request := httptest.NewRequest("GET", url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.BlockRewards(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &BlockRewardsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, "12", resp.Data.ProposerIndex)
|
||||
assert.Equal(t, "125089490", resp.Data.Total)
|
||||
assert.Equal(t, "89442", resp.Data.Attestations)
|
||||
assert.Equal(t, "48", resp.Data.SyncAggregate)
|
||||
assert.Equal(t, "62500000", resp.Data.AttesterSlashings)
|
||||
assert.Equal(t, "62500000", resp.Data.ProposerSlashings)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, false, resp.Finalized)
|
||||
})
|
||||
t.Run("deneb", func(t *testing.T) {
|
||||
st, sbb, err := BlockRewardTestSetup(t, "deneb")
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainService := &mock.ChainService{Optimistic: true}
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
|
||||
0: phase0block,
|
||||
2: sbb,
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))},
|
||||
}
|
||||
|
||||
url := "http://only.the.slot.number.at.the.end.is.important/2"
|
||||
request := httptest.NewRequest("GET", url, nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.BlockRewards(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &BlockRewardsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, "12", resp.Data.ProposerIndex)
|
||||
assert.Equal(t, "125089490", resp.Data.Total)
|
||||
assert.Equal(t, "89442", resp.Data.Attestations)
|
||||
assert.Equal(t, "48", resp.Data.SyncAggregate)
|
||||
assert.Equal(t, "62500000", resp.Data.AttesterSlashings)
|
||||
assert.Equal(t, "62500000", resp.Data.ProposerSlashings)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, false, resp.Finalized)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -560,7 +704,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
|
||||
}},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
ReplayerBuilder: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st)),
|
||||
BlockRewardFetcher: &BlockRewardService{Replayer: mockstategen.NewMockReplayerBuilder(mockstategen.WithMockState(st))},
|
||||
}
|
||||
|
||||
t.Run("ok - filtered vals", func(t *testing.T) {
|
||||
|
||||
@@ -3,15 +3,14 @@ package rewards
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Blocker lookup.Blocker
|
||||
OptimisticModeFetcher blockchain.OptimisticModeFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
ReplayerBuilder stategen.ReplayerBuilder
|
||||
TimeFetcher blockchain.TimeFetcher
|
||||
Stater lookup.Stater
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
BlockRewardFetcher BlockRewardsFetcher
|
||||
}
|
||||
|
||||
124
beacon-chain/rpc/eth/rewards/service.go
Normal file
124
beacon-chain/rpc/eth/rewards/service.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package rewards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/altair"
|
||||
coreblocks "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
)
|
||||
|
||||
// BlockRewardsFetcher is a interface that provides access to reward related responses
|
||||
type BlockRewardsFetcher interface {
|
||||
GetBlockRewardsData(context.Context, interfaces.ReadOnlySignedBeaconBlock) (*BlockRewards, *http2.DefaultErrorJson)
|
||||
GetStateForRewards(context.Context, interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, *http2.DefaultErrorJson)
|
||||
}
|
||||
|
||||
// BlockRewardService implements BlockRewardsFetcher and can be declared to access the underlying functions
|
||||
type BlockRewardService struct {
|
||||
Replayer stategen.ReplayerBuilder
|
||||
}
|
||||
|
||||
// GetBlockRewardsData returns the BlockRewards Object which is used for the BlockRewardsResponse and ProduceBlockV3
|
||||
func (rs *BlockRewardService) GetBlockRewardsData(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (*BlockRewards, *http2.DefaultErrorJson) {
|
||||
st, httpErr := rs.GetStateForRewards(ctx, blk)
|
||||
if httpErr != nil {
|
||||
return nil, httpErr
|
||||
}
|
||||
|
||||
proposerIndex := blk.Block().ProposerIndex()
|
||||
initBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get proposer's balance: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
st, err = altair.ProcessAttestationsNoVerifySignature(ctx, st, blk)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get attestation rewards: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
attBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get proposer's balance: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
st, err = coreblocks.ProcessAttesterSlashings(ctx, st, blk.Block().Body().AttesterSlashings(), validators.SlashValidator)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get attester slashing rewards: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
attSlashingsBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get proposer's balance: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
st, err = coreblocks.ProcessProposerSlashings(ctx, st, blk.Block().Body().ProposerSlashings(), validators.SlashValidator)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get proposer slashing rewards: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
proposerSlashingsBalance, err := st.BalanceAtIndex(proposerIndex)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get proposer's balance: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
sa, err := blk.Block().Body().SyncAggregate()
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get sync aggregate: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
var syncCommitteeReward uint64
|
||||
_, syncCommitteeReward, err = altair.ProcessSyncAggregate(ctx, st, sa)
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get sync aggregate rewards: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
return &BlockRewards{
|
||||
ProposerIndex: strconv.FormatUint(uint64(proposerIndex), 10),
|
||||
Total: strconv.FormatUint(proposerSlashingsBalance-initBalance+syncCommitteeReward, 10),
|
||||
Attestations: strconv.FormatUint(attBalance-initBalance, 10),
|
||||
SyncAggregate: strconv.FormatUint(syncCommitteeReward, 10),
|
||||
ProposerSlashings: strconv.FormatUint(proposerSlashingsBalance-attSlashingsBalance, 10),
|
||||
AttesterSlashings: strconv.FormatUint(attSlashingsBalance-attBalance, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetStateForRewards returns the state replayed up to the block's slot
|
||||
func (rs *BlockRewardService) GetStateForRewards(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, *http2.DefaultErrorJson) {
|
||||
// We want to run several block processing functions that update the proposer's balance.
|
||||
// This will allow us to calculate proposer rewards for each operation (atts, slashings etc).
|
||||
// To do this, we replay the state up to the block's slot, but before processing the block.
|
||||
st, err := rs.Replayer.ReplayerForSlot(blk.Block().Slot()-1).ReplayToSlot(ctx, blk.Block().Slot())
|
||||
if err != nil {
|
||||
return nil, &http2.DefaultErrorJson{
|
||||
Message: "Could not get state: " + err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package rewards
|
||||
|
||||
type BlockRewardsResponse struct {
|
||||
Data BlockRewards `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *BlockRewards `json:"data"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
}
|
||||
|
||||
type BlockRewards struct {
|
||||
|
||||
14
beacon-chain/rpc/eth/rewards/testing/BUILD.bazel
Normal file
14
beacon-chain/rpc/eth/rewards/testing/BUILD.bazel
Normal file
@@ -0,0 +1,14 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["mock.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards/testing",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/rpc/eth/rewards:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
],
|
||||
)
|
||||
30
beacon-chain/rpc/eth/rewards/testing/mock.go
Normal file
30
beacon-chain/rpc/eth/rewards/testing/mock.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
)
|
||||
|
||||
type MockBlockRewardFetcher struct {
|
||||
Rewards *rewards.BlockRewards
|
||||
Error *http2.DefaultErrorJson
|
||||
State state.BeaconState
|
||||
}
|
||||
|
||||
func (m *MockBlockRewardFetcher) GetBlockRewardsData(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) (*rewards.BlockRewards, *http2.DefaultErrorJson) {
|
||||
if m.Error != nil {
|
||||
return nil, m.Error
|
||||
}
|
||||
return m.Rewards, nil
|
||||
}
|
||||
|
||||
func (m *MockBlockRewardFetcher) GetStateForRewards(_ context.Context, _ interfaces.ReadOnlySignedBeaconBlock) (state.BeaconState, *http2.DefaultErrorJson) {
|
||||
if m.Error != nil {
|
||||
return nil, m.Error
|
||||
}
|
||||
return m.State, nil
|
||||
}
|
||||
@@ -36,6 +36,8 @@ go_test(
|
||||
srcs = ["errors_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//network/http:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
|
||||
@@ -53,11 +53,13 @@ type IndexedVerificationFailure struct {
|
||||
// WriteStateFetchError writes an appropriate error based on the supplied argument.
|
||||
// The argument error should be a result of fetching state.
|
||||
func WriteStateFetchError(w http.ResponseWriter, err error) {
|
||||
if stateNotFoundErr, ok := err.(*lookup.StateNotFoundError); ok {
|
||||
http2.HandleError(w, "Could not get state: "+stateNotFoundErr.Error(), http.StatusNotFound)
|
||||
if _, ok := err.(*lookup.StateNotFoundError); ok {
|
||||
http2.HandleError(w, "State not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if parseErr, ok := err.(*lookup.StateIdParseError); ok {
|
||||
http2.HandleError(w, "Invalid state ID: "+parseErr.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
http2.HandleError(w, "Could not get state: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
)
|
||||
|
||||
@@ -14,3 +19,41 @@ func TestDecodeError(t *testing.T) {
|
||||
de = NewDecodeError(de, "X")
|
||||
assert.Equal(t, "could not decode X.Y.Z: not a number", de.Error())
|
||||
}
|
||||
|
||||
// TestWriteStateFetchError tests the WriteStateFetchError function
|
||||
// to ensure that the correct error message and code are written to the response
|
||||
// as an expected JSON format.
|
||||
func TestWriteStateFetchError(t *testing.T) {
|
||||
cases := []struct {
|
||||
err error
|
||||
expectedMessage string
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
err: &lookup.StateNotFoundError{},
|
||||
expectedMessage: "State not found",
|
||||
expectedCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
err: &lookup.StateIdParseError{},
|
||||
expectedMessage: "Invalid state ID",
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
err: errors.New("state not found"),
|
||||
expectedMessage: "Could not get state",
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
writer := httptest.NewRecorder()
|
||||
WriteStateFetchError(writer, c.err)
|
||||
|
||||
assert.Equal(t, c.expectedCode, writer.Code, "incorrect status code")
|
||||
assert.StringContains(t, c.expectedMessage, writer.Body.String(), "incorrect error message")
|
||||
|
||||
e := &http2.DefaultErrorJson{}
|
||||
assert.NoError(t, json.Unmarshal(writer.Body.Bytes(), e), "failed to unmarshal response")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
@@ -114,6 +115,90 @@ type Fork struct {
|
||||
Epoch string `json:"epoch"`
|
||||
}
|
||||
|
||||
type SignedBLSToExecutionChange struct {
|
||||
Message *BLSToExecutionChange `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
func (s *SignedBLSToExecutionChange) ToConsensus() (*eth.SignedBLSToExecutionChange, error) {
|
||||
change, err := s.Message.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Message")
|
||||
}
|
||||
sig, err := DecodeHexWithLength(s.Signature, fieldparams.BLSSignatureLength)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Signature")
|
||||
}
|
||||
return ð.SignedBLSToExecutionChange{
|
||||
Message: change,
|
||||
Signature: sig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type BLSToExecutionChange struct {
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
FromBLSPubkey string `json:"from_bls_pubkey"`
|
||||
ToExecutionAddress string `json:"to_execution_address"`
|
||||
}
|
||||
|
||||
func (b *BLSToExecutionChange) ToConsensus() (*eth.BLSToExecutionChange, error) {
|
||||
index, err := strconv.ParseUint(b.ValidatorIndex, 10, 64)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "ValidatorIndex")
|
||||
}
|
||||
pubkey, err := DecodeHexWithLength(b.FromBLSPubkey, fieldparams.BLSPubkeyLength)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "FromBLSPubkey")
|
||||
}
|
||||
executionAddress, err := DecodeHexWithLength(b.ToExecutionAddress, common.AddressLength)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "ToExecutionAddress")
|
||||
}
|
||||
return ð.BLSToExecutionChange{
|
||||
ValidatorIndex: primitives.ValidatorIndex(index),
|
||||
FromBlsPubkey: pubkey,
|
||||
ToExecutionAddress: executionAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlsToExecutionChangeFromConsensus(blsToExecutionChange *eth.BLSToExecutionChange) (*BLSToExecutionChange, error) {
|
||||
if blsToExecutionChange == nil {
|
||||
return nil, errors.New("BLSToExecutionChange is empty")
|
||||
}
|
||||
|
||||
return &BLSToExecutionChange{
|
||||
ValidatorIndex: strconv.FormatUint(uint64(blsToExecutionChange.ValidatorIndex), 10),
|
||||
FromBLSPubkey: hexutil.Encode(blsToExecutionChange.FromBlsPubkey),
|
||||
ToExecutionAddress: hexutil.Encode(blsToExecutionChange.ToExecutionAddress),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBlsToExecutionChangeFromConsensus(signedBlsToExecutionChange *eth.SignedBLSToExecutionChange) (*SignedBLSToExecutionChange, error) {
|
||||
if signedBlsToExecutionChange == nil {
|
||||
return nil, errors.New("SignedBLSToExecutionChange is empty")
|
||||
}
|
||||
bls, err := BlsToExecutionChangeFromConsensus(signedBlsToExecutionChange.Message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SignedBLSToExecutionChange{
|
||||
Message: bls,
|
||||
Signature: hexutil.Encode(signedBlsToExecutionChange.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SignedBlsToExecutionChangesFromConsensus(blsToExecutionChanges []*eth.SignedBLSToExecutionChange) ([]*SignedBLSToExecutionChange, error) {
|
||||
jsonBlsToExecutionChanges := make([]*SignedBLSToExecutionChange, len(blsToExecutionChanges))
|
||||
for index, signedBlsToExecutionChange := range blsToExecutionChanges {
|
||||
sbls, err := SignedBlsToExecutionChangeFromConsensus(signedBlsToExecutionChange)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("blsExecutionChange message failed to encode at index %d", index))
|
||||
}
|
||||
jsonBlsToExecutionChanges[index] = sbls
|
||||
}
|
||||
return jsonBlsToExecutionChanges, nil
|
||||
}
|
||||
|
||||
func (s *Fork) ToConsensus() (*eth.Fork, error) {
|
||||
previousVersion, err := hexutil.Decode(s.PreviousVersion)
|
||||
if err != nil {
|
||||
@@ -146,13 +231,10 @@ func (s *SignedValidatorRegistration) ToConsensus() (*eth.SignedValidatorRegistr
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Message")
|
||||
}
|
||||
sig, err := hexutil.Decode(s.Signature)
|
||||
sig, err := DecodeHexWithLength(s.Signature, fieldparams.BLSSignatureLength)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "Signature")
|
||||
}
|
||||
if len(sig) != fieldparams.BLSSignatureLength {
|
||||
return nil, fmt.Errorf("Signature length was %d when expecting length %d", len(sig), fieldparams.BLSSignatureLength)
|
||||
}
|
||||
return ð.SignedValidatorRegistrationV1{
|
||||
Message: msg,
|
||||
Signature: sig,
|
||||
@@ -160,19 +242,13 @@ func (s *SignedValidatorRegistration) ToConsensus() (*eth.SignedValidatorRegistr
|
||||
}
|
||||
|
||||
func (s *ValidatorRegistration) ToConsensus() (*eth.ValidatorRegistrationV1, error) {
|
||||
feeRecipient, err := hexutil.Decode(s.FeeRecipient)
|
||||
feeRecipient, err := DecodeHexWithLength(s.FeeRecipient, fieldparams.FeeRecipientLength)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "FeeRecipient")
|
||||
}
|
||||
if len(feeRecipient) != fieldparams.FeeRecipientLength {
|
||||
return nil, fmt.Errorf("feeRecipient length was %d when expecting length %d", len(feeRecipient), fieldparams.FeeRecipientLength)
|
||||
}
|
||||
pubKey, err := hexutil.Decode(s.Pubkey)
|
||||
pubKey, err := DecodeHexWithLength(s.Pubkey, fieldparams.BLSPubkeyLength)
|
||||
if err != nil {
|
||||
return nil, NewDecodeError(err, "FeeRecipient")
|
||||
}
|
||||
if len(pubKey) != fieldparams.BLSPubkeyLength {
|
||||
return nil, fmt.Errorf("FeeRecipient length was %d when expecting length %d", len(pubKey), fieldparams.BLSPubkeyLength)
|
||||
return nil, NewDecodeError(err, "Pubkey")
|
||||
}
|
||||
gasLimit, err := strconv.ParseUint(s.GasLimit, 10, 64)
|
||||
if err != nil {
|
||||
|
||||
@@ -162,7 +162,7 @@ const (
|
||||
}`
|
||||
AltairBlock = `{
|
||||
"message": {
|
||||
"slot": "1",
|
||||
"slot": "2",
|
||||
"proposer_index": "1",
|
||||
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
@@ -204,7 +204,7 @@ const (
|
||||
"attesting_indices": [
|
||||
"1"
|
||||
],
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"signature": "0xabb0124c7574f281a293f4185cad3cb22681d520917ce46665243eacb051000d8bacf75e1451870ca6b3b9e6c9d41a7b02ead2685a84188a4fafd3825daf6a989625d719ccd2d83a40101f4a453fca62878c890eca622363f9ddb8f367a91e84",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
@@ -223,18 +223,18 @@ const (
|
||||
"attesting_indices": [
|
||||
"1"
|
||||
],
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"signature": "0xabb0124c7574f281a293f4185cad3cb22681d520917ce46665243eacb051000d8bacf75e1451870ca6b3b9e6c9d41a7b02ead2685a84188a4fafd3825daf6a989625d719ccd2d83a40101f4a453fca62878c890eca622363f9ddb8f367a91e84",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f3"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,18 +242,18 @@ const (
|
||||
],
|
||||
"attestations": [
|
||||
{
|
||||
"aggregation_bits": "0xffffffffffffffffffffffffffffffffff3f",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"aggregation_bits": "0x07",
|
||||
"signature": "0xabb0124c7574f281a293f4185cad3cb22681d520917ce46665243eacb051000d8bacf75e1451870ca6b3b9e6c9d41a7b02ead2685a84188a4fafd3825daf6a989625d719ccd2d83a40101f4a453fca62878c890eca622363f9ddb8f367a91e84",
|
||||
"data": {
|
||||
"slot": "1",
|
||||
"index": "1",
|
||||
"slot": "0",
|
||||
"index": "0",
|
||||
"beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"source": {
|
||||
"epoch": "1",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"target": {
|
||||
"epoch": "1",
|
||||
"epoch": "0",
|
||||
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ go_library(
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/eth/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/eth/rewards:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
@@ -33,6 +34,8 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
@@ -75,6 +78,8 @@ go_test(
|
||||
"//beacon-chain/operations/synccommittee:go_default_library",
|
||||
"//beacon-chain/p2p/testing:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/eth/rewards:go_default_library",
|
||||
"//beacon-chain/rpc/eth/rewards/testing:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared/testing:go_default_library",
|
||||
"//beacon-chain/rpc/testutil:go_default_library",
|
||||
|
||||
@@ -9,13 +9,15 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
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/consensus-types/primitives"
|
||||
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
@@ -76,24 +78,33 @@ func (s *Server) ProduceBlockV3(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) produceBlockV3(ctx context.Context, w http.ResponseWriter, r *http.Request, v1alpha1req *eth.BlockRequest) {
|
||||
isSSZ := http2.SszRequested(r)
|
||||
if !isSSZ {
|
||||
log.Error("Checking for SSZ failed, defaulting to JSON")
|
||||
}
|
||||
v1alpha1resp, err := s.V1Alpha1Server.GetBeaconBlock(ctx, v1alpha1req)
|
||||
if err != nil {
|
||||
http2.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
consensusBlockValue, httpError := getConsensusBlockValue(ctx, s.BlockRewardFetcher, v1alpha1resp.Block)
|
||||
if httpError != nil {
|
||||
http2.WriteError(w, httpError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set(api.ExecutionPayloadBlindedHeader, fmt.Sprintf("%v", v1alpha1resp.IsBlinded))
|
||||
w.Header().Set(api.ExecutionPayloadValueHeader, fmt.Sprintf("%d", v1alpha1resp.PayloadValue))
|
||||
w.Header().Set(api.ConsensusBlockValueHeader, consensusBlockValue)
|
||||
|
||||
phase0Block, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Phase0)
|
||||
if ok {
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Phase0))
|
||||
// rewards aren't used in phase 0
|
||||
handleProducePhase0V3(ctx, w, isSSZ, phase0Block, v1alpha1resp.PayloadValue)
|
||||
return
|
||||
}
|
||||
altairBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Altair)
|
||||
if ok {
|
||||
handleProduceAltairV3(ctx, w, isSSZ, altairBlock, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Altair))
|
||||
handleProduceAltairV3(ctx, w, isSSZ, altairBlock, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
optimistic, err := s.OptimisticModeFetcher.IsOptimistic(ctx)
|
||||
@@ -107,36 +118,88 @@ func (s *Server) produceBlockV3(ctx context.Context, w http.ResponseWriter, r *h
|
||||
}
|
||||
blindedBellatrixBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedBellatrix)
|
||||
if ok {
|
||||
handleProduceBlindedBellatrixV3(ctx, w, isSSZ, blindedBellatrixBlock, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Bellatrix))
|
||||
handleProduceBlindedBellatrixV3(ctx, w, isSSZ, blindedBellatrixBlock, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
bellatrixBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Bellatrix)
|
||||
if ok {
|
||||
handleProduceBellatrixV3(ctx, w, isSSZ, bellatrixBlock, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Bellatrix))
|
||||
handleProduceBellatrixV3(ctx, w, isSSZ, bellatrixBlock, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
blindedCapellaBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedCapella)
|
||||
if ok {
|
||||
handleProduceBlindedCapellaV3(ctx, w, isSSZ, blindedCapellaBlock, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Capella))
|
||||
handleProduceBlindedCapellaV3(ctx, w, isSSZ, blindedCapellaBlock, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
capellaBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Capella)
|
||||
if ok {
|
||||
handleProduceCapellaV3(ctx, w, isSSZ, capellaBlock, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Capella))
|
||||
handleProduceCapellaV3(ctx, w, isSSZ, capellaBlock, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
blindedDenebBlockContents, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedDeneb)
|
||||
if ok {
|
||||
handleProduceBlindedDenebV3(ctx, w, isSSZ, blindedDenebBlockContents, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Deneb))
|
||||
handleProduceBlindedDenebV3(ctx, w, isSSZ, blindedDenebBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
denebBlockContents, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Deneb)
|
||||
if ok {
|
||||
handleProduceDenebV3(ctx, w, isSSZ, denebBlockContents, v1alpha1resp.PayloadValue)
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Deneb))
|
||||
handleProduceDenebV3(ctx, w, isSSZ, denebBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getConsensusBlockValue(ctx context.Context, blockRewardsFetcher rewards.BlockRewardsFetcher, i interface{} /* block as argument */) (string, *http2.DefaultErrorJson) {
|
||||
var wrapper interfaces.ReadOnlySignedBeaconBlock
|
||||
var err error
|
||||
|
||||
// TODO: we should not require this fake signed wrapper and fix associated functions in the future.
|
||||
switch b := i.(type) {
|
||||
case *eth.GenericBeaconBlock_Phase0:
|
||||
//ignore for phase0
|
||||
return "", nil
|
||||
case *eth.GenericBeaconBlock_Altair:
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Altair{Altair: ð.SignedBeaconBlockAltair{Block: b.Altair}})
|
||||
case *eth.GenericBeaconBlock_Bellatrix:
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Bellatrix{Bellatrix: ð.SignedBeaconBlockBellatrix{Block: b.Bellatrix}})
|
||||
case *eth.GenericBeaconBlock_BlindedBellatrix:
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_BlindedBellatrix{BlindedBellatrix: ð.SignedBlindedBeaconBlockBellatrix{Block: b.BlindedBellatrix}})
|
||||
case *eth.GenericBeaconBlock_Capella:
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Capella{Capella: ð.SignedBeaconBlockCapella{Block: b.Capella}})
|
||||
case *eth.GenericBeaconBlock_BlindedCapella:
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_BlindedCapella{BlindedCapella: ð.SignedBlindedBeaconBlockCapella{Block: b.BlindedCapella}})
|
||||
case *eth.GenericBeaconBlock_Deneb:
|
||||
// no need for sidecar
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_Deneb{Deneb: ð.SignedBeaconBlockAndBlobsDeneb{Block: ð.SignedBeaconBlockDeneb{Block: b.Deneb.Block}}})
|
||||
case *eth.GenericBeaconBlock_BlindedDeneb:
|
||||
// no need for sidecar
|
||||
wrapper, err = blocks.NewSignedBeaconBlock(ð.GenericSignedBeaconBlock_BlindedDeneb{BlindedDeneb: ð.SignedBlindedBeaconBlockAndBlobsDeneb{SignedBlindedBlock: ð.SignedBlindedBeaconBlockDeneb{Message: b.BlindedDeneb.Block}}})
|
||||
default:
|
||||
return "", &http2.DefaultErrorJson{
|
||||
Message: fmt.Errorf("type %T is not supported", b).Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return "", &http2.DefaultErrorJson{
|
||||
Message: err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
//get consensus payload value which is the same as the total from the block rewards api
|
||||
blockRewards, httpError := blockRewardsFetcher.GetBlockRewardsData(ctx, wrapper)
|
||||
if httpError != nil {
|
||||
return "", httpError
|
||||
}
|
||||
return blockRewards.Total, nil
|
||||
}
|
||||
|
||||
func handleProducePhase0V3(
|
||||
ctx context.Context,
|
||||
w http.ResponseWriter,
|
||||
@@ -169,6 +232,7 @@ func handleProducePhase0V3(
|
||||
Version: version.String(version.Phase0),
|
||||
ExecutionPayloadBlinded: false,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point
|
||||
ConsensusBlockValue: "", // rewards not applicable before altair
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -178,10 +242,12 @@ func handleProduceAltairV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_Altair,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceAltairV3")
|
||||
defer span.End()
|
||||
|
||||
if isSSZ {
|
||||
sszResp, err := blk.Altair.MarshalSSZ()
|
||||
if err != nil {
|
||||
@@ -204,7 +270,8 @@ func handleProduceAltairV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Altair),
|
||||
ExecutionPayloadBlinded: false,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -214,7 +281,8 @@ func handleProduceBellatrixV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_Bellatrix,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBellatrixV3")
|
||||
defer span.End()
|
||||
@@ -240,7 +308,8 @@ func handleProduceBellatrixV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Bellatrix),
|
||||
ExecutionPayloadBlinded: false,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -250,7 +319,8 @@ func handleProduceBlindedBellatrixV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_BlindedBellatrix,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBlindedBellatrixV3")
|
||||
defer span.End()
|
||||
@@ -276,7 +346,8 @@ func handleProduceBlindedBellatrixV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Bellatrix),
|
||||
ExecutionPayloadBlinded: true,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue),
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue),
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -286,7 +357,8 @@ func handleProduceBlindedCapellaV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_BlindedCapella,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBlindedCapellaV3")
|
||||
defer span.End()
|
||||
@@ -312,7 +384,8 @@ func handleProduceBlindedCapellaV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Capella),
|
||||
ExecutionPayloadBlinded: true,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue),
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue),
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -322,7 +395,8 @@ func handleProduceCapellaV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_Capella,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceCapellaV3")
|
||||
defer span.End()
|
||||
@@ -348,7 +422,8 @@ func handleProduceCapellaV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Capella),
|
||||
ExecutionPayloadBlinded: false,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -358,7 +433,8 @@ func handleProduceBlindedDenebV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_BlindedDeneb,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceBlindedDenebV3")
|
||||
defer span.End()
|
||||
@@ -384,7 +460,8 @@ func handleProduceBlindedDenebV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Deneb),
|
||||
ExecutionPayloadBlinded: true,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue),
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue),
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
@@ -394,7 +471,8 @@ func handleProduceDenebV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_Deneb,
|
||||
payloadValue uint64,
|
||||
executionPayloadValue uint64,
|
||||
consensusBlockValue string,
|
||||
) {
|
||||
_, span := trace.StartSpan(ctx, "validator.ProduceBlockV3.internal.handleProduceDenebV3")
|
||||
defer span.End()
|
||||
@@ -420,7 +498,8 @@ func handleProduceDenebV3(
|
||||
http2.WriteJson(w, &ProduceBlockV3Response{
|
||||
Version: version.String(version.Deneb),
|
||||
ExecutionPayloadBlinded: false,
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", payloadValue), // mev not available at this point
|
||||
ExecutionPayloadValue: fmt.Sprintf("%d", executionPayloadValue), // mev not available at this point
|
||||
ConsensusBlockValue: consensusBlockValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/prysmaticlabs/prysm/v4/api"
|
||||
blockchainTesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards"
|
||||
rewardtesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
rpctesting "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared/testing"
|
||||
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
|
||||
@@ -46,26 +48,32 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"phase0","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"phase0","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "phase0", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Altair", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockAltair
|
||||
err := json.Unmarshal([]byte(rpctesting.AltairBlock), &block)
|
||||
|
||||
require.NoError(t, err)
|
||||
jsonBytes, err := json.Marshal(block.Message)
|
||||
require.NoError(t, err)
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), gomock.Any()).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -74,11 +82,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"altair","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"altair","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "altair", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Bellatrix", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockBellatrix
|
||||
@@ -92,10 +102,12 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
mockChainService := &blockchainTesting.ChainService{}
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -104,11 +116,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "bellatrix", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("BlindedBellatrix", func(t *testing.T) {
|
||||
var block *shared.SignedBlindedBeaconBlockBellatrix
|
||||
@@ -122,10 +136,12 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
mockChainService := &blockchainTesting.ChainService{}
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -134,11 +150,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":true,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"bellatrix","execution_payload_blinded":true,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "bellatrix", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Capella", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockCapella
|
||||
@@ -152,10 +170,12 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
mockChainService := &blockchainTesting.ChainService{}
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -164,11 +184,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "capella", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Blinded Capella", func(t *testing.T) {
|
||||
var block *shared.SignedBlindedBeaconBlockCapella
|
||||
@@ -185,10 +207,12 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
return g, err
|
||||
}())
|
||||
mockChainService := &blockchainTesting.ChainService{}
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -197,11 +221,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":true,"execution_payload_value":"2000","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"capella","execution_payload_blinded":true,"execution_payload_value":"2000","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "2000", true)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "2000", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "capella", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Deneb", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockContentsDeneb
|
||||
@@ -215,10 +241,12 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
return block.ToUnsigned().ToGeneric()
|
||||
}())
|
||||
mockChainService := &blockchainTesting.ChainService{}
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -227,11 +255,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":false,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":false,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Blinded Deneb", func(t *testing.T) {
|
||||
var block *shared.SignedBlindedBeaconBlockContentsDeneb
|
||||
@@ -245,10 +275,12 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
return block.ToUnsigned().ToGeneric()
|
||||
}())
|
||||
mockChainService := &blockchainTesting.ChainService{}
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -257,11 +289,13 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":true,"execution_payload_value":"0","data":%s}`, string(jsonBytes))
|
||||
want := fmt.Sprintf(`{"version":"deneb","execution_payload_blinded":true,"execution_payload_value":"0","consensus_block_value":"10","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("invalid query parameter slot empty", func(t *testing.T) {
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
@@ -321,6 +355,7 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
|
||||
func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockRewards := &rewards.BlockRewards{Total: "10"}
|
||||
t.Run("Phase 0", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlock
|
||||
err := json.Unmarshal([]byte(rpctesting.Phase0Block), &block)
|
||||
@@ -349,8 +384,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.Phase0.Block.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "phase0", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Altair", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockAltair
|
||||
@@ -361,9 +398,11 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -380,8 +419,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.Altair.Block.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "altair", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Bellatrix", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockBellatrix
|
||||
@@ -397,6 +438,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -413,8 +455,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.Bellatrix.Block.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "bellatrix", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("BlindedBellatrix", func(t *testing.T) {
|
||||
var block *shared.SignedBlindedBeaconBlockBellatrix
|
||||
@@ -430,6 +474,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -446,8 +491,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.BlindedBellatrix.Block.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "bellatrix", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Capella", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockCapella
|
||||
@@ -463,6 +510,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -479,8 +527,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.Capella.Block.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "capella", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Blinded Capella", func(t *testing.T) {
|
||||
var block *shared.SignedBlindedBeaconBlockCapella
|
||||
@@ -499,6 +549,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -515,8 +566,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.BlindedCapella.Block.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "2000", true)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "2000", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "capella", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Deneb", func(t *testing.T) {
|
||||
var block *shared.SignedBeaconBlockContentsDeneb
|
||||
@@ -532,6 +585,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -548,8 +602,10 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.Deneb.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "false", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Blinded Deneb", func(t *testing.T) {
|
||||
var block *shared.SignedBlindedBeaconBlockContentsDeneb
|
||||
@@ -565,6 +621,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: mockRewards},
|
||||
}
|
||||
rr := "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" +
|
||||
"&graffiti=0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
@@ -581,7 +638,9 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
ssz, err := bl.BlindedDeneb.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadBlindedHeader) == "true", true)
|
||||
require.Equal(t, writer.Header().Get(api.ExecutionPayloadValueHeader) == "0", true)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "0", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/synccommittee"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/rewards"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
@@ -34,4 +35,5 @@ type Server struct {
|
||||
BlockBuilder builder.BlockBuilder
|
||||
OperationNotifier operation.Notifier
|
||||
CoreService *core.Service
|
||||
BlockRewardFetcher rewards.BlockRewardsFetcher
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ type ProduceBlockV3Response struct {
|
||||
Version string `json:"version"`
|
||||
ExecutionPayloadBlinded bool `json:"execution_payload_blinded"`
|
||||
ExecutionPayloadValue string `json:"execution_payload_value"`
|
||||
ConsensusBlockValue string `json:"consensus_block_value"`
|
||||
Data json.RawMessage `json:"data"` // represents the block values based on the version
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@ go_library(
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//log:go_default_library",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@com_github_ipfs_go_log_v2//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/protocol:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
],
|
||||
@@ -58,7 +58,7 @@ go_test(
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,10 +14,10 @@ go_library(
|
||||
"//io/logs:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@com_github_golang_protobuf//ptypes/timestamp",
|
||||
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@io_bazel_rules_go//proto/wkt:timestamp_go_proto",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
"@org_golang_google_grpc//codes:go_default_library",
|
||||
"@org_golang_google_grpc//status:go_default_library",
|
||||
|
||||
@@ -162,7 +162,7 @@ func validatorCountByStatus(validators []*eth.Validator, statuses []validator.Va
|
||||
var resp []*ValidatorCount
|
||||
for status, count := range countByStatus {
|
||||
resp = append(resp, &ValidatorCount{
|
||||
Status: strings.ToLower(ethpb.ValidatorStatus_name[int32(status)]),
|
||||
Status: status.String(),
|
||||
Count: strconv.FormatUint(count, 10),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -211,15 +211,15 @@ func (s *Service) Start() {
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
}
|
||||
|
||||
rewardFetcher := &rewards.BlockRewardService{Replayer: ch}
|
||||
rewardsServer := &rewards.Server{
|
||||
Blocker: blocker,
|
||||
OptimisticModeFetcher: s.cfg.OptimisticModeFetcher,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
ReplayerBuilder: ch,
|
||||
TimeFetcher: s.cfg.GenesisTimeFetcher,
|
||||
Stater: stater,
|
||||
HeadFetcher: s.cfg.HeadFetcher,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/rewards/blocks/{block_id}", rewardsServer.BlockRewards).Methods(http.MethodGet)
|
||||
@@ -304,6 +304,7 @@ func (s *Service) Start() {
|
||||
BlockBuilder: s.cfg.BlockBuilder,
|
||||
OperationNotifier: s.cfg.OperationNotifier,
|
||||
CoreService: coreService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
s.cfg.Router.HandleFunc("/eth/v1/validator/aggregate_attestation", validatorServerV1.GetAggregateAttestation).Methods(http.MethodGet)
|
||||
@@ -350,6 +351,12 @@ func (s *Service) Start() {
|
||||
}
|
||||
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/syncing", nodeServerEth.GetSyncStatus).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/identity", nodeServerEth.GetIdentity).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/peers/{peer_id}", nodeServerEth.GetPeer).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/peers", nodeServerEth.GetPeers).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/peer_count", nodeServerEth.GetPeerCount).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/version", nodeServerEth.GetVersion).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/node/health", nodeServerEth.GetHealth).Methods(http.MethodGet)
|
||||
|
||||
nodeServerPrysm := &nodeprysm.Server{
|
||||
BeaconDB: s.cfg.BeaconDB,
|
||||
@@ -432,6 +439,9 @@ func (s *Service) Start() {
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_count", httpServer.GetValidatorCount).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/committees", beaconChainServerV1.GetCommittees).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/fork", beaconChainServerV1.GetStateFork).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/root", beaconChainServerV1.GetStateRoot).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/sync_committees", beaconChainServerV1.GetSyncCommittees).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/randao", beaconChainServerV1.GetRandao).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/blocks", beaconChainServerV1.PublishBlock).Methods(http.MethodPost)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/blinded_blocks", beaconChainServerV1.PublishBlindedBlock).Methods(http.MethodPost)
|
||||
s.cfg.Router.HandleFunc("/eth/v2/beacon/blocks", beaconChainServerV1.PublishBlockV2).Methods(http.MethodPost)
|
||||
@@ -442,6 +452,8 @@ func (s *Service) Start() {
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/voluntary_exits", beaconChainServerV1.ListVoluntaryExits).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/voluntary_exits", beaconChainServerV1.SubmitVoluntaryExit).Methods(http.MethodPost)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/sync_committees", beaconChainServerV1.SubmitSyncCommitteeSignatures).Methods(http.MethodPost)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/bls_to_execution_changes", beaconChainServerV1.ListBLSToExecutionChanges).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/pool/bls_to_execution_changes", beaconChainServerV1.SubmitBLSToExecutionChanges).Methods(http.MethodPost)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/headers", beaconChainServerV1.GetBlockHeaders).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/headers/{block_id}", beaconChainServerV1.GetBlockHeader).Methods(http.MethodGet)
|
||||
s.cfg.Router.HandleFunc("/eth/v1/config/deposit_contract", beaconChainServerV1.GetDepositContract).Methods(http.MethodGet)
|
||||
@@ -452,7 +464,6 @@ func (s *Service) Start() {
|
||||
s.cfg.Router.HandleFunc("/eth/v1/beacon/states/{state_id}/validator_balances", beaconChainServerV1.GetValidatorBalances).Methods(http.MethodGet)
|
||||
|
||||
ethpbv1alpha1.RegisterNodeServer(s.grpcServer, nodeServer)
|
||||
ethpbservice.RegisterBeaconNodeServer(s.grpcServer, nodeServerEth)
|
||||
ethpbv1alpha1.RegisterHealthServer(s.grpcServer, nodeServer)
|
||||
ethpbv1alpha1.RegisterBeaconChainServer(s.grpcServer, beaconChainServer)
|
||||
ethpbservice.RegisterBeaconChainServer(s.grpcServer, beaconChainServerV1)
|
||||
|
||||
@@ -118,7 +118,6 @@ func (s *State) saveStateByRoot(ctx context.Context, blockRoot [32]byte, st stat
|
||||
}
|
||||
}
|
||||
|
||||
// On an intermediate slot, save state summary.
|
||||
if err := s.beaconDB.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: st.Slot(),
|
||||
Root: blockRoot[:],
|
||||
|
||||
@@ -88,6 +88,7 @@ go_library(
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync/verify:go_default_library",
|
||||
"//cache/lru:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
@@ -228,6 +229,7 @@ go_test(
|
||||
"//encoding/ssz/equality:go_default_library",
|
||||
"//network/forks:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//proto/prysm/v1alpha1/metadata:go_default_library",
|
||||
|
||||
@@ -21,12 +21,14 @@ go_library(
|
||||
"//beacon-chain/core/feed/block:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//beacon-chain/das:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/p2p/peers/scorers:go_default_library",
|
||||
"//beacon-chain/p2p/types:go_default_library",
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//beacon-chain/sync/verify:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
@@ -35,7 +37,6 @@ go_library(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/leaky-bucket:go_default_library",
|
||||
"//crypto/rand:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime:go_default_library",
|
||||
@@ -121,6 +122,7 @@ go_test(
|
||||
"//beacon-chain/p2p/types:go_default_library",
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//beacon-chain/sync/verify:go_default_library",
|
||||
"//cmd/beacon-chain/flags:go_default_library",
|
||||
"//config/features:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
p2pTypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/types"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/startup"
|
||||
prysmsync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/verify"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
consensus_types "github.com/prysmaticlabs/prysm/v4/consensus-types"
|
||||
@@ -23,7 +24,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
leakybucket "github.com/prysmaticlabs/prysm/v4/container/leaky-bucket"
|
||||
"github.com/prysmaticlabs/prysm/v4/crypto/rand"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v4/math"
|
||||
p2ppb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/time/slots"
|
||||
@@ -395,9 +395,6 @@ func sortBlobs(blobs []*p2ppb.BlobSidecar) []*p2ppb.BlobSidecar {
|
||||
|
||||
var errBlobVerification = errors.New("peer unable to serve aligned BlobSidecarsByRange and BeaconBlockSidecarsByRange responses")
|
||||
var errMissingBlobsForBlockCommitments = errors.Wrap(errBlobVerification, "blobs unavailable for processing block with kzg commitments")
|
||||
var errMismatchedBlobBlockRoot = errors.Wrap(errBlobVerification, "BlockRoot in BlobSidecar does not match the expected root")
|
||||
var errMissingBlobIndex = errors.Wrap(errBlobVerification, "missing expected blob index")
|
||||
var errMismatchedBlobCommitments = errors.Wrap(errBlobVerification, "commitments at given slot, root and index do not match")
|
||||
|
||||
func verifyAndPopulateBlobs(bwb []blocks2.BlockWithVerifiedBlobs, blobs []*p2ppb.BlobSidecar, blobWindowStart primitives.Slot) ([]blocks2.BlockWithVerifiedBlobs, error) {
|
||||
// Assumes bwb has already been sorted by sortedBlockWithVerifiedBlobSlice.
|
||||
@@ -430,23 +427,8 @@ func verifyAndPopulateBlobs(bwb []blocks2.BlockWithVerifiedBlobs, blobs []*p2ppb
|
||||
return nil, missingCommitError(bb.Block.Root(), bb.Block.Block().Slot(), commits[ci:])
|
||||
}
|
||||
bl := blobs[blobi]
|
||||
if bl.Slot != block.Slot() {
|
||||
return nil, missingCommitError(bb.Block.Root(), bb.Block.Block().Slot(), commits[ci:])
|
||||
}
|
||||
if bytesutil.ToBytes32(bl.BlockRoot) != bb.Block.Root() {
|
||||
return nil, errors.Wrapf(errMismatchedBlobBlockRoot,
|
||||
"block root %#x != BlobSidecar.BlockRoot %#x at slot %d", bb.Block.Root(), bl.BlockRoot, block.Slot())
|
||||
}
|
||||
if ci != int(bl.Index) {
|
||||
return nil, errors.Wrapf(errMissingBlobIndex,
|
||||
"did not receive blob index %d for block root %#x at slot %d", ci, bb.Block.Root(), block.Slot())
|
||||
}
|
||||
ec := bytesutil.ToBytes48(commits[ci])
|
||||
ac := bytesutil.ToBytes48(bl.KzgCommitment)
|
||||
if ec != ac {
|
||||
return nil, errors.Wrapf(errMismatchedBlobCommitments,
|
||||
"commitment %#x != block commitment %#x, at index %d for block root %#x at slot %d ",
|
||||
ac, ec, bl.Index, bb.Block.Root(), block.Slot())
|
||||
if err := verify.BlobAlignsWithBlock(bl, bb.Block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bb.Blobs[ci] = bl
|
||||
blobi += 1
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
p2pt "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/testing"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/startup"
|
||||
beaconsync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/verify"
|
||||
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
@@ -1079,23 +1080,33 @@ func TestVerifyAndPopulateBlobs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
_, err = verifyAndPopulateBlobs(bwb, blobs, firstBlockSlot)
|
||||
require.ErrorIs(t, err, errMissingBlobsForBlockCommitments)
|
||||
require.ErrorContains(t, "BlockSlot in BlobSidecar does not match the expected slot", err)
|
||||
|
||||
bwb, blobs = testSequenceBlockWithBlob(t, 10)
|
||||
blobs[lastBlobIdx].BlockRoot = blobs[0].BlockRoot
|
||||
_, err = verifyAndPopulateBlobs(bwb, blobs, firstBlockSlot)
|
||||
require.ErrorIs(t, err, errMismatchedBlobBlockRoot)
|
||||
require.ErrorIs(t, err, verify.ErrMismatchedBlobBlockRoot)
|
||||
|
||||
bwb, blobs = testSequenceBlockWithBlob(t, 10)
|
||||
blobs[lastBlobIdx].Index = 100
|
||||
_, err = verifyAndPopulateBlobs(bwb, blobs, firstBlockSlot)
|
||||
require.ErrorIs(t, err, errMissingBlobIndex)
|
||||
require.ErrorIs(t, err, verify.ErrIncorrectBlobIndex)
|
||||
|
||||
bwb, blobs = testSequenceBlockWithBlob(t, 10)
|
||||
blobs[lastBlobIdx].ProposerIndex = 100
|
||||
_, err = verifyAndPopulateBlobs(bwb, blobs, firstBlockSlot)
|
||||
require.ErrorIs(t, err, verify.ErrMismatchedProposerIndex)
|
||||
|
||||
bwb, blobs = testSequenceBlockWithBlob(t, 10)
|
||||
blobs[lastBlobIdx].BlockParentRoot = blobs[0].BlockParentRoot
|
||||
_, err = verifyAndPopulateBlobs(bwb, blobs, firstBlockSlot)
|
||||
require.ErrorIs(t, err, verify.ErrMismatchedBlobBlockRoot)
|
||||
|
||||
var emptyKzg [48]byte
|
||||
bwb, blobs = testSequenceBlockWithBlob(t, 10)
|
||||
blobs[lastBlobIdx].KzgCommitment = emptyKzg[:]
|
||||
_, err = verifyAndPopulateBlobs(bwb, blobs, firstBlockSlot)
|
||||
require.ErrorIs(t, err, errMismatchedBlobCommitments)
|
||||
require.ErrorIs(t, err, verify.ErrMismatchedBlobCommitments)
|
||||
|
||||
// happy path
|
||||
bwb, blobs = testSequenceBlockWithBlob(t, 10)
|
||||
|
||||
@@ -326,8 +326,8 @@ func (s *Service) processBatchedBlocks(ctx context.Context, genesis time.Time,
|
||||
if len(bb.Blobs) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := s.cfg.DB.SaveBlobSidecar(ctx, bb.Blobs); err != nil {
|
||||
return errors.Wrapf(err, "failed to save blobs for block %#x", bb.Block.Root())
|
||||
if err := s.cfg.AVS.SaveIfAvailable(ctx, s.clock.CurrentSlot(), bb); err != nil {
|
||||
return errors.Wrapf(err, "failed to verify blob commitments and save to db for root %#x", bb.Block.Root())
|
||||
}
|
||||
blobCount += len(bb.Blobs)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
|
||||
blockfeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/block"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/das"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/startup"
|
||||
@@ -41,6 +42,7 @@ type Config struct {
|
||||
BlockNotifier blockfeed.Notifier
|
||||
ClockWaiter startup.ClockWaiter
|
||||
InitialSyncComplete chan struct{}
|
||||
AVS das.AvailabilityStore
|
||||
}
|
||||
|
||||
// Service service.
|
||||
|
||||
@@ -75,14 +75,19 @@ func (s *Service) processPendingAtts(ctx context.Context) error {
|
||||
delete(s.blkRootToPendingAtts, bRoot)
|
||||
s.pendingAttsLock.Unlock()
|
||||
} else {
|
||||
// Pending attestation's missing block has not arrived yet.
|
||||
log.WithFields(logrus.Fields{
|
||||
"currentSlot": s.cfg.clock.CurrentSlot(),
|
||||
"attSlot": attestations[0].Message.Aggregate.Data.Slot,
|
||||
"attCount": len(attestations),
|
||||
"blockRoot": hex.EncodeToString(bytesutil.Trunc(bRoot[:])),
|
||||
}).Debug("Requesting block for pending attestation")
|
||||
pendingRoots = append(pendingRoots, bRoot)
|
||||
s.pendingQueueLock.RLock()
|
||||
seen := s.seenPendingBlocks[bRoot]
|
||||
s.pendingQueueLock.RUnlock()
|
||||
if !seen {
|
||||
// Pending attestation's missing block has not arrived yet.
|
||||
log.WithFields(logrus.Fields{
|
||||
"currentSlot": s.cfg.clock.CurrentSlot(),
|
||||
"attSlot": attestations[0].Message.Aggregate.Data.Slot,
|
||||
"attCount": len(attestations),
|
||||
"blockRoot": hex.EncodeToString(bytesutil.Trunc(bRoot[:])),
|
||||
}).Debug("Requesting block for pending attestation")
|
||||
pendingRoots = append(pendingRoots, bRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.sendBatchRootRequest(ctx, pendingRoots, randGen)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/async"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain"
|
||||
@@ -49,81 +50,68 @@ func (s *Service) processPendingBlocksQueue() {
|
||||
})
|
||||
}
|
||||
|
||||
// processes the block tree inside the queue
|
||||
// processPendingBlocks validates, processes, and broadcasts pending blocks.
|
||||
func (s *Service) processPendingBlocks(ctx context.Context) error {
|
||||
ctx, span := trace.StartSpan(ctx, "processPendingBlocks")
|
||||
defer span.End()
|
||||
|
||||
pids := s.cfg.p2p.Peers().Connected()
|
||||
// Validate pending slots before processing.
|
||||
if err := s.validatePendingSlots(); err != nil {
|
||||
return errors.Wrap(err, "could not validate pending slots")
|
||||
}
|
||||
ss := s.sortedPendingSlots()
|
||||
var parentRoots [][32]byte
|
||||
|
||||
span.AddAttributes(
|
||||
trace.Int64Attribute("numSlots", int64(len(ss))),
|
||||
trace.Int64Attribute("numPeers", int64(len(pids))),
|
||||
)
|
||||
// Sort slots for ordered processing.
|
||||
sortedSlots := s.sortedPendingSlots()
|
||||
|
||||
span.AddAttributes(trace.Int64Attribute("numSlots", int64(len(sortedSlots))), trace.Int64Attribute("numPeers", int64(len(s.cfg.p2p.Peers().Connected()))))
|
||||
|
||||
randGen := rand.NewGenerator()
|
||||
for _, slot := range ss {
|
||||
// process the blocks during their respective slot.
|
||||
// otherwise wait for the right slot to process the block.
|
||||
var parentRoots [][32]byte
|
||||
|
||||
// Iterate through sorted slots.
|
||||
for _, slot := range sortedSlots {
|
||||
// Skip processing if slot is in the future.
|
||||
if slot > s.cfg.clock.CurrentSlot() {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, span := trace.StartSpan(ctx, "processPendingBlocks.InnerLoop")
|
||||
span.AddAttributes(trace.Int64Attribute("slot", int64(slot))) // lint:ignore uintcast -- This conversion is OK for tracing.
|
||||
ctx, span := startInnerSpan(ctx, slot)
|
||||
|
||||
s.pendingQueueLock.RLock()
|
||||
bs := s.pendingBlocksInCache(slot)
|
||||
// Skip if there's no block in the queue.
|
||||
if len(bs) == 0 {
|
||||
s.pendingQueueLock.RUnlock()
|
||||
// Get blocks in the pending queue for the current slot.
|
||||
blocksInCache := s.getBlocksInQueue(slot)
|
||||
if len(blocksInCache) == 0 {
|
||||
span.End()
|
||||
continue
|
||||
}
|
||||
s.pendingQueueLock.RUnlock()
|
||||
|
||||
// Loop through the pending queue and mark the potential parent blocks as seen.
|
||||
for _, b := range bs {
|
||||
if b == nil || b.IsNil() || b.Block().IsNil() {
|
||||
span.End()
|
||||
// Process each block in the queue.
|
||||
for _, b := range blocksInCache {
|
||||
if err := blocks.BeaconBlockIsNil(b); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
blkRoot, err := b.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
span.End()
|
||||
return err
|
||||
}
|
||||
// No need to process the same block if we are already processing it
|
||||
|
||||
// Skip blocks that are already being processed.
|
||||
if s.cfg.chain.BlockBeingSynced(blkRoot) {
|
||||
rootString := fmt.Sprintf("%#x", blkRoot)
|
||||
log.WithField("BlockRoot", rootString).Info("Skipping pending block already being processed")
|
||||
log.WithField("BlockRoot", fmt.Sprintf("%#x", blkRoot)).Info("Skipping pending block already being processed")
|
||||
continue
|
||||
}
|
||||
|
||||
inDB := s.cfg.beaconDB.HasBlock(ctx, blkRoot)
|
||||
// No need to process the same block twice.
|
||||
if inDB {
|
||||
s.pendingQueueLock.Lock()
|
||||
if err = s.deleteBlockFromPendingQueue(slot, b, blkRoot); err != nil {
|
||||
s.pendingQueueLock.Unlock()
|
||||
// Remove and skip blocks already in the database.
|
||||
if s.cfg.beaconDB.HasBlock(ctx, blkRoot) {
|
||||
if err := s.removeBlockFromQueue(b, blkRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
s.pendingQueueLock.Unlock()
|
||||
span.End()
|
||||
continue
|
||||
}
|
||||
|
||||
s.pendingQueueLock.RLock()
|
||||
inPendingQueue := s.seenPendingBlocks[b.Block().ParentRoot()]
|
||||
s.pendingQueueLock.RUnlock()
|
||||
parentRoot := b.Block().ParentRoot()
|
||||
inPendingQueue := s.isBlockInQueue(parentRoot)
|
||||
|
||||
// Check if block is bad.
|
||||
keepProcessing, err := s.checkIfBlockIsBad(ctx, span, slot, b, blkRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -132,85 +120,119 @@ func (s *Service) processPendingBlocks(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
|
||||
parentRoot := b.Block().ParentRoot()
|
||||
parentInDb := s.cfg.beaconDB.HasBlock(ctx, parentRoot)
|
||||
hasPeer := len(pids) != 0
|
||||
|
||||
// Only request for missing parent block if it's not in beaconDB, not in pending cache
|
||||
// and has peer in the peer list.
|
||||
if !inPendingQueue && !parentInDb && hasPeer {
|
||||
log.WithFields(logrus.Fields{
|
||||
"currentSlot": b.Block().Slot(),
|
||||
"parentRoot": hex.EncodeToString(bytesutil.Trunc(parentRoot[:])),
|
||||
}).Debug("Requesting parent block")
|
||||
parentRoots = append(parentRoots, b.Block().ParentRoot())
|
||||
|
||||
span.End()
|
||||
// Request parent block if not in the pending queue and not in the database.
|
||||
isParentBlockInDB := s.cfg.beaconDB.HasBlock(ctx, parentRoot)
|
||||
if !inPendingQueue && !isParentBlockInDB && s.hasPeer() {
|
||||
log.WithFields(logrus.Fields{"currentSlot": b.Block().Slot(), "parentRoot": hex.EncodeToString(parentRoot[:])}).Debug("Requesting parent block")
|
||||
parentRoots = append(parentRoots, parentRoot)
|
||||
continue
|
||||
}
|
||||
if !isParentBlockInDB {
|
||||
continue
|
||||
}
|
||||
|
||||
if !parentInDb {
|
||||
span.End()
|
||||
// Process and broadcast the block.
|
||||
if err := s.processAndBroadcastBlock(ctx, b, blkRoot); err != nil {
|
||||
s.handleBlockProcessingError(ctx, err, b, blkRoot)
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.validateBeaconBlock(ctx, b, blkRoot)
|
||||
switch {
|
||||
case errors.Is(ErrOptimisticParent, err): // Ok to continue process block with parent that is an optimistic candidate.
|
||||
case err != nil:
|
||||
log.WithError(err).WithField("slot", b.Block().Slot()).Debug("Could not validate block")
|
||||
tracing.AnnotateError(span, err)
|
||||
span.End()
|
||||
continue
|
||||
default:
|
||||
}
|
||||
|
||||
if err := s.cfg.chain.ReceiveBlock(ctx, b, blkRoot); err != nil {
|
||||
if blockchain.IsInvalidBlock(err) {
|
||||
r := blockchain.InvalidBlockRoot(err)
|
||||
if r != [32]byte{} {
|
||||
s.setBadBlock(ctx, r) // Setting head block as bad.
|
||||
} else {
|
||||
s.setBadBlock(ctx, blkRoot)
|
||||
}
|
||||
}
|
||||
log.WithError(err).WithField("slot", b.Block().Slot()).Debug("Could not process block")
|
||||
|
||||
// In the next iteration of the queue, this block will be removed from
|
||||
// the pending queue as it has been marked as a 'bad' block.
|
||||
span.End()
|
||||
continue
|
||||
}
|
||||
|
||||
s.setSeenBlockIndexSlot(b.Block().Slot(), b.Block().ProposerIndex())
|
||||
|
||||
// Broadcasting the block again once a node is able to process it.
|
||||
pb, err := b.Proto()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Could not get protobuf block")
|
||||
} else {
|
||||
if err := s.cfg.p2p.Broadcast(ctx, pb); err != nil {
|
||||
log.WithError(err).Debug("Could not broadcast block")
|
||||
}
|
||||
}
|
||||
|
||||
s.pendingQueueLock.Lock()
|
||||
if err := s.deleteBlockFromPendingQueue(slot, b, blkRoot); err != nil {
|
||||
s.pendingQueueLock.Unlock()
|
||||
// Remove the processed block from the queue.
|
||||
if err := s.removeBlockFromQueue(b, blkRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
s.pendingQueueLock.Unlock()
|
||||
log.WithFields(logrus.Fields{"slot": slot, "blockRoot": hex.EncodeToString(bytesutil.Trunc(blkRoot[:]))}).Debug("Processed pending block and cleared it in cache")
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
return s.sendBatchRootRequest(ctx, parentRoots, randGen)
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": slot,
|
||||
"blockRoot": hex.EncodeToString(bytesutil.Trunc(blkRoot[:])),
|
||||
}).Debug("Processed pending block and cleared it in cache")
|
||||
// startInnerSpan starts a new tracing span for an inner loop and returns the new context and span.
|
||||
func startInnerSpan(ctx context.Context, slot primitives.Slot) (context.Context, *trace.Span) {
|
||||
ctx, span := trace.StartSpan(ctx, "processPendingBlocks.InnerLoop")
|
||||
span.AddAttributes(trace.Int64Attribute("slot", int64(slot))) // lint:ignore uintcast -- This conversion is OK for tracing.
|
||||
return ctx, span
|
||||
}
|
||||
|
||||
span.End()
|
||||
// getBlocksInQueue retrieves the blocks in the pending queue for a given slot.
|
||||
func (s *Service) getBlocksInQueue(slot primitives.Slot) []interfaces.ReadOnlySignedBeaconBlock {
|
||||
s.pendingQueueLock.RLock()
|
||||
defer s.pendingQueueLock.RUnlock()
|
||||
return s.pendingBlocksInCache(slot)
|
||||
}
|
||||
|
||||
// removeBlockFromQueue removes a block from the pending queue.
|
||||
func (s *Service) removeBlockFromQueue(b interfaces.ReadOnlySignedBeaconBlock, blkRoot [32]byte) error {
|
||||
s.pendingQueueLock.Lock()
|
||||
defer s.pendingQueueLock.Unlock()
|
||||
if err := s.deleteBlockFromPendingQueue(b.Block().Slot(), b, blkRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isBlockInQueue checks if a block's parent root is in the pending queue.
|
||||
func (s *Service) isBlockInQueue(parentRoot [32]byte) bool {
|
||||
s.pendingQueueLock.RLock()
|
||||
defer s.pendingQueueLock.RUnlock()
|
||||
|
||||
return s.seenPendingBlocks[parentRoot]
|
||||
}
|
||||
|
||||
func (s *Service) hasPeer() bool {
|
||||
return len(s.cfg.p2p.Peers().Connected()) > 0
|
||||
}
|
||||
|
||||
// processAndBroadcastBlock validates, processes, and broadcasts a block.
|
||||
// part of the function is to request missing blobs from peers if the block contains kzg commitments.
|
||||
func (s *Service) processAndBroadcastBlock(ctx context.Context, b interfaces.ReadOnlySignedBeaconBlock, blkRoot [32]byte) error {
|
||||
if err := s.validateBeaconBlock(ctx, b, blkRoot); err != nil {
|
||||
if !errors.Is(ErrOptimisticParent, err) {
|
||||
log.WithError(err).WithField("slot", b.Block().Slot()).Debug("Could not validate block")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.sendBatchRootRequest(ctx, parentRoots, randGen)
|
||||
peers := s.getBestPeers()
|
||||
peerCount := len(peers)
|
||||
if peerCount > 0 {
|
||||
if err := s.requestPendingBlobs(ctx, b.Block(), blkRoot, peers[rand.NewGenerator().Int()%peerCount]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.cfg.chain.ReceiveBlock(ctx, b, blkRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.setSeenBlockIndexSlot(b.Block().Slot(), b.Block().ProposerIndex())
|
||||
|
||||
pb, err := b.Proto()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Could not get protobuf block")
|
||||
return err
|
||||
}
|
||||
if err := s.cfg.p2p.Broadcast(ctx, pb); err != nil {
|
||||
log.WithError(err).Debug("Could not broadcast block")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBlockProcessingError handles errors during block processing.
|
||||
func (s *Service) handleBlockProcessingError(ctx context.Context, err error, b interfaces.ReadOnlySignedBeaconBlock, blkRoot [32]byte) {
|
||||
if blockchain.IsInvalidBlock(err) {
|
||||
s.setBadBlock(ctx, blkRoot)
|
||||
}
|
||||
log.WithError(err).WithField("slot", b.Block().Slot()).Debug("Could not process block")
|
||||
}
|
||||
|
||||
// getBestPeers returns the list of best peers based on finalized checkpoint epoch.
|
||||
func (s *Service) getBestPeers() []core.PeerID {
|
||||
_, bestPeers := s.cfg.p2p.Peers().BestFinalized(maxPeerRequest, s.cfg.chain.FinalizedCheckpt().Epoch)
|
||||
return bestPeers
|
||||
}
|
||||
|
||||
func (s *Service) checkIfBlockIsBad(
|
||||
@@ -247,16 +269,19 @@ func (s *Service) sendBatchRootRequest(ctx context.Context, roots [][32]byte, ra
|
||||
defer span.End()
|
||||
|
||||
roots = dedupRoots(roots)
|
||||
s.pendingQueueLock.RLock()
|
||||
for i := len(roots) - 1; i >= 0; i-- {
|
||||
if s.cfg.chain.BlockBeingSynced(roots[i]) {
|
||||
r := roots[i]
|
||||
if s.seenPendingBlocks[r] || s.cfg.chain.BlockBeingSynced(r) {
|
||||
roots = append(roots[:i], roots[i+1:]...)
|
||||
}
|
||||
}
|
||||
s.pendingQueueLock.RUnlock()
|
||||
|
||||
if len(roots) == 0 {
|
||||
return nil
|
||||
}
|
||||
cp := s.cfg.chain.FinalizedCheckpt()
|
||||
_, bestPeers := s.cfg.p2p.Peers().BestFinalized(maxPeerRequest, cp.Epoch)
|
||||
bestPeers := s.getBestPeers()
|
||||
if len(bestPeers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ import (
|
||||
libp2pcore "github.com/libp2p/go-libp2p/core"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/types"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/verify"
|
||||
"github.com/prysmaticlabs/prysm/v4/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
)
|
||||
@@ -43,7 +46,7 @@ func (s *Service) sendRecentBeaconBlocksRequest(ctx context.Context, blockRoots
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.requestPendingBlobs(ctx, blk.Block(), blkRoot[:], id); err != nil {
|
||||
if err := s.requestPendingBlobs(ctx, blk.Block(), blkRoot, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -114,41 +117,93 @@ func (s *Service) beaconBlocksRootRPCHandler(ctx context.Context, msg interface{
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) requestPendingBlobs(ctx context.Context, b interfaces.ReadOnlyBeaconBlock, br []byte, id peer.ID) error {
|
||||
// Block before deneb has no blob.
|
||||
if b.Version() < version.Deneb {
|
||||
return nil
|
||||
// requestPendingBlobs handles the request for pending blobs based on the given beacon block.
|
||||
func (s *Service) requestPendingBlobs(ctx context.Context, block interfaces.ReadOnlyBeaconBlock, blockRoot [32]byte, peerID peer.ID) error {
|
||||
if block.Version() < version.Deneb {
|
||||
return nil // Block before deneb has no blob.
|
||||
}
|
||||
c, err := b.Body().BlobKzgCommitments()
|
||||
|
||||
commitments, err := block.Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// No op if the block has no blob commitments.
|
||||
if len(c) == 0 {
|
||||
|
||||
if len(commitments) == 0 {
|
||||
return nil // No operation if the block has no blob commitments.
|
||||
}
|
||||
|
||||
contextByte, err := ContextByteVersionsForValRoot(s.cfg.chain.GenesisValidatorsRoot())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request, err := s.constructPendingBlobsRequest(ctx, blockRoot, len(commitments))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.sendAndSaveBlobSidecars(ctx, request, contextByte, peerID)
|
||||
}
|
||||
|
||||
// sendAndSaveBlobSidecars sends the blob request and saves received sidecars.
|
||||
func (s *Service) sendAndSaveBlobSidecars(ctx context.Context, request types.BlobSidecarsByRootReq, contextByte ContextByteVersions, peerID peer.ID) error {
|
||||
if len(request) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build request for blob sidecars.
|
||||
blobId := make([]*eth.BlobIdentifier, len(c))
|
||||
for i := range c {
|
||||
blobId[i] = ð.BlobIdentifier{Index: uint64(i), BlockRoot: br}
|
||||
}
|
||||
|
||||
ctxByte, err := ContextByteVersionsForValRoot(s.cfg.chain.GenesisValidatorsRoot())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := types.BlobSidecarsByRootReq(blobId)
|
||||
|
||||
// Send request to a random peer.
|
||||
blobSidecars, err := SendBlobSidecarByRoot(ctx, s.cfg.clock, s.cfg.p2p, id, ctxByte, &req)
|
||||
sidecars, err := SendBlobSidecarByRoot(ctx, s.cfg.clock, s.cfg.p2p, peerID, contextByte, &request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, sidecar := range blobSidecars {
|
||||
block, err := s.cfg.beaconDB.Block(ctx, bytesutil.ToBytes32(request[0].BlockRoot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
RoBlock, err := blocks.NewROBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sidecar := range sidecars {
|
||||
if err := verify.BlobAlignsWithBlock(sidecar, RoBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
log.WithFields(blobFields(sidecar)).Debug("Received blob sidecar gossip RPC")
|
||||
}
|
||||
|
||||
return s.cfg.beaconDB.SaveBlobSidecar(ctx, blobSidecars)
|
||||
return s.cfg.beaconDB.SaveBlobSidecar(ctx, sidecars)
|
||||
}
|
||||
|
||||
// constructPendingBlobsRequest creates a request for BlobSidecars by root, considering blobs already in DB.
|
||||
func (s *Service) constructPendingBlobsRequest(ctx context.Context, blockRoot [32]byte, count int) (types.BlobSidecarsByRootReq, error) {
|
||||
knownBlobs, err := s.cfg.beaconDB.BlobSidecarsByRoot(ctx, blockRoot)
|
||||
if err != nil && !errors.Is(err, db.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
knownIndices := indexSetFromBlobs(knownBlobs)
|
||||
requestedIndices := filterUnknownIndices(knownIndices, count, blockRoot)
|
||||
|
||||
return requestedIndices, nil
|
||||
}
|
||||
|
||||
// Helper function to create a set of known indices.
|
||||
func indexSetFromBlobs(blobs []*eth.BlobSidecar) map[uint64]struct{} {
|
||||
indices := make(map[uint64]struct{})
|
||||
for _, blob := range blobs {
|
||||
indices[blob.Index] = struct{}{}
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
// Helper function to filter out known indices.
|
||||
func filterUnknownIndices(knownIndices map[uint64]struct{}, count int, blockRoot [32]byte) []*eth.BlobIdentifier {
|
||||
var ids []*eth.BlobIdentifier
|
||||
for i := uint64(0); i < uint64(count); i++ {
|
||||
if _, exists := knownIndices[i]; exists {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, ð.BlobIdentifier{Index: i, BlockRoot: blockRoot[:]})
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
leakybucket "github.com/prysmaticlabs/prysm/v4/container/leaky-bucket"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v4/proto/eth/v2"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
@@ -296,12 +297,12 @@ func TestRequestPendingBlobs(t *testing.T) {
|
||||
t.Run("old block should not fail", func(t *testing.T) {
|
||||
b, err := blocks.NewBeaconBlock(util.NewBeaconBlock().Block)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.requestPendingBlobs(context.Background(), b, []byte{}, "test"))
|
||||
require.NoError(t, s.requestPendingBlobs(context.Background(), b, [32]byte{}, "test"))
|
||||
})
|
||||
t.Run("empty commitment block should not fail", func(t *testing.T) {
|
||||
b, err := blocks.NewBeaconBlock(util.NewBeaconBlockDeneb().Block)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.requestPendingBlobs(context.Background(), b, []byte{}, "test"))
|
||||
require.NoError(t, s.requestPendingBlobs(context.Background(), b, [32]byte{}, "test"))
|
||||
})
|
||||
t.Run("unsupported protocol", func(t *testing.T) {
|
||||
p1 := p2ptest.NewTestP2P(t)
|
||||
@@ -321,15 +322,88 @@ func TestRequestPendingBlobs(t *testing.T) {
|
||||
p1.Peers().SetChainState(p2.PeerID(), ðpb.Status{FinalizedEpoch: 1})
|
||||
s := &Service{
|
||||
cfg: &config{
|
||||
p2p: p1,
|
||||
chain: chain,
|
||||
clock: startup.NewClock(time.Unix(0, 0), [32]byte{}),
|
||||
p2p: p1,
|
||||
chain: chain,
|
||||
clock: startup.NewClock(time.Unix(0, 0), [32]byte{}),
|
||||
beaconDB: db.SetupDB(t),
|
||||
},
|
||||
}
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
b1, err := blocks.NewBeaconBlock(b.Block)
|
||||
require.NoError(t, err)
|
||||
require.ErrorContains(t, "protocols not supported", s.requestPendingBlobs(context.Background(), b1, []byte{}, p2.PeerID()))
|
||||
require.ErrorContains(t, "protocols not supported", s.requestPendingBlobs(context.Background(), b1, [32]byte{}, p2.PeerID()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestConstructPendingBlobsRequest(t *testing.T) {
|
||||
d := db.SetupDB(t)
|
||||
s := &Service{cfg: &config{beaconDB: d}}
|
||||
ctx := context.Background()
|
||||
|
||||
// No unknown indices.
|
||||
root := [32]byte{1}
|
||||
count := 3
|
||||
actual, err := s.constructPendingBlobsRequest(ctx, root, count)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(actual))
|
||||
for i, id := range actual {
|
||||
require.Equal(t, uint64(i), id.Index)
|
||||
require.DeepEqual(t, root[:], id.BlockRoot)
|
||||
}
|
||||
|
||||
// Has indices.
|
||||
blobSidecars := []*ethpb.BlobSidecar{
|
||||
{Index: 0, BlockRoot: root[:]},
|
||||
{Index: 2, BlockRoot: root[:]},
|
||||
}
|
||||
require.NoError(t, d.SaveBlobSidecar(ctx, blobSidecars))
|
||||
|
||||
expected := []*eth.BlobIdentifier{
|
||||
{Index: 1, BlockRoot: root[:]},
|
||||
}
|
||||
actual, err = s.constructPendingBlobsRequest(ctx, root, count)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected[0].Index, actual[0].Index)
|
||||
require.DeepEqual(t, expected[0].BlockRoot, actual[0].BlockRoot)
|
||||
}
|
||||
|
||||
func TestIndexSetFromBlobs(t *testing.T) {
|
||||
blobs := []*ethpb.BlobSidecar{
|
||||
{Index: 0},
|
||||
{Index: 1},
|
||||
{Index: 2},
|
||||
}
|
||||
|
||||
expected := map[uint64]struct{}{
|
||||
0: {},
|
||||
1: {},
|
||||
2: {},
|
||||
}
|
||||
|
||||
actual := indexSetFromBlobs(blobs)
|
||||
require.DeepEqual(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestFilterUnknownIndices(t *testing.T) {
|
||||
knownIndices := map[uint64]struct{}{
|
||||
0: {},
|
||||
1: {},
|
||||
2: {},
|
||||
}
|
||||
|
||||
blockRoot := [32]byte{}
|
||||
count := 5
|
||||
|
||||
expected := []*eth.BlobIdentifier{
|
||||
{Index: 3, BlockRoot: blockRoot[:]},
|
||||
{Index: 4, BlockRoot: blockRoot[:]},
|
||||
}
|
||||
|
||||
actual := filterUnknownIndices(knownIndices, count, blockRoot)
|
||||
require.Equal(t, len(expected), len(actual))
|
||||
require.Equal(t, expected[0].Index, actual[0].Index)
|
||||
require.DeepEqual(t, actual[0].BlockRoot, expected[0].BlockRoot)
|
||||
require.Equal(t, expected[1].Index, actual[1].Index)
|
||||
require.DeepEqual(t, actual[1].BlockRoot, expected[1].BlockRoot)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,19 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (s *Service) handleBlobParentStatus(ctx context.Context, root [32]byte) pubsub.ValidationResult {
|
||||
if s.cfg.chain.HasBlock(ctx, root) {
|
||||
// the parent will not be kept if it's invalid
|
||||
return pubsub.ValidationAccept
|
||||
}
|
||||
if s.hasBadBlock(root) {
|
||||
// [REJECT] The sidecar's block's parent (defined by sidecar.block_parent_root) passes validation.
|
||||
return pubsub.ValidationReject
|
||||
}
|
||||
// [IGNORE] The sidecar's block's parent (defined by sidecar.block_parent_root) has been seen (via both gossip and non-gossip sources)
|
||||
return pubsub.ValidationIgnore
|
||||
}
|
||||
|
||||
func (s *Service) validateBlob(ctx context.Context, pid peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) {
|
||||
receivedTime := prysmTime.Now()
|
||||
|
||||
@@ -74,19 +87,23 @@ func (s *Service) validateBlob(ctx context.Context, pid peer.ID, msg *pubsub.Mes
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
|
||||
// [IGNORE] The sidecar's block's parent (defined by sidecar.block_parent_root) has been seen (via both gossip and non-gossip sources)
|
||||
// Handle the parent status (not seen or invalid cases)
|
||||
parentRoot := bytesutil.ToBytes32(blob.BlockParentRoot)
|
||||
if !s.cfg.chain.HasBlock(ctx, parentRoot) {
|
||||
switch parentStatus := s.handleBlobParentStatus(ctx, parentRoot); parentStatus {
|
||||
case pubsub.ValidationIgnore:
|
||||
log.WithFields(blobFields(blob)).Debug("Ignored blob: parent block not found")
|
||||
return pubsub.ValidationIgnore, nil
|
||||
case pubsub.ValidationReject:
|
||||
log.WithFields(blobFields(blob)).Warning("Rejected blob: parent block is invalid")
|
||||
return pubsub.ValidationReject, nil
|
||||
default:
|
||||
}
|
||||
|
||||
// [REJECT] The sidecar's block's parent (defined by sidecar.block_parent_root) passes validation.
|
||||
// [REJECT] The sidecar is from a higher slot than the sidecar's block's parent (defined by sidecar.block_parent_root).
|
||||
parentSlot, err := s.cfg.chain.RecentBlockSlot(parentRoot)
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
// [REJECT] The sidecar is from a higher slot than the sidecar's block's parent (defined by sidecar.block_parent_root).
|
||||
if parentSlot >= blob.Slot {
|
||||
err := fmt.Errorf("parent block slot %d greater or equal to blob slot %d", parentSlot, blob.Slot)
|
||||
log.WithFields(blobFields(blob)).Debug(err)
|
||||
|
||||
@@ -370,3 +370,33 @@ func TestValidateBlob_EverythingPasses(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, result, pubsub.ValidationAccept)
|
||||
}
|
||||
|
||||
func TestValidateBlob_handleParentStatus(t *testing.T) {
|
||||
db := dbtest.SetupDB(t)
|
||||
ctx := context.Background()
|
||||
p := p2ptest.NewTestP2P(t)
|
||||
chainService := &mock.ChainService{Genesis: time.Now(), FinalizedCheckPoint: ð.Checkpoint{}, DB: db}
|
||||
s := &Service{
|
||||
cfg: &config{
|
||||
p2p: p,
|
||||
initialSync: &mockSync.Sync{},
|
||||
chain: chainService,
|
||||
clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot)},
|
||||
|
||||
badBlockCache: lruwrpr.New(10),
|
||||
}
|
||||
|
||||
chainService.BlockSlot = chainService.CurrentSlot() + 1
|
||||
bb := util.NewBeaconBlock()
|
||||
bb.Block.Slot = chainService.CurrentSlot() + 1
|
||||
signedBb, err := blocks.NewSignedBeaconBlock(bb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.SaveBlock(ctx, signedBb))
|
||||
r, err := signedBb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, pubsub.ValidationAccept, s.handleBlobParentStatus(ctx, r))
|
||||
badRoot := [32]byte{'a'}
|
||||
require.Equal(t, pubsub.ValidationIgnore, s.handleBlobParentStatus(ctx, badRoot))
|
||||
s.setBadBlock(ctx, badRoot)
|
||||
require.Equal(t, pubsub.ValidationReject, s.handleBlobParentStatus(ctx, badRoot))
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (s *Service) validateVoluntaryExit(ctx context.Context, pid peer.ID, msg *p
|
||||
return pubsub.ValidationIgnore, nil
|
||||
}
|
||||
|
||||
headState, err := s.cfg.chain.HeadState(ctx)
|
||||
headState, err := s.cfg.chain.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return pubsub.ValidationIgnore, err
|
||||
}
|
||||
|
||||
30
beacon-chain/sync/verify/BUILD.bazel
Normal file
30
beacon-chain/sync/verify/BUILD.bazel
Normal file
@@ -0,0 +1,30 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["blob.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/verify",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["blob_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
],
|
||||
)
|
||||
71
beacon-chain/sync/verify/blob.go
Normal file
71
beacon-chain/sync/verify/blob.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package verify
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
field_params "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/runtime/version"
|
||||
)
|
||||
|
||||
var (
|
||||
errBlobVerification = errors.New("unable to verify blobs")
|
||||
ErrMismatchedBlobBlockRoot = errors.Wrap(errBlobVerification, "BlockRoot in BlobSidecar does not match the expected root")
|
||||
ErrMismatchedBlobBlockSlot = errors.Wrap(errBlobVerification, "BlockSlot in BlobSidecar does not match the expected slot")
|
||||
ErrMismatchedBlobCommitments = errors.Wrap(errBlobVerification, "commitments at given slot, root and index do not match")
|
||||
ErrMismatchedProposerIndex = errors.Wrap(errBlobVerification, "proposer index does not match")
|
||||
ErrIncorrectBlobIndex = errors.Wrap(errBlobVerification, "incorrect blob index")
|
||||
)
|
||||
|
||||
// BlobAlignsWithBlock verifies if the blob aligns with the block.
|
||||
func BlobAlignsWithBlock(blob *ethpb.BlobSidecar, block blocks.ROBlock) error {
|
||||
if block.Version() < version.Deneb {
|
||||
return nil
|
||||
}
|
||||
|
||||
commits, err := block.Block().Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(commits) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if blob.Index >= field_params.MaxBlobsPerBlock {
|
||||
return errors.Wrapf(ErrIncorrectBlobIndex, "blob index %d >= max blobs per block %d", blob.Index, field_params.MaxBlobsPerBlock)
|
||||
}
|
||||
|
||||
// Verify slot
|
||||
blobSlot := blob.Slot
|
||||
blockSlot := block.Block().Slot()
|
||||
if blobSlot != blockSlot {
|
||||
return errors.Wrapf(ErrMismatchedBlobBlockSlot, "slot %d != BlobSidecar.Slot %d", blockSlot, blobSlot)
|
||||
}
|
||||
|
||||
// Verify block and parent roots
|
||||
blockRoot := bytesutil.ToBytes32(blob.BlockRoot)
|
||||
if blockRoot != block.Root() {
|
||||
return errors.Wrapf(ErrMismatchedBlobBlockRoot, "block root %#x != BlobSidecar.BlockRoot %#x at slot %d", block.Root(), blockRoot, blobSlot)
|
||||
}
|
||||
|
||||
blockParentRoot := bytesutil.ToBytes32(blob.BlockParentRoot)
|
||||
if blockParentRoot != block.Block().ParentRoot() {
|
||||
return errors.Wrapf(ErrMismatchedBlobBlockRoot, "block parent root %#x != BlobSidecar.BlockParentRoot %#x at slot %d", block.Block().ParentRoot(), blockParentRoot, blobSlot)
|
||||
}
|
||||
|
||||
// Verify proposer index
|
||||
if blob.ProposerIndex != block.Block().ProposerIndex() {
|
||||
return errors.Wrapf(ErrMismatchedProposerIndex, "proposer index %d != BlobSidecar.ProposerIndex %d at slot %d", block.Block().ProposerIndex(), blob.ProposerIndex, blobSlot)
|
||||
}
|
||||
|
||||
// Verify commitment
|
||||
blockCommitment := bytesutil.ToBytes48(commits[blob.Index])
|
||||
blobCommitment := bytesutil.ToBytes48(blob.KzgCommitment)
|
||||
if blobCommitment != blockCommitment {
|
||||
return errors.Wrapf(ErrMismatchedBlobCommitments, "commitment %#x != block commitment %#x, at index %d for block root %#x at slot %d ", blobCommitment, blockCommitment, blob.Index, blockRoot, blobSlot)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
142
beacon-chain/sync/verify/blob_test.go
Normal file
142
beacon-chain/sync/verify/blob_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package verify
|
||||
|
||||
import (
|
||||
"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"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/util"
|
||||
)
|
||||
|
||||
func TestBlobAlignsWithBlock(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
block interfaces.ReadOnlySignedBeaconBlock
|
||||
blob *ethpb.BlobSidecar
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "Block version less than Deneb",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlock()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{},
|
||||
},
|
||||
{
|
||||
name: "No commitments in block",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{},
|
||||
},
|
||||
{
|
||||
name: "Blob index exceeds max blobs per block",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, fieldparams.MaxBlobsPerBlock+1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{Index: fieldparams.MaxBlobsPerBlock},
|
||||
expectedErr: "blob index 6 >= max blobs per block 6: incorrect blob index",
|
||||
},
|
||||
{
|
||||
name: "Blob slot does not match block slot",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Slot = 2
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{Slot: 1},
|
||||
expectedErr: "slot 2 != BlobSidecar.Slot 1: BlockSlot in BlobSidecar does not match the expected slot",
|
||||
},
|
||||
{
|
||||
name: "Blob block root does not match block root",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{BlockRoot: []byte{1}},
|
||||
expectedErr: "block root 0x0200000000000000000000000000000000000000000000000000000000000000 != " +
|
||||
"BlobSidecar.BlockRoot 0x0100000000000000000000000000000000000000000000000000000000000000 at slot 0",
|
||||
},
|
||||
{
|
||||
name: "Blob parent root does not match block parent root",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{BlockRoot: []byte{2}, BlockParentRoot: []byte{1}},
|
||||
expectedErr: "block parent root 0x0000000000000000000000000000000000000000000000000000000000000000 != " +
|
||||
"BlobSidecar.BlockParentRoot 0x0100000000000000000000000000000000000000000000000000000000000000 at slot 0",
|
||||
},
|
||||
{
|
||||
name: "Blob proposer index does not match block proposer index",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{BlockRoot: []byte{2}, ProposerIndex: 1},
|
||||
expectedErr: "proposer index 0 != BlobSidecar.ProposerIndex 1 at slot ",
|
||||
},
|
||||
{
|
||||
name: "Blob commitment does not match block commitment",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{BlockRoot: []byte{2}, KzgCommitment: []byte{1}},
|
||||
expectedErr: "commitment 0x010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 != " +
|
||||
"block commitment 0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
},
|
||||
{
|
||||
name: "All fields are correctly aligned",
|
||||
block: func() interfaces.ReadOnlySignedBeaconBlock {
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.Body.BlobKzgCommitments = make([][]byte, 1)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
return sb
|
||||
}(),
|
||||
blob: ðpb.BlobSidecar{BlockRoot: []byte{2}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
block, err := blocks.NewROBlockWithRoot(tt.block, [32]byte{2})
|
||||
require.NoError(t, err)
|
||||
err = BlobAlignsWithBlock(tt.blob, block)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.StringContains(t, tt.expectedErr, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ go_library(
|
||||
"//api/client/beacon:go_default_library",
|
||||
"//api/client/validator:go_default_library",
|
||||
"//beacon-chain/rpc/apimiddleware:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
"//cmd:go_default_library",
|
||||
"//cmd/validator/accounts:go_default_library",
|
||||
"//cmd/validator/flags:go_default_library",
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v4/api/client/beacon"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/apimiddleware"
|
||||
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -40,8 +41,8 @@ func setWithdrawalAddresses(c *cli.Context) error {
|
||||
return callWithdrawalEndpoints(ctx, beaconNodeHost, setWithdrawalAddressJsons)
|
||||
}
|
||||
|
||||
func getWithdrawalMessagesFromPathFlag(c *cli.Context) ([]*apimiddleware.SignedBLSToExecutionChangeJson, error) {
|
||||
setWithdrawalAddressJsons := make([]*apimiddleware.SignedBLSToExecutionChangeJson, 0)
|
||||
func getWithdrawalMessagesFromPathFlag(c *cli.Context) ([]*shared.SignedBLSToExecutionChange, error) {
|
||||
setWithdrawalAddressJsons := make([]*shared.SignedBLSToExecutionChange, 0)
|
||||
foundFilePaths, err := findWithdrawalFiles(c.String(PathFlag.Name))
|
||||
if err != nil {
|
||||
return setWithdrawalAddressJsons, errors.Wrap(err, "failed to find withdrawal files")
|
||||
@@ -67,8 +68,8 @@ func getWithdrawalMessagesFromPathFlag(c *cli.Context) ([]*apimiddleware.SignedB
|
||||
if len(obj.Signature) == fieldparams.BLSSignatureLength*2 {
|
||||
to[i].Signature = fmt.Sprintf("0x%s", obj.Signature)
|
||||
}
|
||||
setWithdrawalAddressJsons = append(setWithdrawalAddressJsons, &apimiddleware.SignedBLSToExecutionChangeJson{
|
||||
Message: &apimiddleware.BLSToExecutionChangeJson{
|
||||
setWithdrawalAddressJsons = append(setWithdrawalAddressJsons, &shared.SignedBLSToExecutionChange{
|
||||
Message: &shared.BLSToExecutionChange{
|
||||
ValidatorIndex: to[i].Message.ValidatorIndex,
|
||||
FromBLSPubkey: to[i].Message.FromBLSPubkey,
|
||||
ToExecutionAddress: to[i].Message.ToExecutionAddress,
|
||||
@@ -83,7 +84,7 @@ func getWithdrawalMessagesFromPathFlag(c *cli.Context) ([]*apimiddleware.SignedB
|
||||
return setWithdrawalAddressJsons, nil
|
||||
}
|
||||
|
||||
func callWithdrawalEndpoints(ctx context.Context, host string, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
|
||||
func callWithdrawalEndpoints(ctx context.Context, host string, request []*shared.SignedBLSToExecutionChange) error {
|
||||
client, err := beacon.NewClient(host)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -119,7 +120,7 @@ func callWithdrawalEndpoints(ctx context.Context, host string, request []*apimid
|
||||
return checkIfWithdrawsAreInPool(ctx, client, request)
|
||||
}
|
||||
|
||||
func checkIfWithdrawsAreInPool(ctx context.Context, client *beacon.Client, request []*apimiddleware.SignedBLSToExecutionChangeJson) error {
|
||||
func checkIfWithdrawsAreInPool(ctx context.Context, client *beacon.Client, request []*shared.SignedBLSToExecutionChange) error {
|
||||
log.Info("Verifying requested withdrawal messages known to node...")
|
||||
poolResponse, err := client.GetBLStoExecutionChanges(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -31,10 +31,10 @@ func getHappyPathTestServer(file string, t *testing.T) *httptest.Server {
|
||||
if r.RequestURI == "/eth/v1/beacon/pool/bls_to_execution_changes" {
|
||||
b, err := os.ReadFile(filepath.Clean(file))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
var to []*shared.SignedBLSToExecutionChange
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
err = json.NewEncoder(w).Encode(&beacon.BLSToExecutionChangesPoolResponse{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -338,10 +338,10 @@ func TestVerifyWithdrawal_Mutiple(t *testing.T) {
|
||||
if r.Method == http.MethodGet {
|
||||
b, err := os.ReadFile(filepath.Clean(file))
|
||||
require.NoError(t, err)
|
||||
var to []*apimiddleware.SignedBLSToExecutionChangeJson
|
||||
var to []*shared.SignedBLSToExecutionChange
|
||||
err = json.Unmarshal(b, &to)
|
||||
require.NoError(t, err)
|
||||
err = json.NewEncoder(w).Encode(&apimiddleware.BLSToExecutionChangesPoolResponseJson{
|
||||
err = json.NewEncoder(w).Encode(&beacon.BLSToExecutionChangesPoolResponse{
|
||||
Data: to,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -30,10 +30,10 @@ go_library(
|
||||
"//validator/keymanager/local:go_default_library",
|
||||
"//validator/keymanager/remote-web3signer:go_default_library",
|
||||
"//validator/node:go_default_library",
|
||||
"@com_github_golang_protobuf//ptypes/empty",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_urfave_cli_v2//:go_default_library",
|
||||
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
|
||||
"@org_golang_google_grpc//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
6
deps.bzl
6
deps.bzl
@@ -1747,6 +1747,12 @@ def prysm_deps():
|
||||
replace = "github.com/prysmaticlabs/grpc-gateway/v2",
|
||||
sum = "h1:4wctORg/1TkgLgXejv9yOSAm3cDBJxoTzl/RNuZmX28=",
|
||||
version = "v2.3.1-0.20230315201114-09284ba20446",
|
||||
repo_mapping = {"@go_googleapis": "@googleapis"},
|
||||
patch_args = ["-p1"],
|
||||
patches = [
|
||||
"//third_party:com_github_grpc_ecosystem_grpc_gateway_v2.patch",
|
||||
"//third_party:com_github_grpc_ecosystem_grpc_gateway_v2_fix_emptypb.patch",
|
||||
],
|
||||
)
|
||||
go_repository(
|
||||
name = "com_github_guptarohit_asciigraph",
|
||||
|
||||
@@ -57,6 +57,7 @@ done
|
||||
# --------------------------------------------------------
|
||||
iface_mocks=(
|
||||
"$iface_mock_path/beacon_chain_client_mock.go BeaconChainClient"
|
||||
"$iface_mock_path/prysm_beacon_chain_client_mock.go PrysmBeaconChainClient"
|
||||
"$iface_mock_path/node_client_mock.go NodeClient"
|
||||
"$iface_mock_path/slasher_client_mock.go SlasherClient"
|
||||
"$iface_mock_path/validator_client_mock.go ValidatorClient"
|
||||
|
||||
@@ -4,6 +4,9 @@ load(
|
||||
"string_flag",
|
||||
)
|
||||
|
||||
# gazelle:resolve proto proto google/api/annotations.proto @googleapis//google/api:annotations_proto
|
||||
# gazelle:resolve proto go google/api/annotations.proto @googleapis//google/api:annotations_go_proto
|
||||
|
||||
# Example flag: --//proto:network=minimal
|
||||
string_flag(
|
||||
name = "network",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user