Compare commits

...

26 Commits

Author SHA1 Message Date
Kasey Kirkham
e5a2873376 rm unused lock 2023-10-17 15:37:33 -05:00
Kasey Kirkham
4d4753ebf3 init AvailabilityStore in blockchain test setup 2023-10-17 15:14:38 -05:00
Kasey Kirkham
d4fd310615 DA check before blob saving for init-sync 2023-10-17 14:23:24 -05:00
terencechain
58cdb29ef3 Verify blobs that arrived from by root request (#13044)
* Verify blobs that arrived from by root request

* Add tests

* Add tests

* Fix test

* Fix lint

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-10-16 05:30:33 +00:00
hyunchel
cc2b4db582 Add state not found test case (#13034)
* beacon-chain:rpc/eth/shared: prevent mutiple error messages

This commit prevents the error writing function from writing multiple
JSON objects. An error message with more than one JSON object will not
unmarshal into the default error response.

* beacon-chain/rpc/eth/beacon: add a test case for missing state

This commit adds a test on beacon states finality checkpoints endpoint
to cover a case when state is not found.

* beacon-chain/rpc/eth: update error response to meet the spec

This commit updates error message on beacon states finality
checkpoints endpoint to ensure that the response complies to
Ethereum Beacon-API specification.

* beacon-chain/rpc/eth/shared: add build dependency

* beacon-chain/rpc/eth/shared: update test on state not found

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-10-13 17:21:38 +00:00
Andrew Davis
be9b6ea837 fix(beacon-chain/rpc): blob_sidecar event stream handler (#12999)
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Sammy Rosso <15244892+saolyn@users.noreply.github.com>
2023-10-13 13:51:16 +00:00
Potuz
806a394c89 Reject blobs with invalid parent (#13047)
* Reject blobs with invalid parent

* remove unused comment
2023-10-13 13:15:40 +00:00
Potuz
97a99874e8 Remove confusing comment (#13045) 2023-10-13 00:36:03 +00:00
terencechain
945b087ca9 Add more blob and block checks for by range (#13043)
* Add more blob and block checks for by range

* Update beacon-chain/sync/initial-sync/blocks_fetcher.go

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

* Update beacon-chain/sync/initial-sync/blocks_fetcher.go

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

* Update beacon-chain/sync/initial-sync/blocks_fetcher_test.go

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

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2023-10-12 19:35:32 +00:00
Radosław Kapka
b57effd096 HTTP Beacon APIs: 3 state endpoints (#13001)
* HTTP Beacon APIs: 3 state endpoints

* remove API middleware e2e evaluator

* remove evaluator registrations

* review feedback

* compilation fix
2023-10-12 17:53:33 +00:00
terencechain
867db1aeee Exit early if blob by root request is empty (#13038) 2023-10-12 02:27:26 +00:00
terencechain
99843688cd Request missing blobs while processing pending queue (#13015)
* Request missing blobs while processing pending queue

* Fix build

* Lower complexity

* Fix conflict

* Clean up per James's comment

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-10-11 22:07:11 +00:00
Radosław Kapka
a536612c39 HTTP Beacon APIs for node (#13010) 2023-10-11 22:18:49 +02:00
terencechain
c5501f8775 Refactor Pending Block Queue Logic in Sync Package (#13026)
* Clean up pending block queue

* Kasey's feedback

* Kasey's feedback on validateBeaconBlock err handling

* Clean up handleBlockProcessingError

* Clean up old comments

* James feedback

* has peer helper

* Reuse parent reoot

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-10-11 14:41:24 +00:00
james-prysm
55e4c6e1db HTTP API: /eth/v1/beacon/pool/bls_to_execution_changes (#12963)
* wip

* wip changes for beacon handlers

* wip

* updating protos

* fixing conflict

* fixing protos

* fixing more tests

* migrating tests and removing one that is irrelevant

* fixing e2e

* fixing unit tests

* missed adding routes

* gaz

* fixing more tests

* Update beacon-chain/rpc/eth/beacon/handlers_pool.go

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

* Update beacon-chain/rpc/eth/shared/structs.go

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

* Update beacon-chain/rpc/eth/shared/structs.go

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

* Update beacon-chain/rpc/eth/shared/structs.go

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

* Update beacon-chain/rpc/eth/shared/structs.go

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

* radek's comments

* fixing protos after conflict

* accidently removed a proto type

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-10-11 14:08:06 +00:00
Dhruv Bodani
2806326155 integrate validator count endpoint in validator client (#12912)
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-10-11 15:23:02 +02:00
Delweng
d7318ea485 beacon-chain/execution: fix a data race in testcase (#13016)
Signed-off-by: jsvisa <delweng@gmail.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
Co-authored-by: Nishant Das <nishdas93@gmail.com>
2023-10-11 03:37:25 +00:00
Radosław Kapka
e183d1dff4 Improvements to produceBlockV3 (#13027) 2023-10-10 18:06:57 +00:00
Radosław Kapka
a3868e7fc6 Fix comment of BlobSidecarsBySlot (#13019)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-10-10 15:54:29 +00:00
james-prysm
af70677778 Deneb: Produce Block V3 - adding consensus block value (#12948)
* adding in block rewards to represent consensus payload

* Update beacon-chain/rpc/eth/validator/handlers_block.go

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

* radek's comments

* more review changes

* adding more tests for forks

* gaz

* updating names

* gaz

* fixing imports

* fixing variable name

* gaz

* fixing test

* renaming variables to match data

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-10-10 15:12:20 +00:00
Preston Van Loon
8eb82dd378 Update rules_go and gazelle to 0.42 & 0.33 (latest releases) (#13021)
* Provide @go_googleapis, update gazelle

* fix pb build failures

Fix build issues with grpc-gateway
2023-10-10 12:50:29 +08:00
Preston Van Loon
0bd232667b Fix image deps (#13022) 2023-10-09 17:21:49 -05:00
terencechain
39072e1b74 Check block exists in pending queue before requesting from peer (#13013) 2023-10-09 10:35:15 -07:00
terencechain
66011d5d9c Check blob exists before requesting from peer (#13012)
* Check blob exists before requesting from peer

* Potuz's feedback

* Fix err

* Add index check
2023-10-09 22:55:24 +08:00
Delweng
419dbd57f7 beacon-chain/execution: no need to reread and unmarshal the eth1Data twice (#12826)
* beacon-chain/execution: no need to reread and unmarshal the eth1Data multitimes

Signed-off-by: jsvisa <delweng@gmail.com>

* beacon-chain/execution: rename to validPowchainData

Signed-off-by: jsvisa <delweng@gmail.com>

* beacon-chain/execution: no return eth1Data if error

Signed-off-by: jsvisa <delweng@gmail.com>

* beacon-chain/execution: return eth1data even if genstate is nil

Signed-off-by: jsvisa <delweng@gmail.com>

---------

Signed-off-by: jsvisa <delweng@gmail.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: Nishant Das <nishdas93@gmail.com>
2023-10-09 13:51:52 +08:00
Nishant Das
da6ae3c204 use read only head state (#13014) 2023-10-07 14:44:52 -05:00
218 changed files with 6788 additions and 8447 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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"
)

View File

@@ -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",

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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]))
}
})
}
}

View File

@@ -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.

View File

@@ -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...)

View File

@@ -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",

View File

@@ -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())

View File

@@ -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)
}

View 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",
],
)

View 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
}

View 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]))
}
})
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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 = &ethpb.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 {

View File

@@ -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)

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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)
}

