Compare commits

...

88 Commits

Author SHA1 Message Date
Preston Van Loon
a5ba8d4352 Evaluate append vs copy. Apply results 2023-10-02 14:47:43 -05:00
Preston Van Loon
81a5c0c309 Add some benchmarks 2023-10-02 13:30:04 -05:00
Preston Van Loon
de84a0aed6 Merge branch 'develop' into beacon-node-append-copy 2023-10-02 09:26:29 -05:00
Radosław Kapka
ecd55e5462 Remove go-playground/validator from Beacon API (#12973)
* Remove go-playground/validator from Beacon API

* gzl

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-10-02 13:21:59 +00:00
Nishant Das
46b2442127 Do Not Calculate Churn With 0 Exits (#12976) 2023-10-02 01:46:58 +00:00
Radosław Kapka
d8b2d9060f Return RpcError from core service's SubmitSyncMessage (#12974)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-30 14:19:09 +00:00
Justin Traglia
b667c68c87 Default to the portable version of blst (#12720)
* Default to the portable version of blst

* Update based on review comments

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2023-09-29 15:35:15 -05:00
Lucas Saldanha
fb19ee8895 Updating Teku mainnet bootnodes ENRs (#12962)
Co-authored-by: Nishant Das <nishdas93@gmail.com>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2023-09-29 10:11:58 +00:00
terencechain
51b8075474 Use skip mev-boost flag for GetBlock RPC (#12969)
Co-authored-by: Nishant Das <nishdas93@gmail.com>
2023-09-29 16:48:40 +08:00
Radosław Kapka
367504f403 Support comma-separated query params in Beacon API (#12966) 2023-09-29 15:40:23 +08:00
Potuz
b4e72f1e19 Deneb spectests release v1.4.0-beta.2-hotfix (#12959)
* Update and use max per epoch churn limit

* Update spec tests

* Fix e2e test

* deneb fork epoch condition

* Fix lint and better casting

* fix ordering

* fix check

* gaz

* Fix more tests

* Apply proposer boost to first block in equivocation

* Increase timeout

* Don't increase timeout, it's not the reason

* implement deneb forkchoice spectests

expose ReceiveBlob from the blockchain package

* spin_off_helper

* remove minimal tests

* Terence's review

* Add process register test for Deneb

* Terence's suggestion

Co-authored-by: terencechain <terence@prysmaticlabs.com>

* fix forkchoice minimal

* fix minimal sha

* general sha

* different repos

* different repos

---------

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: nisdas <nishdas93@gmail.com>
2023-09-28 21:05:23 +00:00
Preston Van Loon
a7361cd5ab Docker: Add "manual" build tag to reduce build times in CI (#12967)
* Add manual tags to oci images. This reduces build times for build and test in CI.

* Ran buildifier on tools dir
2023-09-28 17:49:26 +00:00
Preston Van Loon
02ee6897bb Multiarch docker containers (#12428)
* Add bazel-zig-cc for a hermetic cc toolchain

* gazelle

* Remove llvm

* remove wl

* Add new URLs for renamed repo

* gazelle

* Update to v2.0.0-rc1

* bump to rc2

* Proof of concept multi-arch containers for beacon-chain

* TODO and gaz

* Refactor to starlark macro. Use a version of bash that actually works

* progress

* gaz

* multirun to use multiple repositories, but doesn't work with tag stamping

* Revert "multirun to use multiple repositories, but doesn't work with tag stamping"

This reverts commit 93afa76f65.

* Add targets for all supported docker images and temporarily set the repository to prysm-dev for testing

* use a temporary fix to see if it works on buildkite

* Revert "use a temporary fix to see if it works on buildkite"

This reverts commit ddc79283ca.

* testing a cURL fix

* try fix with my fix

* Revert "try fix with my fix"

This reverts commit bb7521bf47.

* Revert "testing a cURL fix"

This reverts commit 8a4782110f.

* try tip of main branch for rules_oci

* update to 1.2.0

* Update rules_oci to v1.3.0

* Update rule_oci to 1.3.4

* Disable experimental_remote_downloader

* Remove extra zig bazelrc

* Move image deps to its own file

* PR feedback

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-28 15:24:16 +00:00
terencechain
c20f188966 Deprecate safe slots to import flag (#12964) 2023-09-28 17:40:58 +08:00
terencechain
5870536dca Add a flag to configure blob retention epoch period (#12941)
* Add a flag to configure blob retention epoch period

* Add test

* Kasey's feedback

* More Kasey's feedback

* Fix lint and tests

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-27 14:59:52 +00:00
james-prysm
c8b39e08ef cleanup on api types (#12961)
* removing code duplication using shared types

* gaz

* fixing test

* fixing tests
2023-09-27 09:26:32 -05:00
Sammy Rosso
b0b4e42436 HTTP Beacon API: /eth/v1/beacon/blocks and /eth/v1/beacon/blinded_blocks (#12827)
* Add PublishBlock

* Add endpoints

* Cleanup

* Modify publishBlock to work for v1 and v2

* Remove v2 + add server receiver

* Cleanup remaining endpoints

* Remove error from SszRequested

* Remove unused functions
2023-09-27 12:51:37 +00:00
terencechain
b0caea3fae Refactor getLocalPayloadAndBlobs with New Helper getParentBlockHash (#12951)
* Implement getParentBlockHash helper

* Specify errors
2023-09-27 03:08:17 +00:00
Roberto Bayardo
a46370f5bf clean up code around setting geth client headers (#11748) 2023-09-26 15:15:28 +00:00
Nishant Das
0919b2245f Avoid Public Key Copies During Aggregation (#12944)
* Add in optimization

* add better test

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-26 11:13:19 +00:00
Nishant Das
42c192d97d Fix More Racy Tests in Blockchain (#12957) 2023-09-26 09:17:11 +00:00
Potuz
3394bbe359 forkchoice return last canonical root of epoch (#12954)
* forkchoice return last canonical root of epoch

* move inside loop

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-25 16:36:43 +00:00
Nishant Das
70225186ff Fix TestService_ReceiveBlock (#12953)
* fix tests

* radek's review

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-25 15:24:32 +00:00
terencechain
942d63fcc1 Refactor construct generic beacon block for proposer (#12943) 2023-09-25 07:07:04 -07:00
Sammy Rosso
90fb2325db /eth/v1/config/deposit_contract return string instead of uint (#12952)
* Convert uint to string

* Add test

* Rename test
2023-09-25 13:02:47 +00:00
Preston Van Loon
57a63f37f7 Update holesky config for new genesis (#12919)
* Update holesky config with new genesis. See https://github.com/eth-clients/holesky/pull/73

* Update bootnodes

* Update bootnodes

* Use latest commit

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-23 16:09:28 +00:00
Preston Van Loon
9a2c2470c6 db: Log a warning if the genesis state is smaller than 1Kb (#12897)
* Add warning with a small genesis state

* 1<<10 is 1Kb

* Add genesis state loading with hash information

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-23 15:29:15 +00:00
james-prysm
723f73795f Deneb: blob sidecar events (#12928)
* adding in deneb blob event triggers

* fixing linting

* kasey's feedback

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-22 21:54:10 +00:00
Raul Jordan
6454081577 Amend Bug Report Template Requirements (#12937)
* requirements

* amend

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-22 20:26:24 +00:00
Potuz
489b34a01f Use headstate when requesting current epochs (#12940)
* Use headstate when requesting current epochs

* gazelle

* review requests
2023-09-22 19:48:24 +00:00
Radosław Kapka
e22258caa9 HTTP validator APIs (#12887)
* GetValidators in progress

* in progress

* completed implementation of GetValidators + some tests

* typo

* completed tests for GetValidators

* GetValidator

* GetValidatorBalances

* register

* more tests and e2e fix

* middleware cleanup

* remove struct

* handle all ignored

* test fixes

* more test fixes

* even more test fixes

* remove unused structs

* docs

* fix validator count test

* build fix

* add length checks

* lint fix

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-21 18:16:59 +00:00
Radosław Kapka
3bc9ac37f6 Add Content-Type header to VC POST requests (#12942) 2023-09-21 17:21:06 +00:00
Nishant Das
7247b8bd3c Fix Unhandled Error (#12938)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-21 16:23:56 +00:00
Potuz
e2591f7c5b Do not cache proposer ID on GetProposerDuties (#12939) 2023-09-21 15:22:41 +00:00
Radosław Kapka
b40729f6c0 Return correct root from GetBlockHeader (#12935)
* Return correct root from `GetBlockHeader`

* variable rename
2023-09-21 12:02:14 +00:00
Nishant Das
b7cb5c81be fix it (#12932) 2023-09-21 11:40:03 +08:00
Radosław Kapka
e76aedf1ae HTTP API: GetLiveness and GetBlockHeader (#12916)
* GetLiveness

* GetBlockHeader

* simplify request objects

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-20 19:06:11 +00:00
terencechain
0e5d299d02 Remove unused pending blobs queue (#12913)
* Remove pending blobs queue

* Fix tests

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-20 17:00:01 +00:00
Radosław Kapka
14f040de48 GetFinalityCheckpoints and GetGenesis HTTP endpoints (#12902)
* GetFinalityCheckpoints and GetGenesis

* bzl

* remove unused func

* use const as base path

* bring back headers

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-19 17:53:45 +00:00
terencechain
1a1a30591e Handle Specific Blob Retrieval Error in DB (#12889)
* log db blob by root error

* Add comments
2023-09-19 15:29:13 +00:00
terencechain
3070878d59 Add Blind Blob Sidecar Signing in Validator Client (#12922)
* Add validator signing blind blob sidecar

* Fix tests

* Refactor

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-19 00:36:42 +00:00
terencechain
f59307358e Copy kzg commitments when using builder block (#12923) 2023-09-18 23:54:20 +00:00
Potuz
ef1f5e6dbe remove unused function (#12920)
* remove unused function

* remove the actual method

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-18 21:30:30 +00:00
terencechain
a22ca3fecb Update geth to v1.13.1 (#12911)
* Update geth

* Fix builder

* Update geth to v1.13.1.

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-18 21:12:07 +00:00
james-prysm
14ce051668 Deneb builder fix (#12921)
* fixing conversion for deneb

* updating unit test to catch this in the future

* gaz
2023-09-18 20:34:28 +00:00
terencechain
998a493ee2 Fix builder blind block namings (#12910)
* Fix builder blind block namings

* Fix

* Fix tests

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-18 15:32:10 +00:00
Nishant Das
398f44bb53 fix it (#12917) 2023-09-18 22:30:34 +08:00
Nishant Das
4098b3a1d2 fix it (#12915) 2023-09-18 21:42:54 +08:00
Radosław Kapka
d8e6d2cb2e Fix proposer duties sorting (#12909)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-16 14:11:57 +00:00
Potuz
6b915bab26 Dont process blocks twice (#12905)
* keep track of block being synced

* gazelle

* use maps

* shutup deepsource

* change godoc

* Radek's review

* Do not process block twice if it's already being processed

* add unit test
2023-09-15 20:11:02 +00:00
Potuz
dd73f762ec keep track of block being synced (#12903)
* keep track of block being synced

* gazelle

* use maps

* shutup deepsource

* change godoc

* Radek's review
2023-09-15 17:13:13 +00:00
anukul
4d120b53ae HTTP Beacon API: /eth/v1/beacon/headers (#12817)
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-09-15 12:05:35 +02:00
Delweng
e91716cb7f Merge branch 'develop' into beacon-node-append-copy 2023-09-15 09:14:13 +08:00
terencechain
4d6b3252ae Fix deneb builder bid HTR (#12906)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-15 00:14:03 +00:00
terencechain
9bb81537c8 feat: add blob arrival gossip metric (#12888)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-14 21:35:18 +00:00
Potuz
0fdf63b565 default to 7 seconds for first aggregation (#12876)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-14 17:46:28 +00:00
Sammy Rosso
bd85b0e4e1 Update bug report template (#12891)
Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>
2023-09-14 16:41:49 +00:00
terencechain
d1562bab53 Update blind blobs bundle max commitment size (#12901)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-14 14:21:56 +00:00
terencechain
1e29877406 don't save blob sidecar syncing to head if 0 (#12892) 2023-09-14 13:17:49 +00:00
Sammy Rosso
8c39c55f05 HTTP Beacon API: /eth/v1/beacon/states/{state_id}/committees (#12879)
* Initial setup

* Gaz

* Remove original implementation

* Fix protos

* Cleanup + tests

* Cleanup

* Radek' review + fixes

* Fix merge errors

* Fix imports

* Rename error handling

* Fix broken e2e

* Gaz

* Update comment
2023-09-13 17:29:32 +00:00
Preston Van Loon
0ccfc74e86 Fix build @com_github_ethereum_c_kzg_4844//... (#12890)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-12 16:59:42 +00:00
james-prysm
9ecf4d34f5 Deneb api cleanup (#12852)
* cleanup wip

* updating tests, and updating logs

* fixing unit tests

* address radek's comments

* fixing bad alias name
2023-09-12 16:22:20 +00:00
kasey
a8793c9f21 Simplify DA check to avoid blob/block timing inconsistencies (#12882)
* collect unique idxs and avoid races

* fix init

* remove unused type (lint error)

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-12 15:32:21 +00:00
Radosław Kapka
79445d2bf6 HTTP Beacon API: /eth/v1/validator/duties (#12810)
* impl

* protos

* remove apimiddleware from e2e

* register endpoint

* get epoch from vars

* tests

* test fixes

* remove unused function

* GetProposerDuties

* proto

* fix compilation

* register

* tests

* GetSyncCommitteeDuties

* protos

* register

* tests in progress

* complete tests

* check altair epoch

* more testing

* create variable

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-12 13:57:04 +00:00
terencechain
35fc1c976f Improved Logging for Blob Sidecar (#12883)
* feat: improve blob sidecar logs

* Kasey's feedback
2023-09-11 20:46:06 +00:00
james-prysm
b0423a94af REST implementation of /eth/v1/beacon/states/{state_id}/fork (#12835)
* fork state endpoint

* removing generated files

* fixing linting

* fixing tests using old type

* moving the response object to a new file under shared

* fixing test

* gaz

* fixing generated code

* gaz

* fixing linting

* rolling back some changes

* reverting generated changes

* fixing spacing

* linting

* updating protos after develop merged

* addressing radek's comments

* addressing more radek comments
2023-09-11 18:57:17 +00:00
james-prysm
6c16e90fe9 deneb - validator beacon rest apis (#12871)
* wip

* adding tests for deneb

* adding deneb to get block

* Update validator/client/beacon-api/get_beacon_block.go

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

* Update validator/client/beacon-api/propose_beacon_block.go

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

* Update validator/client/beacon-api/propose_beacon_block.go

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

* Update validator/client/beacon-api/propose_beacon_block.go

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

* Update validator/client/beacon-api/propose_beacon_block.go

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

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2023-09-11 16:50:55 +00:00
kasey
c0a01bb859 Allow equivocating blobs (#12880)
* add eq test

* allow blob at diff roots; defer pruning

* avoid deferred pruning controversy

* lint: already a slice

---------

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2023-09-10 02:23:37 +00:00
Bharath Vedartham
2a408a0dd8 don't send pre-genesis signed validator registration objects to relayers (#12847)
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-09 18:53:02 +00:00
Raul Jordan
af16c71d6e Add Delay Option for Genesis to Prysmctl (#12878)
* add delay option to prysmctl for genesis time

* revert bazel
2023-09-08 20:44:09 +00:00
kasey
ec954ec9a6 function to merge and validate saved/new blobs (#12868)
* function to merge and validate saved/new blobs

* Make it an error to call save with an empty slice

* rename func to match type, undo var rename

* satisfy deepsource; cheap len check before compare

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-08 18:33:51 +00:00
Sammy Rosso
7781a3186b HTTP implementation of /eth/v1/config/deposit_contract (#12872)
* Add endpoint

* Protos

* Forgot to add endpoint

* fix proto double import

* Remove old test

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-08 17:05:29 +00:00
terencechain
440cf32966 fix: correct blob sidecar count (#12865)
* fix: update blob sidecar count

* fix: use beacon config instead of network config

---------

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-08 15:37:53 +00:00
terencechain
13c69af717 fix: use correct blob root in blobs sidecar (#12866)
Co-authored-by: kasey <489222+kasey@users.noreply.github.com>
2023-09-08 14:58:33 +00:00
kasey
ca88e59ee3 Tests for init-sync DA (#12873)
* test coverage for commitmentsToCheck

* test for WithinDAPeriod

* gaz

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2023-09-08 10:47:34 +00:00
kasey
809a67ebcc reject blocks with more commitments than spec (#12863)
* reject blocks with more commitments than spec

* add details of excess commitments to error

* good catch, linter

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2023-09-07 20:21:03 +00:00
Stefan
f17898f658 add a RW lock for duties (#12861)
* add a RW lock for duties

* remove superfluous rlock

* remove defer from if

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2023-09-07 18:23:10 +00:00
Nishant Das
d506f9b2da Fix Execution Block Unmarshalling in Deneb (#12860)
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2023-09-07 16:52:02 +00:00
Sammy Rosso
d55757500f Integrate EIP-4881 Deposit Tree Into Prysm via a Feature Flag (#11942)
* Initial spec rewrite

* Finish adding merkle tree implementation

* Last bits

* Move reverse function

* Add comments

* Add deposit tree snapshot

* Add deposit tree

* Add comments + cleanup

* Fixes

* Add missing errors

* Small fixes

* Add unhandled error

* Cleanup

* Fix unsafe file.Close

* Add missing comments

* Small fixes

* Address some of deepSource' compaints

* Add depositCount check

* Add finalizedDeposit check

* Replace pointer magic with copy()

* Add test for slice reversal

* add back bytes method

* Add package level description

* Remove zerohash gen and add additional checks

* Add additional comments

* Small lint fixes

* Forgot an error

* Small fixes

* Move Uint64ToBytesLittleEndian32 + test

* Fix uint subtraction issue

* Move mixInLength below error handling

* Fix

* Fix deposit root

* integrate 4881

* edits

* added in deposit tree fetcher

* add file

* Add remaining fetcher functions

* Add new file for inserter functions

* Fixes and additional funcs

* Cleanup

* Add

* Graph

* pushed up edits

* fix up

* Updates

* Add EIP4881 toggle flag

* Add interfaces

* Fix tests

* More changes

* Fix

* Remove generated graph

* Fix spacing

* Changes

* Fixes

* Changes

* Test Fix

* gaz

* Fix a couple tests

* Fix last tests

* define protos

* proto methods

* pushed

* regen

* Add proto funcs

* builds

* pushin up

* Fix and cleanup

* Fix spectest

* General cleanup

* add 4881 to e2e

* Remove debug statements + remove test skip

* Implement first set of  missing methods

* Replace Zerohashes + cleanup

* gazelle

* fmt

* Put back defensive check

* Add error logs

* InsertFinalizedDeposits: return an error

* Remove logging

* Radek' Review

* Lint fixes

* build

* Remove cancel

* Update beacon-chain/deterministic-genesis/service.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Update beacon-chain/cache/depositsnapshot/deposit_inserter.go

Co-authored-by: Raul Jordan <raul@prysmaticlabs.com>

* Cleanup

* Fix panic when DepositSnapshot is nil on init

* Gofmt

* Fix RootEquivalence test

* Gofmt

* Add missing comments

* Nishant' review

* Add Insert benchmarks

* fix up copy method

* Fix deep copy

* Fix conflicts

* Return error

* Fix linter issues

* add in migration logic

* Cleanup + tests

* fix

* Fix incorrect index in test

* Fix linter

* Gofmt

* fix it

* fixes for off by 1

* gaz

* fix cast

* fix it

* remove ErrZeroIndex

* Fix merkle_tree_test

* add fallback

* add fix for insertion bug

* add many fixes

* fix empty snapshot

* clean up

* use feature

* remove check

* fix failing tests

* skip it

* fix test

* fix test again

* fix for the last time

* Apply suggestions from code review

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

* fix it

* remove cancel

* fix for voting

* addressing more comments

* fix err

* potuz's review

* one more test

* fix bad test

* make 4881 part of dev mode

* add workaround for new trie

* comment

* preston's review

* james's review

* add comment

* james review

* preston's review

* remove skipped test

* gaz

---------

Co-authored-by: rauljordan <raul@prysmaticlabs.com>
Co-authored-by: nisdas <nishdas93@gmail.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-07 03:19:32 +00:00
james-prysm
41dff74e90 fix phase0 block parsing on submitblock (#12857)
* adding fixes to address new changes in deneb

* removing logs

---------

Co-authored-by: Nishant Das <nishdas93@gmail.com>
2023-09-07 01:49:18 +00:00
kasey
4f48c551da Fix 12784 (#12822)
* add DA check to batched sync path

also fixes a da period calculation bug in the reg sync path.

* update comment

function original took a slot instead of an epoch, but was rewritten to use epoch to avoid the need to import the slot math package

Co-authored-by: terencechain <terence@prysmaticlabs.com>

---------

Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
Co-authored-by: terencechain <terence@prysmaticlabs.com>
2023-09-07 00:26:28 +00:00
terencechain
2f6dcb34b6 fix(builder): handle no blob bundle (#12838)
Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2023-09-06 20:59:42 +00:00
Nishant Das
682b59ca45 Merge branch 'develop' into beacon-node-append-copy 2023-09-04 02:47:05 -06:00
jsvisa
d3614f4529 beacon-chain/state/validator: alloc +1 items for append
Signed-off-by: jsvisa <delweng@gmail.com>
2023-08-31 03:10:34 +00:00
jsvisa
cef997f3f9 beacon-chain/state/participation: alloc +1 items for append
Signed-off-by: jsvisa <delweng@gmail.com>
2023-08-31 03:04:32 +00:00
jsvisa
131d64838d beacon-chain/state/misc: alloc +1 items for append
Signed-off-by: jsvisa <delweng@gmail.com>
2023-08-31 03:04:18 +00:00
jsvisa
f003dc34eb beacon-chain/state/eth1: alloc +1 items for append
Signed-off-by: jsvisa <delweng@gmail.com>
2023-08-31 03:04:05 +00:00
jsvisa
020f9d0547 beacon-chain/state/attenstation: alloc +1 items for append
Signed-off-by: jsvisa <delweng@gmail.com>
2023-08-31 03:03:53 +00:00
318 changed files with 14122 additions and 14968 deletions

View File

@@ -15,7 +15,7 @@ coverage --define=coverage_enabled=1
# Stamp binaries with git information
build --workspace_status_command=./hack/workspace_status.sh
build --define blst_disabled=false --define blst_modern=true
build --define blst_disabled=false
run --define blst_disabled=false
build:blst_disabled --define blst_disabled=true

View File

@@ -12,7 +12,8 @@
#build:remote-cache --disk_cache=
build:remote-cache --remote_download_toplevel
build:remote-cache --remote_cache=grpc://bazel-remote-cache:9092
build:remote-cache --experimental_remote_downloader=grpc://bazel-remote-cache:9092
# Does not work with rules_oci. See https://github.com/bazel-contrib/rules_oci/issues/292
#build:remote-cache --experimental_remote_downloader=grpc://bazel-remote-cache:9092
build:remote-cache --remote_local_fallback
build:remote-cache --experimental_remote_cache_async
build:remote-cache --experimental_remote_merkle_tree_cache

View File

@@ -1,59 +0,0 @@
---
name: "\U0001F41EBug report"
about: Report a bug or problem with running Prysm
---
<!--💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎
Hellooo! 😄
To help us tend to your issue faster, please search our currently open issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.
💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎-->
# 🐞 Bug Report
### Description
<!-- ✍️--> A clear and concise description of the problem...
### Has this worked before in a previous version?
<!-- Did this behavior use to work in the previous version? -->
<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....
## 🔬 Minimal Reproduction
<!--
Please let us know how we can reproduce this issue. Include the exact method you used to run Prysm along with any flags used in your beacon chain and/or validator. Make sure you don't upload any confidential files or private keys.
-->
## 🔥 Error
<pre><code>
<!-- If the issue is accompanied by an error, please share the error logs with us below. If you have a lot of logs, place make a paste bin with your logs and share the link with us here: -->
<!-- ✍️-->
</code></pre>
## 🌍 Your Environment
**Operating System:**
<pre>
<code>
</code>
</pre>
**What version of Prysm are you running? (Which release)**
<pre>
<code>
</code>
</pre>
**Anything else relevant (validator index / public key)?**

66
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: 🐞 Bug report
description: Report a bug or problem with running Prysm
labels: ["Bug"]
body:
- type: markdown
attributes:
value: |
To help us tend to your issue faster, please search our currently open issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.
- type: textarea
id: what-happened
attributes:
label: Describe the bug
description: |
A clear and concise description of the problem...
validations:
required: true
- type: textarea
id: previous-version
attributes:
label: Has this worked before in a previous version?
description: Did this behavior use to work in the previous version?
render: text
- type: textarea
id: reproduction-steps
attributes:
label: 🔬 Minimal Reproduction
description: |
Please let us know how we can reproduce this issue.
Include the exact method you used to run Prysm along with any flags used in your beacon chain and/or validator.
Make sure you don't upload any confidential files or private keys.
placeholder: |
Steps to reproduce:
1. Start '...'
2. Then '...'
3. Check '...'
4. See error
- type: textarea
id: errors
attributes:
label: Error
description: |
If the issue is accompanied by an error, please share the error logs with us below.
If you have a lot of logs, place make a paste bin with your logs and share the link with us here:
render: text
- type: dropdown
id: platform
attributes:
label: Platform(s)
description: What platform(s) did this occur on?
multiple: true
options:
- Linux (x86)
- Linux (ARM)
- Mac (Intel)
- Mac (Apple Silicon)
- Windows (x86)
- Windows (ARM)
- type: input
attributes:
label: What version of Prysm are you running? (Which release)
description: You can check your Prysm version by running your beacon node or validator with the `--version` flag.
- type: textarea
attributes:
label: Anything else relevant (validator index / public key)?

View File

@@ -75,14 +75,14 @@ jobs:
- name: Build
# Use blst tag to allow go and bazel builds for blst.
run: go build -v ./...
env:
CGO_CFLAGS: "-O -D__BLST_PORTABLE__"
env:
CGO_CFLAGS: "-O2 -D__BLST_PORTABLE__"
# fuzz leverage go tag based stubs at compile time.
# Building and testing with these tags should be checked and enforced at pre-submit.
- name: Test for fuzzing
run: go test -tags=fuzz,develop ./... -test.run=^Fuzz
env:
CGO_CFLAGS: "-O -D__BLST_PORTABLE__"
env:
CGO_CFLAGS: "-O2 -D__BLST_PORTABLE__"
# Tests run via Bazel for now...
# - name: Test

View File

@@ -50,8 +50,6 @@ load("@prysm//tools/cross-toolchain:prysm_toolchains.bzl", "configure_prysm_tool
configure_prysm_toolchains()
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "bazel_skylib",
sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c",
@@ -87,6 +85,24 @@ http_archive(
urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.25.0/rules_docker-v0.25.0.tar.gz"],
)
http_archive(
name = "rules_oci",
sha256 = "c71c25ed333a4909d2dd77e0b16c39e9912525a98c7fa85144282be8d04ef54c",
strip_prefix = "rules_oci-1.3.4",
url = "https://github.com/bazel-contrib/rules_oci/releases/download/v1.3.4/rules_oci-v1.3.4.tar.gz",
)
load("@rules_oci//oci:dependencies.bzl", "rules_oci_dependencies")
rules_oci_dependencies()
load("@rules_oci//oci:repositories.bzl", "LATEST_CRANE_VERSION", "oci_register_toolchains")
oci_register_toolchains(
name = "oci",
crane_version = LATEST_CRANE_VERSION,
)
http_archive(
name = "io_bazel_rules_go",
patch_args = ["-p1"],
@@ -167,6 +183,24 @@ container_pull(
repository = "pinglamb/alpine-glibc",
)
load("@rules_oci//oci:pull.bzl", "oci_pull")
# A multi-arch base image
oci_pull(
name = "linux_debian11_multiarch_base", # Debian bullseye
digest = "sha256:9b8e0854865dcaf49470b4ec305df45957020fbcf17b71eeb50ffd3bc5bf885d", # 2023-05-17
image = "gcr.io/distroless/cc-debian11",
platforms = [
"linux/amd64",
"linux/arm64",
],
reproducible = True,
)
load("@prysm//tools:image_deps.bzl", "prysm_image_deps")
prysm_image_deps()
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
go_rules_dependencies()
@@ -213,7 +247,9 @@ filegroup(
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
)
consensus_spec_version = "v1.4.0-beta.1"
consensus_spec_test_version = "v1.4.0-beta.2-hotfix"
consensus_spec_version = "v1.4.0-beta.2"
bls_test_version = "v0.1.1"
@@ -229,8 +265,8 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "24399b60ce3fbeb2311952d213dc3731b6dcb0f8881b016c283de5b518d2bbba",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
sha256 = "99770a001189f66204a4ef79161c8002bcbbcbd8236f1c6479bd5b83a3c68d42",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_test_version,
)
http_archive(
@@ -245,8 +281,8 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "8e656ee48d2e2ebc9cf9baedb81f27925bc625b3e3fbb2883444a08758a5884a",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
sha256 = "56763f6492ee137108271007d62feef60d8e3f1698e53dee4bc4b07e55f7326b",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_test_version,
)
http_archive(
@@ -261,8 +297,8 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "8bd137da6cc57a25383bfac5bc37e31265098145278bd8002b88e24c8b4718b9",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
sha256 = "bc1cac1a991cdc7426efea14385dcf215df85ed3f0572b824ad6a1d7ca0c89ad",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_test_version,
)
http_archive(
@@ -276,7 +312,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "2bc1edb6e4a4f86c00317c04618a90b0ca29ee1eba833d0a64dd67fdd83fdbe3",
sha256 = "c5898001aaab2a5bb38a39ff9d17a52f1f9befcc26e63752cbf556040f0c884e",
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
)
@@ -323,9 +359,9 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
sha256 = "4116c8acb54eb3ca28cc4dc9bc688e08e25da91d70ed1f2622f02d3c33eba922",
strip_prefix = "holesky-76057d57ab1f585519ecb606a9e5f7780e925a37",
url = "https://github.com/eth-clients/holesky/archive/76057d57ab1f585519ecb606a9e5f7780e925a37.tar.gz", # Aug 27, 2023
sha256 = "9f66d8d5644982d3d0d2e3d2b9ebe77a5f96638a5d7fcd715599c32818195cb3",
strip_prefix = "holesky-ea39b9006210848e13f28d92e12a30548cecd41d",
url = "https://github.com/eth-clients/holesky/archive/ea39b9006210848e13f28d92e12a30548cecd41d.tar.gz", # 2023-09-21
)
http_archive(
@@ -337,9 +373,6 @@ http_archive(
],
)
# Group the sources of the library so that CMake rule have access to it
all_content = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])"""
# External dependencies
load("//: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/shared:go_default_library",
"//beacon-chain/state:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives: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/shared"
"github.com/prysmaticlabs/prysm/v4/network/forks"
v1 "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
@@ -148,14 +149,14 @@ func (c *Client) GetFork(ctx context.Context, stateId StateOrBlockId) (*ethpb.Fo
if err != nil {
return nil, errors.Wrapf(err, "error requesting fork by state id = %s", stateId)
}
fr := &forkResponse{}
dataWrapper := &struct{ Data *forkResponse }{Data: fr}
fr := &shared.Fork{}
dataWrapper := &struct{ Data *shared.Fork }{Data: fr}
err = json.Unmarshal(body, dataWrapper)
if err != nil {
return nil, errors.Wrap(err, "error decoding json response in GetFork")
}
return fr.Fork()
return fr.ToConsensus()
}
// GetForkSchedule retrieve all forks, past present and future, of which this node is aware.
@@ -335,40 +336,8 @@ func (c *Client) GetBLStoExecutionChanges(ctx context.Context) (*apimiddleware.B
return poolResponse, nil
}
type forkResponse struct {
PreviousVersion string `json:"previous_version"`
CurrentVersion string `json:"current_version"`
Epoch string `json:"epoch"`
}
func (f *forkResponse) Fork() (*ethpb.Fork, error) {
epoch, err := strconv.ParseUint(f.Epoch, 10, 64)
if err != nil {
return nil, err
}
cSlice, err := hexutil.Decode(f.CurrentVersion)
if err != nil {
return nil, err
}
if len(cSlice) != 4 {
return nil, fmt.Errorf("got %d byte version for CurrentVersion, expected 4 bytes. hex=%s", len(cSlice), f.CurrentVersion)
}
pSlice, err := hexutil.Decode(f.PreviousVersion)
if err != nil {
return nil, err
}
if len(pSlice) != 4 {
return nil, fmt.Errorf("got %d byte version, expected 4 bytes. version hex=%s", len(pSlice), f.PreviousVersion)
}
return &ethpb.Fork{
CurrentVersion: cSlice,
PreviousVersion: pSlice,
Epoch: primitives.Epoch(epoch),
}, nil
}
type forkScheduleResponse struct {
Data []forkResponse
Data []shared.Fork
}
func (fsr *forkScheduleResponse) OrderedForkSchedule() (forks.OrderedSchedule, error) {

View File

@@ -11,6 +11,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v4/api/client/builder",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/rpc/eth/shared:go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types:go_default_library",
"//consensus-types/blocks:go_default_library",
@@ -41,6 +42,7 @@ go_test(
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//beacon-chain/rpc/eth/shared:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
@@ -52,7 +54,7 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)

View File

@@ -13,9 +13,11 @@ import (
"text/template"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/monitoring/tracing"
"github.com/prysmaticlabs/prysm/v4/network"
"github.com/prysmaticlabs/prysm/v4/network/authorization"
@@ -268,9 +270,13 @@ func (c *Client) RegisterValidator(ctx context.Context, svr []*ethpb.SignedValid
tracing.AnnotateError(span, err)
return err
}
vs := make([]*SignedValidatorRegistration, len(svr))
vs := make([]*shared.SignedValidatorRegistration, len(svr))
for i := 0; i < len(svr); i++ {
vs[i] = &SignedValidatorRegistration{SignedValidatorRegistrationV1: svr[i]}
svrJson, err := shared.SignedValidatorRegistrationFromConsensus(svr[i])
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to encode to SignedValidatorRegistration at index %d", i))
}
vs[i] = svrJson
}
body, err := json.Marshal(vs)
if err != nil {
@@ -295,12 +301,14 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
if err != nil {
return nil, nil, errors.Wrapf(err, "could not get protobuf block")
}
b := &SignedBlindedBeaconBlockBellatrix{SignedBlindedBeaconBlockBellatrix: psb}
b, err := shared.SignedBlindedBeaconBlockBellatrixFromConsensus(&ethpb.SignedBlindedBeaconBlockBellatrix{Block: psb.Block, Signature: bytesutil.SafeCopyBytes(psb.Signature)})
if err != nil {
return nil, nil, errors.Wrapf(err, "could not convert SignedBlindedBeaconBlockBellatrix to json marshalable type")
}
body, err := json.Marshal(b)
if err != nil {
return nil, nil, errors.Wrap(err, "error encoding the SignedBlindedBeaconBlockBellatrix value body in SubmitBlindedBlock")
}
versionOpt := func(r *http.Request) {
r.Header.Add("Eth-Consensus-Version", version.String(version.Bellatrix))
}
@@ -330,12 +338,14 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
if err != nil {
return nil, nil, errors.Wrapf(err, "could not get protobuf block")
}
b := &SignedBlindedBeaconBlockCapella{SignedBlindedBeaconBlockCapella: psb}
b, err := shared.SignedBlindedBeaconBlockCapellaFromConsensus(&ethpb.SignedBlindedBeaconBlockCapella{Block: psb.Block, Signature: bytesutil.SafeCopyBytes(psb.Signature)})
if err != nil {
return nil, nil, errors.Wrapf(err, "could not convert SignedBlindedBeaconBlockCapella to json marshalable type")
}
body, err := json.Marshal(b)
if err != nil {
return nil, nil, errors.Wrap(err, "error encoding the SignedBlindedBeaconBlockCapella value body in SubmitBlindedBlockCapella")
}
versionOpt := func(r *http.Request) {
r.Header.Add("Eth-Consensus-Version", version.String(version.Capella))
}
@@ -365,8 +375,10 @@ func (c *Client) SubmitBlindedBlock(ctx context.Context, sb interfaces.ReadOnlyS
if err != nil {
return nil, nil, errors.Wrapf(err, "could not get protobuf block")
}
b := &ethpb.SignedBlindedBeaconBlockAndBlobsDeneb{Block: psb, Blobs: blobs}
b, err := shared.SignedBlindedBeaconBlockContentsDenebFromConsensus(&ethpb.SignedBlindedBeaconBlockAndBlobsDeneb{SignedBlindedBlock: psb, SignedBlindedBlobSidecars: blobs})
if err != nil {
return nil, nil, errors.Wrapf(err, "could not convert SignedBlindedBeaconBlockContentsDeneb to json marshalable type")
}
body, err := json.Marshal(b)
if err != nil {
return nil, nil, errors.Wrap(err, "error encoding the SignedBlindedBeaconBlockDeneb value body in SubmitBlindedBlockDeneb")

View File

@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
@@ -23,6 +24,7 @@ import (
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
log "github.com/sirupsen/logrus"
)
type roundtrip func(*http.Request) (*http.Response, error)
@@ -283,6 +285,42 @@ func TestClient_GetHeader(t *testing.T) {
require.Equal(t, len(bundle.Proofs[i]) == 48, true)
}
})
t.Run("deneb, no bundle", func(t *testing.T) {
hc := &http.Client{
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
require.Equal(t, expectedPath, r.URL.Path)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(testExampleHeaderResponseDenebNoBundle)),
Request: r.Clone(ctx),
}, nil
}),
}
c := &Client{
hc: hc,
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
}
h, err := c.GetHeader(ctx, slot, bytesutil.ToBytes32(parentHash), bytesutil.ToBytes48(pubkey))
require.NoError(t, err)
expectedWithdrawalsRoot := ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2")
bid, err := h.Message()
require.NoError(t, err)
bidHeader, err := bid.Header()
require.NoError(t, err)
withdrawalsRoot, err := bidHeader.WithdrawalsRoot()
require.NoError(t, err)
require.Equal(t, true, bytes.Equal(expectedWithdrawalsRoot, withdrawalsRoot))
value, err := stringToUint256("652312848583266388373324160190187140051835877600158453279131187530910662656")
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("%#x", value.SSZBytes()), fmt.Sprintf("%#x", bid.Value()))
bidValue := bytesutil.ReverseByteOrder(bid.Value())
require.DeepEqual(t, bidValue, value.Bytes())
require.DeepEqual(t, big.NewInt(0).SetBytes(bidValue), value.Int)
bundle, err := bid.BlindedBlobsBundle()
require.NoError(t, err)
require.Equal(t, (*v1.BlindedBlobsBundle)(nil), bundle)
})
t.Run("unsupported version", func(t *testing.T) {
hc := &http.Client{
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
@@ -361,10 +399,18 @@ func TestSubmitBlindedBlock(t *testing.T) {
assert.Equal(t, uint64(1), withdrawals[0].Amount)
})
t.Run("deneb", func(t *testing.T) {
test := testSignedBlindedBeaconBlockAndBlobsDeneb(t)
hc := &http.Client{
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
require.Equal(t, postBlindedBeaconBlockPath, r.URL.Path)
require.Equal(t, "deneb", r.Header.Get("Eth-Consensus-Version"))
var req shared.SignedBlindedBeaconBlockContentsDeneb
err := json.NewDecoder(r.Body).Decode(&req)
require.NoError(t, err)
block, err := req.SignedBlindedBlock.ToConsensus()
require.NoError(t, err)
require.DeepEqual(t, block, test.SignedBlindedBlock)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(testExampleExecutionPayloadDeneb)),
@@ -376,11 +422,11 @@ func TestSubmitBlindedBlock(t *testing.T) {
hc: hc,
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
}
test := testSignedBlindedBeaconBlockAndBlobsDeneb(t)
sbb, err := blocks.NewSignedBeaconBlock(test.Block)
sbb, err := blocks.NewSignedBeaconBlock(test.SignedBlindedBlock)
require.NoError(t, err)
ep, blobBundle, err := c.SubmitBlindedBlock(ctx, sbb, test.Blobs)
ep, blobBundle, err := c.SubmitBlindedBlock(ctx, sbb, test.SignedBlindedBlobSidecars)
require.NoError(t, err)
withdrawals, err := ep.Withdrawals()
require.NoError(t, err)
@@ -708,9 +754,13 @@ func testSignedBlindedBeaconBlockCapella(t *testing.T) *eth.SignedBlindedBeaconB
}
func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedBeaconBlockAndBlobsDeneb {
basebytes, err := shared.Uint256ToSSZBytes("14074904626401341155369551180448584754667373453244490859944217516317499064576")
if err != nil {
log.Error(err)
}
return &eth.SignedBlindedBeaconBlockAndBlobsDeneb{
Block: &eth.SignedBlindedBeaconBlockDeneb{
Block: &eth.BlindedBeaconBlockDeneb{
SignedBlindedBlock: &eth.SignedBlindedBeaconBlockDeneb{
Message: &eth.BlindedBeaconBlockDeneb{
Slot: 1,
ProposerIndex: 1,
ParentRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
@@ -722,7 +772,7 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
DepositCount: 1,
BlockHash: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
},
Graffiti: ezDecode(t, "0xdeadbeefc0ffee"),
Graffiti: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
ProposerSlashings: []*eth.ProposerSlashing{
{
Header_1: &eth.SignedBeaconBlockHeader{
@@ -825,8 +875,8 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
},
},
SyncAggregate: &eth.SyncAggregate{
SyncCommitteeSignature: make([]byte, 48),
SyncCommitteeBits: bitfield.Bitvector512{0x01},
SyncCommitteeSignature: make([]byte, 96),
SyncCommitteeBits: bitfield.Bitvector512(ezDecode(t, "0x6451e9f951ebf05edc01de67e593484b672877054f055903ff0df1a1a945cf30ca26bb4d4b154f94a1bc776bcf5d0efb3603e1f9b8ee2499ccdcfe2a18cef458")),
},
ExecutionPayloadHeader: &v1.ExecutionPayloadHeaderDeneb{
ParentHash: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
@@ -840,7 +890,7 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
GasUsed: 1,
Timestamp: 1,
ExtraData: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
BaseFeePerGas: []byte(strconv.FormatUint(1, 10)),
BaseFeePerGas: basebytes,
BlockHash: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
TransactionsRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
WithdrawalsRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
@@ -851,7 +901,7 @@ func testSignedBlindedBeaconBlockAndBlobsDeneb(t *testing.T) *eth.SignedBlindedB
},
Signature: ezDecode(t, "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"),
},
Blobs: []*eth.SignedBlindedBlobSidecar{
SignedBlindedBlobSidecars: []*eth.SignedBlindedBlobSidecar{
{
Message: &eth.BlindedBlobSidecar{
BlockRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),

File diff suppressed because one or more lines are too long

View File

@@ -16,87 +16,6 @@ import (
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
// SignedValidatorRegistration a struct for signed validator registrations.
type SignedValidatorRegistration struct {
*eth.SignedValidatorRegistrationV1
}
// ValidatorRegistration a struct for validator registrations.
type ValidatorRegistration struct {
*eth.ValidatorRegistrationV1
}
// MarshalJSON returns a json representation copy of signed validator registration.
func (r *SignedValidatorRegistration) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Message *ValidatorRegistration `json:"message"`
Signature hexutil.Bytes `json:"signature"`
}{
Message: &ValidatorRegistration{r.Message},
Signature: r.SignedValidatorRegistrationV1.Signature,
})
}
// UnmarshalJSON returns a byte representation of signed validator registration from json.
func (r *SignedValidatorRegistration) UnmarshalJSON(b []byte) error {
if r.SignedValidatorRegistrationV1 == nil {
r.SignedValidatorRegistrationV1 = &eth.SignedValidatorRegistrationV1{}
}
o := struct {
Message *ValidatorRegistration `json:"message"`
Signature hexutil.Bytes `json:"signature"`
}{}
if err := json.Unmarshal(b, &o); err != nil {
return err
}
r.Message = o.Message.ValidatorRegistrationV1
r.Signature = o.Signature
return nil
}
// MarshalJSON returns a json representation copy of validator registration.
func (r *ValidatorRegistration) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
GasLimit string `json:"gas_limit"`
Timestamp string `json:"timestamp"`
Pubkey hexutil.Bytes `json:"pubkey"`
}{
FeeRecipient: r.FeeRecipient,
GasLimit: fmt.Sprintf("%d", r.GasLimit),
Timestamp: fmt.Sprintf("%d", r.Timestamp),
Pubkey: r.Pubkey,
})
}
// UnmarshalJSON returns a byte representation of validator registration from json.
func (r *ValidatorRegistration) UnmarshalJSON(b []byte) error {
if r.ValidatorRegistrationV1 == nil {
r.ValidatorRegistrationV1 = &eth.ValidatorRegistrationV1{}
}
o := struct {
FeeRecipient hexutil.Bytes `json:"fee_recipient"`
GasLimit string `json:"gas_limit"`
Timestamp string `json:"timestamp"`
Pubkey hexutil.Bytes `json:"pubkey"`
}{}
if err := json.Unmarshal(b, &o); err != nil {
return err
}
r.FeeRecipient = o.FeeRecipient
r.Pubkey = o.Pubkey
var err error
if r.GasLimit, err = strconv.ParseUint(o.GasLimit, 10, 64); err != nil {
return errors.Wrap(err, "failed to parse gas limit")
}
if r.Timestamp, err = strconv.ParseUint(o.Timestamp, 10, 64); err != nil {
return errors.Wrap(err, "failed to parse timestamp")
}
return nil
}
var errInvalidUint256 = errors.New("invalid Uint256")
var errDecodeUint256 = errors.New("unable to decode into Uint256")
@@ -636,44 +555,6 @@ type SignedBlindedBeaconBlockBellatrix struct {
*eth.SignedBlindedBeaconBlockBellatrix
}
// BlindedBeaconBlockBellatrix is a field in SignedBlindedBeaconBlockBellatrix.
type BlindedBeaconBlockBellatrix struct {
*eth.BlindedBeaconBlockBellatrix
}
// BlindedBeaconBlockBodyBellatrix is a field in BlindedBeaconBlockBellatrix.
type BlindedBeaconBlockBodyBellatrix struct {
*eth.BlindedBeaconBlockBodyBellatrix
}
// MarshalJSON returns a JSON byte array representation of SignedBlindedBeaconBlockBellatrix.
func (r *SignedBlindedBeaconBlockBellatrix) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Message *BlindedBeaconBlockBellatrix `json:"message"`
Signature hexutil.Bytes `json:"signature"`
}{
Message: &BlindedBeaconBlockBellatrix{r.SignedBlindedBeaconBlockBellatrix.Block},
Signature: r.SignedBlindedBeaconBlockBellatrix.Signature,
})
}
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockBellatrix.
func (b *BlindedBeaconBlockBellatrix) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Slot string `json:"slot"`
ProposerIndex string `json:"proposer_index"`
ParentRoot hexutil.Bytes `json:"parent_root"`
StateRoot hexutil.Bytes `json:"state_root"`
Body *BlindedBeaconBlockBodyBellatrix `json:"body"`
}{
Slot: fmt.Sprintf("%d", b.Slot),
ProposerIndex: fmt.Sprintf("%d", b.ProposerIndex),
ParentRoot: b.ParentRoot,
StateRoot: b.StateRoot,
Body: &BlindedBeaconBlockBodyBellatrix{b.BlindedBeaconBlockBellatrix.Body},
})
}
// ProposerSlashing is a field in BlindedBeaconBlockBodyCapella.
type ProposerSlashing struct {
*eth.ProposerSlashing
@@ -928,53 +809,6 @@ func (e *Eth1Data) MarshalJSON() ([]byte, error) {
})
}
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockBodyBellatrix.
func (b *BlindedBeaconBlockBodyBellatrix) MarshalJSON() ([]byte, error) {
sve := make([]*SignedVoluntaryExit, len(b.BlindedBeaconBlockBodyBellatrix.VoluntaryExits))
for i := range b.BlindedBeaconBlockBodyBellatrix.VoluntaryExits {
sve[i] = &SignedVoluntaryExit{SignedVoluntaryExit: b.BlindedBeaconBlockBodyBellatrix.VoluntaryExits[i]}
}
deps := make([]*Deposit, len(b.BlindedBeaconBlockBodyBellatrix.Deposits))
for i := range b.BlindedBeaconBlockBodyBellatrix.Deposits {
deps[i] = &Deposit{Deposit: b.BlindedBeaconBlockBodyBellatrix.Deposits[i]}
}
atts := make([]*Attestation, len(b.BlindedBeaconBlockBodyBellatrix.Attestations))
for i := range b.BlindedBeaconBlockBodyBellatrix.Attestations {
atts[i] = &Attestation{Attestation: b.BlindedBeaconBlockBodyBellatrix.Attestations[i]}
}
atsl := make([]*AttesterSlashing, len(b.BlindedBeaconBlockBodyBellatrix.AttesterSlashings))
for i := range b.BlindedBeaconBlockBodyBellatrix.AttesterSlashings {
atsl[i] = &AttesterSlashing{AttesterSlashing: b.BlindedBeaconBlockBodyBellatrix.AttesterSlashings[i]}
}
pros := make([]*ProposerSlashing, len(b.BlindedBeaconBlockBodyBellatrix.ProposerSlashings))
for i := range b.BlindedBeaconBlockBodyBellatrix.ProposerSlashings {
pros[i] = &ProposerSlashing{ProposerSlashing: b.BlindedBeaconBlockBodyBellatrix.ProposerSlashings[i]}
}
return json.Marshal(struct {
RandaoReveal hexutil.Bytes `json:"randao_reveal"`
Eth1Data *Eth1Data `json:"eth1_data"`
Graffiti hexutil.Bytes `json:"graffiti"`
ProposerSlashings []*ProposerSlashing `json:"proposer_slashings"`
AttesterSlashings []*AttesterSlashing `json:"attester_slashings"`
Attestations []*Attestation `json:"attestations"`
Deposits []*Deposit `json:"deposits"`
VoluntaryExits []*SignedVoluntaryExit `json:"voluntary_exits"`
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
ExecutionPayloadHeader *ExecutionPayloadHeader `json:"execution_payload_header"`
}{
RandaoReveal: b.RandaoReveal,
Eth1Data: &Eth1Data{b.BlindedBeaconBlockBodyBellatrix.Eth1Data},
Graffiti: b.BlindedBeaconBlockBodyBellatrix.Graffiti,
ProposerSlashings: pros,
AttesterSlashings: atsl,
Attestations: atts,
Deposits: deps,
VoluntaryExits: sve,
SyncAggregate: &SyncAggregate{b.BlindedBeaconBlockBodyBellatrix.SyncAggregate},
ExecutionPayloadHeader: &ExecutionPayloadHeader{ExecutionPayloadHeader: b.BlindedBeaconBlockBodyBellatrix.ExecutionPayloadHeader},
})
}
// SignedBLSToExecutionChange is a field in Beacon Block Body for capella and above.
type SignedBLSToExecutionChange struct {
*eth.SignedBLSToExecutionChange
@@ -1009,102 +843,6 @@ func (ch *BLSToExecutionChange) MarshalJSON() ([]byte, error) {
})
}
// SignedBlindedBeaconBlockCapella is part of the request object sent to builder API /eth/v1/builder/blinded_blocks for Capella.
type SignedBlindedBeaconBlockCapella struct {
*eth.SignedBlindedBeaconBlockCapella
}
// BlindedBeaconBlockCapella is a field in SignedBlindedBeaconBlockCapella.
type BlindedBeaconBlockCapella struct {
*eth.BlindedBeaconBlockCapella
}
// BlindedBeaconBlockBodyCapella is a field in BlindedBeaconBlockCapella.
type BlindedBeaconBlockBodyCapella struct {
*eth.BlindedBeaconBlockBodyCapella
}
// MarshalJSON returns a JSON byte array representation of SignedBlindedBeaconBlockCapella.
func (b *SignedBlindedBeaconBlockCapella) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Message *BlindedBeaconBlockCapella `json:"message"`
Signature hexutil.Bytes `json:"signature"`
}{
Message: &BlindedBeaconBlockCapella{b.Block},
Signature: b.Signature,
})
}
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockCapella
func (b *BlindedBeaconBlockCapella) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Slot string `json:"slot"`
ProposerIndex string `json:"proposer_index"`
ParentRoot hexutil.Bytes `json:"parent_root"`
StateRoot hexutil.Bytes `json:"state_root"`
Body *BlindedBeaconBlockBodyCapella `json:"body"`
}{
Slot: fmt.Sprintf("%d", b.Slot),
ProposerIndex: fmt.Sprintf("%d", b.ProposerIndex),
ParentRoot: b.ParentRoot,
StateRoot: b.StateRoot,
Body: &BlindedBeaconBlockBodyCapella{b.Body},
})
}
// MarshalJSON returns a JSON byte array representation of BlindedBeaconBlockBodyCapella
func (b *BlindedBeaconBlockBodyCapella) MarshalJSON() ([]byte, error) {
sve := make([]*SignedVoluntaryExit, len(b.VoluntaryExits))
for i := range b.VoluntaryExits {
sve[i] = &SignedVoluntaryExit{SignedVoluntaryExit: b.VoluntaryExits[i]}
}
deps := make([]*Deposit, len(b.Deposits))
for i := range b.Deposits {
deps[i] = &Deposit{Deposit: b.Deposits[i]}
}
atts := make([]*Attestation, len(b.Attestations))
for i := range b.Attestations {
atts[i] = &Attestation{Attestation: b.Attestations[i]}
}
atsl := make([]*AttesterSlashing, len(b.AttesterSlashings))
for i := range b.AttesterSlashings {
atsl[i] = &AttesterSlashing{AttesterSlashing: b.AttesterSlashings[i]}
}
pros := make([]*ProposerSlashing, len(b.ProposerSlashings))
for i := range b.ProposerSlashings {
pros[i] = &ProposerSlashing{ProposerSlashing: b.ProposerSlashings[i]}
}
chs := make([]*SignedBLSToExecutionChange, len(b.BlsToExecutionChanges))
for i := range b.BlsToExecutionChanges {
chs[i] = &SignedBLSToExecutionChange{SignedBLSToExecutionChange: b.BlsToExecutionChanges[i]}
}
return json.Marshal(struct {
RandaoReveal hexutil.Bytes `json:"randao_reveal"`
Eth1Data *Eth1Data `json:"eth1_data"`
Graffiti hexutil.Bytes `json:"graffiti"`
ProposerSlashings []*ProposerSlashing `json:"proposer_slashings"`
AttesterSlashings []*AttesterSlashing `json:"attester_slashings"`
Attestations []*Attestation `json:"attestations"`
Deposits []*Deposit `json:"deposits"`
VoluntaryExits []*SignedVoluntaryExit `json:"voluntary_exits"`
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
SyncAggregate *SyncAggregate `json:"sync_aggregate"`
ExecutionPayloadHeader *ExecutionPayloadHeaderCapella `json:"execution_payload_header"`
}{
RandaoReveal: b.RandaoReveal,
Eth1Data: &Eth1Data{b.Eth1Data},
Graffiti: b.Graffiti,
ProposerSlashings: pros,
AttesterSlashings: atsl,
Attestations: atts,
Deposits: deps,
VoluntaryExits: sve,
BLSToExecutionChanges: chs,
SyncAggregate: &SyncAggregate{b.SyncAggregate},
ExecutionPayloadHeader: &ExecutionPayloadHeaderCapella{ExecutionPayloadHeaderCapella: b.ExecutionPayloadHeader},
})
}
// ExecHeaderResponseDeneb is the header response for builder API /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}.
type ExecHeaderResponseDeneb struct {
Data struct {
@@ -1131,9 +869,12 @@ func (bb *BuilderBidDeneb) ToProto() (*eth.BuilderBidDeneb, error) {
if err != nil {
return nil, err
}
bundle, err := bb.BlindedBlobsBundle.ToProto()
if err != nil {
return nil, err
var bundle *v1.BlindedBlobsBundle
if bb.BlindedBlobsBundle != nil {
bundle, err = bb.BlindedBlobsBundle.ToProto()
if err != nil {
return nil, err
}
}
return &eth.BuilderBidDeneb{
Header: header,

View File

@@ -12,8 +12,8 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/protobuf/proto"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/shared"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/math"
v1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
@@ -38,7 +38,8 @@ func TestSignedValidatorRegistration_MarshalJSON(t *testing.T) {
},
Signature: make([]byte, 96),
}
a := &SignedValidatorRegistration{SignedValidatorRegistrationV1: svr}
a, err := shared.SignedValidatorRegistrationFromConsensus(svr)
require.NoError(t, err)
je, err := json.Marshal(a)
require.NoError(t, err)
// decode with a struct w/ plain strings so we can check the string encoding of the hex fields
@@ -55,11 +56,11 @@ func TestSignedValidatorRegistration_MarshalJSON(t *testing.T) {
require.Equal(t, "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", un.Message.Pubkey)
t.Run("roundtrip", func(t *testing.T) {
b := &SignedValidatorRegistration{}
b := &shared.SignedValidatorRegistration{}
if err := json.Unmarshal(je, b); err != nil {
require.NoError(t, err)
}
require.Equal(t, proto.Equal(a.SignedValidatorRegistrationV1, b.SignedValidatorRegistrationV1), true)
require.DeepEqual(t, a, b)
})
}
@@ -159,6 +160,36 @@ var testExampleHeaderResponseDeneb = `{
}
}`
var testExampleHeaderResponseDenebNoBundle = `{
"version": "deneb",
"data": {
"message": {
"header": {
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"block_number": "1",
"gas_limit": "1",
"gas_used": "1",
"timestamp": "1",
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"base_fee_per_gas": "452312848583266388373324160190187140051835877600158453279131187530910662656",
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"blob_gas_used": "1",
"excess_blob_gas": "2"
},
"value": "652312848583266388373324160190187140051835877600158453279131187530910662656",
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
},
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
}
}`
var testExampleHeaderResponseUnknownVersion = `{
"version": "bad",
"data": {
@@ -1499,7 +1530,7 @@ func TestUint256UnmarshalTooBig(t *testing.T) {
func TestMarshalBlindedBeaconBlockBodyBellatrix(t *testing.T) {
expected, err := os.ReadFile("testdata/blinded-block.json")
require.NoError(t, err)
b := &BlindedBeaconBlockBellatrix{BlindedBeaconBlockBellatrix: &eth.BlindedBeaconBlockBellatrix{
b, err := shared.BlindedBeaconBlockBellatrixFromConsensus(&eth.BlindedBeaconBlockBellatrix{
Slot: 1,
ProposerIndex: 1,
ParentRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
@@ -1516,7 +1547,8 @@ func TestMarshalBlindedBeaconBlockBodyBellatrix(t *testing.T) {
SyncAggregate: pbSyncAggregate(),
ExecutionPayloadHeader: pbExecutionPayloadHeader(t),
},
}}
})
require.NoError(t, err)
m, err := json.Marshal(b)
require.NoError(t, err)
// string error output is easier to deal with
@@ -1528,7 +1560,7 @@ func TestMarshalBlindedBeaconBlockBodyBellatrix(t *testing.T) {
func TestMarshalBlindedBeaconBlockBodyCapella(t *testing.T) {
expected, err := os.ReadFile("testdata/blinded-block-capella.json")
require.NoError(t, err)
b := &BlindedBeaconBlockCapella{BlindedBeaconBlockCapella: &eth.BlindedBeaconBlockCapella{
b, err := shared.BlindedBeaconBlockCapellaFromConsensus(&eth.BlindedBeaconBlockCapella{
Slot: 1,
ProposerIndex: 1,
ParentRoot: ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"),
@@ -1545,7 +1577,8 @@ func TestMarshalBlindedBeaconBlockBodyCapella(t *testing.T) {
SyncAggregate: pbSyncAggregate(),
ExecutionPayloadHeader: pbExecutionPayloadHeaderCapella(t),
},
}}
})
require.NoError(t, err)
m, err := json.Marshal(b)
require.NoError(t, err)
// string error output is easier to deal with

View File

@@ -15,6 +15,9 @@ func StartAndEndPage(pageToken string, pageSize, totalSize int) (int, int, strin
if pageToken == "" {
pageToken = "0"
}
if pageSize < 0 || totalSize < 0 {
return 0, 0, "", errors.Errorf("invalid page and total sizes provided: page size %d , total size %d", pageSize, totalSize)
}
if pageSize == 0 {
pageSize = params.BeaconConfig().DefaultPageSize
}
@@ -23,6 +26,9 @@ func StartAndEndPage(pageToken string, pageSize, totalSize int) (int, int, strin
if err != nil {
return 0, 0, "", errors.Wrap(err, "could not convert page token")
}
if token < 0 {
return 0, 0, "", errors.Errorf("invalid token value provided: %d", token)
}
// Start page can not be greater than set size.
start := token * pageSize

View File

@@ -85,3 +85,19 @@ func TestStartAndEndPage_ExceedsMaxPage(t *testing.T) {
_, _, _, err := pagination.StartAndEndPage("", 0, 0)
assert.ErrorContains(t, wanted, err)
}
func TestStartAndEndPage_InvalidPageValues(t *testing.T) {
_, _, _, err := pagination.StartAndEndPage("10", -1, 10)
assert.ErrorContains(t, "invalid page and total sizes provided", err)
_, _, _, err = pagination.StartAndEndPage("12", 10, -10)
assert.ErrorContains(t, "invalid page and total sizes provided", err)
_, _, _, err = pagination.StartAndEndPage("12", -50, -60)
assert.ErrorContains(t, "invalid page and total sizes provided", err)
}
func TestStartAndEndPage_InvalidTokenValue(t *testing.T) {
_, _, _, err := pagination.StartAndEndPage("-12", 50, 60)
assert.ErrorContains(t, "invalid token value provided", err)
}

View File

@@ -5,6 +5,7 @@ go_library(
srcs = [
"chain_info.go",
"chain_info_forkchoice.go",
"currently_syncing_block.go",
"error.go",
"execution_engine.go",
"forkchoice_update_execution.go",
@@ -38,7 +39,6 @@ go_library(
"//async/event:go_default_library",
"//beacon-chain/blockchain/kzg:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/core/altair:go_default_library",
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/core/epoch/precompute:go_default_library",

View File

@@ -525,3 +525,8 @@ func (s *Service) recoverStateSummary(ctx context.Context, blockRoot [32]byte) (
}
return nil, errBlockDoesNotExist
}
// BlockBeingSynced returns whether the block with the given root is currently being synced
func (s *Service) BlockBeingSynced(root [32]byte) bool {
return s.blockBeingSynced.isSyncing(root)
}

View File

@@ -0,0 +1,27 @@
package blockchain
import "sync"
type currentlySyncingBlock struct {
sync.Mutex
roots map[[32]byte]struct{}
}
func (b *currentlySyncingBlock) set(root [32]byte) {
b.Lock()
defer b.Unlock()
b.roots[root] = struct{}{}
}
func (b *currentlySyncingBlock) unset(root [32]byte) {
b.Lock()
defer b.Unlock()
delete(b.roots, root)
}
func (b *currentlySyncingBlock) isSyncing(root [32]byte) bool {
b.Lock()
defer b.Unlock()
_, ok := b.roots[root]
return ok
}

View File

@@ -405,8 +405,13 @@ func kzgCommitmentsToVersionedHashes(body interfaces.ReadOnlyBeaconBlockBody) ([
versionedHashes := make([]common.Hash, len(commitments))
for i, commitment := range commitments {
versionedHashes[i] = sha256.Sum256(commitment)
versionedHashes[i][0] = blobCommitmentVersionKZG
versionedHashes[i] = ConvertKzgCommitmentToVersionedHash(commitment)
}
return versionedHashes, nil
}
func ConvertKzgCommitmentToVersionedHash(commitment []byte) common.Hash {
versionedHash := sha256.Sum256(commitment)
versionedHash[0] = blobCommitmentVersionKZG
return versionedHash
}

View File

@@ -66,7 +66,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
if err != nil {
log.WithError(err).Error("Failed to get blob KZG commitments")
} else if len(kzgs) > 0 {
log = log.WithField("blobCommitmentCount", len(kzgs))
log = log.WithField("kzgCommitmentCount", len(kzgs))
}
}
log.Info("Finished applying state transition")
@@ -147,3 +147,15 @@ func logPayload(block interfaces.ReadOnlyBeaconBlock) error {
log.WithFields(fields).Debug("Synced new payload")
return nil
}
func logBlobSidecar(scs []*ethpb.BlobSidecar, startTime time.Time) {
if len(scs) == 0 {
return
}
log.WithFields(logrus.Fields{
"count": len(scs),
"slot": scs[0].Slot,
"block": hex.EncodeToString(scs[0].BlockRoot),
"validationTime": time.Since(startTime),
}).Debug("Synced new blob sidecars")
}

View File

@@ -3,7 +3,6 @@ package blockchain
import (
"github.com/prysmaticlabs/prysm/v4/async/event"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
"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/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
@@ -62,7 +61,7 @@ func WithExecutionEngineCaller(c execution.EngineCaller) Option {
}
// WithDepositCache for deposit lifecycle after chain inclusion.
func WithDepositCache(c *depositcache.DepositCache) Option {
func WithDepositCache(c cache.DepositCache) Option {
return func(s *Service) error {
s.cfg.DepositCache = c
return nil

View File

@@ -13,10 +13,10 @@ 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/db"
forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v4/config/features"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
consensusblocks "github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
@@ -265,6 +265,9 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
return err
}
}
if err := s.databaseDACheck(ctx, b); err != nil {
return errors.Wrap(err, "could not validate blob data availability")
}
args := &forkchoicetypes.BlockAndCheckpoints{Block: b.Block(),
JustifiedCheckpoint: jCheckpoints[i],
FinalizedCheckpoint: fCheckpoints[i]}
@@ -330,6 +333,33 @@ 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 {
@@ -503,14 +533,17 @@ func (s *Service) isDataAvailable(ctx context.Context, root [32]byte, signed int
if signed.Version() < version.Deneb {
return nil
}
t := time.Now()
block := signed.Block()
if block == nil {
return errors.New("invalid nil beacon block")
}
// We are only required to check within MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS
if slots.ToEpoch(block.Slot())+params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest > primitives.Epoch(s.CurrentSlot()) {
if !params.WithinDAPeriod(slots.ToEpoch(block.Slot()), slots.ToEpoch(s.CurrentSlot())) {
return nil
}
body := block.Body()
if body == nil {
return errors.New("invalid nil beacon block body")
@@ -519,52 +552,52 @@ func (s *Service) isDataAvailable(ctx context.Context, root [32]byte, signed int
if err != nil {
return errors.Wrap(err, "could not get KZG commitments")
}
existingBlobs := len(kzgCommitments)
if existingBlobs == 0 {
expected := len(kzgCommitments)
if expected == 0 {
return nil
}
// Read first from db in case we have the blobs
s.blobNotifier.Lock()
var nc *blobNotifierChan
var ok bool
nc, ok = s.blobNotifier.chanForRoot[root]
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, root)
if err == nil {
if len(sidecars) >= existingBlobs {
delete(s.blobNotifier.chanForRoot, root)
s.blobNotifier.Unlock()
return kzg.IsDataAvailable(kzgCommitments, sidecars)
switch {
case err == nil:
if len(sidecars) >= expected {
s.blobNotifiers.delete(root)
if err := kzg.IsDataAvailable(kzgCommitments, sidecars); err != nil {
return err
}
logBlobSidecar(sidecars, t)
return nil
}
case errors.Is(err, db.ErrNotFound):
// If the blob sidecars haven't arrived yet, the subsequent code will wait for them.
// Note: The system will not exit with an error in this scenario.
default:
log.WithError(err).Error("could not get blob sidecars from DB")
}
// Create the channel if it didn't exist already the index map will be
// created later anyway
if !ok {
nc = &blobNotifierChan{channel: make(chan struct{}, fieldparams.MaxBlobsPerBlock)}
s.blobNotifier.chanForRoot[root] = nc
found := map[uint64]struct{}{}
for _, sc := range sidecars {
found[sc.Index] = struct{}{}
}
// We have more commitments in the block than blobs in database
// We sync the channel indices with the sidecars
nc.indices = make(map[uint64]struct{})
for _, sidecar := range sidecars {
nc.indices[sidecar.Index] = struct{}{}
}
s.blobNotifier.Unlock()
channelWrites := len(sidecars)
nc := s.blobNotifiers.forRoot(root)
for {
select {
case <-nc.channel:
channelWrites++
if channelWrites == existingBlobs {
s.blobNotifier.Lock()
delete(s.blobNotifier.chanForRoot, root)
s.blobNotifier.Unlock()
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, root)
if err != nil {
return errors.Wrap(err, "could not get blob sidecars")
}
return kzg.IsDataAvailable(kzgCommitments, sidecars)
case idx := <-nc:
found[idx] = struct{}{}
if len(found) != expected {
continue
}
s.blobNotifiers.delete(root)
sidecars, err := s.cfg.BeaconDB.BlobSidecarsByRoot(ctx, root)
if err != nil {
return errors.Wrap(err, "could not get blob sidecars")
}
if err := kzg.IsDataAvailable(kzgCommitments, sidecars); err != nil {
return err
}
logBlobSidecar(sidecars, t)
return nil
case <-ctx.Done():
return errors.Wrap(ctx.Err(), "context deadline waiting for blob sidecars")
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
forkchoicetypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/types"
@@ -235,7 +236,8 @@ func (s *Service) insertFinalizedDeposits(ctx context.Context, fRoot [32]byte) {
// to be included(rather than the last one to be processed). This was most likely
// done as the state cannot represent signed integers.
finalizedEth1DepIdx := eth1DepositIndex - 1
if err = s.cfg.DepositCache.InsertFinalizedDeposits(ctx, int64(finalizedEth1DepIdx)); err != nil {
if err = s.cfg.DepositCache.InsertFinalizedDeposits(ctx, int64(finalizedEth1DepIdx), common.Hash(finalizedState.Eth1Data().BlockHash),
0 /* Setting a zero value as we have no access to block height */); err != nil {
log.WithError(err).Error("could not insert finalized deposits")
return
}
@@ -247,7 +249,7 @@ func (s *Service) insertFinalizedDeposits(ctx context.Context, fRoot [32]byte) {
// to the provided eth1 deposit index.
s.cfg.DepositCache.PrunePendingDeposits(ctx, int64(eth1DepositIndex)) // lint:ignore uintcast -- Deposit index should not exceed int64 in your lifetime.
log.WithField("duration", time.Since(startTime).String()).Debug("Finalized deposit insertion completed")
log.WithField("duration", time.Since(startTime).String()).Debugf("Finalized deposit insertion completed at index %d", finalizedEth1DepIdx)
}
// This ensures that the input root defaults to using genesis root instead of zero hashes. This is needed for handling

View File

@@ -1,6 +1,7 @@
package blockchain
import (
"bytes"
"context"
"fmt"
"math/big"
@@ -38,6 +39,7 @@ 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"
)
@@ -699,7 +701,7 @@ func TestInsertFinalizedDeposits(t *testing.T) {
gs, _ := util.DeterministicGenesisState(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
gs = gs.Copy()
assert.NoError(t, gs.SetEth1Data(&ethpb.Eth1Data{DepositCount: 10}))
assert.NoError(t, gs.SetEth1Data(&ethpb.Eth1Data{DepositCount: 10, BlockHash: make([]byte, 32)}))
assert.NoError(t, gs.SetEth1DepositIndex(8))
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k'}, gs))
var zeroSig [96]byte
@@ -713,8 +715,9 @@ func TestInsertFinalizedDeposits(t *testing.T) {
}, Proof: [][]byte{root}}, 100+i, int64(i), bytesutil.ToBytes32(root)))
}
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k'})
fDeposits := depositCache.FinalizedDeposits(ctx)
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
fDeposits, err := depositCache.FinalizedDeposits(ctx)
require.NoError(t, err)
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
deps := depositCache.AllDeposits(ctx, big.NewInt(107))
for _, d := range deps {
assert.DeepEqual(t, [][]byte(nil), d.Proof, "Proofs are not empty")
@@ -728,7 +731,7 @@ func TestInsertFinalizedDeposits_PrunePendingDeposits(t *testing.T) {
gs, _ := util.DeterministicGenesisState(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
gs = gs.Copy()
assert.NoError(t, gs.SetEth1Data(&ethpb.Eth1Data{DepositCount: 10}))
assert.NoError(t, gs.SetEth1Data(&ethpb.Eth1Data{DepositCount: 10, BlockHash: make([]byte, 32)}))
assert.NoError(t, gs.SetEth1DepositIndex(8))
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k'}, gs))
var zeroSig [96]byte
@@ -748,8 +751,9 @@ func TestInsertFinalizedDeposits_PrunePendingDeposits(t *testing.T) {
}, Proof: [][]byte{root}}, 100+i, int64(i), bytesutil.ToBytes32(root))
}
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k'})
fDeposits := depositCache.FinalizedDeposits(ctx)
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
fDeposits, err := depositCache.FinalizedDeposits(ctx)
require.NoError(t, err)
assert.Equal(t, 7, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
deps := depositCache.AllDeposits(ctx, big.NewInt(107))
for _, d := range deps {
assert.DeepEqual(t, [][]byte(nil), d.Proof, "Proofs are not empty")
@@ -767,11 +771,11 @@ func TestInsertFinalizedDeposits_MultipleFinalizedRoutines(t *testing.T) {
gs, _ := util.DeterministicGenesisState(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
gs = gs.Copy()
assert.NoError(t, gs.SetEth1Data(&ethpb.Eth1Data{DepositCount: 7}))
assert.NoError(t, gs.SetEth1Data(&ethpb.Eth1Data{DepositCount: 7, BlockHash: make([]byte, 32)}))
assert.NoError(t, gs.SetEth1DepositIndex(6))
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k'}, gs))
gs2 := gs.Copy()
assert.NoError(t, gs2.SetEth1Data(&ethpb.Eth1Data{DepositCount: 15}))
assert.NoError(t, gs2.SetEth1Data(&ethpb.Eth1Data{DepositCount: 15, BlockHash: make([]byte, 32)}))
assert.NoError(t, gs2.SetEth1DepositIndex(13))
assert.NoError(t, service.cfg.StateGen.SaveState(ctx, [32]byte{'m', 'o', 'c', 'k', '2'}, gs2))
var zeroSig [96]byte
@@ -785,11 +789,11 @@ func TestInsertFinalizedDeposits_MultipleFinalizedRoutines(t *testing.T) {
}, Proof: [][]byte{root}}, 100+i, int64(i), bytesutil.ToBytes32(root)))
}
// Insert 3 deposits before hand.
require.NoError(t, depositCache.InsertFinalizedDeposits(ctx, 2))
require.NoError(t, depositCache.InsertFinalizedDeposits(ctx, 2, [32]byte{}, 0))
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k'})
fDeposits := depositCache.FinalizedDeposits(ctx)
assert.Equal(t, 5, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
fDeposits, err := depositCache.FinalizedDeposits(ctx)
require.NoError(t, err)
assert.Equal(t, 5, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
deps := depositCache.AllDeposits(ctx, big.NewInt(105))
for _, d := range deps {
@@ -798,8 +802,9 @@ func TestInsertFinalizedDeposits_MultipleFinalizedRoutines(t *testing.T) {
// Insert New Finalized State with higher deposit count.
service.insertFinalizedDeposits(ctx, [32]byte{'m', 'o', 'c', 'k', '2'})
fDeposits = depositCache.FinalizedDeposits(ctx)
assert.Equal(t, 12, int(fDeposits.MerkleTrieIndex), "Finalized deposits not inserted correctly")
fDeposits, err = depositCache.FinalizedDeposits(ctx)
require.NoError(t, err)
assert.Equal(t, 12, int(fDeposits.MerkleTrieIndex()), "Finalized deposits not inserted correctly")
deps = depositCache.AllDeposits(ctx, big.NewInt(112))
for _, d := range deps {
assert.DeepEqual(t, [][]byte(nil), d.Proof, "Proofs are not empty")
@@ -2033,3 +2038,71 @@ 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

@@ -1,22 +1,23 @@
package blockchain
import fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
import (
"context"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
// SendNewBlobEvent sends a message to the BlobNotifier channel that the blob
// for the blocroot `root` is ready in the database
func (s *Service) SendNewBlobEvent(root [32]byte, index uint64) {
s.blobNotifier.Lock()
nc, ok := s.blobNotifier.chanForRoot[root]
if !ok {
nc = &blobNotifierChan{indices: make(map[uint64]struct{}), channel: make(chan struct{}, fieldparams.MaxBlobsPerBlock)}
s.blobNotifier.chanForRoot[root] = nc
}
_, ok = nc.indices[index]
if ok {
s.blobNotifier.Unlock()
return
}
nc.indices[index] = struct{}{}
s.blobNotifier.Unlock()
nc.channel <- struct{}{}
func (s *Service) sendNewBlobEvent(root [32]byte, index uint64) {
s.blobNotifiers.forRoot(root) <- index
}
// ReceiveBlob saves the blob to database and sends the new event
func (s *Service) ReceiveBlob(ctx context.Context, b *ethpb.BlobSidecar) error {
if err := s.cfg.BeaconDB.SaveBlobSidecar(ctx, []*ethpb.BlobSidecar{b}); err != nil {
return err
}
s.sendNewBlobEvent([32]byte(b.BlockRoot), b.Index)
return nil
}

View File

@@ -3,6 +3,7 @@ package blockchain
import (
"bytes"
"context"
"time"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
@@ -21,7 +22,6 @@ import (
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/attestation"
"github.com/prysmaticlabs/prysm/v4/runtime/version"
"github.com/prysmaticlabs/prysm/v4/time"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"go.opencensus.io/trace"
"golang.org/x/sync/errgroup"
@@ -36,12 +36,13 @@ type BlockReceiver interface {
ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock) error
HasBlock(ctx context.Context, root [32]byte) bool
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
BlockBeingSynced([32]byte) bool
}
// BlobReceiver interface defines the methods of chain service for receiving new
// blobs
type BlobReceiver interface {
SendNewBlobEvent([32]byte, uint64)
ReceiveBlob(context.Context, *ethpb.BlobSidecar) error
}
// SlashingReceiver interface defines the methods of chain service for receiving validated slashing over the wire.
@@ -58,6 +59,9 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
ctx, span := trace.StartSpan(ctx, "blockChain.ReceiveBlock")
defer span.End()
receivedTime := time.Now()
s.blockBeingSynced.set(blockRoot)
defer s.blockBeingSynced.unset(blockRoot)
blockCopy, err := block.Copy()
if err != nil {
return err

View File

@@ -23,9 +23,10 @@ func TestService_ReceiveBlock(t *testing.T) {
ctx := context.Background()
genesis, keys := util.DeterministicGenesisState(t, 64)
copiedGen := genesis.Copy()
genFullBlock := func(t *testing.T, conf *util.BlockGenConfig, slot primitives.Slot) *ethpb.SignedBeaconBlock {
blk, err := util.GenerateFullBlock(genesis, keys, conf, slot)
assert.NoError(t, err)
blk, err := util.GenerateFullBlock(copiedGen.Copy(), keys, conf, slot)
require.NoError(t, err)
return blk
}
//params.SetupTestConfigCleanupWithLock(t)
@@ -108,6 +109,9 @@ func TestService_ReceiveBlock(t *testing.T) {
block: genFullBlock(t, util.DefaultBlockGenConfig(), 1 /*slot*/),
},
check: func(t *testing.T, s *Service) {
// Hacky sleep, should use a better way to be able to resolve the race
// between event being sent out and processed.
time.Sleep(100 * time.Millisecond)
if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
t.Errorf("Received %d state notifications, expected at least 1", recvd)
}
@@ -119,6 +123,10 @@ func TestService_ReceiveBlock(t *testing.T) {
for _, tt := range tests {
wg.Add(1)
t.Run(tt.name, func(t *testing.T) {
defer func() {
wg.Done()
}()
genesis = genesis.Copy()
s, tr := minimalTestService(t,
WithFinalizedStateAtStartUp(genesis),
WithExitPool(voluntaryexits.NewPool()),
@@ -139,10 +147,9 @@ func TestService_ReceiveBlock(t *testing.T) {
if tt.wantedErr != "" {
assert.ErrorContains(t, tt.wantedErr, err)
} else {
assert.NoError(t, err)
require.NoError(t, err)
tt.check(t, s)
}
wg.Done()
})
}
wg.Wait()
@@ -173,6 +180,7 @@ func TestService_ReceiveBlockUpdateHead(t *testing.T) {
wg.Done()
}()
wg.Wait()
time.Sleep(100 * time.Millisecond)
if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
t.Errorf("Received %d state notifications, expected at least 1", recvd)
}
@@ -215,6 +223,7 @@ func TestService_ReceiveBlockBatch(t *testing.T) {
block: genFullBlock(t, util.DefaultBlockGenConfig(), 1 /*slot*/),
},
check: func(t *testing.T, s *Service) {
time.Sleep(100 * time.Millisecond)
if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
t.Errorf("Received %d state notifications, expected at least 1", recvd)
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/async/event"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/kzg"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
@@ -33,6 +32,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v4/config/features"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
@@ -61,7 +61,8 @@ type Service struct {
clockSetter startup.ClockSetter
clockWaiter startup.ClockWaiter
syncComplete chan struct{}
blobNotifier *blobNotifier
blobNotifiers *blobNotifierMap
blockBeingSynced *currentlySyncingBlock
}
// config options for the service.
@@ -69,7 +70,7 @@ type config struct {
BeaconBlockBuf int
ChainStartFetcher execution.ChainStartFetcher
BeaconDB db.HeadAccessDatabase
DepositCache *depositcache.DepositCache
DepositCache cache.DepositCache
ProposerSlotIndexCache *cache.ProposerPayloadIDsCache
AttPool attestations.Pool
ExitPool voluntaryexits.PoolManager
@@ -90,14 +91,26 @@ type config struct {
var ErrMissingClockSetter = errors.New("blockchain Service initialized without a startup.ClockSetter")
type blobNotifierChan struct {
indices map[uint64]struct{}
channel chan struct{}
type blobNotifierMap struct {
sync.RWMutex
notifiers map[[32]byte]chan uint64
}
type blobNotifier struct {
sync.RWMutex
chanForRoot map[[32]byte]*blobNotifierChan
func (bn *blobNotifierMap) forRoot(root [32]byte) chan uint64 {
bn.Lock()
defer bn.Unlock()
c, ok := bn.notifiers[root]
if !ok {
c = make(chan uint64, fieldparams.MaxBlobsPerBlock)
bn.notifiers[root] = c
}
return c
}
func (bn *blobNotifierMap) delete(root [32]byte) {
bn.Lock()
defer bn.Unlock()
delete(bn.notifiers, root)
}
// NewService instantiates a new block service instance that will
@@ -111,8 +124,8 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
}
}
ctx, cancel := context.WithCancel(ctx)
bn := &blobNotifier{
chanForRoot: make(map[[32]byte]*blobNotifierChan),
bn := &blobNotifierMap{
notifiers: make(map[[32]byte]chan uint64),
}
srv := &Service{
ctx: ctx,
@@ -120,8 +133,9 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
boundaryRoots: [][32]byte{},
checkpointStateCache: cache.NewCheckpointStateCache(),
initSyncBlocks: make(map[[32]byte]interfaces.ReadOnlySignedBeaconBlock),
blobNotifier: bn,
blobNotifiers: bn,
cfg: &config{ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache()},
blockBeingSynced: &currentlySyncingBlock{roots: make(map[[32]byte]struct{})},
}
for _, opt := range opts {
if err := opt(srv); err != nil {
@@ -368,7 +382,7 @@ func (s *Service) startFromExecutionChain() error {
log.Debug("Context closed, exiting goroutine")
return
case err := <-stateSub.Err():
log.WithError(err).Error("Subscription to state notifier failed")
log.WithError(err).Error("Subscription to state forRoot failed")
return
}
}

View File

@@ -71,6 +71,7 @@ type ChainService struct {
FinalizedRoots map[[32]byte]bool
OptimisticRoots map[[32]byte]bool
BlockSlot primitives.Slot
SyncingRoot [32]byte
}
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
@@ -605,5 +606,12 @@ func (s *ChainService) UnrealizedJustifiedPayloadBlockHash() [32]byte {
return [32]byte{}
}
// SendNewBlobEvent mocks the same method in the chain service
func (s *ChainService) SendNewBlobEvent(_ [32]byte, _ uint64) {}
// BlockBeingSynced mocks the same method in the chain service
func (c *ChainService) BlockBeingSynced(root [32]byte) bool {
return root == c.SyncingRoot
}
// ReceiveBlob implements the same method in the chain service
func (*ChainService) ReceiveBlob(_ context.Context, _ *ethpb.BlobSidecar) error {
return nil
}

View File

@@ -13,6 +13,7 @@ go_library(
"common.go",
"doc.go",
"error.go",
"interfaces.go",
"payload_id.go",
"proposer_indices.go",
"proposer_indices_disabled.go", # keep
@@ -44,6 +45,7 @@ go_library(
"//math:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_hashicorp_golang_lru//:go_default_library",
"@com_github_patrickmn_go_cache//:go_default_library",
"@com_github_pkg_errors//:go_default_library",

View File

@@ -13,12 +13,14 @@ go_library(
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/cache:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
@@ -35,6 +37,7 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/cache:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//encoding/bytesutil:go_default_library",

View File

@@ -11,9 +11,11 @@ import (
"sort"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/container/trie"
@@ -30,20 +32,11 @@ var (
})
)
// DepositFetcher defines a struct which can retrieve deposit information from a store.
type DepositFetcher interface {
AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit
DepositByPubkey(ctx context.Context, pubKey []byte) (*ethpb.Deposit, *big.Int)
DepositsNumberAndRootAtHeight(ctx context.Context, blockHeight *big.Int) (uint64, [32]byte)
FinalizedDeposits(ctx context.Context) *FinalizedDeposits
NonFinalizedDeposits(ctx context.Context, lastFinalizedIndex int64, untilBlk *big.Int) []*ethpb.Deposit
}
// FinalizedDeposits stores the trie of deposits that have been included
// in the beacon state up to the latest finalized checkpoint.
type FinalizedDeposits struct {
Deposits *trie.SparseMerkleTrie
MerkleTrieIndex int64
deposits *trie.SparseMerkleTrie
merkleTrieIndex int64
}
// DepositCache stores all in-memory deposit objects. This
@@ -52,7 +45,7 @@ type DepositCache struct {
// Beacon chain deposits in memory.
pendingDeposits []*ethpb.DepositContainer
deposits []*ethpb.DepositContainer
finalizedDeposits *FinalizedDeposits
finalizedDeposits FinalizedDeposits
depositsByKey map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer
depositsLock sync.RWMutex
}
@@ -64,13 +57,13 @@ func New() (*DepositCache, error) {
return nil, err
}
// finalizedDeposits.MerkleTrieIndex is initialized to -1 because it represents the index of the last trie item.
// finalizedDeposits.merkleTrieIndex is initialized to -1 because it represents the index of the last trie item.
// Inserting the first item into the trie will set the value of the index to 0.
return &DepositCache{
pendingDeposits: []*ethpb.DepositContainer{},
deposits: []*ethpb.DepositContainer{},
depositsByKey: map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer{},
finalizedDeposits: &FinalizedDeposits{Deposits: finalizedDepositsTrie, MerkleTrieIndex: -1},
finalizedDeposits: FinalizedDeposits{deposits: finalizedDepositsTrie, merkleTrieIndex: -1},
}, nil
}
@@ -129,14 +122,15 @@ func (dc *DepositCache) InsertDepositContainers(ctx context.Context, ctrs []*eth
}
// InsertFinalizedDeposits inserts deposits up to eth1DepositIndex (inclusive) into the finalized deposits cache.
func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context, eth1DepositIndex int64) error {
func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context,
eth1DepositIndex int64, _ common.Hash, _ uint64) error {
ctx, span := trace.StartSpan(ctx, "DepositsCache.InsertFinalizedDeposits")
defer span.End()
dc.depositsLock.Lock()
defer dc.depositsLock.Unlock()
depositTrie := dc.finalizedDeposits.Deposits
insertIndex := int(dc.finalizedDeposits.MerkleTrieIndex + 1)
depositTrie := dc.finalizedDeposits.Deposits()
insertIndex := int(dc.finalizedDeposits.merkleTrieIndex + 1)
// Don't insert into finalized trie if there is no deposit to
// insert.
@@ -154,7 +148,7 @@ func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context, eth1Deposit
return nil
}
for _, d := range dc.deposits {
if d.Index <= dc.finalizedDeposits.MerkleTrieIndex {
if d.Index <= dc.finalizedDeposits.merkleTrieIndex {
continue
}
if d.Index > eth1DepositIndex {
@@ -169,10 +163,13 @@ func (dc *DepositCache) InsertFinalizedDeposits(ctx context.Context, eth1Deposit
}
insertIndex++
}
dc.finalizedDeposits = &FinalizedDeposits{
Deposits: depositTrie,
MerkleTrieIndex: eth1DepositIndex,
tree, ok := depositTrie.(*trie.SparseMerkleTrie)
if !ok {
return errors.New("not a sparse merkle tree")
}
dc.finalizedDeposits = FinalizedDeposits{
deposits: tree,
merkleTrieIndex: eth1DepositIndex,
}
return nil
}
@@ -204,8 +201,6 @@ func (dc *DepositCache) AllDepositContainers(ctx context.Context) []*ethpb.Depos
// AllDeposits returns a list of historical deposits until the given block number
// (inclusive). If no block is specified then this method returns all historical deposits.
func (dc *DepositCache) AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
ctx, span := trace.StartSpan(ctx, "DepositsCache.AllDeposits")
defer span.End()
dc.depositsLock.RLock()
defer dc.depositsLock.RUnlock()
@@ -261,16 +256,16 @@ func (dc *DepositCache) DepositByPubkey(ctx context.Context, pubKey []byte) (*et
}
// FinalizedDeposits returns the finalized deposits trie.
func (dc *DepositCache) FinalizedDeposits(ctx context.Context) *FinalizedDeposits {
func (dc *DepositCache) FinalizedDeposits(ctx context.Context) (cache.FinalizedDeposits, error) {
ctx, span := trace.StartSpan(ctx, "DepositsCache.FinalizedDeposits")
defer span.End()
dc.depositsLock.RLock()
defer dc.depositsLock.RUnlock()
return &FinalizedDeposits{
Deposits: dc.finalizedDeposits.Deposits.Copy(),
MerkleTrieIndex: dc.finalizedDeposits.MerkleTrieIndex,
}
deposits: dc.finalizedDeposits.deposits.Copy(),
merkleTrieIndex: dc.finalizedDeposits.merkleTrieIndex,
}, nil
}
// NonFinalizedDeposits returns the list of non-finalized deposits until the given block number (inclusive).
@@ -281,7 +276,7 @@ func (dc *DepositCache) NonFinalizedDeposits(ctx context.Context, lastFinalizedI
dc.depositsLock.RLock()
defer dc.depositsLock.RUnlock()
if dc.finalizedDeposits == nil {
if dc.finalizedDeposits.Deposits() == nil {
return dc.allDeposits(untilBlk)
}
@@ -307,9 +302,6 @@ func (dc *DepositCache) PruneProofs(ctx context.Context, untilDepositIndex int64
}
for i := untilDepositIndex; i >= 0; i-- {
if ctx.Err() != nil {
return ctx.Err()
}
// Finding a nil proof means that all proofs up to this deposit have been already pruned.
if dc.deposits[i].Deposit.Proof == nil {
break
@@ -319,3 +311,14 @@ func (dc *DepositCache) PruneProofs(ctx context.Context, untilDepositIndex int64
return nil
}
// Deposits returns the cached internal deposit tree.
func (fd *FinalizedDeposits) Deposits() cache.MerkleTree {
return fd.deposits
}
// MerkleTrieIndex represents the last finalized index in
// the finalized deposit container.
func (fd *FinalizedDeposits) MerkleTrieIndex() int64 {
return fd.merkleTrieIndex
}

View File

@@ -7,6 +7,7 @@ import (
"math/big"
"testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
@@ -18,7 +19,7 @@ import (
const nilDepositErr = "Ignoring nil deposit insertion"
var _ DepositFetcher = (*DepositCache)(nil)
var _ cache.DepositFetcher = (*DepositCache)(nil)
func TestInsertDeposit_LogsOnNilDepositInsertion(t *testing.T) {
hook := logTest.NewGlobal()
@@ -416,11 +417,12 @@ func TestFinalizedDeposits_DepositsCachedCorrectly(t *testing.T) {
Index: 3,
})
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
cachedDeposits := dc.FinalizedDeposits(context.Background())
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
require.NotNil(t, cachedDeposits, "Deposits not cached")
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex)
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex())
var deps [][]byte
for _, d := range finalizedDeposits {
@@ -432,7 +434,7 @@ func TestFinalizedDeposits_DepositsCachedCorrectly(t *testing.T) {
require.NoError(t, err, "Could not generate deposit trie")
rootA, err := generatedTrie.HashTreeRoot()
require.NoError(t, err)
rootB, err := cachedDeposits.Deposits.HashTreeRoot()
rootB, err := cachedDeposits.Deposits().HashTreeRoot()
require.NoError(t, err)
assert.Equal(t, rootA, rootB)
}
@@ -474,15 +476,16 @@ func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) {
Index: 2,
}
dc.deposits = oldFinalizedDeposits
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1, [32]byte{}, 0))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
dc.deposits = append(dc.deposits, []*ethpb.DepositContainer{newFinalizedDeposit}...)
cachedDeposits := dc.FinalizedDeposits(context.Background())
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
require.NotNil(t, cachedDeposits, "Deposits not cached")
assert.Equal(t, int64(1), cachedDeposits.MerkleTrieIndex)
assert.Equal(t, int64(1), cachedDeposits.MerkleTrieIndex())
var deps [][]byte
for _, d := range oldFinalizedDeposits {
@@ -494,7 +497,7 @@ func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) {
require.NoError(t, err, "Could not generate deposit trie")
rootA, err := generatedTrie.HashTreeRoot()
require.NoError(t, err)
rootB, err := cachedDeposits.Deposits.HashTreeRoot()
rootB, err := cachedDeposits.Deposits().HashTreeRoot()
require.NoError(t, err)
assert.Equal(t, rootA, rootB)
}
@@ -503,11 +506,12 @@ func TestFinalizedDeposits_HandleZeroDeposits(t *testing.T) {
dc, err := New()
require.NoError(t, err)
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
cachedDeposits := dc.FinalizedDeposits(context.Background())
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
require.NotNil(t, cachedDeposits, "Deposits not cached")
assert.Equal(t, int64(-1), cachedDeposits.MerkleTrieIndex)
assert.Equal(t, int64(-1), cachedDeposits.MerkleTrieIndex())
}
func TestFinalizedDeposits_HandleSmallerThanExpectedDeposits(t *testing.T) {
@@ -548,11 +552,12 @@ func TestFinalizedDeposits_HandleSmallerThanExpectedDeposits(t *testing.T) {
}
dc.deposits = finalizedDeposits
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5, [32]byte{}, 0))
cachedDeposits := dc.FinalizedDeposits(context.Background())
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
require.NotNil(t, cachedDeposits, "Deposits not cached")
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex)
assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex())
}
func TestFinalizedDeposits_HandleLowerEth1DepositIndex(t *testing.T) {
@@ -623,14 +628,15 @@ func TestFinalizedDeposits_HandleLowerEth1DepositIndex(t *testing.T) {
}
dc.deposits = finalizedDeposits
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 5, [32]byte{}, 0))
// Reinsert finalized deposits with a lower index.
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
cachedDeposits := dc.FinalizedDeposits(context.Background())
cachedDeposits, err := dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
require.NotNil(t, cachedDeposits, "Deposits not cached")
assert.Equal(t, int64(5), cachedDeposits.MerkleTrieIndex)
assert.Equal(t, int64(5), cachedDeposits.MerkleTrieIndex())
}
func TestFinalizedDeposits_InitializedCorrectly(t *testing.T) {
@@ -640,7 +646,7 @@ func TestFinalizedDeposits_InitializedCorrectly(t *testing.T) {
finalizedDeposits := dc.finalizedDeposits
assert.NotNil(t, finalizedDeposits)
assert.NotNil(t, finalizedDeposits.Deposits)
assert.Equal(t, int64(-1), finalizedDeposits.MerkleTrieIndex)
assert.Equal(t, int64(-1), finalizedDeposits.merkleTrieIndex)
}
func TestNonFinalizedDeposits_ReturnsAllNonFinalizedDeposits(t *testing.T) {
@@ -694,7 +700,7 @@ func TestNonFinalizedDeposits_ReturnsAllNonFinalizedDeposits(t *testing.T) {
},
Index: 3,
})
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1, [32]byte{}, 0))
deps := dc.NonFinalizedDeposits(context.Background(), 1, nil)
assert.Equal(t, 2, len(deps))
@@ -751,7 +757,7 @@ func TestNonFinalizedDeposits_ReturnsNonFinalizedDepositsUpToBlockNumber(t *test
},
Index: 3,
})
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 1, [32]byte{}, 0))
deps := dc.NonFinalizedDeposits(context.Background(), 1, big.NewInt(10))
assert.Equal(t, 1, len(deps))
@@ -799,41 +805,43 @@ func TestFinalizedDeposits_ReturnsTrieCorrectly(t *testing.T) {
assert.NoError(t, err)
// Perform this in a non-sensical ordering
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 10))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 3))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 4))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 10, [32]byte{}, 0))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 2, [32]byte{}, 0))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 3, [32]byte{}, 0))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 4, [32]byte{}, 0))
// Mimic finalized deposit trie fetch.
fd := dc.FinalizedDeposits(context.Background())
deps := dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex, big.NewInt(14))
insertIndex := fd.MerkleTrieIndex + 1
fd, err := dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
deps := dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex(), big.NewInt(14))
insertIndex := fd.MerkleTrieIndex() + 1
for _, dep := range deps {
depHash, err := dep.Data.HashTreeRoot()
assert.NoError(t, err)
if err = fd.Deposits.Insert(depHash[:], int(insertIndex)); err != nil {
if err = fd.Deposits().Insert(depHash[:], int(insertIndex)); err != nil {
assert.NoError(t, err)
}
insertIndex++
}
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 14))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15, [32]byte{}, 0))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 15, [32]byte{}, 0))
require.NoError(t, dc.InsertFinalizedDeposits(context.Background(), 14, [32]byte{}, 0))
fd = dc.FinalizedDeposits(context.Background())
deps = dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex, big.NewInt(30))
insertIndex = fd.MerkleTrieIndex + 1
fd, err = dc.FinalizedDeposits(context.Background())
require.NoError(t, err)
deps = dc.NonFinalizedDeposits(context.Background(), fd.MerkleTrieIndex(), big.NewInt(30))
insertIndex = fd.MerkleTrieIndex() + 1
for _, dep := range deps {
depHash, err := dep.Data.HashTreeRoot()
assert.NoError(t, err)
if err = fd.Deposits.Insert(depHash[:], int(insertIndex)); err != nil {
if err = fd.Deposits().Insert(depHash[:], int(insertIndex)); err != nil {
assert.NoError(t, err)
}
insertIndex++
}
assert.Equal(t, fd.Deposits.NumOfItems(), depositTrie.NumOfItems())
assert.Equal(t, fd.Deposits().NumOfItems(), depositTrie.NumOfItems())
}
func TestPruneProofs_Ok(t *testing.T) {
@@ -1056,3 +1064,11 @@ func makeDepositProof() [][]byte {
}
return proof
}
func TestEmptyTree(t *testing.T) {
finalizedDepositsTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
require.NoError(t, err)
v, err := finalizedDepositsTrie.HashTreeRoot()
require.NoError(t, err)
fmt.Printf("%x", v)
}

View File

@@ -17,7 +17,7 @@ func TestInsertPendingDeposit_OK(t *testing.T) {
dc := DepositCache{}
dc.InsertPendingDeposit(context.Background(), &ethpb.Deposit{}, 111, 100, [32]byte{})
assert.Equal(t, 1, len(dc.pendingDeposits), "Deposit not inserted")
assert.Equal(t, 1, len(dc.pendingDeposits), "deposit not inserted")
}
func TestInsertPendingDeposit_ignoresNilDeposit(t *testing.T) {
@@ -56,7 +56,7 @@ func TestRemovePendingDeposit_IgnoresNilDeposit(t *testing.T) {
dc := DepositCache{}
dc.pendingDeposits = []*ethpb.DepositContainer{{Deposit: &ethpb.Deposit{}}}
dc.RemovePendingDeposit(context.Background(), nil /*deposit*/)
assert.Equal(t, 1, len(dc.pendingDeposits), "Deposit unexpectedly removed")
assert.Equal(t, 1, len(dc.pendingDeposits), "deposit unexpectedly removed")
}
func TestPendingDeposit_RoundTrip(t *testing.T) {

View File

@@ -3,26 +3,37 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"deposit_fetcher.go",
"deposit_inserter.go",
"deposit_tree.go",
"deposit_tree_snapshot.go",
"merkle_tree.go",
"zerohashes.gen.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/cache:go_default_library",
"//config/fieldparams:go_default_library",
"//container/slice:go_default_library",
"//container/trie:go_default_library",
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_wealdtech_go_bytesutil//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"deposit_cache_test.go",
"deposit_tree_snapshot_test.go",
"merkle_tree_test.go",
"spec_test.go",
@@ -32,10 +43,16 @@ go_test(
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/cache:go_default_library",
"//config/params:go_default_library",
"//container/trie:go_default_library",
"//crypto/hash:go_default_library",
"//encoding/bytesutil:go_default_library",
"//io/file:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@in_gopkg_yaml_v3//:go_default_library",
"@io_bazel_rules_go//go/tools/bazel:go_default_library",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,304 @@
package depositsnapshot
import (
"context"
"math/big"
"sort"
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/sirupsen/logrus"
"github.com/wealdtech/go-bytesutil"
"go.opencensus.io/trace"
)
var (
pendingDepositsCount = promauto.NewGauge(prometheus.GaugeOpts{
Name: "beacondb_pending_deposits_eip4881",
Help: "The number of pending deposits in memory",
})
)
// Cache stores all in-memory deposit objects. This
// stores all the deposit related data that is required by the beacon-node.
type Cache struct {
pendingDeposits []*ethpb.DepositContainer
deposits []*ethpb.DepositContainer
finalizedDeposits finalizedDepositsContainer
depositsByKey map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer
depositsLock sync.RWMutex
}
// finalizedDepositsContainer stores the trie of deposits that have been included
// in the beacon state up to the latest finalized checkpoint.
type finalizedDepositsContainer struct {
depositTree *DepositTree
merkleTrieIndex int64
}
// New instantiates a new deposit cache
func New() (*Cache, error) {
finalizedDepositsTrie := NewDepositTree()
// finalizedDeposits.merkleTrieIndex is initialized to -1 because it represents the index of the last trie item.
// Inserting the first item into the trie will set the value of the index to 0.
return &Cache{
pendingDeposits: []*ethpb.DepositContainer{},
deposits: []*ethpb.DepositContainer{},
depositsByKey: map[[fieldparams.BLSPubkeyLength]byte][]*ethpb.DepositContainer{},
finalizedDeposits: toFinalizedDepositsContainer(finalizedDepositsTrie, -1),
}, nil
}
// AllDeposits returns a list of historical deposits until the given block number
// (inclusive). If no block is specified then this method returns all historical deposits.
func (c *Cache) AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
return c.allDeposits(untilBlk)
}
func (c *Cache) allDeposits(untilBlk *big.Int) []*ethpb.Deposit {
var deposits []*ethpb.Deposit
for _, ctnr := range c.deposits {
cBlk := big.NewInt(0).SetUint64(ctnr.Eth1BlockHeight)
if untilBlk == nil || untilBlk.Cmp(cBlk) >= 0 {
deposits = append(deposits, ctnr.Deposit)
}
}
return deposits
}
// AllDepositContainers returns all historical deposit containers.
func (c *Cache) AllDepositContainers(ctx context.Context) []*ethpb.DepositContainer {
ctx, span := trace.StartSpan(ctx, "Cache.AllDepositContainers")
defer span.End()
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
// Make a shallow copy of the deposits and return that. This way, the
// caller can safely iterate over the returned list of deposits without
// the possibility of new deposits showing up. If we were to return the
// list without a copy, when a new deposit is added to the cache, it
// would also be present in the returned value. This could result in a
// race condition if the list is being iterated over.
//
// It's not necessary to make a deep copy of this list because the
// deposits in the cache should never be modified. It is still possible
// for the caller to modify one of the underlying deposits and modify
// the cache, but that's not a race condition. Also, a deep copy would
// take too long and use too much memory.
deposits := make([]*ethpb.DepositContainer, len(c.deposits))
copy(deposits, c.deposits)
return deposits
}
// DepositByPubkey looks through historical deposits and finds one which contains
// a certain public key within its deposit data.
func (c *Cache) DepositByPubkey(ctx context.Context, pubKey []byte) (*ethpb.Deposit, *big.Int) {
ctx, span := trace.StartSpan(ctx, "Cache.DepositByPubkey")
defer span.End()
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
var deposit *ethpb.Deposit
var blockNum *big.Int
deps, ok := c.depositsByKey[bytesutil.ToBytes48(pubKey)]
if !ok || len(deps) == 0 {
return deposit, blockNum
}
// We always return the first deposit if a particular
// validator key has multiple deposits assigned to
// it.
deposit = deps[0].Deposit
blockNum = big.NewInt(int64(deps[0].Eth1BlockHeight))
return deposit, blockNum
}
// DepositsNumberAndRootAtHeight returns number of deposits made up to blockheight and the
// root that corresponds to the latest deposit at that blockheight.
func (c *Cache) DepositsNumberAndRootAtHeight(ctx context.Context, blockHeight *big.Int) (uint64, [32]byte) {
ctx, span := trace.StartSpan(ctx, "Cache.DepositsNumberAndRootAtHeight")
defer span.End()
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
heightIdx := sort.Search(len(c.deposits), func(i int) bool {
dBlkHeight := big.NewInt(0).SetUint64(c.deposits[i].Eth1BlockHeight)
return dBlkHeight.Cmp(blockHeight) > 0
})
// send the deposit root of the empty trie, if eth1follow distance is greater than the time of the earliest
// deposit.
if heightIdx == 0 {
return 0, [32]byte{}
}
return uint64(heightIdx), bytesutil.ToBytes32(c.deposits[heightIdx-1].DepositRoot)
}
// FinalizedDeposits returns the finalized deposits trie.
func (c *Cache) FinalizedDeposits(ctx context.Context) (cache.FinalizedDeposits, error) {
ctx, span := trace.StartSpan(ctx, "Cache.FinalizedDeposits")
defer span.End()
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
tree, err := c.finalizedDeposits.depositTree.Copy()
if err != nil {
return nil, err
}
return &finalizedDepositsContainer{
depositTree: tree,
merkleTrieIndex: c.finalizedDeposits.merkleTrieIndex,
}, nil
}
// NonFinalizedDeposits returns the list of non-finalized deposits until the given block number (inclusive).
// If no block is specified then this method returns all non-finalized deposits.
func (c *Cache) NonFinalizedDeposits(ctx context.Context, lastFinalizedIndex int64, untilBlk *big.Int) []*ethpb.Deposit {
ctx, span := trace.StartSpan(ctx, "Cache.NonFinalizedDeposits")
defer span.End()
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
if c.finalizedDeposits.depositTree == nil {
return c.allDeposits(untilBlk)
}
var deposits []*ethpb.Deposit
for _, d := range c.deposits {
if (d.Index > lastFinalizedIndex) && (untilBlk == nil || untilBlk.Uint64() >= d.Eth1BlockHeight) {
deposits = append(deposits, d.Deposit)
}
}
return deposits
}
// PruneProofs removes proofs from all deposits whose index is equal or less than untilDepositIndex.
func (c *Cache) PruneProofs(ctx context.Context, untilDepositIndex int64) error {
ctx, span := trace.StartSpan(ctx, "Cache.PruneProofs")
defer span.End()
c.depositsLock.Lock()
defer c.depositsLock.Unlock()
if untilDepositIndex >= int64(len(c.deposits)) {
untilDepositIndex = int64(len(c.deposits) - 1)
}
for i := untilDepositIndex; i >= 0; i-- {
// Finding a nil proof means that all proofs up to this deposit have been already pruned.
if c.deposits[i].Deposit.Proof == nil {
break
}
c.deposits[i].Deposit.Proof = nil
}
return nil
}
// PrunePendingDeposits removes any deposit which is older than the given deposit merkle tree index.
func (c *Cache) PrunePendingDeposits(ctx context.Context, merkleTreeIndex int64) {
ctx, span := trace.StartSpan(ctx, "Cache.PrunePendingDeposits")
defer span.End()
if merkleTreeIndex == 0 {
log.Debug("Ignoring 0 deposit removal")
return
}
c.depositsLock.Lock()
defer c.depositsLock.Unlock()
cleanDeposits := make([]*ethpb.DepositContainer, 0, len(c.pendingDeposits))
for _, dp := range c.pendingDeposits {
if dp.Index >= merkleTreeIndex {
cleanDeposits = append(cleanDeposits, dp)
}
}
c.pendingDeposits = cleanDeposits
pendingDepositsCount.Set(float64(len(c.pendingDeposits)))
}
// InsertPendingDeposit into the database. If deposit or block number are nil
// then this method does nothing.
func (c *Cache) InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) {
ctx, span := trace.StartSpan(ctx, "Cache.InsertPendingDeposit")
defer span.End()
if d == nil {
log.WithFields(logrus.Fields{
"block": blockNum,
"deposit": d,
}).Debug("Ignoring nil deposit insertion")
return
}
c.depositsLock.Lock()
defer c.depositsLock.Unlock()
c.pendingDeposits = append(c.pendingDeposits,
&ethpb.DepositContainer{Deposit: d, Eth1BlockHeight: blockNum, Index: index, DepositRoot: depositRoot[:]})
pendingDepositsCount.Set(float64(len(c.pendingDeposits)))
span.AddAttributes(trace.Int64Attribute("count", int64(len(c.pendingDeposits))))
}
// Deposits returns the cached internal deposit tree.
func (fd *finalizedDepositsContainer) Deposits() cache.MerkleTree {
return fd.depositTree
}
// MerkleTrieIndex represents the last finalized index in
// the finalized deposit container.
func (fd *finalizedDepositsContainer) MerkleTrieIndex() int64 {
return fd.merkleTrieIndex
}
func toFinalizedDepositsContainer(deposits *DepositTree, index int64) finalizedDepositsContainer {
return finalizedDepositsContainer{
depositTree: deposits,
merkleTrieIndex: index,
}
}
// PendingDeposits returns a list of deposits until the given block number
// (inclusive). If no block is specified then this method returns all pending
// deposits.
func (c *Cache) PendingDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
ctx, span := trace.StartSpan(ctx, "Cache.PendingDeposits")
defer span.End()
depositCntrs := c.PendingContainers(ctx, untilBlk)
deposits := make([]*ethpb.Deposit, 0, len(depositCntrs))
for _, dep := range depositCntrs {
deposits = append(deposits, dep.Deposit)
}
return deposits
}
// PendingContainers returns a list of deposit containers until the given block number
// (inclusive).
func (c *Cache) PendingContainers(ctx context.Context, untilBlk *big.Int) []*ethpb.DepositContainer {
ctx, span := trace.StartSpan(ctx, "Cache.PendingContainers")
defer span.End()
c.depositsLock.RLock()
defer c.depositsLock.RUnlock()
depositCntrs := make([]*ethpb.DepositContainer, 0, len(c.pendingDeposits))
for _, ctnr := range c.pendingDeposits {
if untilBlk == nil || untilBlk.Uint64() >= ctnr.Eth1BlockHeight {
depositCntrs = append(depositCntrs, ctnr)
}
}
// Sort the deposits by Merkle index.
sort.SliceStable(depositCntrs, func(i, j int) bool {
return depositCntrs[i].Index < depositCntrs[j].Index
})
span.AddAttributes(trace.Int64Attribute("count", int64(len(depositCntrs))))
return depositCntrs
}

View File

@@ -0,0 +1,134 @@
package depositsnapshot
import (
"context"
"encoding/hex"
"sort"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
var (
historicalDepositsCount = promauto.NewCounter(prometheus.CounterOpts{
Name: "beacondb_all_deposits_eip4881",
Help: "The number of total deposits in memory",
})
log = logrus.WithField("prefix", "cache")
)
// InsertDeposit into the database. If deposit or block number are nil
// then this method does nothing.
func (c *Cache) InsertDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) error {
ctx, span := trace.StartSpan(ctx, "Cache.InsertDeposit")
defer span.End()
if d == nil {
log.WithFields(logrus.Fields{
"block": blockNum,
"deposit": d,
"index": index,
"deposit root": hex.EncodeToString(depositRoot[:]),
}).Warn("Ignoring nil deposit insertion")
return errors.New("nil deposit inserted into the cache")
}
c.depositsLock.Lock()
defer c.depositsLock.Unlock()
if int(index) != len(c.deposits) {
return errors.Errorf("wanted deposit with index %d to be inserted but received %d", len(c.deposits), index)
}
// Keep the slice sorted on insertion in order to avoid costly sorting on retrieval.
heightIdx := sort.Search(len(c.deposits), func(i int) bool { return c.deposits[i].Index >= index })
depCtr := &ethpb.DepositContainer{Deposit: d, Eth1BlockHeight: blockNum, DepositRoot: depositRoot[:], Index: index}
newDeposits := append(
[]*ethpb.DepositContainer{depCtr},
c.deposits[heightIdx:]...)
c.deposits = append(c.deposits[:heightIdx], newDeposits...)
// Append the deposit to our map, in the event no deposits
// exist for the pubkey , it is simply added to the map.
pubkey := bytesutil.ToBytes48(d.Data.PublicKey)
c.depositsByKey[pubkey] = append(c.depositsByKey[pubkey], depCtr)
historicalDepositsCount.Inc()
return nil
}
// InsertDepositContainers inserts a set of deposit containers into our deposit cache.
func (c *Cache) InsertDepositContainers(ctx context.Context, ctrs []*ethpb.DepositContainer) {
ctx, span := trace.StartSpan(ctx, "Cache.InsertDepositContainers")
defer span.End()
c.depositsLock.Lock()
defer c.depositsLock.Unlock()
// Initialize slice if nil object provided.
if ctrs == nil {
ctrs = make([]*ethpb.DepositContainer, 0)
}
sort.SliceStable(ctrs, func(i int, j int) bool { return ctrs[i].Index < ctrs[j].Index })
c.deposits = ctrs
for _, ctr := range ctrs {
// Use a new value, as the reference
// changes in the next iteration.
newPtr := ctr
pKey := bytesutil.ToBytes48(newPtr.Deposit.Data.PublicKey)
c.depositsByKey[pKey] = append(c.depositsByKey[pKey], newPtr)
}
historicalDepositsCount.Add(float64(len(ctrs)))
}
// InsertFinalizedDeposits inserts deposits up to eth1DepositIndex (inclusive) into the finalized deposits cache.
func (c *Cache) InsertFinalizedDeposits(ctx context.Context, eth1DepositIndex int64,
executionHash common.Hash, executionNumber uint64) error {
ctx, span := trace.StartSpan(ctx, "Cache.InsertFinalizedDeposits")
defer span.End()
c.depositsLock.Lock()
defer c.depositsLock.Unlock()
depositTrie := c.finalizedDeposits.depositTree
insertIndex := int(c.finalizedDeposits.MerkleTrieIndex() + 1)
// Don't insert into finalized trie if there is no deposit to
// insert.
if len(c.deposits) == 0 {
return nil
}
// In the event we have less deposits than we need to
// finalize we finalize till the index on which we do have it.
if len(c.deposits) <= int(eth1DepositIndex) {
eth1DepositIndex = int64(len(c.deposits)) - 1
}
// If we finalize to some lower deposit index, we
// ignore it.
if int(eth1DepositIndex) < insertIndex {
return nil
}
currIdx := int64(depositTrie.depositCount) - 1
// Insert deposits into deposit trie.
for _, ctr := range c.deposits {
if ctr.Index > currIdx && ctr.Index <= eth1DepositIndex {
rt, err := ctr.Deposit.Data.HashTreeRoot()
if err != nil {
return err
}
if err := depositTrie.Insert(rt[:], int(ctr.Index)); err != nil {
return err
}
}
}
if err := depositTrie.Finalize(eth1DepositIndex, executionHash, executionNumber); err != nil {
return err
}
c.finalizedDeposits = finalizedDepositsContainer{
depositTree: depositTrie,
merkleTrieIndex: eth1DepositIndex,
}
return nil
}

View File

@@ -4,12 +4,14 @@
package depositsnapshot
import (
"crypto/sha256"
"encoding/binary"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/math"
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
protodb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
var (
@@ -17,12 +19,10 @@ var (
ErrEmptyExecutionBlock = errors.New("empty execution block")
// ErrInvalidSnapshotRoot occurs when the snapshot root does not match the calculated root.
ErrInvalidSnapshotRoot = errors.New("snapshot root is invalid")
// ErrInvalidMixInLength occurs when the value for mix in length is 0.
ErrInvalidMixInLength = errors.New("mixInLength should be greater than 0")
// ErrInvalidDepositCount occurs when the value for mix in length is 0.
ErrInvalidDepositCount = errors.New("deposit count should be greater than 0")
// ErrInvalidIndex occurs when the index is less than the number of finalized deposits.
ErrInvalidIndex = errors.New("index should be greater than finalizedDeposits - 1")
// ErrNoDeposits occurs when the number of deposits is 0.
ErrNoDeposits = errors.New("number of deposits should be greater than 0")
// ErrTooManyDeposits occurs when the number of deposits exceeds the capacity of the tree.
ErrTooManyDeposits = errors.New("number of deposits should not be greater than the capacity of the tree")
)
@@ -30,7 +30,7 @@ var (
// DepositTree is the Merkle tree representation of deposits.
type DepositTree struct {
tree MerkleTreeNode
mixInLength uint64 // number of deposits in the tree, reference implementation calls this mix_in_length.
depositCount uint64 // number of deposits in the tree, reference implementation calls this mix_in_length.
finalizedExecutionBlock executionBlock
}
@@ -39,82 +39,67 @@ type executionBlock struct {
Depth uint64
}
// New creates an empty deposit tree.
//
//nolint:unused
func New() *DepositTree {
// NewDepositTree creates an empty deposit tree.
func NewDepositTree() *DepositTree {
var leaves [][32]byte
merkle := create(leaves, DepositContractDepth)
return &DepositTree{
tree: merkle,
mixInLength: 0,
depositCount: 0,
finalizedExecutionBlock: executionBlock{},
}
}
// getSnapshot returns a deposit tree snapshot.
//
//nolint:unused
func (d *DepositTree) getSnapshot() (DepositTreeSnapshot, error) {
if d.finalizedExecutionBlock == (executionBlock{}) {
return DepositTreeSnapshot{}, ErrEmptyExecutionBlock
}
// GetSnapshot returns a deposit tree snapshot.
func (d *DepositTree) GetSnapshot() (DepositTreeSnapshot, error) {
var finalized [][32]byte
depositCount, finalized := d.tree.GetFinalized(finalized)
return fromTreeParts(finalized, depositCount, d.finalizedExecutionBlock)
}
// fromSnapshot returns a deposit tree from a deposit tree snapshot.
//
//nolint:unused
func fromSnapshot(snapshot DepositTreeSnapshot) (DepositTree, error) {
func fromSnapshot(snapshot DepositTreeSnapshot) (*DepositTree, error) {
root, err := snapshot.CalculateRoot()
if err != nil {
return DepositTree{}, err
return nil, err
}
if snapshot.depositRoot != root {
return DepositTree{}, ErrInvalidSnapshotRoot
return nil, ErrInvalidSnapshotRoot
}
if snapshot.depositCount >= math.PowerOf2(uint64(DepositContractDepth)) {
return DepositTree{}, ErrTooManyDeposits
return nil, ErrTooManyDeposits
}
tree, err := fromSnapshotParts(snapshot.finalized, snapshot.depositCount, DepositContractDepth)
if err != nil {
return DepositTree{}, err
return nil, err
}
if snapshot.depositCount == 0 {
return DepositTree{}, ErrNoDeposits
}
return DepositTree{
return &DepositTree{
tree: tree,
mixInLength: snapshot.depositCount,
depositCount: snapshot.depositCount,
finalizedExecutionBlock: snapshot.executionBlock,
}, nil
}
// finalize marks a deposit as finalized.
//
//nolint:unused
func (d *DepositTree) finalize(eth1data *eth.Eth1Data, executionBlockHeight uint64) error {
// Finalize marks a deposit as finalized.
func (d *DepositTree) Finalize(eth1DepositIndex int64, executionHash common.Hash, executionNumber uint64) error {
var blockHash [32]byte
copy(blockHash[:], eth1data.BlockHash)
copy(blockHash[:], executionHash[:])
d.finalizedExecutionBlock = executionBlock{
Hash: blockHash,
Depth: executionBlockHeight,
Depth: executionNumber,
}
_, err := d.tree.Finalize(eth1data.DepositCount, DepositContractDepth)
depositCount := uint64(eth1DepositIndex + 1)
_, err := d.tree.Finalize(depositCount, DepositContractDepth)
if err != nil {
return err
}
return nil
}
// getProof returns the Deposit tree proof.
//
//nolint:unused
// getProof returns the deposit tree proof.
func (d *DepositTree) getProof(index uint64) ([32]byte, [][32]byte, error) {
if d.mixInLength <= 0 {
return [32]byte{}, nil, ErrInvalidMixInLength
if d.depositCount <= 0 {
return [32]byte{}, nil, ErrInvalidDepositCount
}
finalizedDeposits, _ := d.tree.GetFinalized([][32]byte{})
if finalizedDeposits != 0 {
@@ -125,28 +110,81 @@ func (d *DepositTree) getProof(index uint64) ([32]byte, [][32]byte, error) {
}
leaf, proof := generateProof(d.tree, index, DepositContractDepth)
var mixInLength [32]byte
copy(mixInLength[:], bytesutil.Uint64ToBytesLittleEndian32(d.mixInLength))
copy(mixInLength[:], bytesutil.Uint64ToBytesLittleEndian32(d.depositCount))
proof = append(proof, mixInLength)
return leaf, proof, nil
}
// getRoot returns the root of the deposit tree.
//
//nolint:unused
func (d *DepositTree) getRoot() [32]byte {
var enc [32]byte
binary.LittleEndian.PutUint64(enc[:], d.depositCount)
root := d.tree.GetRoot()
return sha256.Sum256(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(d.mixInLength)...))
return hash.Hash(append(root[:], enc[:]...))
}
// pushLeaf adds a new leaf to the tree.
//
//nolint:unused
func (d *DepositTree) pushLeaf(leaf [32]byte) error {
var err error
d.tree, err = d.tree.PushLeaf(leaf, DepositContractDepth)
if err != nil {
return err
}
d.mixInLength++
d.depositCount++
return nil
}
// Insert is defined as part of MerkleTree interface and adds a new leaf to the tree.
func (d *DepositTree) Insert(item []byte, _ int) error {
var leaf [32]byte
copy(leaf[:], item[:32])
return d.pushLeaf(leaf)
}
// HashTreeRoot is defined as part of MerkleTree interface and calculates the hash tree root.
func (d *DepositTree) HashTreeRoot() ([32]byte, error) {
root := d.getRoot()
if root == [32]byte{} {
return [32]byte{}, errors.New("could not retrieve hash tree root")
}
return root, nil
}
// NumOfItems is defined as part of MerkleTree interface and returns the number of deposits in the tree.
func (d *DepositTree) NumOfItems() int {
return int(d.depositCount)
}
// MerkleProof is defined as part of MerkleTree interface and generates a merkle proof.
func (d *DepositTree) MerkleProof(index int) ([][]byte, error) {
_, proof, err := d.getProof(uint64(index))
if err != nil {
return nil, err
}
byteSlices := make([][]byte, len(proof))
for i, p := range proof {
copied := p
byteSlices[i] = copied[:]
}
return byteSlices, nil
}
// Copy performs a deep copy of the tree.
func (d *DepositTree) Copy() (*DepositTree, error) {
snapshot, err := d.GetSnapshot()
if err != nil {
return nil, err
}
return fromSnapshot(snapshot)
}
// ToProto returns a proto object of the deposit snapshot of
// the tree.
func (d *DepositTree) ToProto() (*protodb.DepositSnapshot, error) {
snapshot, err := d.GetSnapshot()
if err != nil {
return nil, err
}
return snapshot.ToProto(), nil
}

View File

@@ -1,21 +1,13 @@
package depositsnapshot
import (
"crypto/sha256"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
protodb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
var (
// ErrZeroIndex occurs when the value of index is 0.
ErrZeroIndex = errors.New("index should be greater than 0")
)
// DepositTreeSnapshot represents the data used to create a
// deposit tree given a snapshot.
//
//nolint:unused
// DepositTreeSnapshot represents the data used to create a deposit tree given a snapshot.
type DepositTreeSnapshot struct {
finalized [][32]byte
depositRoot [32]byte
@@ -27,29 +19,27 @@ type DepositTreeSnapshot struct {
func (ds *DepositTreeSnapshot) CalculateRoot() ([32]byte, error) {
size := ds.depositCount
index := len(ds.finalized)
root := Zerohashes[0]
root := trie.ZeroHashes[0]
for i := 0; i < DepositContractDepth; i++ {
if (size & 1) == 1 {
if index == 0 {
return [32]byte{}, ErrZeroIndex
break
}
index--
root = sha256.Sum256(append(ds.finalized[index][:], root[:]...))
root = hash.Hash(append(ds.finalized[index][:], root[:]...))
} else {
root = sha256.Sum256(append(root[:], Zerohashes[i][:]...))
root = hash.Hash(append(root[:], trie.ZeroHashes[i][:]...))
}
size >>= 1
}
return sha256.Sum256(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(ds.depositCount)...)), nil
return hash.Hash(append(root[:], bytesutil.Uint64ToBytesLittleEndian32(ds.depositCount)...)), nil
}
// fromTreeParts constructs the deposit tree from pre-existing data.
//
//nolint:unused
func fromTreeParts(finalised [][32]byte, depositCount uint64, executionBlock executionBlock) (DepositTreeSnapshot, error) {
snapshot := DepositTreeSnapshot{
finalized: finalised,
depositRoot: Zerohashes[0],
depositRoot: trie.ZeroHashes[0],
depositCount: depositCount,
executionBlock: executionBlock,
}
@@ -60,3 +50,36 @@ func fromTreeParts(finalised [][32]byte, depositCount uint64, executionBlock exe
snapshot.depositRoot = root
return snapshot, nil
}
// ToProto converts the underlying trie into its corresponding proto object.
func (ds *DepositTreeSnapshot) ToProto() *protodb.DepositSnapshot {
tree := &protodb.DepositSnapshot{
Finalized: make([][]byte, len(ds.finalized)),
DepositRoot: bytesutil.SafeCopyBytes(ds.depositRoot[:]),
DepositCount: ds.depositCount,
ExecutionHash: bytesutil.SafeCopyBytes(ds.executionBlock.Hash[:]),
ExecutionDepth: ds.executionBlock.Depth,
}
for i := range ds.finalized {
tree.Finalized[i] = bytesutil.SafeCopyBytes(ds.finalized[i][:])
}
return tree
}
// DepositTreeFromSnapshotProto generates a deposit tree object from a provided snapshot.
func DepositTreeFromSnapshotProto(snapshotProto *protodb.DepositSnapshot) (*DepositTree, error) {
finalized := make([][32]byte, len(snapshotProto.Finalized))
for i := range snapshotProto.Finalized {
finalized[i] = bytesutil.ToBytes32(snapshotProto.Finalized[i])
}
snapshot := DepositTreeSnapshot{
finalized: finalized,
depositRoot: bytesutil.ToBytes32(snapshotProto.DepositRoot),
depositCount: snapshotProto.DepositCount,
executionBlock: executionBlock{
Hash: bytesutil.ToBytes32(snapshotProto.ExecutionHash),
Depth: snapshotProto.ExecutionDepth,
},
}
return fromSnapshot(snapshot)
}

View File

@@ -3,6 +3,7 @@ package depositsnapshot
import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/container/slice"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
"github.com/prysmaticlabs/prysm/v4/math"
)
@@ -58,9 +59,8 @@ func create(leaves [][32]byte, depth uint64) MerkleTreeNode {
}
// fromSnapshotParts creates a new Merkle tree from a list of finalized leaves, number of deposits and specified depth.
//
//nolint:unused
func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (_ MerkleTreeNode, err error) {
func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (MerkleTreeNode, error) {
var err error
if len(finalized) < 1 || deposits == 0 {
return &ZeroNode{
depth: level,
@@ -96,8 +96,6 @@ func fromSnapshotParts(finalized [][32]byte, deposits uint64, level uint64) (_ M
}
// generateProof returns a merkle proof and root
//
//nolint:unused
func generateProof(tree MerkleTreeNode, index uint64, depth uint64) ([32]byte, [][32]byte) {
var proof [][32]byte
node := tree
@@ -219,7 +217,8 @@ func (n *InnerNode) IsFull() bool {
}
// Finalize marks deposits of the Merkle tree as finalized.
func (n *InnerNode) Finalize(depositsToFinalize uint64, depth uint64) (_ MerkleTreeNode, err error) {
func (n *InnerNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
var err error
deposits := math.PowerOf2(depth)
if deposits <= depositsToFinalize {
return &FinalizedNode{deposits, n.GetRoot()}, nil
@@ -286,9 +285,9 @@ type ZeroNode struct {
// GetRoot returns the root of the Merkle tree.
func (z *ZeroNode) GetRoot() [32]byte {
if z.depth == DepositContractDepth {
return hash.Hash(append(Zerohashes[z.depth-1][:], Zerohashes[z.depth-1][:]...))
return hash.Hash(append(trie.ZeroHashes[z.depth-1][:], trie.ZeroHashes[z.depth-1][:]...))
}
return Zerohashes[z.depth]
return trie.ZeroHashes[z.depth]
}
// IsFull returns wh ether there is space left for deposits.
@@ -300,7 +299,7 @@ func (_ *ZeroNode) IsFull() bool {
// Finalize marks deposits of the Merkle tree as finalized.
func (_ *ZeroNode) Finalize(depositsToFinalize uint64, depth uint64) (MerkleTreeNode, error) {
return nil, nil
return &ZeroNode{}, nil
}
// GetFinalized returns a list of hashes of all the finalized nodes and the number of deposits.

View File

@@ -6,6 +6,8 @@ import (
"reflect"
"testing"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
)
@@ -60,45 +62,54 @@ func Test_fromSnapshotParts(t *testing.T) {
tests := []struct {
name string
finalized [][32]byte
deposits uint64
level uint64
want MerkleTreeNode
}{
{
name: "empty",
finalized: nil,
deposits: 0,
level: 0,
want: &ZeroNode{},
},
{
name: "single finalized node",
finalized: [][32]byte{hexString(t, fmt.Sprintf("%064d", 0))},
deposits: 1,
level: 0,
want: &FinalizedNode{
depositCount: 1,
hash: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
},
{
name: "multiple deposits and 1 Finalized",
finalized: [][32]byte{hexString(t, fmt.Sprintf("%064d", 0))},
deposits: 2,
level: 4,
want: &InnerNode{
left: &InnerNode{&InnerNode{&FinalizedNode{depositCount: 2, hash: hexString(t, fmt.Sprintf("%064d", 0))}, &ZeroNode{1}}, &ZeroNode{2}},
right: &ZeroNode{3},
},
name: "multiple deposits and multiple Finalized",
finalized: [][32]byte{hexString(t, fmt.Sprintf("%064d", 1)), hexString(t, fmt.Sprintf("%064d", 2))},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tree, err := fromSnapshotParts(tt.finalized, tt.deposits, tt.level)
require.NoError(t, err)
if got := tree; !reflect.DeepEqual(got, tt.want) {
require.DeepEqual(t, tt.want, got)
test := NewDepositTree()
for _, leaf := range tt.finalized {
err := test.pushLeaf(leaf)
require.NoError(t, err)
}
got, err := test.HashTreeRoot()
require.NoError(t, err)
transformed := make([][]byte, len(tt.finalized))
for i := 0; i < len(tt.finalized); i++ {
transformed[i] = bytesutil.SafeCopyBytes(tt.finalized[i][:])
}
generatedTrie, err := trie.GenerateTrieFromItems(transformed, 32)
require.NoError(t, err)
want, err := generatedTrie.HashTreeRoot()
require.NoError(t, err)
require.Equal(t, want, got)
// Test finalization
for i := 0; i < len(tt.finalized); i++ {
err = test.Finalize(int64(i), tt.finalized[i], 0)
require.NoError(t, err)
}
sShot, err := test.GetSnapshot()
require.NoError(t, err)
got, err = sShot.CalculateRoot()
require.NoError(t, err)
require.Equal(t, 1, len(sShot.finalized))
require.Equal(t, want, got)
// Build from the snapshot once more
recovered, err := fromSnapshot(sShot)
require.NoError(t, err)
got, err = recovered.HashTreeRoot()
require.NoError(t, err)
require.Equal(t, want, got)
})
}
}
@@ -125,7 +136,7 @@ func Test_generateProof(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
testCases, err := readTestCases()
require.NoError(t, err)
tree := New()
tree := NewDepositTree()
for _, c := range testCases[:tt.leaves] {
err = tree.pushLeaf(c.DepositDataRoot)
require.NoError(t, err)

View File

@@ -1,16 +1,18 @@
package depositsnapshot
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"strings"
"testing"
"github.com/bazelbuild/rules_go/go/tools/bazel"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/io/file"
eth "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"gopkg.in/yaml.v3"
)
@@ -176,6 +178,9 @@ func readTestCases() ([]testCase, error) {
if err != nil {
return []testCase{}, err
}
if len(testCases) == 0 {
return nil, errors.New("no test cases found")
}
return testCases, nil
}
}
@@ -183,11 +188,8 @@ func readTestCases() ([]testCase, error) {
}
func TestRead(t *testing.T) {
tcs, err := readTestCases()
_, err := readTestCases()
require.NoError(t, err)
for _, tc := range tcs {
t.Log(tc)
}
}
func hexStringToByteArray(s string) (b [32]byte, err error) {
@@ -222,9 +224,9 @@ func merkleRootFromBranch(leaf [32]byte, branch [][32]byte, index uint64) [32]by
for i, l := range branch {
ithBit := (index >> i) & 0x1
if ithBit == 1 {
root = sha256.Sum256(append(l[:], root[:]...))
root = hash.Hash(append(l[:], root[:]...))
} else {
root = sha256.Sum256(append(root[:], l[:]...))
root = hash.Hash(append(root[:], l[:]...))
}
}
return root
@@ -250,11 +252,11 @@ func cloneFromSnapshot(t *testing.T, snapshot DepositTreeSnapshot, testCases []t
err = cp.pushLeaf(c.DepositDataRoot)
require.NoError(t, err)
}
return &cp
return cp
}
func TestDepositCases(t *testing.T) {
tree := New()
tree := NewDepositTree()
testCases, err := readTestCases()
require.NoError(t, err)
for _, c := range testCases {
@@ -263,8 +265,35 @@ func TestDepositCases(t *testing.T) {
}
}
type Test struct {
DepositDataRoot [32]byte
}
func TestRootEquivalence(t *testing.T) {
var err error
tree := NewDepositTree()
testCases, err := readTestCases()
require.NoError(t, err)
transformed := make([][]byte, len(testCases[:128]))
for i, c := range testCases[:128] {
err = tree.pushLeaf(c.DepositDataRoot)
require.NoError(t, err)
transformed[i] = bytesutil.SafeCopyBytes(c.DepositDataRoot[:])
}
originalRoot, err := tree.HashTreeRoot()
require.NoError(t, err)
generatedTrie, err := trie.GenerateTrieFromItems(transformed, 32)
require.NoError(t, err)
rootA, err := generatedTrie.HashTreeRoot()
require.NoError(t, err)
require.Equal(t, rootA, originalRoot)
}
func TestFinalization(t *testing.T) {
tree := New()
tree := NewDepositTree()
testCases, err := readTestCases()
require.NoError(t, err)
for _, c := range testCases[:128] {
@@ -273,15 +302,11 @@ func TestFinalization(t *testing.T) {
}
originalRoot := tree.getRoot()
require.DeepEqual(t, testCases[127].Eth1Data.DepositRoot, originalRoot)
err = tree.finalize(&eth.Eth1Data{
DepositRoot: testCases[100].Eth1Data.DepositRoot[:],
DepositCount: testCases[100].Eth1Data.DepositCount,
BlockHash: testCases[100].Eth1Data.BlockHash[:],
}, testCases[100].BlockHeight)
err = tree.Finalize(int64(testCases[100].Eth1Data.DepositCount-1), testCases[100].Eth1Data.BlockHash, testCases[100].BlockHeight)
require.NoError(t, err)
// ensure finalization doesn't change root
require.Equal(t, tree.getRoot(), originalRoot)
snapshotData, err := tree.getSnapshot()
snapshotData, err := tree.GetSnapshot()
require.NoError(t, err)
require.DeepEqual(t, testCases[100].Snapshot.DepositTreeSnapshot, snapshotData)
// create a copy of the tree from a snapshot by replaying
@@ -290,20 +315,16 @@ func TestFinalization(t *testing.T) {
// ensure original and copy have the same root
require.Equal(t, tree.getRoot(), cp.getRoot())
// finalize original again to check double finalization
err = tree.finalize(&eth.Eth1Data{
DepositRoot: testCases[105].Eth1Data.DepositRoot[:],
DepositCount: testCases[105].Eth1Data.DepositCount,
BlockHash: testCases[105].Eth1Data.BlockHash[:],
}, testCases[105].BlockHeight)
err = tree.Finalize(int64(testCases[105].Eth1Data.DepositCount-1), testCases[105].Eth1Data.BlockHash, testCases[105].BlockHeight)
require.NoError(t, err)
// root should still be the same
require.Equal(t, originalRoot, tree.getRoot())
// create a copy of the tree by taking a snapshot again
snapshotData, err = tree.getSnapshot()
snapshotData, err = tree.GetSnapshot()
require.NoError(t, err)
cp = cloneFromSnapshot(t, snapshotData, testCases[106:128])
// create a copy of the tree by replaying ALL deposits from nothing
fullTreeCopy := New()
fullTreeCopy := NewDepositTree()
for _, c := range testCases[:128] {
err = fullTreeCopy.pushLeaf(c.DepositDataRoot)
require.NoError(t, err)
@@ -315,7 +336,7 @@ func TestFinalization(t *testing.T) {
}
func TestSnapshotCases(t *testing.T) {
tree := New()
tree := NewDepositTree()
testCases, err := readTestCases()
require.NoError(t, err)
for _, c := range testCases {
@@ -323,33 +344,29 @@ func TestSnapshotCases(t *testing.T) {
require.NoError(t, err)
}
for _, c := range testCases {
err = tree.finalize(&eth.Eth1Data{
DepositRoot: c.Eth1Data.DepositRoot[:],
DepositCount: c.Eth1Data.DepositCount,
BlockHash: c.Eth1Data.BlockHash[:],
}, c.BlockHeight)
err = tree.Finalize(int64(c.Eth1Data.DepositCount-1), c.Eth1Data.BlockHash, c.BlockHeight)
require.NoError(t, err)
s, err := tree.getSnapshot()
s, err := tree.GetSnapshot()
require.NoError(t, err)
require.DeepEqual(t, c.Snapshot.DepositTreeSnapshot, s)
}
}
func TestEmptyTreeSnapshot(t *testing.T) {
_, err := New().getSnapshot()
require.ErrorContains(t, "empty execution block", err)
}
func TestInvalidSnapshot(t *testing.T) {
invalidSnapshot := DepositTreeSnapshot{
finalized: nil,
depositRoot: Zerohashes[0],
depositRoot: trie.ZeroHashes[0],
depositCount: 0,
executionBlock: executionBlock{
Hash: Zerohashes[0],
Hash: trie.ZeroHashes[0],
Depth: 0,
},
}
_, err := fromSnapshot(invalidSnapshot)
require.ErrorContains(t, "snapshot root is invalid", err)
}
func TestEmptyTree(t *testing.T) {
tree := NewDepositTree()
require.Equal(t, fmt.Sprintf("%x", tree.getRoot()), "d70a234731285c6804c2a4f56711ddb8c82c99740f207854891028af34e27e5e")
}

View File

@@ -1,69 +0,0 @@
// Code generated by gen_zerohashes. DO NOT EDIT.
package depositsnapshot
var Zerohashes = [][32]byte{
// 0000000000000000000000000000000000000000000000000000000000000000
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
// f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b
{245, 165, 253, 66, 209, 106, 32, 48, 39, 152, 239, 110, 211, 9, 151, 155, 67, 0, 61, 35, 32, 217, 240, 232, 234, 152, 49, 169, 39, 89, 251, 75},
// db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71
{219, 86, 17, 78, 0, 253, 212, 193, 248, 92, 137, 43, 243, 90, 201, 168, 146, 137, 170, 236, 177, 235, 208, 169, 108, 222, 96, 106, 116, 139, 93, 113},
// c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c
{199, 128, 9, 253, 240, 127, 197, 106, 17, 241, 34, 55, 6, 88, 163, 83, 170, 165, 66, 237, 99, 228, 76, 75, 193, 95, 244, 205, 16, 90, 179, 60},
// 536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c
{83, 109, 152, 131, 127, 45, 209, 101, 165, 93, 94, 234, 233, 20, 133, 149, 68, 114, 213, 111, 36, 109, 242, 86, 191, 60, 174, 25, 53, 42, 18, 60},
// 9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30
{158, 253, 224, 82, 170, 21, 66, 159, 174, 5, 186, 212, 208, 177, 215, 198, 77, 166, 77, 3, 215, 161, 133, 74, 88, 140, 44, 184, 67, 12, 13, 48},
// d88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1
{216, 141, 223, 238, 212, 0, 168, 117, 85, 150, 178, 25, 66, 193, 73, 126, 17, 76, 48, 46, 97, 24, 41, 15, 145, 230, 119, 41, 118, 4, 31, 161},
// 87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c
{135, 235, 13, 219, 165, 126, 53, 246, 210, 134, 103, 56, 2, 164, 175, 89, 117, 226, 37, 6, 199, 207, 76, 100, 187, 107, 229, 238, 17, 82, 127, 44},
// 26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193
{38, 132, 100, 118, 253, 95, 197, 74, 93, 67, 56, 81, 103, 201, 81, 68, 242, 100, 63, 83, 60, 200, 91, 185, 209, 107, 120, 47, 141, 125, 177, 147},
// 506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1
{80, 109, 134, 88, 45, 37, 36, 5, 184, 64, 1, 135, 146, 202, 210, 191, 18, 89, 241, 239, 90, 165, 248, 135, 225, 60, 178, 240, 9, 79, 81, 225},
// ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b
{255, 255, 10, 215, 230, 89, 119, 47, 149, 52, 193, 149, 200, 21, 239, 196, 1, 78, 241, 225, 218, 237, 68, 4, 192, 99, 133, 209, 17, 146, 233, 43},
// 6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220
{108, 240, 65, 39, 219, 5, 68, 28, 216, 51, 16, 122, 82, 190, 133, 40, 104, 137, 14, 67, 23, 230, 160, 42, 180, 118, 131, 170, 117, 150, 66, 32},
// b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f
{183, 208, 95, 135, 95, 20, 0, 39, 239, 81, 24, 162, 36, 123, 187, 132, 206, 143, 47, 15, 17, 35, 98, 48, 133, 218, 247, 150, 12, 50, 159, 95},
// df6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e
{223, 106, 245, 245, 187, 219, 107, 233, 239, 138, 166, 24, 228, 191, 128, 115, 150, 8, 103, 23, 30, 41, 103, 111, 139, 40, 77, 234, 106, 8, 168, 94},
// b58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784
{181, 141, 144, 15, 94, 24, 46, 60, 80, 239, 116, 150, 158, 161, 108, 119, 38, 197, 73, 117, 124, 194, 53, 35, 195, 105, 88, 125, 167, 41, 55, 132},
// d49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb
{212, 154, 117, 2, 255, 207, 176, 52, 11, 29, 120, 133, 104, 133, 0, 202, 48, 129, 97, 167, 249, 107, 98, 223, 157, 8, 59, 113, 252, 200, 242, 187},
// 8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb
{143, 230, 177, 104, 146, 86, 192, 211, 133, 244, 47, 91, 190, 32, 39, 162, 44, 25, 150, 225, 16, 186, 151, 193, 113, 211, 229, 148, 141, 233, 43, 235},
// 8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab
{141, 13, 99, 195, 158, 186, 222, 133, 9, 224, 174, 60, 156, 56, 118, 251, 95, 161, 18, 190, 24, 249, 5, 236, 172, 254, 203, 146, 5, 118, 3, 171},
// 95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4
{149, 238, 200, 178, 229, 65, 202, 212, 233, 29, 227, 131, 133, 242, 224, 70, 97, 159, 84, 73, 108, 35, 130, 203, 108, 172, 213, 185, 140, 38, 245, 164},
// f893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f
{248, 147, 233, 8, 145, 119, 117, 182, 43, 255, 35, 41, 77, 187, 227, 161, 205, 142, 108, 193, 195, 91, 72, 1, 136, 123, 100, 106, 111, 129, 241, 127},
// cddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa
{205, 219, 167, 181, 146, 227, 19, 51, 147, 193, 97, 148, 250, 199, 67, 26, 191, 47, 84, 133, 237, 113, 29, 178, 130, 24, 60, 129, 158, 8, 235, 170},
// 8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c
{138, 141, 127, 227, 175, 140, 170, 8, 90, 118, 57, 168, 50, 0, 20, 87, 223, 185, 18, 138, 128, 97, 20, 42, 208, 51, 86, 41, 255, 35, 255, 156},
// feb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167
{254, 179, 195, 55, 215, 165, 26, 111, 191, 0, 185, 227, 76, 82, 225, 201, 25, 92, 150, 155, 212, 231, 160, 191, 213, 29, 92, 91, 237, 156, 17, 103},
// e71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7
{231, 31, 10, 168, 60, 195, 46, 223, 190, 250, 159, 77, 62, 1, 116, 202, 133, 24, 46, 236, 159, 58, 9, 246, 166, 192, 223, 99, 119, 165, 16, 215},
// 31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0
{49, 32, 111, 168, 10, 80, 187, 106, 190, 41, 8, 80, 88, 241, 98, 18, 33, 42, 96, 238, 200, 240, 73, 254, 203, 146, 216, 200, 224, 168, 75, 192},
// 21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544
{33, 53, 43, 254, 203, 237, 221, 233, 147, 131, 159, 97, 76, 61, 172, 10, 62, 227, 117, 67, 249, 180, 18, 177, 97, 153, 220, 21, 142, 35, 181, 68},
// 619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765
{97, 158, 49, 39, 36, 187, 109, 124, 49, 83, 237, 157, 231, 145, 215, 100, 163, 102, 179, 137, 175, 19, 197, 139, 248, 168, 217, 4, 129, 164, 103, 101},
// 7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4
{124, 221, 41, 134, 38, 130, 80, 98, 141, 12, 16, 227, 133, 197, 140, 97, 145, 230, 251, 224, 81, 145, 188, 192, 79, 19, 63, 44, 234, 114, 193, 196},
// 848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1
{132, 137, 48, 189, 123, 168, 202, 197, 70, 97, 7, 33, 19, 251, 39, 136, 105, 224, 123, 184, 88, 127, 145, 57, 41, 51, 55, 77, 1, 123, 203, 225},
// 8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636
{136, 105, 255, 44, 34, 178, 140, 193, 5, 16, 217, 133, 50, 146, 128, 51, 40, 190, 79, 176, 232, 4, 149, 232, 187, 141, 39, 31, 91, 136, 150, 54},
// b5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c
{181, 254, 40, 231, 159, 27, 133, 15, 134, 88, 36, 108, 233, 182, 161, 231, 180, 159, 192, 109, 183, 20, 62, 143, 224, 180, 242, 176, 197, 82, 58, 92},
// 985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7
{152, 94, 146, 159, 112, 175, 40, 208, 189, 209, 169, 10, 128, 143, 151, 127, 89, 124, 124, 119, 140, 72, 158, 152, 211, 189, 137, 16, 211, 26, 192, 247},
}

57
beacon-chain/cache/interfaces.go vendored Normal file
View File

@@ -0,0 +1,57 @@
package cache
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/common"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
)
// DepositCache combines the interfaces for retrieving and inserting deposit information.
type DepositCache interface {
DepositFetcher
DepositInserter
}
// DepositFetcher defines a struct which can retrieve deposit information from a store.
type DepositFetcher interface {
AllDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit
AllDepositContainers(ctx context.Context) []*ethpb.DepositContainer
DepositByPubkey(ctx context.Context, pubKey []byte) (*ethpb.Deposit, *big.Int)
DepositsNumberAndRootAtHeight(ctx context.Context, blockHeight *big.Int) (uint64, [32]byte)
InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte)
PendingDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit
PendingContainers(ctx context.Context, untilBlk *big.Int) []*ethpb.DepositContainer
PrunePendingDeposits(ctx context.Context, merkleTreeIndex int64)
PruneProofs(ctx context.Context, untilDepositIndex int64) error
FinalizedFetcher
}
// DepositInserter defines a struct which can insert deposit information from a store.
type DepositInserter interface {
InsertDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) error
InsertDepositContainers(ctx context.Context, ctrs []*ethpb.DepositContainer)
InsertFinalizedDeposits(ctx context.Context, eth1DepositIndex int64, executionHash common.Hash, executionNumber uint64) error
}
// FinalizedFetcher is a smaller interface defined to be the bare minimum to satisfy “Service”.
// It extends the "DepositFetcher" interface with additional methods for fetching finalized deposits.
type FinalizedFetcher interface {
FinalizedDeposits(ctx context.Context) (FinalizedDeposits, error)
NonFinalizedDeposits(ctx context.Context, lastFinalizedIndex int64, untilBlk *big.Int) []*ethpb.Deposit
}
// FinalizedDeposits defines a method to access a merkle tree containing deposits and their indexes.
type FinalizedDeposits interface {
Deposits() MerkleTree
MerkleTrieIndex() int64
}
// MerkleTree defines methods for constructing and manipulating a merkle tree.
type MerkleTree interface {
HashTreeRoot() ([32]byte, error)
NumOfItems() int
Insert(item []byte, index int) error
MerkleProof(index int) ([][]byte, error)
}

View File

@@ -51,6 +51,10 @@ func ProcessVoluntaryExits(
beaconState state.BeaconState,
exits []*ethpb.SignedVoluntaryExit,
) (state.BeaconState, error) {
// Avoid calculating the epoch churn if no exits exist.
if len(exits) == 0 {
return beaconState, nil
}
maxExitEpoch, churn := v.ValidatorsMaxExitEpochAndChurn(beaconState)
var exitEpoch primitives.Epoch
for idx, exit := range exits {

View File

@@ -142,6 +142,13 @@ func ProcessRegistryUpdates(ctx context.Context, state state.BeaconState) (state
return nil, errors.Wrap(err, "could not get churn limit")
}
if state.Version() >= version.Deneb {
// Cap churn limit to max per epoch churn limit. New in EIP7514.
if churnLimit > params.BeaconConfig().MaxPerEpochActivationChurnLimit {
churnLimit = params.BeaconConfig().MaxPerEpochActivationChurnLimit
}
}
// Prevent churn limit cause index out of bound.
if churnLimit < limit {
limit = churnLimit

View File

@@ -338,6 +338,42 @@ func TestProcessRegistryUpdates_EligibleToActivate(t *testing.T) {
}
}
func TestProcessRegistryUpdates_EligibleToActivate_Cancun(t *testing.T) {
base := &ethpb.BeaconStateDeneb{
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
FinalizedCheckpoint: &ethpb.Checkpoint{Epoch: 6, Root: make([]byte, fieldparams.RootLength)},
}
cfg := params.BeaconConfig()
cfg.MinPerEpochChurnLimit = 10
cfg.ChurnLimitQuotient = 1
params.OverrideBeaconConfig(cfg)
for i := uint64(0); i < 10; i++ {
base.Validators = append(base.Validators, &ethpb.Validator{
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
})
}
beaconState, err := state_native.InitializeFromProtoDeneb(base)
require.NoError(t, err)
currentEpoch := time.CurrentEpoch(beaconState)
newState, err := epoch.ProcessRegistryUpdates(context.Background(), beaconState)
require.NoError(t, err)
for i, validator := range newState.Validators() {
assert.Equal(t, currentEpoch+1, validator.ActivationEligibilityEpoch, "Could not update registry %d, unexpected activation eligibility epoch", i)
// Note: In Deneb, only validators indices before `MaxPerEpochActivationChurnLimit` should be activated.
if uint64(i) < params.BeaconConfig().MaxPerEpochActivationChurnLimit && validator.ActivationEpoch != helpers.ActivationExitEpoch(currentEpoch) {
t.Errorf("Could not update registry %d, validators failed to activate: wanted activation epoch %d, got %d",
i, helpers.ActivationExitEpoch(currentEpoch), validator.ActivationEpoch)
}
if uint64(i) >= params.BeaconConfig().MaxPerEpochActivationChurnLimit && validator.ActivationEpoch != params.BeaconConfig().FarFutureEpoch {
t.Errorf("Could not update registry %d, validators should not have been activated, wanted activation epoch: %d, got %d",
i, params.BeaconConfig().FarFutureEpoch, validator.ActivationEpoch)
}
}
}
func TestProcessRegistryUpdates_ActivationCompletes(t *testing.T) {
base := &ethpb.BeaconState{
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,

View File

@@ -22,6 +22,9 @@ const (
// BLSToExecutionChangeReceived is sent after a BLS to execution change object has been received from gossip or rpc.
BLSToExecutionChangeReceived
// BlobSidecarReceived is sent after a blob sidecar is received from gossip or rpc.
BlobSidecarReceived = 6
)
// UnAggregatedAttReceivedData is the data sent with UnaggregatedAttReceived events.
@@ -52,3 +55,8 @@ type SyncCommitteeContributionReceivedData struct {
type BLSToExecutionChangeReceivedData struct {
Change *ethpb.SignedBLSToExecutionChange
}
// BlobSidecarReceivedData is the data sent with BlobSidecarReceived events.
type BlobSidecarReceivedData struct {
Blob *ethpb.SignedBlobSidecar
}

View File

@@ -13,6 +13,7 @@ go_library(
"error.go",
"execution_chain.go",
"finalized_block_roots.go",
"flags.go",
"genesis.go",
"key.go",
"kv.go",
@@ -38,6 +39,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/genesis:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//cmd/beacon-chain/flags:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
@@ -65,6 +67,7 @@ go_library(
"@com_github_prysmaticlabs_prombbolt//:go_default_library",
"@com_github_schollz_progressbar_v3//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
"@io_etcd_go_bbolt//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
@@ -83,6 +86,7 @@ go_test(
"encoding_test.go",
"execution_chain_test.go",
"finalized_block_roots_test.go",
"flags_test.go",
"genesis_test.go",
"init_test.go",
"kv_test.go",
@@ -103,6 +107,7 @@ go_test(
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/genesis:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//cmd/beacon-chain/flags:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
@@ -119,6 +124,7 @@ go_test(
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_golang_snappy//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_urfave_cli_v2//:go_default_library",
"@io_bazel_rules_go//go/tools/bazel:go_default_library",
"@io_etcd_go_bbolt//:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",

View File

@@ -12,11 +12,20 @@ import (
types "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/time/slots"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
var (
errBlobSlotMismatch = errors.New("sidecar slot mismatch")
errBlobParentMismatch = errors.New("sidecar parent root mismatch")
errBlobRootMismatch = errors.New("sidecar root mismatch")
errBlobProposerMismatch = errors.New("sidecar proposer index mismatch")
errBlobSidecarLimit = errors.New("sidecar exceeds maximum number of blobs")
errEmptySidecar = errors.New("nil or empty blob sidecars")
errNewerBlobExists = errors.New("Will not overwrite newer blobs in db")
)
// A blob rotating key is represented as bytes(slot_to_rotating_buffer(blob.slot)) ++ bytes(blob.slot) ++ blob.block_root
type blobRotatingKey []byte
@@ -45,94 +54,119 @@ func (rk blobRotatingKey) BlockRoot() []byte {
//
// 3. Begin the save algorithm: If the incoming blob has a slot bigger than the saved slot at the spot
// in the rotating keys buffer, we overwrite all elements for that slot. Otherwise, we merge the blob with an existing one.
// Trying to replace a newer blob with an older one is an error.
func (s *Store) SaveBlobSidecar(ctx context.Context, scs []*ethpb.BlobSidecar) error {
if len(scs) == 0 {
return errEmptySidecar
}
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveBlobSidecar")
defer span.End()
sortSideCars(scs)
if err := s.verifySideCars(scs); err != nil {
return err
}
first := scs[0]
newKey := blobSidecarKey(first)
prefix := newKey.BufferPrefix()
var prune []blobRotatingKey
return s.db.Update(func(tx *bolt.Tx) error {
encodedBlobSidecar, err := encode(ctx, &ethpb.BlobSidecars{Sidecars: scs})
var existing []byte
sc := &ethpb.BlobSidecars{}
bkt := tx.Bucket(blobsBucket)
c := bkt.Cursor()
for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() {
key := blobRotatingKey(k)
ks := key.Slot()
if ks < first.Slot {
// Mark older blobs at the same position of the ring buffer for deletion.
prune = append(prune, key)
continue
}
if ks > first.Slot {
// We shouldn't be overwriting newer blobs with older blobs. Something is wrong.
return errNewerBlobExists
}
// The slot isn't older or newer, so it must be equal.
// If the roots match, then we want to merge the new sidecars with the existing data.
if bytes.Equal(first.BlockRoot, key.BlockRoot()) {
existing = v
if err := decode(ctx, v, sc); err != nil {
return err
}
}
// If the slot is equal but the roots don't match, leave the existing key alone and allow the sidecar
// to be written to the new key with the same prefix. In this case sc will be empty, so it will just
// contain the incoming sidecars when we write it.
}
sc.Sidecars = append(sc.Sidecars, scs...)
sortSidecars(sc.Sidecars)
var err error
sc.Sidecars, err = validUniqueSidecars(sc.Sidecars)
if err != nil {
return err
}
bkt := tx.Bucket(blobsBucket)
c := bkt.Cursor()
newKey := blobSidecarKey(scs[0])
rotatingBufferPrefix := newKey.BufferPrefix()
var replacingKey blobRotatingKey
for k, _ := c.Seek(rotatingBufferPrefix); bytes.HasPrefix(k, rotatingBufferPrefix); k, _ = c.Next() {
if len(k) != 0 {
replacingKey = k
break
encoded, err := encode(ctx, sc)
if err != nil {
return err
}
// don't write if the merged result is the same as before
if len(existing) == len(encoded) && bytes.Equal(existing, encoded) {
return nil
}
// Only prune if we're actually going through with the update.
for _, k := range prune {
if err := bkt.Delete(k); err != nil {
// note: attempting to delete a key that does not exist should not return an error.
log.WithError(err).Warnf("Could not delete blob key %#x.", k)
}
}
// If there is no element stored at blob.slot % MAX_SLOTS_TO_PERSIST_BLOBS, then we simply
// store the blob by key and exit early.
if len(replacingKey) != 0 {
oldSlot := replacingKey.Slot()
oldEpoch := slots.ToEpoch(oldSlot)
// The blob we are replacing is too old, so we delete it.
if slots.ToEpoch(scs[0].Slot) >= oldEpoch.Add(uint64(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)) {
if err := bkt.Delete(replacingKey); err != nil {
log.WithError(err).Warnf("Could not delete blob with key %#x", replacingKey)
}
} else {
// Otherwise, we need to merge the new blob with the old blob.
enc := bkt.Get(replacingKey)
sc := &ethpb.BlobSidecars{}
if err := decode(ctx, enc, sc); err != nil {
return err
}
sc.Sidecars = append(sc.Sidecars, scs...)
sortSideCars(sc.Sidecars)
encodedBlobSidecar, err = encode(ctx, &ethpb.BlobSidecars{Sidecars: sc.Sidecars})
if err != nil {
return err
}
}
}
return bkt.Put(newKey, encodedBlobSidecar)
return bkt.Put(newKey, encoded)
})
}
// verifySideCars ensures that all sidecars have the same slot, parent root, block root, and proposer index, and no more than MAX_BLOB_EPOCHS.
func (s *Store) verifySideCars(scs []*ethpb.BlobSidecar) error {
// validUniqueSidecars ensures that all sidecars have the same slot, parent root, block root, and proposer index, and no more than MAX_BLOB_EPOCHS.
func validUniqueSidecars(scs []*ethpb.BlobSidecar) ([]*ethpb.BlobSidecar, error) {
if len(scs) == 0 {
return errors.New("nil or empty blob sidecars")
}
if uint64(len(scs)) > fieldparams.MaxBlobsPerBlock {
return fmt.Errorf("too many sidecars: %d > %d", len(scs), fieldparams.MaxBlobsPerBlock)
return nil, errEmptySidecar
}
sl := scs[0].Slot
pr := scs[0].BlockParentRoot
r := scs[0].BlockRoot
p := scs[0].ProposerIndex
for _, sc := range scs {
if sc.Slot != sl {
return fmt.Errorf("sidecar slot mismatch: %d != %d", sc.Slot, sl)
}
if !bytes.Equal(sc.BlockParentRoot, pr) {
return fmt.Errorf("sidecar parent root mismatch: %x != %x", sc.BlockParentRoot, pr)
}
if !bytes.Equal(sc.BlockRoot, r) {
return fmt.Errorf("sidecar root mismatch: %x != %x", sc.BlockRoot, r)
}
if sc.ProposerIndex != p {
return fmt.Errorf("sidecar proposer index mismatch: %d != %d", sc.ProposerIndex, p)
}
// If there's only 1 sidecar, we've got nothing to compare.
if len(scs) == 1 {
return scs, nil
}
return nil
prev := scs[0]
didx := 1
for i := 1; i < len(scs); i++ {
sc := scs[i]
if sc.Slot != prev.Slot {
return nil, errors.Wrapf(errBlobSlotMismatch, "%d != %d", sc.Slot, prev.Slot)
}
if !bytes.Equal(sc.BlockParentRoot, prev.BlockParentRoot) {
return nil, errors.Wrapf(errBlobParentMismatch, "%x != %x", sc.BlockParentRoot, prev.BlockParentRoot)
}
if !bytes.Equal(sc.BlockRoot, prev.BlockRoot) {
return nil, errors.Wrapf(errBlobRootMismatch, "%x != %x", sc.BlockRoot, prev.BlockRoot)
}
if sc.ProposerIndex != prev.ProposerIndex {
return nil, errors.Wrapf(errBlobProposerMismatch, "%d != %d", sc.ProposerIndex, prev.ProposerIndex)
}
// skip duplicate
if sc.Index == prev.Index {
continue
}
if didx != i {
scs[didx] = scs[i]
}
prev = scs[i]
didx += 1
}
if didx > fieldparams.MaxBlobsPerBlock {
return nil, errors.Wrapf(errBlobSidecarLimit, "%d > %d", didx, fieldparams.MaxBlobsPerBlock)
}
return scs[0:didx], nil
}
// sortSideCars sorts the sidecars by their index.
func sortSideCars(scs []*ethpb.BlobSidecar) {
// sortSidecars sorts the sidecars by their index.
func sortSidecars(scs []*ethpb.BlobSidecar) {
sort.Slice(scs, func(i, j int) bool {
return scs[i].Index < scs[j].Index
})
@@ -255,7 +289,6 @@ func blobSidecarKey(blob *ethpb.BlobSidecar) blobRotatingKey {
func slotKey(slot types.Slot) []byte {
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
maxEpochsToPersistBlobs := params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest
maxSlotsToPersistBlobs := types.Slot(maxEpochsToPersistBlobs.Mul(uint64(slotsPerEpoch)))
return bytesutil.SlotToBytesBigEndian(slot.ModSlot(maxSlotsToPersistBlobs))
}
@@ -265,14 +298,14 @@ func checkEpochsForBlobSidecarsRequestBucket(db *bolt.DB) error {
b := tx.Bucket(chainMetadataBucket)
v := b.Get(blobRetentionEpochsKey)
if v == nil {
if err := b.Put(blobRetentionEpochsKey, bytesutil.Uint64ToBytesBigEndian(uint64(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest))); err != nil {
if err := b.Put(blobRetentionEpochsKey, bytesutil.Uint64ToBytesBigEndian(uint64(maxEpochsToPersistBlobs))); err != nil {
return err
}
return nil
}
e := bytesutil.BytesToUint64BigEndian(v)
if e != uint64(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest) {
return fmt.Errorf("epochs for blobs request value in DB %d does not match config value %d", e, params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)
if e != uint64(maxEpochsToPersistBlobs) {
return fmt.Errorf("epochs for blobs request value in DB %d does not match config value %d", e, maxEpochsToPersistBlobs)
}
return nil
}); err != nil {

View File

@@ -3,10 +3,13 @@ package kv
import (
"context"
"crypto/rand"
"flag"
"fmt"
"strconv"
"testing"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
@@ -15,6 +18,7 @@ import (
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assertions"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/urfave/cli/v2"
bolt "go.etcd.io/bbolt"
)
@@ -294,6 +298,24 @@ func TestStore_BlobSidecars(t *testing.T) {
require.NoError(t, err)
require.NoError(t, equalBlobSlices(scs, got))
})
t.Run("save equivocating blobs", func(t *testing.T) {
db := setupDB(t)
scs := generateBlobSidecars(t, fieldparams.MaxBlobsPerBlock/2)
eScs := generateEquivocatingBlobSidecars(t, fieldparams.MaxBlobsPerBlock/2)
for i, sc := range scs {
require.NoError(t, db.SaveBlobSidecar(ctx, []*ethpb.BlobSidecar{sc}))
require.NoError(t, db.SaveBlobSidecar(ctx, []*ethpb.BlobSidecar{eScs[i]}))
}
got, err := db.BlobSidecarsByRoot(ctx, bytesutil.ToBytes32(scs[0].BlockRoot))
require.NoError(t, err)
require.NoError(t, equalBlobSlices(scs, got))
got, err = db.BlobSidecarsByRoot(ctx, bytesutil.ToBytes32(eScs[0].BlockRoot))
require.NoError(t, err)
require.NoError(t, equalBlobSlices(eScs, got))
})
}
func generateBlobSidecars(t *testing.T, n uint64) []*ethpb.BlobSidecar {
@@ -314,7 +336,6 @@ func generateBlobSidecar(t *testing.T, index uint64) *ethpb.BlobSidecar {
kzgProof := make([]byte, 48)
_, err = rand.Read(kzgProof)
require.NoError(t, err)
return &ethpb.BlobSidecar{
BlockRoot: bytesutil.PadTo([]byte{'a'}, 32),
Index: index,
@@ -327,26 +348,56 @@ func generateBlobSidecar(t *testing.T, index uint64) *ethpb.BlobSidecar {
}
}
func TestStore_verifySideCars(t *testing.T) {
s := setupDB(t)
func generateEquivocatingBlobSidecars(t *testing.T, n uint64) []*ethpb.BlobSidecar {
blobSidecars := make([]*ethpb.BlobSidecar, n)
for i := uint64(0); i < n; i++ {
blobSidecars[i] = generateEquivocatingBlobSidecar(t, i)
}
return blobSidecars
}
func generateEquivocatingBlobSidecar(t *testing.T, index uint64) *ethpb.BlobSidecar {
blob := make([]byte, 131072)
_, err := rand.Read(blob)
require.NoError(t, err)
kzgCommitment := make([]byte, 48)
_, err = rand.Read(kzgCommitment)
require.NoError(t, err)
kzgProof := make([]byte, 48)
_, err = rand.Read(kzgProof)
require.NoError(t, err)
return &ethpb.BlobSidecar{
BlockRoot: bytesutil.PadTo([]byte{'c'}, 32),
Index: index,
Slot: 100,
BlockParentRoot: bytesutil.PadTo([]byte{'b'}, 32),
ProposerIndex: 102,
Blob: blob,
KzgCommitment: kzgCommitment,
KzgProof: kzgProof,
}
}
func Test_validUniqueSidecars_validation(t *testing.T) {
tests := []struct {
name string
scs []*ethpb.BlobSidecar
error string
name string
scs []*ethpb.BlobSidecar
err error
}{
{name: "empty", scs: []*ethpb.BlobSidecar{}, error: "nil or empty blob sidecars"},
{name: "too many sidecars", scs: generateBlobSidecars(t, fieldparams.MaxBlobsPerBlock+1), error: "too many sidecars: 7 > 6"},
{name: "invalid slot", scs: []*ethpb.BlobSidecar{{Slot: 1}, {Slot: 2}}, error: "sidecar slot mismatch: 2 != 1"},
{name: "invalid proposer index", scs: []*ethpb.BlobSidecar{{ProposerIndex: 1}, {ProposerIndex: 2}}, error: "sidecar proposer index mismatch: 2 != 1"},
{name: "invalid root", scs: []*ethpb.BlobSidecar{{BlockRoot: []byte{1}}, {BlockRoot: []byte{2}}}, error: "sidecar root mismatch: 02 != 01"},
{name: "invalid parent root", scs: []*ethpb.BlobSidecar{{BlockParentRoot: []byte{1}}, {BlockParentRoot: []byte{2}}}, error: "sidecar parent root mismatch: 02 != 01"},
{name: "happy path", scs: []*ethpb.BlobSidecar{{Index: 0}, {Index: 1}}, error: ""},
{name: "empty", scs: []*ethpb.BlobSidecar{}, err: errEmptySidecar},
{name: "too many sidecars", scs: generateBlobSidecars(t, fieldparams.MaxBlobsPerBlock+1), err: errBlobSidecarLimit},
{name: "invalid slot", scs: []*ethpb.BlobSidecar{{Slot: 1}, {Slot: 2}}, err: errBlobSlotMismatch},
{name: "invalid proposer index", scs: []*ethpb.BlobSidecar{{ProposerIndex: 1}, {ProposerIndex: 2}}, err: errBlobProposerMismatch},
{name: "invalid root", scs: []*ethpb.BlobSidecar{{BlockRoot: []byte{1}}, {BlockRoot: []byte{2}}}, err: errBlobRootMismatch},
{name: "invalid parent root", scs: []*ethpb.BlobSidecar{{BlockParentRoot: []byte{1}}, {BlockParentRoot: []byte{2}}}, err: errBlobParentMismatch},
{name: "happy path", scs: []*ethpb.BlobSidecar{{Index: 0}, {Index: 1}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := s.verifySideCars(tt.scs)
if tt.error != "" {
require.Equal(t, tt.error, err.Error())
_, err := validUniqueSidecars(tt.scs)
if tt.err != nil {
require.ErrorIs(t, err, tt.err)
} else {
require.NoError(t, err)
}
@@ -354,6 +405,62 @@ func TestStore_verifySideCars(t *testing.T) {
}
}
func Test_validUniqueSidecars_dedup(t *testing.T) {
cases := []struct {
name string
scs []*ethpb.BlobSidecar
expected []*ethpb.BlobSidecar
err error
}{
{
name: "duplicate sidecar",
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 1}},
expected: []*ethpb.BlobSidecar{{Index: 1}},
},
{
name: "single sidecar",
scs: []*ethpb.BlobSidecar{{Index: 1}},
expected: []*ethpb.BlobSidecar{{Index: 1}},
},
{
name: "multiple duplicates",
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 2}, {Index: 3}, {Index: 3}},
expected: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}},
},
{
name: "ok number after de-dupe, > 6 before",
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 2}, {Index: 2}, {Index: 2}, {Index: 3}, {Index: 3}},
expected: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}},
},
{
name: "max unique, no dupes",
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}},
expected: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}},
},
{
name: "too many unique",
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}, {Index: 7}},
err: errBlobSidecarLimit,
},
{
name: "too many unique with dupes",
scs: []*ethpb.BlobSidecar{{Index: 1}, {Index: 1}, {Index: 1}, {Index: 2}, {Index: 3}, {Index: 4}, {Index: 5}, {Index: 6}, {Index: 7}},
err: errBlobSidecarLimit,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
u, err := validUniqueSidecars(c.scs)
if c.err != nil {
require.ErrorIs(t, err, c.err)
} else {
require.NoError(t, err)
}
require.Equal(t, len(c.expected), len(u))
})
}
}
func TestStore_sortSidecars(t *testing.T) {
scs := []*ethpb.BlobSidecar{
{Index: 6},
@@ -364,7 +471,7 @@ func TestStore_sortSidecars(t *testing.T) {
{Index: 5},
{},
}
sortSideCars(scs)
sortSidecars(scs)
for i := 0; i < len(scs)-1; i++ {
require.Equal(t, uint64(i), scs[i].Index)
}
@@ -412,9 +519,12 @@ func Test_checkEpochsForBlobSidecarsRequestBucket(t *testing.T) {
require.NoError(t, checkEpochsForBlobSidecarsRequestBucket(dbStore.db)) // First write
require.NoError(t, checkEpochsForBlobSidecarsRequestBucket(dbStore.db)) // First check
nConfig := params.BeaconNetworkConfig()
nConfig.MinEpochsForBlobsSidecarsRequest = 42069
params.OverrideBeaconNetworkConfig(nConfig)
params.SetupTestConfigCleanup(t)
set := flag.NewFlagSet("test", 0)
set.Uint64(flags.BlobRetentionEpoch.Name, 0, "")
require.NoError(t, set.Set(flags.BlobRetentionEpoch.Name, strconv.FormatUint(42069, 10)))
cliCtx := cli.NewContext(&cli.App{}, set, nil)
require.NoError(t, ConfigureBlobRetentionEpoch(cliCtx))
require.ErrorContains(t, "epochs for blobs request value in DB 4096 does not match config value 42069", checkEpochsForBlobSidecarsRequestBucket(dbStore.db))
}

View File

@@ -111,10 +111,10 @@ var blockTests = []struct {
name: "deneb blind",
newBlock: func(slot primitives.Slot, root []byte) (interfaces.ReadOnlySignedBeaconBlock, error) {
b := util.NewBlindedBeaconBlockDeneb()
b.Block.Slot = slot
b.Message.Slot = slot
if root != nil {
b.Block.ParentRoot = root
b.Block.Body.BlobKzgCommitments = [][]byte{
b.Message.ParentRoot = root
b.Message.Body.BlobKzgCommitments = [][]byte{
bytesutil.PadTo([]byte{0x05}, 48),
bytesutil.PadTo([]byte{0x06}, 48),
bytesutil.PadTo([]byte{0x07}, 48),

View File

@@ -0,0 +1,33 @@
package kv
import (
"fmt"
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/urfave/cli/v2"
)
var maxEpochsToPersistBlobs = params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest
// ConfigureBlobRetentionEpoch sets the for blob retention based on command-line context. It sets the local config `maxEpochsToPersistBlobs`.
// If the flag is not set, the spec default `MinEpochsForBlobsSidecarsRequest` is used.
// An error if the input epoch is smaller than the spec default value.
func ConfigureBlobRetentionEpoch(cliCtx *cli.Context) error {
// Check if the blob retention epoch flag is set.
if cliCtx.IsSet(flags.BlobRetentionEpoch.Name) {
// Retrieve and cast the epoch value.
epochValue := cliCtx.Uint64(flags.BlobRetentionEpoch.Name)
e := primitives.Epoch(epochValue)
// Validate the epoch value against the spec default.
if e < params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest {
return fmt.Errorf("%s smaller than spec default, %d < %d", flags.BlobRetentionEpoch.Name, e, params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)
}
maxEpochsToPersistBlobs = e
}
return nil
}

View File

@@ -0,0 +1,39 @@
package kv
import (
"flag"
"strconv"
"testing"
"github.com/prysmaticlabs/prysm/v4/cmd/beacon-chain/flags"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/urfave/cli/v2"
)
func TestConfigureBlobRetentionEpoch(t *testing.T) {
maxEpochsToPersistBlobs = params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest
params.SetupTestConfigCleanup(t)
app := cli.App{}
set := flag.NewFlagSet("test", 0)
// Test case: Spec default.
require.NoError(t, ConfigureBlobRetentionEpoch(cli.NewContext(&app, set, nil)))
require.Equal(t, params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest, maxEpochsToPersistBlobs)
set.Uint64(flags.BlobRetentionEpoch.Name, 0, "")
minEpochsForSidecarRequest := uint64(params.BeaconNetworkConfig().MinEpochsForBlobsSidecarsRequest)
require.NoError(t, set.Set(flags.BlobRetentionEpoch.Name, strconv.FormatUint(2*minEpochsForSidecarRequest, 10)))
cliCtx := cli.NewContext(&app, set, nil)
// Test case: Input epoch is greater than or equal to spec value.
require.NoError(t, ConfigureBlobRetentionEpoch(cliCtx))
require.Equal(t, primitives.Epoch(2*minEpochsForSidecarRequest), maxEpochsToPersistBlobs)
// Test case: Input epoch is less than spec value.
require.NoError(t, set.Set(flags.BlobRetentionEpoch.Name, strconv.FormatUint(minEpochsForSidecarRequest-1, 10)))
cliCtx = cli.NewContext(&app, set, nil)
err := ConfigureBlobRetentionEpoch(cliCtx)
require.ErrorContains(t, "extend-blob-retention-epoch smaller than spec default", err)
}

View File

@@ -2,6 +2,7 @@ package kv
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks"
@@ -45,6 +46,10 @@ func (s *Store) SaveGenesisData(ctx context.Context, genesisState state.BeaconSt
// LoadGenesis loads a genesis state from a ssz-serialized byte slice, if no genesis exists already.
func (s *Store) LoadGenesis(ctx context.Context, sb []byte) error {
if len(sb) < (1 << 10) {
log.WithField("size", fmt.Sprintf("%d bytes", len(sb))).
Warn("Genesis state is smaller than one 1Kb. This could be an empty file, git lfs metadata file, or corrupt genesis state.")
}
vu, err := detect.FromState(sb)
if err != nil {
return err

View File

@@ -9,7 +9,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/deterministic-genesis",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/execution:go_default_library",
"//beacon-chain/state:go_default_library",

View File

@@ -9,7 +9,7 @@ import (
"os"
"time"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
@@ -22,7 +22,7 @@ import (
)
var _ runtime.Service = (*Service)(nil)
var _ depositcache.DepositFetcher = (*Service)(nil)
var _ cache.FinalizedFetcher = (*Service)(nil)
var _ execution.ChainStartFetcher = (*Service)(nil)
// Service spins up an client interoperability service that handles responsibilities such
@@ -34,12 +34,42 @@ type Service struct {
chainStartDeposits []*ethpb.Deposit
}
// All of these methods are stubs as they are not used by a node running with deterministic-genesis.
func (s *Service) AllDepositContainers(ctx context.Context) []*ethpb.DepositContainer {
log.Errorf("AllDepositContainers should not be called")
return nil
}
func (s *Service) InsertPendingDeposit(ctx context.Context, d *ethpb.Deposit, blockNum uint64, index int64, depositRoot [32]byte) {
log.Errorf("InsertPendingDeposit should not be called")
}
func (s *Service) PendingDeposits(ctx context.Context, untilBlk *big.Int) []*ethpb.Deposit {
log.Errorf("PendingDeposits should not be called")
return nil
}
func (s *Service) PendingContainers(ctx context.Context, untilBlk *big.Int) []*ethpb.DepositContainer {
log.Errorf("PendingContainers should not be called")
return nil
}
func (s *Service) PrunePendingDeposits(ctx context.Context, merkleTreeIndex int64) {
log.Errorf("PrunePendingDeposits should not be called")
}
func (s *Service) PruneProofs(ctx context.Context, untilDepositIndex int64) error {
log.Errorf("PruneProofs should not be called")
return nil
}
// Config options for the interop service.
type Config struct {
GenesisTime uint64
NumValidators uint64
BeaconDB db.HeadAccessDatabase
DepositCache *depositcache.DepositCache
DepositCache cache.DepositCache
GenesisPath string
}
@@ -148,8 +178,8 @@ func (_ *Service) DepositsNumberAndRootAtHeight(_ context.Context, _ *big.Int) (
}
// FinalizedDeposits mocks out the deposit cache functionality for interop.
func (_ *Service) FinalizedDeposits(_ context.Context) *depositcache.FinalizedDeposits {
return nil
func (_ *Service) FinalizedDeposits(ctx context.Context) (cache.FinalizedDeposits, error) {
return nil, nil
}
// NonFinalizedDeposits mocks out the deposit cache functionality for interop.

View File

@@ -25,7 +25,8 @@ go_library(
"//testing/spectest:__subpackages__",
],
deps = [
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/cache/depositsnapshot:go_default_library",
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/core/feed:go_default_library",
"//beacon-chain/core/feed/state:go_default_library",

View File

@@ -13,13 +13,16 @@ import (
"github.com/ethereum/go-ethereum/common"
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
coreState "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/execution/types"
statenative "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v4/config/features"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/container/trie"
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
"github.com/prysmaticlabs/prysm/v4/crypto/hash"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
@@ -141,7 +144,6 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog *gethtypes.L
if err != nil {
return errors.Wrap(err, "unable to determine hashed value of deposit")
}
// Defensive check to validate incoming index.
if s.depositTrie.NumOfItems() != int(index) {
return errors.Errorf("invalid deposit index received: wanted %d but got %d", s.depositTrie.NumOfItems(), index)
@@ -149,7 +151,6 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog *gethtypes.L
if err = s.depositTrie.Insert(depositHash[:], int(index)); err != nil {
return err
}
deposit := &ethpb.Deposit{
Data: depositData,
}
@@ -221,6 +222,18 @@ func (s *Service) ProcessDepositLog(ctx context.Context, depositLog *gethtypes.L
"merkleTreeIndex": index,
}).Info("Invalid deposit registered in deposit contract")
}
if features.Get().EnableEIP4881 {
// We finalize the trie here so that old deposits are not kept around, as they make
// deposit tree htr computation expensive.
dTrie, ok := s.depositTrie.(*depositsnapshot.DepositTree)
if !ok {
return errors.Errorf("wrong trie type initialized: %T", dTrie)
}
if err := dTrie.Finalize(index, depositLog.BlockHash, depositLog.BlockNumber); err != nil {
log.WithError(err).Error("Could not finalize trie")
}
}
return nil
}
@@ -337,7 +350,7 @@ func (s *Service) processPastLogs(ctx context.Context) error {
}
}
if fState != nil && !fState.IsNil() && fState.Eth1DepositIndex() > 0 {
s.cfg.depositCache.PrunePendingDeposits(ctx, int64(fState.Eth1DepositIndex())) // lint:ignore uintcast -- Deposit index should not exceed int64 in your lifetime.
s.cfg.depositCache.PrunePendingDeposits(ctx, int64(fState.Eth1DepositIndex())) // lint:ignore uintcast -- deposit index should not exceed int64 in your lifetime.
}
return nil
}
@@ -559,8 +572,27 @@ func (s *Service) savePowchainData(ctx context.Context) error {
CurrentEth1Data: s.latestEth1Data,
ChainstartData: s.chainStartData,
BeaconState: pbState, // I promise not to mutate it!
Trie: s.depositTrie.ToProto(),
DepositContainers: s.cfg.depositCache.AllDepositContainers(ctx),
}
if features.Get().EnableEIP4881 {
fd, err := s.cfg.depositCache.FinalizedDeposits(ctx)
if err != nil {
return errors.Errorf("could not get finalized deposit tree: %v", err)
}
tree, ok := fd.Deposits().(*depositsnapshot.DepositTree)
if !ok {
return errors.New("deposit tree was not EIP4881 DepositTree")
}
eth1Data.DepositSnapshot, err = tree.ToProto()
if err != nil {
return err
}
} else {
tree, ok := s.depositTrie.(*trie.SparseMerkleTrie)
if !ok {
return errors.New("deposit tree was not SparseMerkleTrie")
}
eth1Data.Trie = tree.ToProto()
}
return s.cfg.beaconDB.SaveExecutionChainData(ctx, eth1Data)
}

View File

@@ -2,7 +2,7 @@ package execution
import (
"github.com/ethereum/go-ethereum/common"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
"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/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
@@ -62,7 +62,7 @@ func WithDatabase(database db.HeadAccessDatabase) Option {
}
// WithDepositCache for caching deposits.
func WithDepositCache(cache *depositcache.DepositCache) Option {
func WithDepositCache(cache cache.DepositCache) Option {
return func(s *Service) error {
s.cfg.depositCache = cache
return nil

View File

@@ -3,6 +3,7 @@ package execution
import (
"context"
"fmt"
"net/http"
"strings"
"time"
@@ -106,28 +107,26 @@ func (s *Service) retryExecutionClientConnection(ctx context.Context, err error)
// Initializes an RPC connection with authentication headers.
func (s *Service) newRPCClientWithAuth(ctx context.Context, endpoint network.Endpoint) (*gethRPC.Client, error) {
client, err := network.NewExecutionRPCClient(ctx, endpoint)
if err != nil {
return nil, err
}
headers := http.Header{}
if endpoint.Auth.Method != authorization.None {
header, err := endpoint.Auth.ToHeaderValue()
if err != nil {
return nil, err
}
client.SetHeader("Authorization", header)
headers.Set("Authorization", header)
}
for _, h := range s.cfg.headers {
if h != "" {
keyValue := strings.Split(h, "=")
if len(keyValue) < 2 {
log.Warnf("Incorrect HTTP header flag format. Skipping %v", keyValue[0])
continue
}
client.SetHeader(keyValue[0], strings.Join(keyValue[1:], "="))
if h == "" {
continue
}
keyValue := strings.Split(h, "=")
if len(keyValue) < 2 {
log.Warnf("Incorrect HTTP header flag format. Skipping %v", keyValue[0])
continue
}
headers.Set(keyValue[0], strings.Join(keyValue[1:], "="))
}
return client, nil
return network.NewExecutionRPCClient(ctx, endpoint, headers)
}
// Checks the chain ID of the execution client to ensure

View File

@@ -20,7 +20,8 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositcache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/cache/depositsnapshot"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed"
statefeed "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
@@ -29,6 +30,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
native "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v4/config/features"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/container/trie"
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
@@ -119,7 +121,7 @@ func (RPCClientEmpty) CallContext(context.Context, interface{}, string, ...inter
type config struct {
depositContractAddr common.Address
beaconDB db.HeadAccessDatabase
depositCache *depositcache.DepositCache
depositCache cache.DepositCache
stateNotifier statefeed.Notifier
stateGen *stategen.State
eth1HeaderReqLimit uint64
@@ -149,7 +151,7 @@ type Service struct {
headerCache *headerCache // cache to store block hash/block height.
latestEth1Data *ethpb.LatestETH1Data
depositContractCaller *contracts.DepositContractCaller
depositTrie *trie.SparseMerkleTrie
depositTrie cache.MerkleTree
chainStartData *ethpb.ChainStartData
lastReceivedMerkleIndex int64 // Keeps track of the last received index to prevent log spam.
runError error
@@ -160,10 +162,15 @@ type Service struct {
func NewService(ctx context.Context, opts ...Option) (*Service, error) {
ctx, cancel := context.WithCancel(ctx)
_ = cancel // govet fix for lost cancel. Cancel is handled in service.Stop()
depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
if err != nil {
cancel()
return nil, errors.Wrap(err, "could not set up deposit trie")
var depositTrie cache.MerkleTree
var err error
if features.Get().EnableEIP4881 {
depositTrie = depositsnapshot.NewDepositTree()
} else {
depositTrie, err = trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
if err != nil {
return nil, errors.Wrap(err, "could not set up deposit trie")
}
}
genState, err := transition.EmptyGenesisState()
if err != nil {
@@ -209,7 +216,6 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
if err != nil {
return nil, errors.Wrap(err, "unable to retrieve eth1 data")
}
if err := s.initializeEth1Data(ctx, eth1Data); err != nil {
return nil, err
}
@@ -370,7 +376,8 @@ func (s *Service) initDepositCaches(ctx context.Context, ctrs []*ethpb.DepositCo
// to be included (rather than the last one to be processed). This was most likely
// done as the state cannot represent signed integers.
actualIndex := int64(currIndex) - 1 // lint:ignore uintcast -- deposit index will not exceed int64 in your lifetime.
if err = s.cfg.depositCache.InsertFinalizedDeposits(ctx, actualIndex); err != nil {
if err = s.cfg.depositCache.InsertFinalizedDeposits(ctx, actualIndex, common.Hash(fState.Eth1Data().BlockHash),
0 /* Setting a zero value as we have no access to block height */); err != nil {
return err
}
@@ -742,7 +749,17 @@ func (s *Service) initializeEth1Data(ctx context.Context, eth1DataInDB *ethpb.ET
return nil
}
var err error
s.depositTrie, err = trie.CreateTrieFromProto(eth1DataInDB.Trie)
if features.Get().EnableEIP4881 {
if eth1DataInDB.DepositSnapshot != nil {
s.depositTrie, err = depositsnapshot.DepositTreeFromSnapshotProto(eth1DataInDB.DepositSnapshot)
} else {
if err := s.migrateOldDepositTree(eth1DataInDB); err != nil {
return err
}
}
} else {
s.depositTrie, err = trie.CreateTrieFromProto(eth1DataInDB.Trie)
}
if err != nil {
return err
}
@@ -754,6 +771,24 @@ func (s *Service) initializeEth1Data(ctx context.Context, eth1DataInDB *ethpb.ET
}
}
s.latestEth1Data = eth1DataInDB.CurrentEth1Data
if features.Get().EnableEIP4881 {
ctrs := eth1DataInDB.DepositContainers
// Look at previously finalized index, as we are building off a finalized
// snapshot rather than the full trie.
lastFinalizedIndex := int64(s.depositTrie.NumOfItems() - 1)
// Correctly initialize missing deposits into active trie.
for _, c := range ctrs {
if c.Index > lastFinalizedIndex {
depRoot, err := c.Deposit.Data.HashTreeRoot()
if err != nil {
return err
}
if err := s.depositTrie.Insert(depRoot[:], int(c.Index)); err != nil {
return err
}
}
}
}
numOfItems := s.depositTrie.NumOfItems()
s.lastReceivedMerkleIndex = int64(numOfItems - 1)
if err := s.initDepositCaches(ctx, eth1DataInDB.DepositContainers); err != nil {
@@ -816,9 +851,24 @@ func (s *Service) ensureValidPowchainData(ctx context.Context) error {
CurrentEth1Data: s.latestEth1Data,
ChainstartData: s.chainStartData,
BeaconState: pbState,
Trie: s.depositTrie.ToProto(),
DepositContainers: s.cfg.depositCache.AllDepositContainers(ctx),
}
if features.Get().EnableEIP4881 {
trie, ok := s.depositTrie.(*depositsnapshot.DepositTree)
if !ok {
return errors.New("deposit trie was not EIP4881 DepositTree")
}
eth1Data.DepositSnapshot, err = trie.ToProto()
if err != nil {
return err
}
} else {
trie, ok := s.depositTrie.(*trie.SparseMerkleTrie)
if !ok {
return errors.New("deposit trie was not SparseMerkleTrie")
}
eth1Data.Trie = trie.ToProto()
}
return s.cfg.beaconDB.SaveExecutionChainData(ctx, eth1Data)
}
return nil
@@ -836,3 +886,29 @@ func dedupEndpoints(endpoints []string) []string {
}
return newEndpoints
}
func (s *Service) migrateOldDepositTree(eth1DataInDB *ethpb.ETH1ChainData) error {
oldDepositTrie, err := trie.CreateTrieFromProto(eth1DataInDB.Trie)
if err != nil {
return err
}
newDepositTrie := depositsnapshot.NewDepositTree()
for i, item := range oldDepositTrie.Items() {
if err = newDepositTrie.Insert(item, i); err != nil {
return errors.Wrapf(err, "could not insert item at index %d into deposit snapshot tree", i)
}
}
newDepositRoot, err := newDepositTrie.HashTreeRoot()
if err != nil {
return err
}
depositRoot, err := oldDepositTrie.HashTreeRoot()
if err != nil {
return err
}
if newDepositRoot != depositRoot {
return errors.Wrapf(err, "mismatched deposit roots, old %#x != new %#x", depositRoot, newDepositRoot)
}
s.depositTrie = newDepositTrie
return nil
}

View File

@@ -22,6 +22,7 @@ import (
doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/container/trie"
contracts "github.com/prysmaticlabs/prysm/v4/contracts/deposit"
"github.com/prysmaticlabs/prysm/v4/contracts/deposit/mock"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
@@ -433,8 +434,9 @@ func TestInitDepositCacheWithFinalization_OK(t *testing.T) {
s.chainStartData.Chainstarted = true
require.NoError(t, s.initDepositCaches(context.Background(), ctrs))
fDeposits := s.cfg.depositCache.FinalizedDeposits(ctx)
deps := s.cfg.depositCache.NonFinalizedDeposits(context.Background(), fDeposits.MerkleTrieIndex, nil)
fDeposits, err := s.cfg.depositCache.FinalizedDeposits(ctx)
require.NoError(t, err)
deps := s.cfg.depositCache.NonFinalizedDeposits(context.Background(), fDeposits.MerkleTrieIndex(), nil)
assert.Equal(t, 0, len(deps))
}
@@ -801,3 +803,50 @@ func (s *slowRPCClient) BatchCall(b []rpc.BatchElem) error {
func (s *slowRPCClient) CallContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
panic("implement me")
}
func TestService_migrateOldDepositTree(t *testing.T) {
beaconDB := dbutil.SetupDB(t)
cache, err := depositcache.New()
require.NoError(t, err)
srv, endpoint, err := mockExecution.SetupRPCServer()
require.NoError(t, err)
t.Cleanup(func() {
srv.Stop()
})
s, err := NewService(context.Background(),
WithHttpEndpoint(endpoint),
WithDatabase(beaconDB),
WithDepositCache(cache),
)
require.NoError(t, err)
eth1Data := &ethpb.ETH1ChainData{
BeaconState: &ethpb.BeaconState{
Eth1Data: &ethpb.Eth1Data{
DepositCount: 800,
},
},
CurrentEth1Data: &ethpb.LatestETH1Data{
BlockHeight: 100,
},
}
totalDeposits := 1000
input := bytesutil.ToBytes32([]byte("foo"))
dt, err := trie.NewTrie(32)
require.NoError(t, err)
for i := 0; i < totalDeposits; i++ {
err := dt.Insert(input[:], i)
require.NoError(t, err)
}
eth1Data.Trie = dt.ToProto()
err = s.migrateOldDepositTree(eth1Data)
require.NoError(t, err)
oldDepositTreeRoot, err := dt.HashTreeRoot()
require.NoError(t, err)
newDepositTreeRoot, err := s.depositTrie.HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, oldDepositTreeRoot, newDepositTreeRoot)
}

View File

@@ -6,6 +6,7 @@ go_library(
"doc.go",
"errors.go",
"forkchoice.go",
"last_root.go",
"metrics.go",
"node.go",
"on_tick.go",
@@ -49,6 +50,7 @@ go_test(
srcs = [
"ffg_update_test.go",
"forkchoice_test.go",
"last_root_test.go",
"no_vote_test.go",
"node_test.go",
"on_tick_test.go",

View File

@@ -0,0 +1,29 @@
package doublylinkedtree
import (
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/time/slots"
)
// LastRoot returns the last canonical block root in the given epoch
func (f *ForkChoice) LastRoot(epoch primitives.Epoch) [32]byte {
head := f.store.headNode
headEpoch := slots.ToEpoch(head.slot)
epochEnd, err := slots.EpochEnd(epoch)
if err != nil {
return [32]byte{}
}
if headEpoch < epoch {
return [32]byte{}
}
if headEpoch == epoch {
return head.root
}
for head != nil && head.slot > epochEnd {
head = head.parent
}
if head == nil {
return [32]byte{}
}
return head.root
}

View File

@@ -0,0 +1,39 @@
package doublylinkedtree
import (
"context"
"testing"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/testing/require"
)
func TestLastRoot(t *testing.T) {
f := setup(0, 0)
ctx := context.Background()
st, root, err := prepareForkchoiceState(ctx, 1, [32]byte{'1'}, params.BeaconConfig().ZeroHash, [32]byte{'1'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'2'}, [32]byte{'1'}, [32]byte{'2'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 3, [32]byte{'3'}, [32]byte{'1'}, [32]byte{'3'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 32, [32]byte{'4'}, [32]byte{'3'}, [32]byte{'4'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 33, [32]byte{'5'}, [32]byte{'2'}, [32]byte{'5'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 34, [32]byte{'6'}, [32]byte{'5'}, [32]byte{'6'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
headNode, _ := f.store.nodeByRoot[[32]byte{'6'}]
f.store.headNode = headNode
require.Equal(t, [32]byte{'6'}, f.store.headNode.root)
require.Equal(t, [32]byte{'2'}, f.LastRoot(0))
require.Equal(t, [32]byte{'6'}, f.LastRoot(1))
require.Equal(t, [32]byte{}, f.LastRoot(2))
}

View File

@@ -49,6 +49,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
slot := primitives.Slot(1)
driftGenesisTime(f, slot, 0)
newRoot := indexToHash(1)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err := prepareForkchoiceState(
ctx,
slot,
@@ -74,6 +75,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
slot = primitives.Slot(2)
driftGenesisTime(f, slot, 0)
newRoot = indexToHash(2)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err = prepareForkchoiceState(
ctx,
slot,
@@ -101,6 +103,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
slot = primitives.Slot(3)
driftGenesisTime(f, slot, 0)
newRoot = indexToHash(3)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err = prepareForkchoiceState(
ctx,
slot,
@@ -129,6 +132,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
slot = primitives.Slot(4)
driftGenesisTime(f, slot, 0)
newRoot = indexToHash(4)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err = prepareForkchoiceState(
ctx,
slot,
@@ -335,6 +339,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
cSlot := primitives.Slot(2)
driftGenesisTime(f, cSlot, 0)
c := indexToHash(2)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err := prepareForkchoiceState(
ctx,
cSlot,
@@ -354,6 +359,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
bSlot := primitives.Slot(1)
b := indexToHash(1)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err = prepareForkchoiceState(
ctx,
bSlot,
@@ -378,6 +384,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// A block D, building on B, is received at slot N+3. It should not be able to win without boosting.
dSlot := primitives.Slot(3)
d := indexToHash(3)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err = prepareForkchoiceState(
ctx,
dSlot,
@@ -398,6 +405,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// If the same block arrives with boosting then it becomes head:
driftGenesisTime(f, dSlot, 0)
d2 := indexToHash(30)
f.store.proposerBoostRoot = [32]byte{}
state, blkRoot, err = prepareForkchoiceState(
ctx,
dSlot,

View File

@@ -109,7 +109,8 @@ func (s *Store) insert(ctx context.Context,
secondsIntoSlot := (timeNow - s.genesisTime) % params.BeaconConfig().SecondsPerSlot
currentSlot := slots.CurrentSlot(s.genesisTime)
boostThreshold := params.BeaconConfig().SecondsPerSlot / params.BeaconConfig().IntervalsPerSlot
if currentSlot == slot && secondsIntoSlot < boostThreshold {
isFirstBlock := s.proposerBoostRoot == [32]byte{}
if currentSlot == slot && secondsIntoSlot < boostThreshold && isFirstBlock {
s.proposerBoostRoot = root
}

View File

@@ -68,6 +68,7 @@ type Getter interface {
IsOptimistic(root [32]byte) (bool, error)
ShouldOverrideFCU() bool
Slot([32]byte) (primitives.Slot, error)
LastRoot(primitives.Epoch) [32]byte
}
// Setter allows to set forkchoice information

View File

@@ -21,6 +21,7 @@ go_library(
"//beacon-chain/builder:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/cache/depositcache:go_default_library",
"//beacon-chain/cache/depositsnapshot: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

@@ -23,6 +23,7 @@ import (
"github.com/prysmaticlabs/prysm/v4/beacon-chain/builder"
"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/db"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/kv"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/slasherkv"
@@ -94,7 +95,7 @@ type BeaconNode struct {
slashingsPool slashings.PoolManager
syncCommitteePool synccommittee.Pool
blsToExecPool blstoexec.PoolManager
depositCache *depositcache.DepositCache
depositCache cache.DepositCache
proposerIdsCache *cache.ProposerPayloadIDsCache
stateFeed *event.Feed
blockFeed *event.Feed
@@ -152,6 +153,9 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) {
if err := configureExecutionSetting(cliCtx); err != nil {
return nil, err
}
if err := kv.ConfigureBlobRetentionEpoch(cliCtx); err != nil {
return nil, err
}
configureFastSSZHashingAlgorithm()
// Initializes any forks here.
@@ -406,10 +410,16 @@ func (b *BeaconNode) startDB(cliCtx *cli.Context, depositAddress string) error {
b.db = d
depositCache, err := depositcache.New()
var depositCache cache.DepositCache
if features.Get().EnableEIP4881 {
depositCache, err = depositsnapshot.New()
} else {
depositCache, err = depositcache.New()
}
if err != nil {
return errors.Wrap(err, "could not create deposit cache")
}
b.depositCache = depositCache
if b.GenesisInitializer != nil {
@@ -781,7 +791,7 @@ func (b *BeaconNode) registerRPCService(router *mux.Router) error {
}
genesisValidators := b.cliCtx.Uint64(flags.InteropNumValidatorsFlag.Name)
var depositFetcher depositcache.DepositFetcher
var depositFetcher cache.DepositFetcher
var chainStartFetcher execution.ChainStartFetcher
if genesisValidators > 0 {
var interopService *interopcoldstart.Service

View File

@@ -477,8 +477,10 @@ func TestKV_Aggregated_HasAggregatedAttestation(t *testing.T) {
// Same test for block attestations
cache = NewAttCaches()
assert.NoError(t, cache.SaveBlockAttestations(tt.existing))
for _, att := range tt.existing {
require.NoError(t, cache.SaveBlockAttestation(att))
}
result, err = cache.HasAggregatedAttestation(tt.input)
require.NoError(t, err)
assert.Equal(t, tt.want, result)

View File

@@ -36,17 +36,6 @@ func (c *AttCaches) SaveBlockAttestation(att *ethpb.Attestation) error {
return nil
}
// SaveBlockAttestations saves a list of block attestations in cache.
func (c *AttCaches) SaveBlockAttestations(atts []*ethpb.Attestation) error {
for _, att := range atts {
if err := c.SaveBlockAttestation(att); err != nil {
return err
}
}
return nil
}
// BlockAttestations returns the block attestations in cache.
func (c *AttCaches) BlockAttestations() []*ethpb.Attestation {
atts := make([]*ethpb.Attestation, 0)

View File

@@ -32,7 +32,6 @@ type Pool interface {
UnaggregatedAttestationCount() int
// For attestations that were included in the block.
SaveBlockAttestation(att *ethpb.Attestation) error
SaveBlockAttestations(atts []*ethpb.Attestation) error
BlockAttestations() []*ethpb.Attestation
DeleteBlockAttestation(att *ethpb.Attestation) error
// For attestations to be passed to fork choice.

View File

@@ -94,7 +94,9 @@ func TestBatchAttestations_Multiple(t *testing.T) {
}
require.NoError(t, s.cfg.Pool.SaveUnaggregatedAttestations(unaggregatedAtts))
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(aggregatedAtts))
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(blockAtts))
for _, att := range blockAtts {
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
}
require.NoError(t, s.batchForkChoiceAtts(context.Background()))
wanted, err := attaggregation.Aggregate([]*ethpb.Attestation{aggregatedAtts[0], blockAtts[0]})
@@ -148,7 +150,10 @@ func TestBatchAttestations_Single(t *testing.T) {
}
require.NoError(t, s.cfg.Pool.SaveUnaggregatedAttestations(unaggregatedAtts))
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(aggregatedAtts))
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(blockAtts))
for _, att := range blockAtts {
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
}
require.NoError(t, s.batchForkChoiceAtts(context.Background()))
wanted, err := attaggregation.Aggregate(append(aggregatedAtts, unaggregatedAtts...))

View File

@@ -42,7 +42,9 @@ func TestPruneExpired_Ticker(t *testing.T) {
}
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(atts))
assert.Equal(t, 2, s.cfg.Pool.AggregatedAttestationCount())
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(atts))
for _, att := range atts {
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
}
// Rewind back one epoch worth of time.
s.genesisTime = uint64(prysmTime.Now().Unix()) - uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))
@@ -95,7 +97,9 @@ func TestPruneExpired_PruneExpiredAtts(t *testing.T) {
att4 := &ethpb.Attestation{Data: ad2, AggregationBits: bitfield.Bitlist{0b1110}}
atts := []*ethpb.Attestation{att1, att2, att3, att4}
require.NoError(t, s.cfg.Pool.SaveAggregatedAttestations(atts))
require.NoError(t, s.cfg.Pool.SaveBlockAttestations(atts))
for _, att := range atts {
require.NoError(t, s.cfg.Pool.SaveBlockAttestation(att))
}
// Rewind back one epoch worth of time.
s.genesisTime = uint64(prysmTime.Now().Unix()) - uint64(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot))

View File

@@ -70,19 +70,6 @@ func handleGetBlindedBeaconBlockSSZ(
return handleGetSSZ(m, endpoint, w, req, config)
}
func handleSubmitBlockSSZ(m *apimiddleware.ApiProxyMiddleware, endpoint apimiddleware.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
return handlePostSSZ(m, endpoint, w, req)
}
func handleSubmitBlindedBlockSSZ(
m *apimiddleware.ApiProxyMiddleware,
endpoint apimiddleware.Endpoint,
w http.ResponseWriter,
req *http.Request,
) (handled bool) {
return handlePostSSZ(m, endpoint, w, req)
}
func handleProduceBlockSSZ(m *apimiddleware.ApiProxyMiddleware, endpoint apimiddleware.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
config := sszConfig{
fileName: "produce_beacon_block.ssz",
@@ -111,11 +98,7 @@ func handleGetSSZ(
req *http.Request,
config sszConfig,
) (handled bool) {
ssz, err := http2.SszRequested(req)
if err != nil {
apimiddleware.WriteError(w, apimiddleware.InternalServerError(err), nil)
return true
}
ssz := http2.SszRequested(req)
if !ssz {
return false
}
@@ -163,58 +146,6 @@ func handleGetSSZ(
return true
}
func handlePostSSZ(m *apimiddleware.ApiProxyMiddleware, endpoint apimiddleware.Endpoint, w http.ResponseWriter, req *http.Request) (handled bool) {
if !sszPosted(req) {
return false
}
if errJson := prepareSSZRequestForProxying(m, endpoint, req); errJson != nil {
apimiddleware.WriteError(w, errJson, nil)
return true
}
prepareCustomHeaders(req)
if errJson := preparePostedSSZData(req); errJson != nil {
apimiddleware.WriteError(w, errJson, nil)
return true
}
grpcResponse, errJson := m.ProxyRequest(req)
if errJson != nil {
apimiddleware.WriteError(w, errJson, nil)
return true
}
grpcResponseBody, errJson := apimiddleware.ReadGrpcResponseBody(grpcResponse.Body)
if errJson != nil {
apimiddleware.WriteError(w, errJson, nil)
return true
}
respHasError, errJson := apimiddleware.HandleGrpcResponseError(endpoint.Err, grpcResponse, grpcResponseBody, w)
if errJson != nil {
apimiddleware.WriteError(w, errJson, nil)
return true
}
if respHasError {
return true
}
if errJson := apimiddleware.Cleanup(grpcResponse.Body); errJson != nil {
apimiddleware.WriteError(w, errJson, nil)
return true
}
return true
}
func sszPosted(req *http.Request) bool {
ct, ok := req.Header["Content-Type"]
if !ok {
return false
}
if len(ct) != 1 {
return false
}
return ct[0] == api.OctetStreamMediaType
}
func prepareSSZRequestForProxying(m *apimiddleware.ApiProxyMiddleware, endpoint apimiddleware.Endpoint, req *http.Request) apimiddleware.ErrorJson {
req.URL.Scheme = "http"
req.URL.Host = m.GatewayAddress
@@ -230,14 +161,6 @@ func prepareSSZRequestForProxying(m *apimiddleware.ApiProxyMiddleware, endpoint
return nil
}
func prepareCustomHeaders(req *http.Request) {
ver := req.Header.Get(api.VersionHeader)
if ver != "" {
req.Header.Del(api.VersionHeader)
req.Header.Add(grpc.WithPrefix(api.VersionHeader), ver)
}
}
func preparePostedSSZData(req *http.Request) apimiddleware.ErrorJson {
buf, err := io.ReadAll(req.Body)
if err != nil {

View File

@@ -40,30 +40,13 @@ func wrapBLSChangesArray(
return true, nil
}
// Some endpoints e.g. https://ethereum.github.io/beacon-apis/#/Validator/getAttesterDuties expect posting a top-level array of validator indices.
// We make it more proto-friendly by wrapping it in a struct with an 'Index' field.
func wrapValidatorIndicesArray(
endpoint *apimiddleware.Endpoint,
_ http.ResponseWriter,
req *http.Request,
) (apimiddleware.RunDefault, apimiddleware.ErrorJson) {
if _, ok := endpoint.PostRequest.(*ValidatorIndicesJson); ok {
indices := make([]string, 0)
if err := json.NewDecoder(req.Body).Decode(&indices); err != nil {
return false, apimiddleware.InternalServerErrorWithMessage(err, "could not decode body")
}
j := &ValidatorIndicesJson{Index: indices}
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"
Signature string `json:"signature" hex:"true"`
}
type phase0PublishBlockRequestJson struct {
Phase0Block *SignedBeaconBlockJson `json:"phase0_block"`
Message *v1alpha1SignedPhase0Block `json:"phase0_block"`
}
type altairPublishBlockRequestJson struct {
@@ -160,42 +143,40 @@ func setInitialPublishBlockPostRequest(endpoint *apimiddleware.Endpoint,
func preparePublishedBlock(endpoint *apimiddleware.Endpoint, _ http.ResponseWriter, _ *http.Request) apimiddleware.ErrorJson {
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockJson); ok {
// Prepare post request that can be properly decoded on gRPC side.
actualPostReq := &phase0PublishBlockRequestJson{
Phase0Block: block,
endpoint.PostRequest = &phase0PublishBlockRequestJson{
Message: &v1alpha1SignedPhase0Block{
Block: block.Message,
Signature: block.Signature,
},
}
endpoint.PostRequest = actualPostReq
return nil
}
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockAltairJson); ok {
// Prepare post request that can be properly decoded on gRPC side.
actualPostReq := &altairPublishBlockRequestJson{
endpoint.PostRequest = &altairPublishBlockRequestJson{
AltairBlock: block,
}
endpoint.PostRequest = actualPostReq
return nil
}
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockBellatrixJson); ok {
// Prepare post request that can be properly decoded on gRPC side.
actualPostReq := &bellatrixPublishBlockRequestJson{
endpoint.PostRequest = &bellatrixPublishBlockRequestJson{
BellatrixBlock: block,
}
endpoint.PostRequest = actualPostReq
return nil
}
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockCapellaJson); ok {
// Prepare post request that can be properly decoded on gRPC side.
actualPostReq := &capellaPublishBlockRequestJson{
endpoint.PostRequest = &capellaPublishBlockRequestJson{
CapellaBlock: block,
}
endpoint.PostRequest = actualPostReq
return nil
}
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockContentsDenebJson); ok {
// Prepare post request that can be properly decoded on gRPC side.
actualPostReq := &denebPublishBlockRequestJson{
endpoint.PostRequest = &denebPublishBlockRequestJson{
DenebContents: block,
}
endpoint.PostRequest = actualPostReq
return nil
}
return apimiddleware.InternalServerError(errors.New("unsupported block type"))
@@ -266,11 +247,12 @@ func setInitialPublishBlindedBlockPostRequest(endpoint *apimiddleware.Endpoint,
// (which was filled out previously in setInitialPublishBlockPostRequest).
func preparePublishedBlindedBlock(endpoint *apimiddleware.Endpoint, _ http.ResponseWriter, _ *http.Request) apimiddleware.ErrorJson {
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockJson); ok {
// Prepare post request that can be properly decoded on gRPC side.
actualPostReq := &phase0PublishBlockRequestJson{
Phase0Block: block,
endpoint.PostRequest = &phase0PublishBlockRequestJson{
Message: &v1alpha1SignedPhase0Block{
Block: block.Message,
Signature: block.Signature,
},
}
endpoint.PostRequest = actualPostReq
return nil
}
if block, ok := endpoint.PostRequest.(*SignedBeaconBlockAltairJson); ok {

View File

@@ -19,47 +19,6 @@ import (
"github.com/prysmaticlabs/prysm/v4/time/slots"
)
func TestWrapValidatorIndicesArray(t *testing.T) {
t.Run("ok", func(t *testing.T) {
endpoint := &apimiddleware.Endpoint{
PostRequest: &ValidatorIndicesJson{},
}
unwrappedIndices := []string{"1", "2"}
unwrappedIndicesJson, err := json.Marshal(unwrappedIndices)
require.NoError(t, err)
var body bytes.Buffer
_, err = body.Write(unwrappedIndicesJson)
require.NoError(t, err)
request := httptest.NewRequest("POST", "http://foo.example", &body)
runDefault, errJson := wrapValidatorIndicesArray(endpoint, nil, request)
require.Equal(t, true, errJson == nil)
assert.Equal(t, apimiddleware.RunDefault(true), runDefault)
wrappedIndices := &ValidatorIndicesJson{}
require.NoError(t, json.NewDecoder(request.Body).Decode(wrappedIndices))
require.Equal(t, 2, len(wrappedIndices.Index), "wrong number of wrapped items")
assert.Equal(t, "1", wrappedIndices.Index[0])
assert.Equal(t, "2", wrappedIndices.Index[1])
})
t.Run("invalid_body", func(t *testing.T) {
endpoint := &apimiddleware.Endpoint{
PostRequest: &ValidatorIndicesJson{},
}
var body bytes.Buffer
_, err := body.Write([]byte("invalid"))
require.NoError(t, err)
request := httptest.NewRequest("POST", "http://foo.example", &body)
runDefault, errJson := wrapValidatorIndicesArray(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 TestWrapBLSChangesArray(t *testing.T) {
t.Run("ok", func(t *testing.T) {
endpoint := &apimiddleware.Endpoint{

View File

@@ -16,19 +16,9 @@ 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/genesis",
"/eth/v1/beacon/states/{state_id}/root",
"/eth/v1/beacon/states/{state_id}/fork",
"/eth/v1/beacon/states/{state_id}/finality_checkpoints",
"/eth/v1/beacon/states/{state_id}/validators",
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}",
"/eth/v1/beacon/states/{state_id}/validator_balances",
"/eth/v1/beacon/states/{state_id}/committees",
"/eth/v1/beacon/states/{state_id}/sync_committees",
"/eth/v1/beacon/states/{state_id}/randao",
"/eth/v1/beacon/headers",
"/eth/v1/beacon/headers/{block_id}",
"/eth/v1/beacon/blocks",
"/eth/v1/beacon/blinded_blocks",
"/eth/v1/beacon/blocks/{block_id}",
"/eth/v2/beacon/blocks/{block_id}",
@@ -51,16 +41,11 @@ func (_ *BeaconEndpointFactory) Paths() []string {
"/eth/v2/debug/beacon/heads",
"/eth/v1/debug/fork_choice",
"/eth/v1/config/fork_schedule",
"/eth/v1/config/deposit_contract",
"/eth/v1/config/spec",
"/eth/v1/events",
"/eth/v1/validator/duties/attester/{epoch}",
"/eth/v1/validator/duties/proposer/{epoch}",
"/eth/v1/validator/duties/sync/{epoch}",
"/eth/v1/validator/blocks/{slot}",
"/eth/v2/validator/blocks/{slot}",
"/eth/v1/validator/blinded_blocks/{slot}",
"/eth/v1/validator/liveness/{epoch}",
}
}
@@ -68,25 +53,8 @@ func (_ *BeaconEndpointFactory) Paths() []string {
func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, error) {
endpoint := apimiddleware.DefaultEndpoint()
switch path {
case "/eth/v1/beacon/genesis":
endpoint.GetResponse = &GenesisResponseJson{}
case "/eth/v1/beacon/states/{state_id}/root":
endpoint.GetResponse = &StateRootResponseJson{}
case "/eth/v1/beacon/states/{state_id}/fork":
endpoint.GetResponse = &StateForkResponseJson{}
case "/eth/v1/beacon/states/{state_id}/finality_checkpoints":
endpoint.GetResponse = &StateFinalityCheckpointResponseJson{}
case "/eth/v1/beacon/states/{state_id}/validators":
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "id", Hex: true}, {Name: "status", Enum: true}}
endpoint.GetResponse = &StateValidatorsResponseJson{}
case "/eth/v1/beacon/states/{state_id}/validators/{validator_id}":
endpoint.GetResponse = &StateValidatorResponseJson{}
case "/eth/v1/beacon/states/{state_id}/validator_balances":
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "id", Hex: true}}
endpoint.GetResponse = &ValidatorBalancesResponseJson{}
case "/eth/v1/beacon/states/{state_id}/committees":
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}, {Name: "index"}, {Name: "slot"}}
endpoint.GetResponse = &StateCommitteesResponseJson{}
case "/eth/v1/beacon/states/{state_id}/sync_committees":
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}}
endpoint.GetResponse = &SyncCommitteesResponseJson{}
@@ -96,23 +64,6 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
case "/eth/v1/beacon/states/{state_id}/randao":
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "epoch"}}
endpoint.GetResponse = &RandaoResponseJson{}
case "/eth/v1/beacon/headers":
endpoint.RequestQueryParams = []apimiddleware.QueryParam{{Name: "slot"}, {Name: "parent_root", Hex: true}}
endpoint.GetResponse = &BlockHeadersResponseJson{}
case "/eth/v1/beacon/headers/{block_id}":
endpoint.GetResponse = &BlockHeaderResponseJson{}
case "/eth/v1/beacon/blocks":
endpoint.Hooks = apimiddleware.HookCollection{
OnPreDeserializeRequestBodyIntoContainer: setInitialPublishBlockPostRequest,
OnPostDeserializeRequestBodyIntoContainer: preparePublishedBlock,
}
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleSubmitBlockSSZ}
case "/eth/v1/beacon/blinded_blocks":
endpoint.Hooks = apimiddleware.HookCollection{
OnPreDeserializeRequestBodyIntoContainer: setInitialPublishBlindedBlockPostRequest,
OnPostDeserializeRequestBodyIntoContainer: preparePublishedBlindedBlock,
}
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleSubmitBlindedBlockSSZ}
case "/eth/v1/beacon/blocks/{block_id}":
endpoint.GetResponse = &BlockResponseJson{}
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleGetBeaconBlockSSZ}
@@ -179,32 +130,10 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
}
case "/eth/v1/config/fork_schedule":
endpoint.GetResponse = &ForkScheduleResponseJson{}
case "/eth/v1/config/deposit_contract":
endpoint.GetResponse = &DepositContractResponseJson{}
case "/eth/v1/config/spec":
endpoint.GetResponse = &SpecResponseJson{}
case "/eth/v1/events":
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleEvents}
case "/eth/v1/validator/duties/attester/{epoch}":
endpoint.PostRequest = &ValidatorIndicesJson{}
endpoint.PostResponse = &AttesterDutiesResponseJson{}
endpoint.RequestURLLiterals = []string{"epoch"}
endpoint.Err = &NodeSyncDetailsErrorJson{}
endpoint.Hooks = apimiddleware.HookCollection{
OnPreDeserializeRequestBodyIntoContainer: wrapValidatorIndicesArray,
}
case "/eth/v1/validator/duties/proposer/{epoch}":
endpoint.GetResponse = &ProposerDutiesResponseJson{}
endpoint.RequestURLLiterals = []string{"epoch"}
endpoint.Err = &NodeSyncDetailsErrorJson{}
case "/eth/v1/validator/duties/sync/{epoch}":
endpoint.PostRequest = &ValidatorIndicesJson{}
endpoint.PostResponse = &SyncCommitteeDutiesResponseJson{}
endpoint.RequestURLLiterals = []string{"epoch"}
endpoint.Err = &NodeSyncDetailsErrorJson{}
endpoint.Hooks = apimiddleware.HookCollection{
OnPreDeserializeRequestBodyIntoContainer: wrapValidatorIndicesArray,
}
case "/eth/v1/validator/blocks/{slot}":
endpoint.GetResponse = &ProduceBlockResponseJson{}
endpoint.RequestURLLiterals = []string{"slot"}
@@ -225,14 +154,6 @@ func (_ *BeaconEndpointFactory) Create(path string) (*apimiddleware.Endpoint, er
OnPreSerializeMiddlewareResponseIntoJson: serializeProducedBlindedBlock,
}
endpoint.CustomHandlers = []apimiddleware.CustomHandler{handleProduceBlindedBlockSSZ}
case "/eth/v1/validator/liveness/{epoch}":
endpoint.PostRequest = &ValidatorIndicesJson{}
endpoint.PostResponse = &LivenessResponseJson{}
endpoint.RequestURLLiterals = []string{"epoch"}
endpoint.Err = &NodeSyncDetailsErrorJson{}
endpoint.Hooks = apimiddleware.HookCollection{
OnPreDeserializeRequestBodyIntoContainer: wrapValidatorIndicesArray,
}
default:
return nil, errors.New("invalid path")
}

View File

@@ -12,16 +12,6 @@ import (
// Requests and responses.
//----------------
type GenesisResponseJson struct {
Data *GenesisResponse_GenesisJson `json:"data"`
}
type GenesisResponse_GenesisJson struct {
GenesisTime string `json:"genesis_time" time:"true"`
GenesisValidatorsRoot string `json:"genesis_validators_root" hex:"true"`
GenesisForkVersion string `json:"genesis_fork_version" hex:"true"`
}
// WeakSubjectivityResponse is used to marshal/unmarshal the response for the
// /eth/v1/beacon/weak_subjectivity endpoint.
type WeakSubjectivityResponse struct {
@@ -41,48 +31,6 @@ type StateRootResponse_StateRootJson struct {
StateRoot string `json:"root" hex:"true"`
}
type StateForkResponseJson struct {
Data *ForkJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type StateFinalityCheckpointResponseJson struct {
Data *StateFinalityCheckpointResponse_StateFinalityCheckpointJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type StateFinalityCheckpointResponse_StateFinalityCheckpointJson struct {
PreviousJustified *CheckpointJson `json:"previous_justified"`
CurrentJustified *CheckpointJson `json:"current_justified"`
Finalized *CheckpointJson `json:"finalized"`
}
type StateValidatorsResponseJson struct {
Data []*ValidatorContainerJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type StateValidatorResponseJson struct {
Data *ValidatorContainerJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type ValidatorBalancesResponseJson struct {
Data []*ValidatorBalanceJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type StateCommitteesResponseJson struct {
Data []*CommitteeJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type SyncCommitteesResponseJson struct {
Data *SyncCommitteeValidatorsJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
@@ -97,18 +45,6 @@ type RandaoResponseJson struct {
Finalized bool `json:"finalized"`
}
type BlockHeadersResponseJson struct {
Data []*BlockHeaderContainerJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type BlockHeaderResponseJson struct {
Data *BlockHeaderContainerJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type BlockResponseJson struct {
Data *SignedBeaconBlockJson `json:"data"`
}
@@ -213,27 +149,6 @@ type SpecResponseJson struct {
Data interface{} `json:"data"`
}
type ValidatorIndicesJson struct {
Index []string `json:"index"`
}
type AttesterDutiesResponseJson struct {
DependentRoot string `json:"dependent_root" hex:"true"`
Data []*AttesterDutyJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
}
type ProposerDutiesResponseJson struct {
DependentRoot string `json:"dependent_root" hex:"true"`
Data []*ProposerDutyJson `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
}
type SyncCommitteeDutiesResponseJson struct {
Data []*SyncCommitteeDuty `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
}
type ProduceBlockResponseJson struct {
Data *BeaconBlockJson `json:"data"`
}
@@ -300,13 +215,6 @@ type ForkChoiceResponseExtraDataJson struct {
HeadRoot string `json:"head_root" hex:"true"`
}
type LivenessResponseJson struct {
Data []*struct {
Index string `json:"index"`
IsLive bool `json:"is_live"`
} `json:"data"`
}
//----------------
// Reusable types.
//----------------
@@ -740,17 +648,6 @@ type SyncAggregateJson struct {
SyncCommitteeSignature string `json:"sync_committee_signature" hex:"true"`
}
type BlockHeaderContainerJson struct {
Root string `json:"root" hex:"true"`
Canonical bool `json:"canonical"`
Header *BeaconBlockHeaderContainerJson `json:"header"`
}
type BeaconBlockHeaderContainerJson struct {
Message *BeaconBlockHeaderJson `json:"message"`
Signature string `json:"signature" hex:"true"`
}
type SignedBeaconBlockHeaderJson struct {
Header *BeaconBlockHeaderJson `json:"message"`
Signature string `json:"signature" hex:"true"`
@@ -1024,13 +921,6 @@ type ForkJson struct {
Epoch string `json:"epoch"`
}
type ValidatorContainerJson struct {
Index string `json:"index"`
Balance string `json:"balance"`
Status string `json:"status" enum:"true"`
Validator *ValidatorJson `json:"validator"`
}
type ValidatorJson struct {
PublicKey string `json:"pubkey" hex:"true"`
WithdrawalCredentials string `json:"withdrawal_credentials" hex:"true"`
@@ -1042,17 +932,6 @@ type ValidatorJson struct {
WithdrawableEpoch string `json:"withdrawable_epoch"`
}
type ValidatorBalanceJson struct {
Index string `json:"index"`
Balance string `json:"balance"`
}
type CommitteeJson struct {
Index string `json:"index"`
Slot string `json:"slot"`
Validators []string `json:"validators"`
}
type SyncCommitteeJson struct {
Pubkeys []string `json:"pubkeys" hex:"true"`
AggregatePubkey string `json:"aggregate_pubkey" hex:"true"`
@@ -1086,28 +965,6 @@ type DepositContractJson struct {
Address string `json:"address"`
}
type AttesterDutyJson struct {
Pubkey string `json:"pubkey" hex:"true"`
ValidatorIndex string `json:"validator_index"`
CommitteeIndex string `json:"committee_index"`
CommitteeLength string `json:"committee_length"`
CommitteesAtSlot string `json:"committees_at_slot"`
ValidatorCommitteeIndex string `json:"validator_committee_index"`
Slot string `json:"slot"`
}
type ProposerDutyJson struct {
Pubkey string `json:"pubkey" hex:"true"`
ValidatorIndex string `json:"validator_index"`
Slot string `json:"slot"`
}
type SyncCommitteeDuty struct {
Pubkey string `json:"pubkey" hex:"true"`
ValidatorIndex string `json:"validator_index"`
ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"`
}
type SignedAggregateAttestationAndProofJson struct {
Message *AggregateAttestationAndProofJson `json:"message"`
Signature string `json:"signature" hex:"true"`
@@ -1335,11 +1192,6 @@ type SingleIndexedVerificationFailureJson struct {
Message string `json:"message"`
}
type NodeSyncDetailsErrorJson struct {
apimiddleware.DefaultErrorJson
SyncDetails shared.SyncDetails `json:"sync_details"`
}
type EventErrorJson struct {
StatusCode int `json:"status_code"`
Message string `json:"message"`

View File

@@ -459,12 +459,12 @@ func (s *Service) GetAttestationData(
// SubmitSyncMessage submits the sync committee message to the network.
// It also saves the sync committee message into the pending pool for block inclusion.
func (s *Service) SubmitSyncMessage(ctx context.Context, msg *ethpb.SyncCommitteeMessage) error {
func (s *Service) SubmitSyncMessage(ctx context.Context, msg *ethpb.SyncCommitteeMessage) *RpcError {
errs, ctx := errgroup.WithContext(ctx)
headSyncCommitteeIndices, err := s.HeadFetcher.HeadSyncCommitteeIndices(ctx, msg.ValidatorIndex, msg.Slot)
if err != nil {
return err
return &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not get head sync committee indices")}
}
// Broadcasting and saving message into the pool in parallel. As one fail should not affect another.
// This broadcasts for all subnets.
@@ -477,9 +477,12 @@ func (s *Service) SubmitSyncMessage(ctx context.Context, msg *ethpb.SyncCommitte
}
if err := s.SyncCommitteePool.SaveSyncCommitteeMessage(msg); err != nil {
return err
return &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not save sync committee message")}
}
// Wait for p2p broadcast to complete and return the first error (if any)
return errs.Wait()
if err = errs.Wait(); err != nil {
return &RpcError{Reason: Internal, Err: errors.Wrap(err, "could not broadcast sync committee message")}
}
return nil
}

View File

@@ -8,16 +8,16 @@ go_library(
"config.go",
"handlers.go",
"handlers_pool.go",
"handlers_validator.go",
"log.go",
"pool.go",
"server.go",
"state.go",
"structs.go",
"sync_committee.go",
"validator.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/beacon",
visibility = ["//beacon-chain:__subpackages__"],
visibility = ["//visibility:public"],
deps = [
"//api:go_default_library",
"//api/grpc:go_default_library",
@@ -41,7 +41,6 @@ go_library(
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/rpc/eth/shared:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
"//beacon-chain/rpc/prysm/v1alpha1/validator:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
@@ -56,7 +55,6 @@ go_library(
"//consensus-types/validator:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//encoding/ssz/detect:go_default_library",
"//network/forks:go_default_library",
"//network/http:go_default_library",
"//proto/eth/v1:go_default_library",
@@ -66,7 +64,6 @@ go_library(
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_go_playground_validator_v10//:go_default_library",
"@com_github_gorilla_mux//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
@@ -77,7 +74,6 @@ go_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",
"@org_golang_google_protobuf//types/known/timestamppb:go_default_library",
],
)
@@ -89,12 +85,12 @@ go_test(
"config_test.go",
"handlers_pool_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",
"validator_test.go",
],
embed = [":go_default_library"],
deps = [
@@ -115,7 +111,6 @@ go_test(
"//beacon-chain/p2p/testing:go_default_library",
"//beacon-chain/rpc/apimiddleware:go_default_library",
"//beacon-chain/rpc/core:go_default_library",
"//beacon-chain/rpc/eth/helpers:go_default_library",
"//beacon-chain/rpc/eth/shared:go_default_library",
"//beacon-chain/rpc/eth/shared/testing:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
@@ -128,7 +123,6 @@ go_test(
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/validator:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/bls/common:go_default_library",
"//crypto/hash:go_default_library",
@@ -141,6 +135,7 @@ go_test(
"//proto/eth/v2:go_default_library",
"//proto/migration:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/mock:go_default_library",
"//testing/require:go_default_library",
@@ -156,7 +151,6 @@ go_test(
"@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//metadata:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
],

View File

@@ -2,30 +2,20 @@ package beacon
import (
"context"
"strings"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api"
rpchelpers "github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/prysm/v1alpha1/validator"
"github.com/prysmaticlabs/prysm/v4/config/params"
consensus_types "github.com/prysmaticlabs/prysm/v4/consensus-types"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/encoding/ssz/detect"
"github.com/prysmaticlabs/prysm/v4/network/forks"
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"
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"
)
// GetBlindedBlock retrieves blinded block for given block id.
@@ -158,217 +148,6 @@ func (bs *Server) GetBlindedBlockSSZ(ctx context.Context, req *ethpbv1.BlockRequ
return nil, status.Errorf(codes.Internal, "Unknown block type %T", blk)
}
// SubmitBlindedBlock instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct
// and publish a `ReadOnlySignedBeaconBlock` by swapping out the `transactions_root` for the corresponding full list of `transactions`.
// The beacon node should broadcast a newly constructed `ReadOnlySignedBeaconBlock` to the beacon network,
// to be included in the beacon chain. The beacon node is not required to validate the signed
// `ReadOnlyBeaconBlock`, and a successful response (20X) only indicates that the broadcast has been
// successful. The beacon node is expected to integrate the new block into its state, and
// therefore validate the block internally, however blocks which fail the validation are still
// broadcast but a different status code is returned (202).
func (bs *Server) SubmitBlindedBlock(ctx context.Context, req *ethpbv2.SignedBlindedBeaconBlockContentsContainer) (*emptypb.Empty, error) {
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlindedBlock")
defer span.End()
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
// We simply return the error because it's already a gRPC error.
return nil, err
}
switch blkContainer := req.Message.(type) {
case *ethpbv2.SignedBlindedBeaconBlockContentsContainer_DenebContents:
if err := bs.submitBlindedDenebContents(ctx, blkContainer.DenebContents); err != nil {
return nil, err
}
case *ethpbv2.SignedBlindedBeaconBlockContentsContainer_CapellaBlock:
if err := bs.submitBlindedCapellaBlock(ctx, blkContainer.CapellaBlock.Message, blkContainer.CapellaBlock.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBlindedBeaconBlockContentsContainer_BellatrixBlock:
if err := bs.submitBlindedBellatrixBlock(ctx, blkContainer.BellatrixBlock.Message, blkContainer.BellatrixBlock.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBlindedBeaconBlockContentsContainer_Phase0Block:
if err := bs.submitPhase0Block(ctx, blkContainer.Phase0Block.Block, blkContainer.Phase0Block.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBlindedBeaconBlockContentsContainer_AltairBlock:
if err := bs.submitAltairBlock(ctx, blkContainer.AltairBlock.Message, blkContainer.AltairBlock.Signature); err != nil {
return nil, err
}
default:
return nil, status.Errorf(codes.InvalidArgument, "Unsupported block container type %T", blkContainer)
}
return &emptypb.Empty{}, nil
}
// SubmitBlindedBlockSSZ instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct
// and publish a `ReadOnlySignedBeaconBlock` by swapping out the `transactions_root` for the corresponding full list of `transactions`.
// The beacon node should broadcast a newly constructed `ReadOnlySignedBeaconBlock` to the beacon network,
// to be included in the beacon chain. The beacon node is not required to validate the signed
// `ReadOnlyBeaconBlock`, and a successful response (20X) only indicates that the broadcast has been
// successful. The beacon node is expected to integrate the new block into its state, and
// therefore validate the block internally, however blocks which fail the validation are still
// broadcast but a different status code is returned (202).
//
// The provided block must be SSZ-serialized.
func (bs *Server) SubmitBlindedBlockSSZ(ctx context.Context, req *ethpbv2.SSZContainer) (*emptypb.Empty, error) {
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlindedBlockSSZ")
defer span.End()
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
// We simply return the error because it's already a gRPC error.
return nil, err
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not read"+api.VersionHeader+" header")
}
ver := md.Get(api.VersionHeader)
if len(ver) == 0 {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not read"+api.VersionHeader+" header")
}
schedule := forks.NewOrderedSchedule(params.BeaconConfig())
forkVer, err := schedule.VersionForName(ver[0])
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not determine fork version: %v", err)
}
unmarshaler, err := detect.FromForkVersion(forkVer)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not create unmarshaler: %v", err)
}
switch forkVer {
case bytesutil.ToBytes4(params.BeaconConfig().DenebForkVersion):
blkContent := &ethpbv2.SignedBlindedBeaconBlockContentsDeneb{}
if err := blkContent.UnmarshalSSZ(req.Data); err != nil {
return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "Could not unmarshal ssz signed blinded block contents: %v", err)
}
blindedBlock, err := migration.BlindedDenebToV1Alpha1SignedBlock(blkContent.SignedBlindedBlock)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Could not convert signed blinded block to v1alpha1: %v", err)
}
block, err := blocks.NewSignedBeaconBlock(blindedBlock)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not init block: %v", err)
}
b, err := block.PbBlindedDenebBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_BlindedDeneb{
BlindedDeneb: &eth.SignedBlindedBeaconBlockAndBlobsDeneb{
Block: b,
Blobs: migration.SignedBlindedBlobsToV1Alpha1SignedBlindedBlobs(blkContent.SignedBlindedBlobSidecars),
},
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().CapellaForkVersion):
block, err := unmarshaler.UnmarshalBlindedBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
if !block.IsBlinded() {
return nil, status.Error(codes.InvalidArgument, "Submitted block is not blinded")
}
b, err := block.PbBlindedCapellaBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_BlindedCapella{
BlindedCapella: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().BellatrixForkVersion):
block, err := unmarshaler.UnmarshalBlindedBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
if !block.IsBlinded() {
return nil, status.Error(codes.InvalidArgument, "Submitted block is not blinded")
}
b, err := block.PbBlindedBellatrixBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_BlindedBellatrix{
BlindedBellatrix: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().AltairForkVersion):
block, err := unmarshaler.UnmarshalBlindedBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
b, err := block.PbAltairBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Altair{
Altair: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().GenesisForkVersion):
block, err := unmarshaler.UnmarshalBlindedBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
b, err := block.PbPhase0Block()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Phase0{
Phase0: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
default:
return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "Unsupported fork %s", string(forkVer[:]))
}
}
func getBlindedBlockPhase0(blk interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.BlindedBlockResponse, error) {
phase0Blk, err := blk.PbPhase0Block()
if err != nil {
@@ -563,7 +342,7 @@ func (bs *Server) getBlindedBlockDeneb(ctx context.Context, blk interfaces.ReadO
if blindedDenebBlk == nil {
return nil, errNilBlock
}
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Block)
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Message)
if err != nil {
return nil, errors.Wrapf(err, "Could not convert beacon block")
}
@@ -601,7 +380,7 @@ func (bs *Server) getBlindedBlockDeneb(ctx context.Context, blk interfaces.ReadO
if err != nil {
return nil, errors.Wrapf(err, "could not get signed beacon block")
}
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Block)
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Message)
if err != nil {
return nil, errors.Wrapf(err, "could not convert beacon block")
}
@@ -783,7 +562,7 @@ func (bs *Server) getBlindedSSZBlockDeneb(ctx context.Context, blk interfaces.Re
if blindedDenebBlk == nil {
return nil, errNilBlock
}
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Block)
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlk.Message)
if err != nil {
return nil, errors.Wrapf(err, "could not get signed beacon block")
}
@@ -825,7 +604,7 @@ func (bs *Server) getBlindedSSZBlockDeneb(ctx context.Context, blk interfaces.Re
if err != nil {
return nil, errors.Wrapf(err, "could not get signed beacon block")
}
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Block)
v2Blk, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(blindedDenebBlock.Message)
if err != nil {
return nil, errors.Wrapf(err, "could not get signed beacon block")
}
@@ -848,73 +627,3 @@ func (bs *Server) getBlindedSSZBlockDeneb(ctx context.Context, blk interfaces.Re
}
return &ethpbv2.SSZContainer{Version: ethpbv2.Version_DENEB, ExecutionOptimistic: isOptimistic, Data: sszData}, nil
}
func (bs *Server) submitBlindedBellatrixBlock(ctx context.Context, blindedBellatrixBlk *ethpbv2.BlindedBeaconBlockBellatrix, sig []byte) error {
b, err := migration.BlindedBellatrixToV1Alpha1SignedBlock(&ethpbv2.SignedBlindedBeaconBlockBellatrix{
Message: blindedBellatrixBlk,
Signature: sig,
})
if err != nil {
return status.Errorf(codes.Internal, "Could not convert block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_BlindedBellatrix{
BlindedBellatrix: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose blinded block: %v", err)
}
return nil
}
func (bs *Server) submitBlindedCapellaBlock(ctx context.Context, blindedCapellaBlk *ethpbv2.BlindedBeaconBlockCapella, sig []byte) error {
b, err := migration.BlindedCapellaToV1Alpha1SignedBlock(&ethpbv2.SignedBlindedBeaconBlockCapella{
Message: blindedCapellaBlk,
Signature: sig,
})
if err != nil {
return status.Errorf(codes.Internal, "Could not convert block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_BlindedCapella{
BlindedCapella: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose blinded block: %v", err)
}
return nil
}
func (bs *Server) submitBlindedDenebContents(ctx context.Context, blindedDenebContents *ethpbv2.SignedBlindedBeaconBlockContentsDeneb) error {
blk, err := migration.BlindedDenebToV1Alpha1SignedBlock(&ethpbv2.SignedBlindedBeaconBlockDeneb{
Message: blindedDenebContents.SignedBlindedBlock.Message,
Signature: blindedDenebContents.SignedBlindedBlock.Signature,
})
if err != nil {
return status.Errorf(codes.Internal, "Could not get blinded block: %v", err)
}
blobs := migration.SignedBlindedBlobsToV1Alpha1SignedBlindedBlobs(blindedDenebContents.SignedBlindedBlobSidecars)
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_BlindedDeneb{
BlindedDeneb: &eth.SignedBlindedBeaconBlockAndBlobsDeneb{
Block: blk,
Blobs: blobs,
},
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose blinded block: %v", err)
}
return nil
}

View File

@@ -4,24 +4,17 @@ import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/prysmaticlabs/prysm/v4/api"
mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
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"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
mock2 "github.com/prysmaticlabs/prysm/v4/testing/mock"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func TestServer_GetBlindedBlock(t *testing.T) {
@@ -120,7 +113,7 @@ func TestServer_GetBlindedBlock(t *testing.T) {
OptimisticModeFetcher: mockChainService,
}
expected, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(b.Block)
expected, err := migration.V1Alpha1BeaconBlockBlindedDenebToV2Blinded(b.Message)
require.NoError(t, err)
resp, err := bs.GetBlindedBlock(ctx, &ethpbv1.BlockRequest{})
require.NoError(t, err)
@@ -347,273 +340,3 @@ func TestServer_GetBlindedBlockSSZ(t *testing.T) {
assert.Equal(t, false, resp.Finalized)
})
}
func TestServer_SubmitBlindedBlockSSZ(t *testing.T) {
ctrl := gomock.NewController(t)
ctx := context.Background()
t.Run("Phase 0", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlock()
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "phase0")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Altair", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlockAltair()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().AltairForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "altair")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Bellatrix", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBlindedBeaconBlockBellatrix()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().BellatrixForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "bellatrix")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Bellatrix full", func(t *testing.T) {
server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlockBellatrix()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().BellatrixForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "bellatrix")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NotNil(t, err)
})
t.Run("Capella", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBlindedBeaconBlockCapella()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "capella")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Capella full", func(t *testing.T) {
server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlockCapella()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "capella")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NotNil(t, err)
})
t.Run("Deneb", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b, err := util.NewBlindedBeaconBlockContentsDeneb(fieldparams.MaxBlobsPerBlock)
require.NoError(t, err)
// TODO: replace when deneb fork epoch is known
b.SignedBlindedBlock.Message.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "deneb")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Deneb full", func(t *testing.T) {
server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b, err := util.NewBeaconBlockContentsDeneb(fieldparams.MaxBlobsPerBlock)
require.NoError(t, err)
// TODO: replace when deneb fork epoch is known
b.SignedBlock.Message.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "deneb")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlindedBlockSSZ(sszCtx, blockReq)
assert.NotNil(t, err)
})
t.Run("sync not ready", func(t *testing.T) {
chainService := &mock.ChainService{}
v1Server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: true},
HeadFetcher: chainService,
TimeFetcher: chainService,
OptimisticModeFetcher: chainService,
}
_, err := v1Server.SubmitBlindedBlockSSZ(context.Background(), nil)
require.ErrorContains(t, "Syncing to latest head", err)
})
}
func TestSubmitBlindedBlock(t *testing.T) {
ctrl := gomock.NewController(t)
t.Run("Phase 0", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBlindedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBlindedBeaconBlockContentsContainer_Phase0Block{Phase0Block: &ethpbv1.SignedBeaconBlock{}},
}
_, err := server.SubmitBlindedBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Altair", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBlindedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBlindedBeaconBlockContentsContainer_AltairBlock{AltairBlock: &ethpbv2.SignedBeaconBlockAltair{}},
}
_, err := server.SubmitBlindedBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Bellatrix", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBlindedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBlindedBeaconBlockContentsContainer_BellatrixBlock{BellatrixBlock: &ethpbv2.SignedBlindedBeaconBlockBellatrix{}},
}
_, err := server.SubmitBlindedBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Capella", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBlindedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBlindedBeaconBlockContentsContainer_CapellaBlock{CapellaBlock: &ethpbv2.SignedBlindedBeaconBlockCapella{}},
}
_, err := server.SubmitBlindedBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Deneb", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBlindedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBlindedBeaconBlockContentsContainer_DenebContents{
DenebContents: &ethpbv2.SignedBlindedBeaconBlockContentsDeneb{
SignedBlindedBlock: &ethpbv2.SignedBlindedBeaconBlockDeneb{},
SignedBlindedBlobSidecars: []*ethpbv2.SignedBlindedBlobSidecar{},
},
},
}
_, err := server.SubmitBlindedBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("sync not ready", func(t *testing.T) {
chainService := &mock.ChainService{}
v1Server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: true},
HeadFetcher: chainService,
TimeFetcher: chainService,
OptimisticModeFetcher: chainService,
}
_, err := v1Server.SubmitBlindedBlock(context.Background(), nil)
require.ErrorContains(t, "Syncing to latest head", err)
})
}

View File

@@ -3,27 +3,20 @@ package beacon
import (
"context"
"fmt"
"strings"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v4/api"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db/filters"
rpchelpers "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/rpc/prysm/v1alpha1/validator"
"github.com/prysmaticlabs/prysm/v4/config/params"
consensus_types "github.com/prysmaticlabs/prysm/v4/consensus-types"
"github.com/prysmaticlabs/prysm/v4/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v4/encoding/ssz/detect"
"github.com/prysmaticlabs/prysm/v4/network/forks"
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"
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"
@@ -31,7 +24,6 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
var (
@@ -80,326 +72,6 @@ func (bs *Server) GetWeakSubjectivity(ctx context.Context, _ *empty.Empty) (*eth
}, nil
}
// GetBlockHeader retrieves block header for given block id.
func (bs *Server) GetBlockHeader(ctx context.Context, req *ethpbv1.BlockRequest) (*ethpbv1.BlockHeaderResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.GetBlockHeader")
defer span.End()
blk, err := bs.Blocker.Block(ctx, req.BlockId)
err = handleGetBlockError(blk, err)
if err != nil {
return nil, err
}
v1alpha1Header, err := blk.Header()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block header from block: %v", err)
}
header := migration.V1Alpha1SignedHeaderToV1(v1alpha1Header)
headerRoot, err := header.Message.HashTreeRoot()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not hash block header: %v", err)
}
blkRoot, err := blk.Block().HashTreeRoot()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not hash block: %v", err)
}
canonical, err := bs.ChainInfoFetcher.IsCanonical(ctx, blkRoot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not determine if block root is canonical: %v", err)
}
isOptimistic, err := bs.OptimisticModeFetcher.IsOptimisticForRoot(ctx, blkRoot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if block is optimistic: %v", err)
}
return &ethpbv1.BlockHeaderResponse{
Data: &ethpbv1.BlockHeaderContainer{
Root: headerRoot[:],
Canonical: canonical,
Header: &ethpbv1.BeaconBlockHeaderContainer{
Message: header.Message,
Signature: header.Signature,
},
},
ExecutionOptimistic: isOptimistic,
Finalized: bs.FinalizationFetcher.IsFinalized(ctx, blkRoot),
}, nil
}
// ListBlockHeaders retrieves block headers matching given query. By default it will fetch current head slot blocks.
func (bs *Server) ListBlockHeaders(ctx context.Context, req *ethpbv1.BlockHeadersRequest) (*ethpbv1.BlockHeadersResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.ListBlockHeaders")
defer span.End()
var err error
var blks []interfaces.ReadOnlySignedBeaconBlock
var blkRoots [][32]byte
if len(req.ParentRoot) == 32 {
blks, blkRoots, err = bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetParentRoot(req.ParentRoot))
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not retrieve blocks: %v", err)
}
} else {
slot := bs.ChainInfoFetcher.HeadSlot()
if req.Slot != nil {
slot = *req.Slot
}
blks, err = bs.BeaconDB.BlocksBySlot(ctx, slot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not retrieve blocks for slot %d: %v", req.Slot, err)
}
_, blkRoots, err = bs.BeaconDB.BlockRootsBySlot(ctx, slot)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not retrieve block roots for slot %d: %v", req.Slot, err)
}
}
if len(blks) == 0 {
return nil, status.Error(codes.NotFound, "Could not find requested blocks")
}
isOptimistic := false
isFinalized := true
blkHdrs := make([]*ethpbv1.BlockHeaderContainer, len(blks))
for i, bl := range blks {
v1alpha1Header, err := bl.Header()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get block header from block: %v", err)
}
header := migration.V1Alpha1SignedHeaderToV1(v1alpha1Header)
headerRoot, err := header.Message.HashTreeRoot()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not hash block header: %v", err)
}
canonical, err := bs.ChainInfoFetcher.IsCanonical(ctx, blkRoots[i])
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not determine if block root is canonical: %v", err)
}
if !isOptimistic {
isOptimistic, err = bs.OptimisticModeFetcher.IsOptimisticForRoot(ctx, blkRoots[i])
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not check if block is optimistic: %v", err)
}
}
if isFinalized {
isFinalized = bs.FinalizationFetcher.IsFinalized(ctx, blkRoots[i])
}
blkHdrs[i] = &ethpbv1.BlockHeaderContainer{
Root: headerRoot[:],
Canonical: canonical,
Header: &ethpbv1.BeaconBlockHeaderContainer{
Message: header.Message,
Signature: header.Signature,
},
}
}
return &ethpbv1.BlockHeadersResponse{Data: blkHdrs, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
}
// SubmitBlock instructs the beacon node to broadcast a newly signed beacon block to the beacon network, to be
// included in the beacon chain. The beacon node is not required to validate the signed ReadOnlyBeaconBlock, and a successful
// response (20X) only indicates that the broadcast has been successful. The beacon node is expected to integrate the
// new block into its state, and therefore validate the block internally, however blocks which fail the validation are
// still broadcast but a different status code is returned (202).
func (bs *Server) SubmitBlock(ctx context.Context, req *ethpbv2.SignedBeaconBlockContentsContainer) (*emptypb.Empty, error) {
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlock")
defer span.End()
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
// We simply return the error because it's already a gRPC error.
return nil, err
}
switch blkContainer := req.Message.(type) {
case *ethpbv2.SignedBeaconBlockContentsContainer_Phase0Block:
if err := bs.submitPhase0Block(ctx, blkContainer.Phase0Block.Block, blkContainer.Phase0Block.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBeaconBlockContentsContainer_AltairBlock:
if err := bs.submitAltairBlock(ctx, blkContainer.AltairBlock.Message, blkContainer.AltairBlock.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBeaconBlockContentsContainer_BellatrixBlock:
if err := bs.submitBellatrixBlock(ctx, blkContainer.BellatrixBlock.Message, blkContainer.BellatrixBlock.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBeaconBlockContentsContainer_CapellaBlock:
if err := bs.submitCapellaBlock(ctx, blkContainer.CapellaBlock.Message, blkContainer.CapellaBlock.Signature); err != nil {
return nil, err
}
case *ethpbv2.SignedBeaconBlockContentsContainer_DenebContents:
if err := bs.submitDenebContents(ctx, blkContainer.DenebContents); err != nil {
return nil, err
}
default:
return nil, status.Errorf(codes.InvalidArgument, "Unsupported block container type %T", blkContainer)
}
return &emptypb.Empty{}, nil
}
// SubmitBlockSSZ instructs the beacon node to broadcast a newly signed beacon block to the beacon network, to be
// included in the beacon chain. The beacon node is not required to validate the signed ReadOnlyBeaconBlock, and a successful
// response (20X) only indicates that the broadcast has been successful. The beacon node is expected to integrate the
// new block into its state, and therefore validate the block internally, however blocks which fail the validation are
// still broadcast but a different status code is returned (202).
//
// The provided block must be SSZ-serialized.
func (bs *Server) SubmitBlockSSZ(ctx context.Context, req *ethpbv2.SSZContainer) (*emptypb.Empty, error) {
ctx, span := trace.StartSpan(ctx, "beacon.SubmitBlockSSZ")
defer span.End()
if err := rpchelpers.ValidateSyncGRPC(ctx, bs.SyncChecker, bs.HeadFetcher, bs.TimeFetcher, bs.OptimisticModeFetcher); err != nil {
// We simply return the error because it's already a gRPC error.
return nil, err
}
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not read "+api.VersionHeader+" header")
}
ver := md.Get(api.VersionHeader)
if len(ver) == 0 {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not read "+api.VersionHeader+" header")
}
schedule := forks.NewOrderedSchedule(params.BeaconConfig())
forkVer, err := schedule.VersionForName(ver[0])
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not determine fork version: %v", err)
}
unmarshaler, err := detect.FromForkVersion(forkVer)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not create unmarshaler: %v", err)
}
switch forkVer {
case bytesutil.ToBytes4(params.BeaconConfig().DenebForkVersion):
blkContent := &ethpbv2.SignedBeaconBlockContentsDeneb{}
if err := blkContent.UnmarshalSSZ(req.Data); err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal ssz block contents: %v", err)
}
v1block, err := migration.DenebToV1Alpha1SignedBlock(blkContent.SignedBlock)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Submitted block is not valid: %v", err)
}
block, err := blocks.NewSignedBeaconBlock(v1block)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not init block: %v", err)
}
b, err := block.PbDenebBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Deneb{
Deneb: &eth.SignedBeaconBlockAndBlobsDeneb{
Block: b,
Blobs: migration.SignedBlobsToV1Alpha1SignedBlobs(blkContent.SignedBlobSidecars),
},
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().CapellaForkVersion):
block, err := unmarshaler.UnmarshalBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
if block.IsBlinded() {
return nil, status.Error(codes.InvalidArgument, "Submitted block is blinded")
}
b, err := block.PbCapellaBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Capella{
Capella: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().BellatrixForkVersion):
block, err := unmarshaler.UnmarshalBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
if block.IsBlinded() {
return nil, status.Error(codes.InvalidArgument, "Submitted block is blinded")
}
b, err := block.PbBellatrixBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Bellatrix{
Bellatrix: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().AltairForkVersion):
block, err := unmarshaler.UnmarshalBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
b, err := block.PbAltairBlock()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Altair{
Altair: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
case bytesutil.ToBytes4(params.BeaconConfig().GenesisForkVersion):
block, err := unmarshaler.UnmarshalBeaconBlock(req.Data)
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not unmarshal request data into block: %v", err)
}
b, err := block.PbPhase0Block()
if err != nil {
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not get proto block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Phase0{
Phase0: b,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())
}
return &emptypb.Empty{}, status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return &emptypb.Empty{}, nil
default:
return &emptypb.Empty{}, status.Errorf(codes.InvalidArgument, "Unsupported fork %s", string(forkVer[:]))
}
}
// GetBlock retrieves block details for given block ID.
// DEPRECATED: please use GetBlockV2 instead
func (bs *Server) GetBlock(ctx context.Context, req *ethpbv1.BlockRequest) (*ethpbv1.BlockResponse, error) {
@@ -1150,105 +822,3 @@ func (bs *Server) getSSZBlockDeneb(ctx context.Context, blk interfaces.ReadOnlyS
}
return &ethpbv2.SSZContainer{Version: ethpbv2.Version_DENEB, ExecutionOptimistic: isOptimistic, Data: sszData}, nil
}
func (bs *Server) submitPhase0Block(ctx context.Context, phase0Blk *ethpbv1.BeaconBlock, sig []byte) error {
v1alpha1Blk, err := migration.V1ToV1Alpha1SignedBlock(&ethpbv1.SignedBeaconBlock{Block: phase0Blk, Signature: sig})
if err != nil {
return status.Errorf(codes.InvalidArgument, "Could not convert block: %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Phase0{
Phase0: v1alpha1Blk,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return nil
}
func (bs *Server) submitAltairBlock(ctx context.Context, altairBlk *ethpbv2.BeaconBlockAltair, sig []byte) error {
v1alpha1Blk, err := migration.AltairToV1Alpha1SignedBlock(&ethpbv2.SignedBeaconBlockAltair{Message: altairBlk, Signature: sig})
if err != nil {
return status.Errorf(codes.InvalidArgument, "Could not convert block %v", err)
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Altair{
Altair: v1alpha1Blk,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return nil
}
func (bs *Server) submitBellatrixBlock(ctx context.Context, bellatrixBlk *ethpbv2.BeaconBlockBellatrix, sig []byte) error {
v1alpha1Blk, err := migration.BellatrixToV1Alpha1SignedBlock(&ethpbv2.SignedBeaconBlockBellatrix{Message: bellatrixBlk, Signature: sig})
if err != nil {
return status.Errorf(codes.InvalidArgument, "Could not convert block to v1 block")
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Bellatrix{
Bellatrix: v1alpha1Blk,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return nil
}
func (bs *Server) submitCapellaBlock(ctx context.Context, capellaBlk *ethpbv2.BeaconBlockCapella, sig []byte) error {
v1alpha1Blk, err := migration.CapellaToV1Alpha1SignedBlock(&ethpbv2.SignedBeaconBlockCapella{Message: capellaBlk, Signature: sig})
if err != nil {
return status.Errorf(codes.InvalidArgument, "Could not convert block to v1 block")
}
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Capella{
Capella: v1alpha1Blk,
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return nil
}
func (bs *Server) submitDenebContents(ctx context.Context, denebContents *ethpbv2.SignedBeaconBlockContentsDeneb) error {
blk, err := migration.DenebToV1Alpha1SignedBlock(&ethpbv2.SignedBeaconBlockDeneb{
Message: denebContents.SignedBlock.Message,
Signature: denebContents.SignedBlock.Signature,
})
if err != nil {
return status.Errorf(codes.Internal, "Could not get block: %v", err)
}
blobs := migration.SignedBlobsToV1Alpha1SignedBlobs(denebContents.SignedBlobSidecars)
_, err = bs.V1Alpha1ValidatorServer.ProposeBeaconBlock(ctx, &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Deneb{
Deneb: &eth.SignedBeaconBlockAndBlobsDeneb{
Block: blk,
Blobs: blobs,
},
},
})
if err != nil {
if strings.Contains(err.Error(), validator.CouldNotDecodeBlock) {
return status.Error(codes.InvalidArgument, err.Error())
}
return status.Errorf(codes.Internal, "Could not propose block: %v", err)
}
return nil
}

View File

@@ -4,17 +4,11 @@ import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v4/api"
mock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/db"
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/testutil"
mockSync "github.com/prysmaticlabs/prysm/v4/beacon-chain/sync/initial-sync/testing"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"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/consensus-types/primitives"
@@ -24,11 +18,9 @@ import (
"github.com/prysmaticlabs/prysm/v4/proto/migration"
ethpbalpha "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/assert"
mock2 "github.com/prysmaticlabs/prysm/v4/testing/mock"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func fillDBTestBlocks(ctx context.Context, t *testing.T, beaconDB db.Database) (*ethpbalpha.SignedBeaconBlock, []*ethpbalpha.BeaconBlockContainer) {
@@ -67,562 +59,6 @@ func fillDBTestBlocks(ctx context.Context, t *testing.T, beaconDB db.Database) (
return genBlk, blkContainers
}
func TestServer_GetBlockHeader(t *testing.T) {
ctx := context.Background()
b := util.NewBeaconBlock()
b.Block.Slot = 123
b.Block.ProposerIndex = 123
b.Block.StateRoot = bytesutil.PadTo([]byte("stateroot"), 32)
sb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
t.Run("get header", func(t *testing.T) {
mockChainService := &mock.ChainService{
FinalizedRoots: map[[32]byte]bool{},
}
bs := &Server{
ChainInfoFetcher: mockChainService,
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
}
header, err := bs.GetBlockHeader(ctx, &ethpbv1.BlockRequest{})
require.NoError(t, err)
expectedBodyRoot, err := sb.Block().Body().HashTreeRoot()
require.NoError(t, err)
expectedParentRoot := sb.Block().ParentRoot()
expectedHeader := &ethpbv1.BeaconBlockHeader{
Slot: sb.Block().Slot(),
ProposerIndex: sb.Block().ProposerIndex(),
ParentRoot: expectedParentRoot[:],
StateRoot: bytesutil.PadTo([]byte("stateroot"), 32),
BodyRoot: expectedBodyRoot[:],
}
expectedHeaderRoot, err := expectedHeader.HashTreeRoot()
require.NoError(t, err)
headerRoot, err := header.Data.Header.Message.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedHeaderRoot, headerRoot)
assert.Equal(t, sb.Block().Slot(), header.Data.Header.Message.Slot)
expectedStateRoot := sb.Block().StateRoot()
assert.DeepEqual(t, expectedStateRoot[:], header.Data.Header.Message.StateRoot)
assert.DeepEqual(t, expectedParentRoot[:], header.Data.Header.Message.ParentRoot)
assert.DeepEqual(t, expectedBodyRoot[:], header.Data.Header.Message.BodyRoot)
assert.Equal(t, sb.Block().ProposerIndex(), header.Data.Header.Message.ProposerIndex)
})
t.Run("execution optimistic", func(t *testing.T) {
r, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
mockChainService := &mock.ChainService{
OptimisticRoots: map[[32]byte]bool{r: true},
FinalizedRoots: map[[32]byte]bool{},
}
bs := &Server{
ChainInfoFetcher: mockChainService,
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
}
header, err := bs.GetBlockHeader(ctx, &ethpbv1.BlockRequest{BlockId: []byte("head")})
require.NoError(t, err)
assert.Equal(t, true, header.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
r, err := sb.Block().HashTreeRoot()
require.NoError(t, err)
t.Run("true", func(t *testing.T) {
mockChainService := &mock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
bs := &Server{
ChainInfoFetcher: mockChainService,
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
}
header, err := bs.GetBlockHeader(ctx, &ethpbv1.BlockRequest{BlockId: r[:]})
require.NoError(t, err)
assert.Equal(t, true, header.Finalized)
})
t.Run("false", func(t *testing.T) {
mockChainService := &mock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
bs := &Server{
ChainInfoFetcher: mockChainService,
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
Blocker: mockBlockFetcher,
}
header, err := bs.GetBlockHeader(ctx, &ethpbv1.BlockRequest{BlockId: r[:]})
require.NoError(t, err)
assert.Equal(t, false, header.Finalized)
})
})
}
func TestServer_ListBlockHeaders(t *testing.T) {
beaconDB := dbTest.SetupDB(t)
ctx := context.Background()
_, blkContainers := fillDBTestBlocks(ctx, t, beaconDB)
headBlock := blkContainers[len(blkContainers)-1]
b1 := util.NewBeaconBlock()
b1.Block.Slot = 30
b1.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
util.SaveBlock(t, ctx, beaconDB, b1)
b2 := util.NewBeaconBlock()
b2.Block.Slot = 30
b2.Block.ParentRoot = bytesutil.PadTo([]byte{4}, 32)
util.SaveBlock(t, ctx, beaconDB, b2)
b3 := util.NewBeaconBlock()
b3.Block.Slot = 31
b3.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
util.SaveBlock(t, ctx, beaconDB, b3)
b4 := util.NewBeaconBlock()
b4.Block.Slot = 28
b4.Block.ParentRoot = bytesutil.PadTo([]byte{1}, 32)
util.SaveBlock(t, ctx, beaconDB, b4)
t.Run("list headers", func(t *testing.T) {
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block)
require.NoError(t, err)
mockChainFetcher := &mock.ChainService{
DB: beaconDB,
Block: wsb,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
FinalizedRoots: map[[32]byte]bool{},
}
bs := &Server{
BeaconDB: beaconDB,
ChainInfoFetcher: mockChainFetcher,
OptimisticModeFetcher: mockChainFetcher,
FinalizationFetcher: mockChainFetcher,
}
tests := []struct {
name string
slot primitives.Slot
parentRoot []byte
want []*ethpbalpha.SignedBeaconBlock
wantErr bool
}{
{
name: "slot",
slot: primitives.Slot(30),
want: []*ethpbalpha.SignedBeaconBlock{
blkContainers[30].Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block,
b1,
b2,
},
},
{
name: "parent root",
parentRoot: b1.Block.ParentRoot,
want: []*ethpbalpha.SignedBeaconBlock{
blkContainers[1].Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block,
b1,
b3,
b4,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
headers, err := bs.ListBlockHeaders(ctx, &ethpbv1.BlockHeadersRequest{
Slot: &tt.slot,
ParentRoot: tt.parentRoot,
})
require.NoError(t, err)
require.Equal(t, len(tt.want), len(headers.Data))
for i, blk := range tt.want {
expectedBodyRoot, err := blk.Block.Body.HashTreeRoot()
require.NoError(t, err)
expectedHeader := &ethpbv1.BeaconBlockHeader{
Slot: blk.Block.Slot,
ProposerIndex: blk.Block.ProposerIndex,
ParentRoot: blk.Block.ParentRoot,
StateRoot: make([]byte, 32),
BodyRoot: expectedBodyRoot[:],
}
expectedHeaderRoot, err := expectedHeader.HashTreeRoot()
require.NoError(t, err)
headerRoot, err := headers.Data[i].Header.Message.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedHeaderRoot, headerRoot)
assert.Equal(t, blk.Block.Slot, headers.Data[i].Header.Message.Slot)
assert.DeepEqual(t, blk.Block.StateRoot, headers.Data[i].Header.Message.StateRoot)
assert.DeepEqual(t, blk.Block.ParentRoot, headers.Data[i].Header.Message.ParentRoot)
expectedRoot, err := blk.Block.Body.HashTreeRoot()
require.NoError(t, err)
assert.DeepEqual(t, expectedRoot[:], headers.Data[i].Header.Message.BodyRoot)
assert.Equal(t, blk.Block.ProposerIndex, headers.Data[i].Header.Message.ProposerIndex)
}
})
}
})
t.Run("execution optimistic", func(t *testing.T) {
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block)
require.NoError(t, err)
mockChainFetcher := &mock.ChainService{
DB: beaconDB,
Block: wsb,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
Optimistic: true,
FinalizedRoots: map[[32]byte]bool{},
OptimisticRoots: map[[32]byte]bool{
bytesutil.ToBytes32(blkContainers[30].BlockRoot): true,
},
}
bs := &Server{
BeaconDB: beaconDB,
ChainInfoFetcher: mockChainFetcher,
OptimisticModeFetcher: mockChainFetcher,
FinalizationFetcher: mockChainFetcher,
}
slot := primitives.Slot(30)
headers, err := bs.ListBlockHeaders(ctx, &ethpbv1.BlockHeadersRequest{
Slot: &slot,
})
require.NoError(t, err)
assert.Equal(t, true, headers.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
wsb, err := blocks.NewSignedBeaconBlock(headBlock.Block.(*ethpbalpha.BeaconBlockContainer_Phase0Block).Phase0Block)
require.NoError(t, err)
child1 := util.NewBeaconBlock()
child1.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32)
child1.Block.Slot = 999
util.SaveBlock(t, ctx, beaconDB, child1)
child2 := util.NewBeaconBlock()
child2.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32)
child2.Block.Slot = 1000
util.SaveBlock(t, ctx, beaconDB, child2)
child1Root, err := child1.Block.HashTreeRoot()
require.NoError(t, err)
child2Root, err := child2.Block.HashTreeRoot()
require.NoError(t, err)
mockChainFetcher := &mock.ChainService{
DB: beaconDB,
Block: wsb,
Root: headBlock.BlockRoot,
FinalizedCheckPoint: &ethpbalpha.Checkpoint{Root: blkContainers[64].BlockRoot},
FinalizedRoots: map[[32]byte]bool{child1Root: true, child2Root: false},
}
bs := &Server{
BeaconDB: beaconDB,
ChainInfoFetcher: mockChainFetcher,
OptimisticModeFetcher: mockChainFetcher,
FinalizationFetcher: mockChainFetcher,
}
t.Run("true", func(t *testing.T) {
slot := primitives.Slot(999)
headers, err := bs.ListBlockHeaders(ctx, &ethpbv1.BlockHeadersRequest{
Slot: &slot,
})
require.NoError(t, err)
assert.Equal(t, true, headers.Finalized)
})
t.Run("false", func(t *testing.T) {
slot := primitives.Slot(1000)
headers, err := bs.ListBlockHeaders(ctx, &ethpbv1.BlockHeadersRequest{
Slot: &slot,
})
require.NoError(t, err)
assert.Equal(t, false, headers.Finalized)
})
t.Run("false when at least one not finalized", func(t *testing.T) {
headers, err := bs.ListBlockHeaders(ctx, &ethpbv1.BlockHeadersRequest{
ParentRoot: []byte("parent"),
})
require.NoError(t, err)
assert.Equal(t, false, headers.Finalized)
})
})
}
func TestServer_SubmitBlock(t *testing.T) {
ctrl := gomock.NewController(t)
t.Run("Phase 0", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBeaconBlockContentsContainer_Phase0Block{Phase0Block: &ethpbv1.SignedBeaconBlock{}},
}
_, err := server.SubmitBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Altair", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBeaconBlockContentsContainer_AltairBlock{AltairBlock: &ethpbv2.SignedBeaconBlockAltair{}},
}
_, err := server.SubmitBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Bellatrix", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBeaconBlockContentsContainer_BellatrixBlock{BellatrixBlock: &ethpbv2.SignedBeaconBlockBellatrix{}},
}
_, err := server.SubmitBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Capella", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBeaconBlockContentsContainer_CapellaBlock{CapellaBlock: &ethpbv2.SignedBeaconBlockCapella{}},
}
_, err := server.SubmitBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("Deneb", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
blockReq := &ethpbv2.SignedBeaconBlockContentsContainer{
Message: &ethpbv2.SignedBeaconBlockContentsContainer_DenebContents{
DenebContents: &ethpbv2.SignedBeaconBlockContentsDeneb{
SignedBlock: &ethpbv2.SignedBeaconBlockDeneb{},
SignedBlobSidecars: []*ethpbv2.SignedBlobSidecar{},
},
},
}
_, err := server.SubmitBlock(context.Background(), blockReq)
assert.NoError(t, err)
})
t.Run("sync not ready", func(t *testing.T) {
chainService := &mock.ChainService{}
v1Server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: true},
HeadFetcher: chainService,
TimeFetcher: chainService,
OptimisticModeFetcher: chainService,
}
_, err := v1Server.SubmitBlock(context.Background(), nil)
require.ErrorContains(t, "Syncing to latest head", err)
})
}
func TestServer_SubmitBlockSSZ(t *testing.T) {
ctrl := gomock.NewController(t)
ctx := context.Background()
t.Run("Phase 0", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlock()
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "phase0")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Altair", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlockAltair()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().AltairForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "altair")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Bellatrix", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlockBellatrix()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().BellatrixForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "bellatrix")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Bellatrix blinded", func(t *testing.T) {
server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBlindedBeaconBlockBellatrix()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().BellatrixForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "bellatrix")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NotNil(t, err)
})
t.Run("Capella", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBeaconBlockCapella()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "capella")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Capella blinded", func(t *testing.T) {
server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b := util.NewBlindedBeaconBlockCapella()
b.Block.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "capella")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NotNil(t, err)
})
t.Run("Deneb", func(t *testing.T) {
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().ProposeBeaconBlock(gomock.Any(), gomock.Any())
server := &Server{
V1Alpha1ValidatorServer: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b, err := util.NewBeaconBlockContentsDeneb(fieldparams.MaxBlobsPerBlock)
require.NoError(t, err)
// TODO: update to deneb fork epoch
b.SignedBlock.Message.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "deneb")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NoError(t, err)
})
t.Run("Deneb blinded", func(t *testing.T) {
server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
}
b, err := util.NewBlindedBeaconBlockContentsDeneb(fieldparams.MaxBlobsPerBlock)
require.NoError(t, err)
// TODO: update to deneb fork epoch
b.SignedBlindedBlock.Message.Slot = params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().CapellaForkEpoch))
ssz, err := b.MarshalSSZ()
require.NoError(t, err)
blockReq := &ethpbv2.SSZContainer{
Data: ssz,
}
md := metadata.MD{}
md.Set(api.VersionHeader, "deneb")
sszCtx := metadata.NewIncomingContext(ctx, md)
_, err = server.SubmitBlockSSZ(sszCtx, blockReq)
assert.NotNil(t, err)
})
t.Run("sync not ready", func(t *testing.T) {
chainService := &mock.ChainService{}
v1Server := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: true},
HeadFetcher: chainService,
TimeFetcher: chainService,
OptimisticModeFetcher: chainService,
}
_, err := v1Server.SubmitBlockSSZ(context.Background(), nil)
require.ErrorContains(t, "Syncing to latest head", err)
})
}
func TestServer_GetBlock(t *testing.T) {
ctx := context.Background()
b := util.NewBeaconBlock()

View File

@@ -67,19 +67,6 @@ func (_ *Server) GetSpec(ctx context.Context, _ *emptypb.Empty) (*ethpb.SpecResp
return &ethpb.SpecResponse{Data: data}, nil
}
// GetDepositContract retrieves deposit contract address and genesis fork version.
func (_ *Server) GetDepositContract(ctx context.Context, _ *emptypb.Empty) (*ethpb.DepositContractResponse, error) {
ctx, span := trace.StartSpan(ctx, "beaconv1.GetDepositContract")
defer span.End()
return &ethpb.DepositContractResponse{
Data: &ethpb.DepositContract{
ChainId: params.BeaconConfig().DepositChainID,
Address: params.BeaconConfig().DepositContractAddress,
},
}, nil
}
func prepareConfigSpec() (map[string]string, error) {
data := make(map[string]string)
config := *params.BeaconConfig()

View File

@@ -141,7 +141,7 @@ func TestGetSpec(t *testing.T) {
resp, err := server.GetSpec(context.Background(), &emptypb.Empty{})
require.NoError(t, err)
assert.Equal(t, 111, len(resp.Data))
assert.Equal(t, 112, len(resp.Data))
for k, v := range resp.Data {
switch k {
case "CONFIG_NAME":
@@ -380,6 +380,8 @@ func TestGetSpec(t *testing.T) {
assert.Equal(t, "20", v)
case "REORG_PARENT_WEIGHT_THRESHOLD":
assert.Equal(t, "160", v)
case "MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT":
assert.Equal(t, "8", v)
case "SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY":
default:
t.Errorf("Incorrect key: %s", k)
@@ -387,22 +389,6 @@ func TestGetSpec(t *testing.T) {
}
}
func TestGetDepositContract(t *testing.T) {
const chainId = 99
const address = "0x0000000000000000000000000000000000000009"
params.SetupTestConfigCleanup(t)
config := params.BeaconConfig().Copy()
config.DepositChainID = chainId
config.DepositContractAddress = address
params.OverrideBeaconConfig(config)
s := Server{}
resp, err := s.GetDepositContract(context.Background(), &emptypb.Empty{})
require.NoError(t, err)
assert.Equal(t, uint64(chainId), resp.Data.ChainId)
assert.Equal(t, address, resp.Data.Address)
}
func TestForkSchedule_Ok(t *testing.T) {
genesisForkVersion := []byte("Genesis")
firstForkVersion, firstForkEpoch := []byte("Firs"), primitives.Epoch(100)

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,12 @@ import (
"strconv"
"strings"
"github.com/go-playground/validator/v10"
"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"
corehelpers "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/transition"
"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/consensus-types/primitives"
@@ -89,11 +89,6 @@ func (s *Server) SubmitAttestations(w http.ResponseWriter, r *http.Request) {
http2.HandleError(w, "No data submitted", http.StatusBadRequest)
return
}
validate := validator.New()
if err := validate.Struct(req); err != nil {
http2.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
var validAttestations []*ethpbalpha.Attestation
var attFailures []*shared.IndexedVerificationFailure
@@ -209,11 +204,6 @@ func (s *Server) SubmitVoluntaryExit(w http.ResponseWriter, r *http.Request) {
http2.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
validate := validator.New()
if err := validate.Struct(req); err != nil {
http2.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
exit, err := req.ToConsensus()
if err != nil {
@@ -292,8 +282,8 @@ func (s *Server) SubmitSyncCommitteeSignatures(w http.ResponseWriter, r *http.Re
}
for _, msg := range validMessages {
if err = s.CoreService.SubmitSyncMessage(ctx, msg); err != nil {
http2.HandleError(w, "Could not submit message: "+err.Error(), http.StatusInternalServerError)
if rpcerr := s.CoreService.SubmitSyncMessage(ctx, msg); rpcerr != nil {
http2.HandleError(w, "Could not submit message: "+rpcerr.Err.Error(), core.ErrorReasonToHTTP(rpcerr.Reason))
return
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,387 @@
package beacon
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
"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/state"
statenative "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/state-native"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
http2 "github.com/prysmaticlabs/prysm/v4/network/http"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"go.opencensus.io/trace"
)
// GetValidators returns filterable list of validators with their balance, status and index.
func (s *Server) GetValidators(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetValidators")
defer span.End()
query := r.URL.Query()
helpers.NormalizeQueryValues(query)
r.URL.RawQuery = query.Encode()
stateId := mux.Vars(r)["state_id"]
if stateId == "" {
http2.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
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)
rawIds := r.URL.Query()["id"]
ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */)
if !ok {
return
}
// return no data if all IDs are ignored
if len(rawIds) > 0 && len(ids) == 0 {
resp := &GetValidatorsResponse{
Data: []*ValidatorContainer{},
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
http2.WriteJson(w, resp)
return
}
readOnlyVals, ok := valsFromIds(w, st, ids)
if !ok {
return
}
epoch := slots.ToEpoch(st.Slot())
allBalances := st.Balances()
statuses := r.URL.Query()["status"]
for i, ss := range statuses {
statuses[i] = strings.ToLower(ss)
}
// Exit early if no matching validators were found or we don't want to further filter validators by status.
if len(readOnlyVals) == 0 || len(statuses) == 0 {
containers := make([]*ValidatorContainer, len(readOnlyVals))
for i, val := range readOnlyVals {
valStatus, err := helpers.ValidatorSubStatus(val, epoch)
if err != nil {
http2.HandleError(w, "Could not get validator status: "+err.Error(), http.StatusInternalServerError)
return
}
if len(ids) == 0 {
containers[i] = valContainerFromReadOnlyVal(val, primitives.ValidatorIndex(i), allBalances[i], valStatus)
} else {
containers[i] = valContainerFromReadOnlyVal(val, ids[i], allBalances[ids[i]], valStatus)
}
}
resp := &GetValidatorsResponse{
Data: containers,
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
http2.WriteJson(w, resp)
return
}
filteredStatuses := make(map[validator.ValidatorStatus]bool, len(statuses))
for _, ss := range statuses {
ok, vs := validator.ValidatorStatusFromString(ss)
if !ok {
http2.HandleError(w, "Invalid status "+ss, http.StatusBadRequest)
return
}
filteredStatuses[vs] = true
}
valContainers := make([]*ValidatorContainer, 0, len(readOnlyVals))
for i, val := range readOnlyVals {
valStatus, err := helpers.ValidatorStatus(val, epoch)
if err != nil {
http2.HandleError(w, "Could not get validator status: "+err.Error(), http.StatusInternalServerError)
return
}
valSubStatus, err := helpers.ValidatorSubStatus(val, epoch)
if err != nil {
http2.HandleError(w, "Could not get validator status: "+err.Error(), http.StatusInternalServerError)
return
}
if filteredStatuses[valStatus] || filteredStatuses[valSubStatus] {
var container *ValidatorContainer
if len(ids) == 0 {
container = valContainerFromReadOnlyVal(val, primitives.ValidatorIndex(i), allBalances[i], valSubStatus)
} else {
container = valContainerFromReadOnlyVal(val, ids[i], allBalances[ids[i]], valSubStatus)
}
valContainers = append(valContainers, container)
}
}
resp := &GetValidatorsResponse{
Data: valContainers,
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
http2.WriteJson(w, resp)
}
// GetValidator returns a validator specified by state and id or public key along with status and balance.
func (s *Server) GetValidator(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetValidator")
defer span.End()
stateId := mux.Vars(r)["state_id"]
if stateId == "" {
http2.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
return
}
valId := mux.Vars(r)["validator_id"]
if valId == "" {
http2.HandleError(w, "validator_id is required in URL params", http.StatusBadRequest)
return
}
st, err := s.Stater.State(ctx, []byte(stateId))
if err != nil {
shared.WriteStateFetchError(w, err)
return
}
ids, ok := decodeIds(w, st, []string{valId}, false /* ignore unknown */)
if !ok {
return
}
readOnlyVals, ok := valsFromIds(w, st, ids)
if !ok {
return
}
if len(ids) == 0 || len(readOnlyVals) == 0 {
http2.HandleError(w, "No validator returned for the given ID", http.StatusInternalServerError)
return
}
valSubStatus, err := helpers.ValidatorSubStatus(readOnlyVals[0], slots.ToEpoch(st.Slot()))
if err != nil {
http2.HandleError(w, "Could not get validator status: "+err.Error(), http.StatusInternalServerError)
return
}
bal, err := st.BalanceAtIndex(ids[0])
if err != nil {
http2.HandleError(w, "Could not get validator balance: "+err.Error(), http.StatusInternalServerError)
return
}
container := valContainerFromReadOnlyVal(readOnlyVals[0], ids[0], bal, valSubStatus)
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 := &GetValidatorResponse{
Data: container,
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
http2.WriteJson(w, resp)
}
// GetValidatorBalances returns a filterable list of validator balances.
func (bs *Server) GetValidatorBalances(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetValidatorBalances")
defer span.End()
query := r.URL.Query()
helpers.NormalizeQueryValues(query)
r.URL.RawQuery = query.Encode()
stateId := mux.Vars(r)["state_id"]
if stateId == "" {
http2.HandleError(w, "state_id is required in URL params", http.StatusBadRequest)
return
}
st, err := bs.Stater.State(ctx, []byte(stateId))
if err != nil {
shared.WriteStateFetchError(w, err)
return
}
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), bs.OptimisticModeFetcher, bs.Stater, bs.ChainInfoFetcher, bs.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 := bs.FinalizationFetcher.IsFinalized(ctx, blockRoot)
rawIds := r.URL.Query()["id"]
ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */)
if !ok {
return
}
// return no data if all IDs are ignored
if len(rawIds) > 0 && len(ids) == 0 {
resp := &GetValidatorBalancesResponse{
Data: []*ValidatorBalance{},
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
http2.WriteJson(w, resp)
return
}
bals := st.Balances()
var valBalances []*ValidatorBalance
if len(ids) == 0 {
valBalances = make([]*ValidatorBalance, len(bals))
for i, b := range bals {
valBalances[i] = &ValidatorBalance{
Index: strconv.FormatUint(uint64(i), 10),
Balance: strconv.FormatUint(b, 10),
}
}
} else {
valBalances = make([]*ValidatorBalance, len(ids))
for i, id := range ids {
valBalances[i] = &ValidatorBalance{
Index: strconv.FormatUint(uint64(id), 10),
Balance: strconv.FormatUint(bals[id], 10),
}
}
}
resp := &GetValidatorBalancesResponse{
Data: valBalances,
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
http2.WriteJson(w, resp)
}
// decodeIds takes in a list of validator ID strings (as either a pubkey or a validator index)
// and returns the corresponding validator indices. It can be configured to ignore well-formed but unknown indices.
func decodeIds(w http.ResponseWriter, st state.BeaconState, rawIds []string, ignoreUnknown bool) ([]primitives.ValidatorIndex, bool) {
ids := make([]primitives.ValidatorIndex, 0, len(rawIds))
numVals := uint64(st.NumValidators())
for _, rawId := range rawIds {
pubkey, err := hexutil.Decode(rawId)
if err == nil {
if len(pubkey) != fieldparams.BLSPubkeyLength {
http2.HandleError(w, fmt.Sprintf("Pubkey length is %d instead of %d", len(pubkey), fieldparams.BLSPubkeyLength), http.StatusBadRequest)
return nil, false
}
valIndex, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubkey))
if !ok {
if ignoreUnknown {
continue
}
http2.HandleError(w, fmt.Sprintf("Unknown pubkey %s", pubkey), http.StatusBadRequest)
return nil, false
}
ids = append(ids, valIndex)
continue
}
index, err := strconv.ParseUint(rawId, 10, 64)
if err != nil {
http2.HandleError(w, fmt.Sprintf("Invalid validator index %s", rawId), http.StatusBadRequest)
return nil, false
}
if index >= numVals {
if ignoreUnknown {
continue
}
http2.HandleError(w, fmt.Sprintf("Invalid validator index %d", index), http.StatusBadRequest)
return nil, false
}
ids = append(ids, primitives.ValidatorIndex(index))
}
return ids, true
}
// valsFromIds returns read-only validators based on the supplied validator indices.
func valsFromIds(w http.ResponseWriter, st state.BeaconState, ids []primitives.ValidatorIndex) ([]state.ReadOnlyValidator, bool) {
var vals []state.ReadOnlyValidator
if len(ids) == 0 {
allVals := st.Validators()
vals = make([]state.ReadOnlyValidator, len(allVals))
for i, val := range allVals {
readOnlyVal, err := statenative.NewValidator(val)
if err != nil {
http2.HandleError(w, "Could not convert validator: "+err.Error(), http.StatusInternalServerError)
return nil, false
}
vals[i] = readOnlyVal
}
} else {
vals = make([]state.ReadOnlyValidator, 0, len(ids))
for _, id := range ids {
val, err := st.ValidatorAtIndex(id)
if err != nil {
http2.HandleError(w, fmt.Sprintf("Could not get validator at index %d: %s", id, err.Error()), http.StatusInternalServerError)
return nil, false
}
readOnlyVal, err := statenative.NewValidator(val)
if err != nil {
http2.HandleError(w, "Could not convert validator: "+err.Error(), http.StatusInternalServerError)
return nil, false
}
vals = append(vals, readOnlyVal)
}
}
return vals, true
}
func valContainerFromReadOnlyVal(
val state.ReadOnlyValidator,
id primitives.ValidatorIndex,
bal uint64,
valStatus validator.ValidatorStatus,
) *ValidatorContainer {
pubkey := val.PublicKey()
return &ValidatorContainer{
Index: strconv.FormatUint(uint64(id), 10),
Balance: strconv.FormatUint(bal, 10),
Status: valStatus.String(),
Validator: &Validator{
Pubkey: hexutil.Encode(pubkey[:]),
WithdrawalCredentials: hexutil.Encode(val.WithdrawalCredentials()),
EffectiveBalance: strconv.FormatUint(val.EffectiveBalance(), 10),
Slashed: val.Slashed(),
ActivationEligibilityEpoch: strconv.FormatUint(uint64(val.ActivationEligibilityEpoch()), 10),
ActivationEpoch: strconv.FormatUint(uint64(val.ActivationEpoch()), 10),
ExitEpoch: strconv.FormatUint(uint64(val.ExitEpoch()), 10),
WithdrawableEpoch: strconv.FormatUint(uint64(val.WithdrawableEpoch()), 10),
},
}
}

View File

@@ -0,0 +1,984 @@
package beacon
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gorilla/mux"
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/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"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"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/testing/assert"
"github.com/prysmaticlabs/prysm/v4/testing/require"
"github.com/prysmaticlabs/prysm/v4/testing/util"
)
func TestGetValidators(t *testing.T) {
var st state.BeaconState
st, _ = util.DeterministicGenesisState(t, 8192)
t.Run("get all", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 8192, len(resp.Data))
val := resp.Data[0]
assert.Equal(t, "0", val.Index)
assert.Equal(t, "32000000000", val.Balance)
assert.Equal(t, "active_ongoing", val.Status)
require.NotNil(t, val.Validator)
assert.Equal(t, "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", val.Validator.Pubkey)
assert.Equal(t, "0x00ec7ef7780c9d151597924036262dd28dc60e1228f4da6fecf9d402cb3f3594", val.Validator.WithdrawalCredentials)
assert.Equal(t, "32000000000", val.Validator.EffectiveBalance)
assert.Equal(t, false, val.Validator.Slashed)
assert.Equal(t, "0", val.Validator.ActivationEligibilityEpoch)
assert.Equal(t, "0", val.Validator.ActivationEpoch)
assert.Equal(t, "18446744073709551615", val.Validator.ExitEpoch)
assert.Equal(t, "18446744073709551615", val.Validator.WithdrawableEpoch)
})
t.Run("get by index", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(
http.MethodGet,
"http://example.com/eth/v1/beacon/states/{state_id}/validators?id=15&id=26",
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "15", resp.Data[0].Index)
assert.Equal(t, "26", resp.Data[1].Index)
})
t.Run("get by pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20))
pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66))
hexPubkey1 := hexutil.Encode(pubkey1[:])
hexPubkey2 := hexutil.Encode(pubkey2[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=%s", hexPubkey1, hexPubkey2),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "20", resp.Data[0].Index)
assert.Equal(t, "66", resp.Data[1].Index)
})
t.Run("get by both index and pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(20))
hexPubkey := hexutil.Encode(pubkey[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=60", hexPubkey),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "20", resp.Data[0].Index)
assert.Equal(t, "60", resp.Data[1].Index)
})
t.Run("multiple comma-separated", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(20))
hexPubkey := hexutil.Encode(pubkey[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s,60", hexPubkey),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "20", resp.Data[0].Index)
assert.Equal(t, "60", resp.Data[1].Index)
})
t.Run("state ID required", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: &chainMock.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(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.StringContains(t, "state_id is required in URL params", e.Message)
})
t.Run("unknown pubkey is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(1))
hexPubkey := hexutil.Encode(pubkey[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=%s", hexPubkey, hexutil.Encode([]byte(strings.Repeat("x", fieldparams.BLSPubkeyLength)))),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 1, len(resp.Data))
assert.Equal(t, "1", resp.Data[0].Index)
})
t.Run("unknown index is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?id=1&id=99999", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 1, len(resp.Data))
assert.Equal(t, "1", resp.Data[0].Index)
})
t.Run("execution optimistic", func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: true}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
headerRoot, err := st.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,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
})
}
func TestListValidators_FilterByStatus(t *testing.T) {
var st state.BeaconState
st, _ = util.DeterministicGenesisState(t, 8192)
farFutureEpoch := params.BeaconConfig().FarFutureEpoch
validators := []*eth.Validator{
// Pending initialized.
{
ActivationEpoch: farFutureEpoch,
ActivationEligibilityEpoch: farFutureEpoch,
},
// Pending queued.
{
ActivationEpoch: 10,
ActivationEligibilityEpoch: 4,
},
// Active ongoing.
{
ActivationEpoch: 0,
ExitEpoch: farFutureEpoch,
},
// Active slashed.
{
ActivationEpoch: 0,
ExitEpoch: 30,
Slashed: true,
},
// Active exiting.
{
ActivationEpoch: 3,
ExitEpoch: 30,
Slashed: false,
},
// Exited slashed (at epoch 35).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
Slashed: true,
},
// Exited unslashed (at epoch 35).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
Slashed: false,
},
// Withdrawable (at epoch 45).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
Slashed: false,
},
// Withdrawal done (at epoch 45).
{
ActivationEpoch: 3,
ExitEpoch: 30,
WithdrawableEpoch: 40,
EffectiveBalance: 0,
Slashed: false,
},
}
for _, val := range validators {
require.NoError(t, st.AppendValidator(val))
require.NoError(t, st.AppendBalance(params.BeaconConfig().MaxEffectiveBalance))
}
t.Run("active", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &lookup.BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: st},
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?status=active", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, 8192+2, len(resp.Data))
for _, vc := range resp.Data {
assert.Equal(
t,
true,
vc.Status == "active_ongoing" ||
vc.Status == "active_exiting" ||
vc.Status == "active_slashed",
)
}
})
t.Run("active_ongoing", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &lookup.BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: st},
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?status=active_ongoing", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, 8192+1, len(resp.Data))
for _, vc := range resp.Data {
require.Equal(
t,
true,
vc.Status == "active_ongoing",
)
}
})
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch*35))
t.Run("exited", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &lookup.BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: st},
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators?status=exited", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, 4, len(resp.Data))
for _, vc := range resp.Data {
require.Equal(
t,
true,
vc.Status == "exited_unslashed" || vc.Status == "exited_slashed",
)
}
})
t.Run("pending_initialized and exited_unslashed", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &lookup.BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: st},
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(
http.MethodGet,
"http://example.com/eth/v1/beacon/states/{state_id}/validators?status=pending_initialized&status=exited_unslashed",
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, 4, len(resp.Data))
for _, vc := range resp.Data {
require.Equal(
t,
true,
vc.Status == "pending_initialized" || vc.Status == "exited_unslashed",
)
}
})
t.Run("pending and exited_slashed", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &lookup.BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: st},
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(
http.MethodGet,
"http://example.com/eth/v1/beacon/states/{state_id}/validators?status=pending&status=exited_slashed",
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, 2, len(resp.Data))
for _, vc := range resp.Data {
require.Equal(
t,
true,
vc.Status == "pending_initialized" || vc.Status == "exited_slashed",
)
}
})
}
func TestGetValidator(t *testing.T) {
var st state.BeaconState
st, _ = util.DeterministicGenesisState(t, 8192)
t.Run("get by index", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "15"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "15", resp.Data.Index)
assert.Equal(t, "32000000000", resp.Data.Balance)
assert.Equal(t, "active_ongoing", resp.Data.Status)
require.NotNil(t, resp.Data.Validator)
assert.Equal(t, "0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f", resp.Data.Validator.Pubkey)
assert.Equal(t, "0x00b24fc624e56a5ed42a9639691e27e34b783c7237030367bd17cbef65fa6ccf", resp.Data.Validator.WithdrawalCredentials)
assert.Equal(t, "32000000000", resp.Data.Validator.EffectiveBalance)
assert.Equal(t, false, resp.Data.Validator.Slashed)
assert.Equal(t, "0", resp.Data.Validator.ActivationEligibilityEpoch)
assert.Equal(t, "0", resp.Data.Validator.ActivationEpoch)
assert.Equal(t, "18446744073709551615", resp.Data.Validator.ExitEpoch)
assert.Equal(t, "18446744073709551615", resp.Data.Validator.WithdrawableEpoch)
})
t.Run("get by pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubKey := st.PubkeyAtIndex(primitives.ValidatorIndex(20))
hexPubkey := hexutil.Encode(pubKey[:])
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": hexPubkey})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "20", resp.Data.Index)
})
t.Run("state ID required", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: &chainMock.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"validator_id": "1"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(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.StringContains(t, "state_id is required in URL params", e.Message)
})
t.Run("validator ID required", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: &chainMock.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(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.StringContains(t, "validator_id is required in URL params", e.Message)
})
t.Run("unknown index", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: &chainMock.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "99999"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(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.StringContains(t, "Invalid validator index", e.Message)
})
t.Run("unknown pubkey", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: &chainMock.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": hexutil.Encode([]byte(strings.Repeat("x", fieldparams.BLSPubkeyLength)))})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(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.StringContains(t, "Unknown pubkey", e.Message)
})
t.Run("execution optimistic", func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: true}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "15"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
headerRoot, err := st.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,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validators/{validator_id}", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head", "validator_id": "15"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
})
}
func TestGetValidatorBalances(t *testing.T) {
var st state.BeaconState
count := uint64(8192)
st, _ = util.DeterministicGenesisState(t, count)
balances := make([]uint64, count)
for i := uint64(0); i < count; i++ {
balances[i] = i
}
require.NoError(t, st.SetBalances(balances))
t.Run("get all", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 8192, len(resp.Data))
val := resp.Data[123]
assert.Equal(t, "123", val.Index)
assert.Equal(t, "123", val.Balance)
})
t.Run("get by index", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(
http.MethodGet,
"http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=15&id=26",
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "15", resp.Data[0].Index)
assert.Equal(t, "26", resp.Data[1].Index)
})
t.Run("get by pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey1 := st.PubkeyAtIndex(primitives.ValidatorIndex(20))
pubkey2 := st.PubkeyAtIndex(primitives.ValidatorIndex(66))
hexPubkey1 := hexutil.Encode(pubkey1[:])
hexPubkey2 := hexutil.Encode(pubkey2[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=%s&id=%s", hexPubkey1, hexPubkey2),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "20", resp.Data[0].Index)
assert.Equal(t, "66", resp.Data[1].Index)
})
t.Run("get by both index and pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(20))
hexPubkey := hexutil.Encode(pubkey[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=60", hexPubkey),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "20", resp.Data[0].Index)
assert.Equal(t, "60", resp.Data[1].Index)
})
t.Run("unknown pubkey is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.PubkeyAtIndex(primitives.ValidatorIndex(1))
hexPubkey := hexutil.Encode(pubkey[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=%s&id=%s", hexPubkey, hexutil.Encode([]byte(strings.Repeat("x", fieldparams.BLSPubkeyLength)))),
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 1, len(resp.Data))
assert.Equal(t, "1", resp.Data[0].Index)
})
t.Run("unknown index is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=1&id=99999", nil)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 1, len(resp.Data))
assert.Equal(t, "1", resp.Data[0].Index)
})
t.Run("state ID required", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: &chainMock.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/states/{state_id}/validator_balances", nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(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.StringContains(t, "state_id is required in URL params", e.Message)
})
t.Run("execution optimistic", func(t *testing.T) {
chainService := &chainMock.ChainService{Optimistic: true}
s := Server{
Stater: &testutil.MockStater{
BeaconState: st,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
request := httptest.NewRequest(
http.MethodGet,
"http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=15",
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
headerRoot, err := st.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,
}
request := httptest.NewRequest(
http.MethodGet,
"http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=15",
nil,
)
request = mux.SetURLVars(request, map[string]string{"state_id": "head"})
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
})
}

View File

@@ -1,7 +1,6 @@
package beacon
import (
"bytes"
"context"
"strconv"
@@ -12,13 +11,10 @@ import (
"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"
eth "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"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type stateRequest struct {
@@ -26,33 +22,6 @@ type stateRequest struct {
stateId []byte
}
// GetGenesis retrieves details of the chain's genesis which can be used to identify chain.
func (bs *Server) GetGenesis(ctx context.Context, _ *emptypb.Empty) (*ethpb.GenesisResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.GetGenesis")
defer span.End()
genesisTime := bs.GenesisTimeFetcher.GenesisTime()
if genesisTime.IsZero() {
return nil, status.Errorf(codes.NotFound, "Chain genesis info is not yet known")
}
validatorRoot := bs.ChainInfoFetcher.GenesisValidatorsRoot()
if bytes.Equal(validatorRoot[:], params.BeaconConfig().ZeroHash[:]) {
return nil, status.Errorf(codes.NotFound, "Chain genesis info is not yet known")
}
forkVersion := params.BeaconConfig().GenesisForkVersion
return &ethpb.GenesisResponse{
Data: &ethpb.GenesisResponse_Genesis{
GenesisTime: &timestamppb.Timestamp{
Seconds: genesisTime.Unix(),
Nanos: 0,
},
GenesisValidatorsRoot: validatorRoot[:],
GenesisForkVersion: forkVersion,
},
}, nil
}
// 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")
@@ -90,68 +59,6 @@ func (bs *Server) GetStateRoot(ctx context.Context, req *ethpb.StateRequest) (*e
}, nil
}
// GetStateFork returns Fork object for state with given 'stateId'.
func (bs *Server) GetStateFork(ctx context.Context, req *ethpb.StateRequest) (*ethpb.StateForkResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.GetStateFork")
defer span.End()
st, err := bs.Stater.State(ctx, req.StateId)
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
fork := st.Fork()
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.StateForkResponse{
Data: &ethpb.Fork{
PreviousVersion: fork.PreviousVersion,
CurrentVersion: fork.CurrentVersion,
Epoch: fork.Epoch,
},
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}, nil
}
// GetFinalityCheckpoints returns finality checkpoints for state with given 'stateId'. In case finality is
// not yet achieved, checkpoint should return epoch 0 and ZERO_HASH as root.
func (bs *Server) GetFinalityCheckpoints(ctx context.Context, req *ethpb.StateRequest) (*ethpb.StateFinalityCheckpointResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.GetFinalityCheckpoints")
defer span.End()
st, err := bs.Stater.State(ctx, req.StateId)
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(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.StateFinalityCheckpointResponse{
Data: &ethpb.StateFinalityCheckpointResponse_StateFinalityCheckpoint{
PreviousJustified: checkpoint(st.PreviousJustifiedCheckpoint()),
CurrentJustified: checkpoint(st.CurrentJustifiedCheckpoint()),
Finalized: checkpoint(st.FinalizedCheckpoint()),
},
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.
@@ -228,16 +135,3 @@ func (bs *Server) stateFromRequest(ctx context.Context, req *stateRequest) (stat
}
return st, nil
}
func checkpoint(sourceCheckpoint *eth.Checkpoint) *ethpb.Checkpoint {
if sourceCheckpoint != nil {
return &ethpb.Checkpoint{
Epoch: sourceCheckpoint.Epoch,
Root: sourceCheckpoint.Root,
}
}
return &ethpb.Checkpoint{
Epoch: 0,
Root: params.BeaconConfig().ZeroHash[:],
}
}

View File

@@ -3,7 +3,6 @@ package beacon
import (
"context"
"testing"
"time"
chainMock "github.com/prysmaticlabs/prysm/v4/beacon-chain/blockchain/testing"
dbTest "github.com/prysmaticlabs/prysm/v4/beacon-chain/db/testing"
@@ -13,66 +12,11 @@ import (
"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"
ethpb "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/protobuf/types/known/emptypb"
)
func TestGetGenesis(t *testing.T) {
ctx := context.Background()
params.SetupTestConfigCleanup(t)
config := params.BeaconConfig().Copy()
config.GenesisForkVersion = []byte("genesis")
params.OverrideBeaconConfig(config)
genesis := time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
validatorsRoot := [32]byte{1, 2, 3, 4, 5, 6}
t.Run("OK", func(t *testing.T) {
chainService := &chainMock.ChainService{
Genesis: genesis,
ValidatorsRoot: validatorsRoot,
}
s := Server{
GenesisTimeFetcher: chainService,
ChainInfoFetcher: chainService,
}
resp, err := s.GetGenesis(ctx, &emptypb.Empty{})
require.NoError(t, err)
assert.Equal(t, genesis.Unix(), resp.Data.GenesisTime.Seconds)
assert.Equal(t, int32(0), resp.Data.GenesisTime.Nanos)
assert.DeepEqual(t, validatorsRoot[:], resp.Data.GenesisValidatorsRoot)
assert.DeepEqual(t, []byte("genesis"), resp.Data.GenesisForkVersion)
})
t.Run("No genesis time", func(t *testing.T) {
chainService := &chainMock.ChainService{
Genesis: time.Time{},
ValidatorsRoot: validatorsRoot,
}
s := Server{
GenesisTimeFetcher: chainService,
ChainInfoFetcher: chainService,
}
_, err := s.GetGenesis(ctx, &emptypb.Empty{})
assert.ErrorContains(t, "Chain genesis info is not yet known", err)
})
t.Run("No genesis validators root", func(t *testing.T) {
chainService := &chainMock.ChainService{
Genesis: genesis,
ValidatorsRoot: [32]byte{},
}
s := Server{
GenesisTimeFetcher: chainService,
ChainInfoFetcher: chainService,
}
_, err := s.GetGenesis(ctx, &emptypb.Empty{})
assert.ErrorContains(t, "Chain genesis info is not yet known", err)
})
}
func TestGetStateRoot(t *testing.T) {
ctx := context.Background()
fakeState, err := util.NewBeaconState()
@@ -163,207 +107,6 @@ func TestGetStateRoot(t *testing.T) {
})
}
func TestGetStateFork(t *testing.T) {
ctx := context.Background()
fillFork := func(state *ethpb.BeaconState) error {
state.Fork = &ethpb.Fork{
PreviousVersion: []byte("prev"),
CurrentVersion: []byte("curr"),
Epoch: 123,
}
return nil
}
fakeState, err := util.NewBeaconState(fillFork)
require.NoError(t, err)
db := dbTest.SetupDB(t)
chainService := &chainMock.ChainService{}
server := &Server{
Stater: &testutil.MockStater{
BeaconState: fakeState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
}
resp, err := server.GetStateFork(ctx, &eth.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
expectedFork := fakeState.Fork()
assert.Equal(t, expectedFork.Epoch, resp.Data.Epoch)
assert.DeepEqual(t, expectedFork.CurrentVersion, resp.Data.CurrentVersion)
assert.DeepEqual(t, expectedFork.PreviousVersion, resp.Data.PreviousVersion)
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: fakeState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
}
resp, err := server.GetStateFork(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{
BeaconState: fakeState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
}
resp, err := server.GetStateFork(context.Background(), &eth.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
assert.DeepEqual(t, true, resp.Finalized)
})
}
func TestGetFinalityCheckpoints(t *testing.T) {
ctx := context.Background()
fillCheckpoints := func(state *ethpb.BeaconState) error {
state.PreviousJustifiedCheckpoint = &ethpb.Checkpoint{
Root: bytesutil.PadTo([]byte("previous"), 32),
Epoch: 113,
}
state.CurrentJustifiedCheckpoint = &ethpb.Checkpoint{
Root: bytesutil.PadTo([]byte("current"), 32),
Epoch: 123,
}
state.FinalizedCheckpoint = &ethpb.Checkpoint{
Root: bytesutil.PadTo([]byte("finalized"), 32),
Epoch: 103,
}
return nil
}
fakeState, err := util.NewBeaconState(fillCheckpoints)
require.NoError(t, err)
db := dbTest.SetupDB(t)
chainService := &chainMock.ChainService{}
server := &Server{
Stater: &testutil.MockStater{
BeaconState: fakeState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
}
resp, err := server.GetFinalityCheckpoints(ctx, &eth.StateRequest{
StateId: []byte("head"),
})
require.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, fakeState.FinalizedCheckpoint().Epoch, resp.Data.Finalized.Epoch)
assert.DeepEqual(t, fakeState.FinalizedCheckpoint().Root, resp.Data.Finalized.Root)
assert.Equal(t, fakeState.CurrentJustifiedCheckpoint().Epoch, resp.Data.CurrentJustified.Epoch)
assert.DeepEqual(t, fakeState.CurrentJustifiedCheckpoint().Root, resp.Data.CurrentJustified.Root)
assert.Equal(t, fakeState.PreviousJustifiedCheckpoint().Epoch, resp.Data.PreviousJustified.Epoch)
assert.DeepEqual(t, fakeState.PreviousJustifiedCheckpoint().Root, resp.Data.PreviousJustified.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{
BeaconState: fakeState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
}
resp, err := server.GetFinalityCheckpoints(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{
BeaconState: fakeState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
BeaconDB: db,
}
resp, err := server.GetFinalityCheckpoints(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.PadTo([]byte("current"), 32)
mixOld := bytesutil.PadTo([]byte("old"), 32)

View File

@@ -12,6 +12,19 @@ type BlockRootResponse struct {
Finalized bool `json:"finalized"`
}
type GetCommitteesResponse struct {
Data []*shared.Committee `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type DepositContractResponse struct {
Data *struct {
ChainId string `json:"chain_id"`
Address string `json:"address"`
} `json:"data"`
}
type ListAttestationsResponse struct {
Data []*shared.Attestation `json:"data"`
}
@@ -27,3 +40,84 @@ type ListVoluntaryExitsResponse struct {
type SubmitSyncCommitteeSignaturesRequest struct {
Data []*shared.SyncCommitteeMessage `json:"data"`
}
type GetStateForkResponse struct {
Data *shared.Fork `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type GetFinalityCheckpointsResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data *FinalityCheckpoints `json:"data"`
}
type FinalityCheckpoints struct {
PreviousJustified *shared.Checkpoint `json:"previous_justified"`
CurrentJustified *shared.Checkpoint `json:"current_justified"`
Finalized *shared.Checkpoint `json:"finalized"`
}
type GetGenesisResponse struct {
Data *Genesis `json:"data"`
}
type Genesis struct {
GenesisTime string `json:"genesis_time"`
GenesisValidatorsRoot string `json:"genesis_validators_root"`
GenesisForkVersion string `json:"genesis_fork_version"`
}
type GetBlockHeadersResponse struct {
Data []*shared.SignedBeaconBlockHeaderContainer `json:"data"`
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
}
type GetBlockHeaderResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data *shared.SignedBeaconBlockHeaderContainer `json:"data"`
}
type GetValidatorsResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data []*ValidatorContainer `json:"data"`
}
type GetValidatorResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data *ValidatorContainer `json:"data"`
}
type GetValidatorBalancesResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data []*ValidatorBalance `json:"data"`
}
type ValidatorContainer struct {
Index string `json:"index"`
Balance string `json:"balance"`
Status string `json:"status"`
Validator *Validator `json:"validator"`
}
type Validator struct {
Pubkey string `json:"pubkey"`
WithdrawalCredentials string `json:"withdrawal_credentials"`
EffectiveBalance string `json:"effective_balance"`
Slashed bool `json:"slashed"`
ActivationEligibilityEpoch string `json:"activation_eligibility_epoch"`
ActivationEpoch string `json:"activation_epoch"`
ExitEpoch string `json:"exit_epoch"`
WithdrawableEpoch string `json:"withdrawable_epoch"`
}
type ValidatorBalance struct {
Index string `json:"index"`
Balance string `json:"balance"`
}

View File

@@ -1,319 +0,0 @@
package beacon
import (
"context"
"strconv"
"github.com/pkg/errors"
corehelpers "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/rpc/eth/helpers"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/state"
statenative "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/consensus-types/validator"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v4/proto/eth/v1"
"github.com/prysmaticlabs/prysm/v4/proto/migration"
"github.com/prysmaticlabs/prysm/v4/time/slots"
"go.opencensus.io/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// invalidValidatorIdError represents an error scenario where a validator's ID is invalid.
type invalidValidatorIdError struct {
message string
}
// newInvalidValidatorIdError creates a new error instance.
func newInvalidValidatorIdError(validatorId []byte, reason error) invalidValidatorIdError {
return invalidValidatorIdError{
message: errors.Wrapf(reason, "could not decode validator id '%s'", string(validatorId)).Error(),
}
}
// Error returns the underlying error message.
func (e *invalidValidatorIdError) Error() string {
return e.message
}
// GetValidator returns a validator specified by state and id or public key along with status and balance.
func (bs *Server) GetValidator(ctx context.Context, req *ethpb.StateValidatorRequest) (*ethpb.StateValidatorResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.GetValidator")
defer span.End()
st, err := bs.Stater.State(ctx, req.StateId)
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
if len(req.ValidatorId) == 0 {
return nil, status.Error(codes.InvalidArgument, "Validator ID is required")
}
valContainer, err := valContainersByRequestIds(st, [][]byte{req.ValidatorId})
if err != nil {
return nil, handleValContainerErr(err)
}
if len(valContainer) == 0 {
return nil, status.Error(codes.NotFound, "Could not find validator")
}
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.StateValidatorResponse{Data: valContainer[0], ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
}
// ListValidators returns filterable list of validators with their balance, status and index.
func (bs *Server) ListValidators(ctx context.Context, req *ethpb.StateValidatorsRequest) (*ethpb.StateValidatorsResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.ListValidators")
defer span.End()
st, err := bs.Stater.State(ctx, req.StateId)
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
valContainers, err := valContainersByRequestIds(st, req.Id)
if err != nil {
return nil, handleValContainerErr(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)
// Exit early if no matching validators we found or we don't want to further filter validators by status.
if len(valContainers) == 0 || len(req.Status) == 0 {
return &ethpb.StateValidatorsResponse{Data: valContainers, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
}
filterStatus := make(map[validator.ValidatorStatus]bool, len(req.Status))
const lastValidStatusValue = 12
for _, ss := range req.Status {
if ss > lastValidStatusValue {
return nil, status.Errorf(codes.InvalidArgument, "Invalid status "+ss.String())
}
filterStatus[validator.ValidatorStatus(ss)] = true
}
epoch := slots.ToEpoch(st.Slot())
filteredVals := make([]*ethpb.ValidatorContainer, 0, len(valContainers))
for _, vc := range valContainers {
readOnlyVal, err := statenative.NewValidator(migration.V1ValidatorToV1Alpha1(vc.Validator))
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert validator: %v", err)
}
valStatus, err := helpers.ValidatorStatus(readOnlyVal, epoch)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get validator status: %v", err)
}
valSubStatus, err := helpers.ValidatorSubStatus(readOnlyVal, epoch)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get validator sub status: %v", err)
}
if filterStatus[valStatus] || filterStatus[valSubStatus] {
filteredVals = append(filteredVals, vc)
}
}
return &ethpb.StateValidatorsResponse{Data: filteredVals, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
}
// ListValidatorBalances returns a filterable list of validator balances.
func (bs *Server) ListValidatorBalances(ctx context.Context, req *ethpb.ValidatorBalancesRequest) (*ethpb.ValidatorBalancesResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.ListValidatorBalances")
defer span.End()
st, err := bs.Stater.State(ctx, req.StateId)
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
valContainers, err := valContainersByRequestIds(st, req.Id)
if err != nil {
return nil, handleValContainerErr(err)
}
valBalances := make([]*ethpb.ValidatorBalance, len(valContainers))
for i := 0; i < len(valContainers); i++ {
valBalances[i] = &ethpb.ValidatorBalance{
Index: valContainers[i].Index,
Balance: valContainers[i].Balance,
}
}
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.ValidatorBalancesResponse{Data: valBalances, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
}
// ListCommittees retrieves the committees for the given state at the given epoch.
// If the requested slot and index are defined, only those committees are returned.
func (bs *Server) ListCommittees(ctx context.Context, req *ethpb.StateCommitteesRequest) (*ethpb.StateCommitteesResponse, error) {
ctx, span := trace.StartSpan(ctx, "beacon.ListCommittees")
defer span.End()
st, err := bs.Stater.State(ctx, req.StateId)
if err != nil {
return nil, helpers.PrepareStateFetchGRPCError(err)
}
epoch := slots.ToEpoch(st.Slot())
if req.Epoch != nil {
epoch = *req.Epoch
}
activeCount, err := corehelpers.ActiveValidatorCount(ctx, st, epoch)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get active validator count: %v", err)
}
startSlot, err := slots.EpochStart(epoch)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Invalid epoch: %v", err)
}
endSlot, err := slots.EpochEnd(epoch)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Invalid epoch: %v", err)
}
committeesPerSlot := corehelpers.SlotCommitteeCount(activeCount)
committees := make([]*ethpb.Committee, 0)
for slot := startSlot; slot <= endSlot; slot++ {
if req.Slot != nil && slot != *req.Slot {
continue
}
for index := primitives.CommitteeIndex(0); index < primitives.CommitteeIndex(committeesPerSlot); index++ {
if req.Index != nil && index != *req.Index {
continue
}
committee, err := corehelpers.BeaconCommitteeFromState(ctx, st, slot, index)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get committee: %v", err)
}
committeeContainer := &ethpb.Committee{
Index: index,
Slot: slot,
Validators: committee,
}
committees = append(committees, committeeContainer)
}
}
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.StateCommitteesResponse{Data: committees, ExecutionOptimistic: isOptimistic, Finalized: isFinalized}, nil
}
// This function returns the validator object based on the passed in ID. The validator ID could be its public key,
// or its index.
func valContainersByRequestIds(state state.BeaconState, validatorIds [][]byte) ([]*ethpb.ValidatorContainer, error) {
epoch := slots.ToEpoch(state.Slot())
var valContainers []*ethpb.ValidatorContainer
allBalances := state.Balances()
if len(validatorIds) == 0 {
allValidators := state.Validators()
valContainers = make([]*ethpb.ValidatorContainer, len(allValidators))
for i, val := range allValidators {
readOnlyVal, err := statenative.NewValidator(val)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert validator: %v", err)
}
subStatus, err := helpers.ValidatorSubStatus(readOnlyVal, epoch)
if err != nil {
return nil, errors.Wrap(err, "could not get validator sub status")
}
valContainers[i] = &ethpb.ValidatorContainer{
Index: primitives.ValidatorIndex(i),
Balance: allBalances[i],
Status: ethpb.ValidatorStatus(subStatus),
Validator: migration.V1Alpha1ValidatorToV1(val),
}
}
} else {
valContainers = make([]*ethpb.ValidatorContainer, 0, len(validatorIds))
for _, validatorId := range validatorIds {
var valIndex primitives.ValidatorIndex
if len(validatorId) == params.BeaconConfig().BLSPubkeyLength {
var ok bool
valIndex, ok = state.ValidatorIndexByPubkey(bytesutil.ToBytes48(validatorId))
if !ok {
// Ignore well-formed yet unknown public keys.
continue
}
} else {
index, err := strconv.ParseUint(string(validatorId), 10, 64)
if err != nil {
e := newInvalidValidatorIdError(validatorId, err)
return nil, &e
}
valIndex = primitives.ValidatorIndex(index)
}
val, err := state.ValidatorAtIndex(valIndex)
if _, ok := err.(*statenative.ValidatorIndexOutOfRangeError); ok {
// Ignore well-formed yet unknown indexes.
continue
}
if err != nil {
return nil, errors.Wrap(err, "could not get validator")
}
v1Validator := migration.V1Alpha1ValidatorToV1(val)
readOnlyVal, err := statenative.NewValidator(val)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not convert validator: %v", err)
}
subStatus, err := helpers.ValidatorSubStatus(readOnlyVal, epoch)
if err != nil {
return nil, errors.Wrap(err, "could not get validator sub status")
}
valContainers = append(valContainers, &ethpb.ValidatorContainer{
Index: valIndex,
Balance: allBalances[valIndex],
Status: ethpb.ValidatorStatus(subStatus),
Validator: v1Validator,
})
}
}
return valContainers, nil
}
func handleValContainerErr(err error) error {
if outOfRangeErr, ok := err.(*statenative.ValidatorIndexOutOfRangeError); ok {
return status.Errorf(codes.InvalidArgument, "Invalid validator ID: %v", outOfRangeErr)
}
if invalidIdErr, ok := err.(*invalidValidatorIdError); ok {
return status.Errorf(codes.InvalidArgument, "Invalid validator ID: %v", invalidIdErr)
}
return status.Errorf(codes.Internal, "Could not get validator container: %v", err)
}

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