View File

@@ -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",

View File

@@ -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:

View File

@@ -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"`

View File

@@ -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{

View File

@@ -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}

View File

@@ -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"`

View File

@@ -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",
],
)

View File

@@ -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()

View File

@@ -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,
})
}

View File

@@ -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 := &ethpbv1alpha1.SignedBLSToExecutionChange{
Message: &ethpbv1alpha1.BLSToExecutionChange{
ValidatorIndex: 1,
FromBlsPubkey: bytesutil.PadTo([]byte("pubkey1"), 48),
ToExecutionAddress: bytesutil.PadTo([]byte("address1"), 20),
},
Signature: bytesutil.PadTo([]byte("signature1"), 96),
}
change2 := &ethpbv1alpha1.SignedBLSToExecutionChange{
Message: &ethpbv1alpha1.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 := &ethpbv1alpha1.BeaconStateCapella{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv1alpha1.BeaconStateBellatrix{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv1alpha1.BeaconStateCapella{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.BeaconStateCapella{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv1alpha1.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 = `[
{

View 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
}

View 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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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++
}
}
}

View File

@@ -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{

View File

@@ -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 &ethpbv2.BLSToExecutionChangesPoolResponse{
Data: changes,
}, nil
}

View File

@@ -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 := &ethpbv1alpha1.SignedBLSToExecutionChange{
Message: &ethpbv1alpha1.BLSToExecutionChange{
ValidatorIndex: 1,
FromBlsPubkey: bytesutil.PadTo([]byte("pubkey1"), 48),
ToExecutionAddress: bytesutil.PadTo([]byte("address1"), 20),
},
Signature: bytesutil.PadTo([]byte("signature1"), 96),
}
change2 := &ethpbv1alpha1.SignedBLSToExecutionChange{
Message: &ethpbv1alpha1.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 := &ethpbv1alpha1.BeaconStateCapella{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv2.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 := &ethpbv2.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, &ethpbv2.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 := &ethpbv1alpha1.BeaconStateBellatrix{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv2.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 := &ethpbv1alpha1.BeaconStateCapella{
Fork: &ethpbv1alpha1.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 := &ethpbv2.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, &ethpbv2.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 := &ethpbv1alpha1.BeaconStateCapella{
Fork: &ethpbv1alpha1.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 := &ethpbv1alpha1.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 := &ethpbv2.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 := &ethpbv2.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, &ethpbv2.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])
}
}

View File

@@ -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 &ethpb.StateRootResponse{
Data: &ethpb.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 &eth2.RandaoResponse{
Data: &eth2.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
}

View File

@@ -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(), &eth.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(), &eth.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(), &eth.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, &eth2.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, &eth2.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, &eth2.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, &eth2.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, &eth2.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, &eth2.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(), &eth2.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(), &eth2.RandaoRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
assert.DeepEqual(t, true, resp.Finalized)
})
}

View File

@@ -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"`
}

View File

@@ -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 = &currentPeriodStartEpoch
}
}
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 &ethpbv2.StateSyncCommitteesResponse{
Data: &ethpbv2.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 := &ethpbv2.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
}

View File

@@ -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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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(&ethpbalpha.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 := &ethpbv2.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(&ethpbalpha.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 := &ethpbv2.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++
}
}
}

View File

@@ -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",
],
)

View File

@@ -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",
],
)

View File

@@ -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)
}

View 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
}

View 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)
}
}

View File

@@ -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)
})
}

View File

@@ -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 := &ethpb.Metadata{
SeqNumber: ns.MetadataProvider.MetadataSeq(),
Attnets: ns.MetadataProvider.Metadata().AttnetsBitfield(),
}
return &ethpb.IdentityResponse{
Data: &ethpb.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 &ethpb.PeerResponse{
Data: &ethpb.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 &ethpb.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 &ethpb.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 &ethpb.PeerCountResponse{
Data: &ethpb.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 &ethpb.VersionResponse{
Data: &ethpb.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
}

View File

@@ -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, &ethpb.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, &ethpb.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, &ethpb.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(), &ethpb.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(), &ethpb.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(), &ethpb.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(), &ethpb.PeersRequest{
State: []ethpb.ConnectionState{},
Direction: []ethpb.PeerDirection{},
})
require.NoError(b, err)
}
}

View File

@@ -1,7 +0,0 @@
package node
import (
ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service"
)
var _ ethpbservice.BeaconNodeServer = (*Server)(nil)

View File

@@ -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"`
}

View File

@@ -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",
],
)

View File

@@ -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()

View File

@@ -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(&eth.AttestationData{}),
@@ -83,7 +113,8 @@ func TestBlockRewards(t *testing.T) {
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
}
})
attData1 := util.HydrateAttestationData(&eth.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32)})
attData2 := util.HydrateAttestationData(&eth.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: &eth.IndexedAttestation{
AttestingIndices: []uint64{0},
@@ -105,7 +136,7 @@ func TestBlockRewards(t *testing.T) {
Signature: secretKeys[0].Sign(sigRoot2[:]).Marshal(),
},
},
}
})
header1 := &eth.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: &eth.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 = &eth.SyncAggregate{SyncCommitteeBits: scBits, SyncCommitteeSignature: aggSig}
sbb, err := blocks.NewSignedBeaconBlock(b)
err = sbb.SetSyncAggregate(&eth.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) {

View File

@@ -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
}

View 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
}

View File

@@ -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 {

View 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",
],
)

View 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
}

View File

@@ -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",
],

View File

@@ -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)
}

View File

@@ -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")
}
}

View File

@@ -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 &eth.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 &eth.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 &eth.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 {

View File

@@ -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"
}
}

View File

@@ -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",

View File

@@ -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(&eth.GenericSignedBeaconBlock_Altair{Altair: &eth.SignedBeaconBlockAltair{Block: b.Altair}})
case *eth.GenericBeaconBlock_Bellatrix:
wrapper, err = blocks.NewSignedBeaconBlock(&eth.GenericSignedBeaconBlock_Bellatrix{Bellatrix: &eth.SignedBeaconBlockBellatrix{Block: b.Bellatrix}})
case *eth.GenericBeaconBlock_BlindedBellatrix:
wrapper, err = blocks.NewSignedBeaconBlock(&eth.GenericSignedBeaconBlock_BlindedBellatrix{BlindedBellatrix: &eth.SignedBlindedBeaconBlockBellatrix{Block: b.BlindedBellatrix}})
case *eth.GenericBeaconBlock_Capella:
wrapper, err = blocks.NewSignedBeaconBlock(&eth.GenericSignedBeaconBlock_Capella{Capella: &eth.SignedBeaconBlockCapella{Block: b.Capella}})
case *eth.GenericBeaconBlock_BlindedCapella:
wrapper, err = blocks.NewSignedBeaconBlock(&eth.GenericSignedBeaconBlock_BlindedCapella{BlindedCapella: &eth.SignedBlindedBeaconBlockCapella{Block: b.BlindedCapella}})
case *eth.GenericBeaconBlock_Deneb:
// no need for sidecar
wrapper, err = blocks.NewSignedBeaconBlock(&eth.GenericSignedBeaconBlock_Deneb{Deneb: &eth.SignedBeaconBlockAndBlobsDeneb{Block: &eth.SignedBeaconBlockDeneb{Block: b.Deneb.Block}}})
case *eth.GenericBeaconBlock_BlindedDeneb:
// no need for sidecar
wrapper, err = blocks.NewSignedBeaconBlock(&eth.GenericSignedBeaconBlock_BlindedDeneb{BlindedDeneb: &eth.SignedBlindedBeaconBlockAndBlobsDeneb{SignedBlindedBlock: &eth.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,
})
}

View File

@@ -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))
})
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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",
],
)

View File

@@ -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",

View File

@@ -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),
})
}

View File

@@ -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)

View File

@@ -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, &ethpb.StateSummary{
Slot: st.Slot(),
Root: blockRoot[:],

View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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.

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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] = &eth.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, &eth.BlobIdentifier{Index: i, BlockRoot: blockRoot[:]})
}
return ids
}

View File

@@ -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(), &ethpb.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)
}

View File

@@ -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)

View File

@@ -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: &eth.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))
}

View File

@@ -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
}

View 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",
],
)

View 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
}

View 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: &ethpb.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: &ethpb.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: &ethpb.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: &ethpb.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: &ethpb.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: &ethpb.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: &ethpb.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: &ethpb.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: &ethpb.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())
}
})
}
}

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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",
],
)

View File

@@ -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",

View File

@@ -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"

View File

@@ -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