Compare commits

...

48 Commits

Author SHA1 Message Date
terence tsao
eebc2c8d20 fix no go 2025-04-08 13:29:50 -07:00
potuz
7a47c03b60 Add actual roots to the event stream 2025-04-08 16:38:20 -03:00
potuz
bfd33b64ce Use dependent root to request duties 2025-04-07 19:30:40 -03:00
Potuz
92cf0bc0ab Use forkchoice dependent root helper (#15137) 2025-04-07 18:02:27 +00:00
james-prysm
9a79f49514 Remove old web3signer metrics: v6 (#14920)
* removing repeated fork specific metrics for a universal one

* changelog
2025-04-07 17:02:54 +00:00
Potuz
0d7d9bd5fc Forkchoice helper for dependent root (#15136)
* Forkchoice helper for dependent root

* James' suggestion
2025-04-07 14:42:39 +00:00
Nishant Das
a6052efefb Fix Operations Length Check For Attestations (#15134)
* fix check for electra

* changelog
2025-04-06 22:35:12 +00:00
Bastin
fa5d2a88ce Fix LC test utils problem (#15133)
* fix problem with MinSyncCommitteeParticipants

* fix usages

* changelog entry
2025-04-04 16:30:55 +00:00
james-prysm
ff02661229 fixes health tracker usage (#15126)
* fixing health interface

* reran mockgen

* breaking out mock into its own file so it's visible elsewhere

* lint

* gaz
2025-04-04 15:27:59 +00:00
Bastin
09309ab1f2 Refactor LC Test Utils (#15131)
* refactor test utils

* refactor test utils

* rename parameter
2025-04-04 11:35:21 +00:00
Bastin
cb9621702e Add LC Store to BeaconNode (#15120)
* add lcStore to Node

* changelog entry

* add atomic getters and setters for the store

* change store fields visibility to private

* refactor method names and add tests

* remove get from getters
2025-04-04 10:06:15 +00:00
Nishant Das
efba931610 Handle Pending Balance Bug (#15123)
* Fix Getter and Add regression test

* changelog
2025-04-04 04:00:07 +00:00
james-prysm
4a1c627f6f migrates some helper functions from beacon API to apiutil (#15125)
* migration and changelog

* missed valid root

* removing unneeded test, more optimization

* adding strict dependency

* linting

* fixing 1 more test

* bastin's suggestion
2025-04-03 19:48:26 +00:00
Nishant Das
0c2464c497 Handle Consolidation Processing Edge Case (#15122)
* Clean it up

* Add regression test case

* changelog
2025-04-03 16:07:33 +00:00
james-prysm
2cfc204e9a removed redundant mock validator and replaced with test util one (#15111) 2025-04-02 21:17:20 +00:00
Nishant Das
877d9ee948 Revert Execution Requests in E2E (#15119)
* Revert "Disable Execution Request Testing On Mainnet (#15115)"

This reverts commit 70c31949ba.

* Revert "Trigger Execution Requests In E2E (#14971)"

This reverts commit e38fdb09a4.

* Changelog
2025-04-02 16:12:35 +00:00
Kaloyan Tanev
785fefa3f1 Do not cache slot committee aggregations for DVs (#15110)
* Do not cache slot committee for DV agggregations

* Add changelog

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-02 15:54:47 +00:00
kevincatty
0c22d91a55 fix: remove duplicate WithBlobStorage in options initialization (#15036)
Signed-off-by: kevincatty <zhanshanmao@outlook.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-02 15:25:00 +00:00
NikolaiKryshnev
fb60456116 Improved README structure and visual presentation (#14860)
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-02 15:23:10 +00:00
jinjiadu
be56711892 fix: fix slice init length (#14407)
Signed-off-by: jinjiadu <jinjiadu@aliyun.com>
Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-02 15:21:26 +00:00
kasey
96f1ebf706 more efficient ancestry db queries, stategen (#15063)
Co-authored-by: Kasey <kasey@users.noreply.github.com>
2025-04-01 20:16:58 +00:00
John
bdb12c7d2f fix: use io.ReadFull instead of stream.Read (#15089) 2025-04-01 19:23:04 +00:00
LesCyber
83cf0f8658 refactor: use errors.New to replace fmt.Errorf with no parameters (#15007)
Signed-off-by: LesCyber <andi4cing@gmail.com>
2025-04-01 19:03:16 +00:00
VolodymyrBg
762594a368 feat(proposer): add gas limit range warnings (#15078)
* feat(proposer): add gas limit range warnings

* Update config/proposer/loader/loader.go

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>

* Update config/proposer/loader/loader.go

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>

* Create volodymyrbg_gas_limit_warnings.md

---------

Co-authored-by: Preston Van Loon <preston@prysmaticlabs.com>
2025-04-01 19:00:21 +00:00
kilavvy
c1fc812a38 Fix typos in test code (#14991)
* Update keymanager_test.go

* Update fork_test.go

* Update README.md

* Update README.md
2025-04-01 18:31:59 +00:00
riyueguang
340935af9c refactor: use the built-in min to simplify the code (#15091)
Signed-off-by: riyueguang <rustruby@outlook.com>
2025-04-01 17:57:22 +00:00
kasey
17d0082c5c cleanup block indices for missing blocks (#15040)
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2025-04-01 17:54:35 +00:00
terence
e998b5ec97 Use latest state to pack attestation (#15113)
* Use latest state to pack attestation

* Add a test to make sure it would have failed when using head state instead of latest state
2025-04-01 15:31:45 +00:00
james-prysm
5d0eb3168c small optimization (#15116) 2025-04-01 14:55:29 +00:00
terence
e0c2aa71d4 Update spec tests to beta 4 (#15114)
* Update spec tests to beta 4

* Change log to ignored
2025-04-01 14:32:57 +00:00
Nishant Das
70c31949ba Disable Execution Request Testing On Mainnet (#15115)
* DisableOnMainnet

* Changelog
2025-04-01 10:38:05 +00:00
Nishant Das
e38fdb09a4 Trigger Execution Requests In E2E (#14971)
* Trigger Consolidation

* Finally have it working

* Fix Build

* Revert Change

* Fix Context

* Finally have consolidations working

* Get Evaluator Working

* Get Withdrawals Working

* Changelog

* Finally Passes

* New line

* Try again

* fmt

* fmt

* Fix Test
2025-04-01 06:49:51 +00:00
james-prysm
a50e981c74 removing redundant loop in computeOnChainAggregate (#15108)
* removing redundant loop

* Update beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_electra.go

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

* removing unused import

* replacing with more used function

* resolving Unsafe cast from uint64 to int error

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-03-31 21:22:47 +00:00
Bastin
8be205cf3d Use fieldparams.RootLength instead of local variable in p2p types.go (#15106)
* use fieldparams.RootLength instead of local var

* gazelle fix
2025-03-31 12:42:07 +00:00
kasey
1b65e00096 refactor state-by-root test to table-driven (#15087)
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2025-03-28 23:34:34 +00:00
james-prysm
e3fb4e86ec validator client initialization cleanup (#15080)
* cleanup

* fixing optimal sort order for struct

* reverting clictx change based on kasey's feedback

* adding in fix for old trace file flag

* more cleanup for clarity

* optimizing if statement check and fixing wallet open on web enable

* optimizing if statement check and fixing wallet open on web enable

* some more cleanup and bug fix on open wallet

* reverting debug.go changes will handle in a separate PR

* removing useless comment

* changing useWeb to enableAPI

* fixing tests and linting

* manu feedback and one optimization removing auth token check

* gaz
2025-03-28 14:03:42 +00:00
terence
70aaad1904 Add more tests to process pending deposits (#15099) 2025-03-27 14:16:41 +00:00
Radosław Kapka
e42611ec72 Allow hex strings in /eth/v1/beacon/states/{state_id}/root endpoint (#15098)
* Allow hex strings in `/eth/v1/beacon/states/{state_id}/root` endpoint

* changelog <3

* remove redundant conversion

* use `bytesutil.IsHex`
2025-03-27 14:13:57 +00:00
Bastin
a3e61275a3 Add light client types to spectest (#15097)
* add light client types to spectest

* changelog entry

* remove lightclientSnapshot from tests
2025-03-26 17:44:30 +00:00
terence
e82f9ccca3 Proposer: change attestation sorting to reward numerator (#15093)
* Change proposer block's sorting algo to proposer reward numerator

* Feedback

* Comments

* Add a cache for attestation reward numerator
2025-03-26 16:21:41 +00:00
Bastin
38a6a7a4ea Add SSZ support for light client updates by range API (#15082)
* create ssz payload

* remove unused function

* remove access to state

* gazelle fix

* fix ssz size for electra finality update

* fix fork digest version problem

* fix chunk size

* fix fork version

* fix fork version

* add tests

* add changelog entry

* add PR link in changelog entry

* fix lint error

* Update beacon-chain/rpc/eth/light-client/handlers.go

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

* check for context error

* send response in chunks

* remove content disposition header

---------

Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
2025-03-26 15:06:03 +00:00
Bastin
1295c987e8 Remove content disposition header from httputil.WriteSSZ (#15092)
* remove content disposition header from httputil.WriteSSZ

* fix changelog

* fix newly added calls to WriteSSZ
2025-03-26 14:17:19 +00:00
Radosław Kapka
6a27c41aad Implement validator identities Beacon API endpoint (#15086)
* implementation

* tests

* changelog <3

* linter fix

* test fix
2025-03-25 16:49:35 +00:00
Radosław Kapka
98b13ea144 Update changelog for v5.3.2 release (#15096)
* Changelog for v5.3.2

* add header section

* fragment file
2025-03-25 16:38:29 +00:00
james-prysm
c735ed2e32 Remove use of committee list from validator client (#15039)
* wip

* fixing unit tests

* changing is aggregator function

* wip

* fully removing the use of committee from validator client, adding a wrapper type for duties

* fixing tests

* fixing linting

* fixing more tests

* changelog

* adding some more tests

* Update proto/prysm/v1alpha1/validator.go

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

* radek's feedback

* removing accidently checked in

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-03-25 16:25:42 +00:00
Potuz
bd17779231 Use headstate to validate canonical attestations for old targets (#15095)
* Use headstate to validate canonical attestations for old targets

* Update beacon-chain/blockchain/process_attestation_helpers.go

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

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-03-25 13:36:47 +00:00
james-prysm
e08ed0d823 Deprecate broken and unused debug flags in favor of --pprof (#15083)
* deprecating the trace and cpuprofile flags in favor of pprof

* gaz

* fixing change log title

* added hidden tags
2025-03-24 19:30:32 +00:00
xinhangzhou
2b4d8a09ff refactor: use maps.Copy for cleaner map handling (#15090)
Signed-off-by: xinhangzhou <shuangcui@aliyun.com>
2025-03-24 19:23:10 +00:00
256 changed files with 5941 additions and 3795 deletions

View File

@@ -4,6 +4,43 @@ All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
## [v5.3.2](https://github.com/prysmaticlabs/prysm/compare/v5.3.1...v5.3.2) - 2025-03-25
This release introduces support for the `Hoodi` testnet.
Release highlights:
- Ability to run the node on the `Hoodi` tesnet. See https://blog.ethereum.org/2025/03/18/hoodi-holesky for more information about `Hoodi`.
- A new feature that allows treat certain blocks as invalid. This is especially useful when the network is split, allowing the node to discontinue following unwanted forks.
Testnet operators are required to update to this release. Without this release you will be unable to run the node on the `Hoodi` testnet.
Mainnet operators are recommended to update to this release at their regular cadence.
### Added
- enable SSZ for builder API calls. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14976)
- Add Hoodi testnet flag `--hoodi` to specify Hoodi testnet config and bootnodes. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15057)
- block_gossip topic support to the beacon api event stream. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15038)
- Added a static analyzer to discourage use of panic() in Prysm. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15075)
- Add a feature flag `--blacklist-roots` to allow the node to specify blocks that will be treated as invalid. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15030)
### Changed
- changed request object for `POST /eth/v1/beacon/states/head/validators` to omit the field if empty for satisfying other clients. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15031)
- Update spec test to v1.5.0-beta.3. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15050)
- Update Gossip and RPC message limits to comply with the spec. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14799)
- Return 404 instead of 500 from API when when a blob for a requested index is not found. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14845)
- Save Electra orphaned attestations into attestations pool's block attestations. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15060)
- Removed redundant string conversion in `BeaconDbStater.State` to improve code clarity and maintainability. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15081)
### Fixed
- Update seen unaggregated att cache to properly handle Electra attestations. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15034)
- cosmetic fix for calling `/prysm/validators/performance` when connecting to non prysm beacon nodes and removing the 404 error log. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15062)
- Tracked validator cache: Make sure no to loose the reference. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15077)
- Fixed proposing at genesis when starting post Bellatrix. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15084)
## [v5.3.1](https://github.com/prysmaticlabs/prysm/compare/v5.3.0...v5.3.1) - 2025-03-13
This release is packed with critical fixes for **Electra** and some important fixes for mainnet too.
@@ -21,7 +58,7 @@ Known issues in **Electra**:
Testnet operators are strongly encouraged to update to this release. There are many fixes and improvements from the Holesky upgrade incident.
Mainner operators are recommended to update to this release at their regular cadence.
Mainnet operators are recommended to update to this release at their regular cadence.
### Added
@@ -3218,4 +3255,4 @@ There are no security updates in this release.
# Older than v2.0.0
For changelog history for releases older than v2.0.0, please refer to https://github.com/prysmaticlabs/prysm/releases
For changelog history for releases older than v2.0.0, please refer to https://github.com/prysmaticlabs/prysm/releases

View File

@@ -1,5 +1,7 @@
# Prysm: An Ethereum Consensus Implementation Written in Go
<h1 align="left">Prysm: An Ethereum Consensus Implementation Written in Go</h1>
<div align="left">
[![Build status](https://badge.buildkite.com/b555891daf3614bae4284dcf365b2340cefc0089839526f096.svg?branch=master)](https://buildkite.com/prysmatic-labs/prysm)
[![Go Report Card](https://goreportcard.com/badge/github.com/prysmaticlabs/prysm)](https://goreportcard.com/report/github.com/prysmaticlabs/prysm)
[![Consensus_Spec_Version 1.4.0](https://img.shields.io/badge/Consensus%20Spec%20Version-v1.4.0-blue.svg)](https://github.com/ethereum/consensus-specs/tree/v1.4.0)
@@ -7,31 +9,60 @@
[![Discord](https://user-images.githubusercontent.com/7288322/34471967-1df7808a-efbb-11e7-9088-ed0b04151291.png)](https://discord.gg/prysmaticlabs)
[![GitPOAP Badge](https://public-api.gitpoap.io/v1/repo/prysmaticlabs/prysm/badge)](https://www.gitpoap.io/gh/prysmaticlabs/prysm)
This is the core repository for Prysm, a [Golang](https://golang.org/) implementation of the [Ethereum Consensus](https://ethereum.org/en/developers/docs/consensus-mechanisms/#proof-of-stake) [specification](https://github.com/ethereum/consensus-specs), developed by [Offchain Labs](https://www.offchainlabs.com). See the [Changelog](https://github.com/prysmaticlabs/prysm/releases) for details of the latest releases and upcoming breaking changes.
</div>
### Getting Started
---
A detailed set of installation and usage instructions as well as breakdowns of each individual component are available in the [official documentation portal](https://docs.prylabs.network). If you still have questions, feel free to stop by our [Discord](https://discord.gg/prysmaticlabs).
## 📖 Overview
### Staking on Mainnet
This is the core repository for Prysm, a [Golang](https://golang.org/) implementation of the [Ethereum Consensus](https://ethereum.org/en/developers/docs/consensus-mechanisms/#proof-of-stake) [specification](https://github.com/ethereum/consensus-specs), developed by [Offchain Labs](https://www.offchainlabs.com).
To participate in staking, you can join the [official eth2 launchpad](https://launchpad.ethereum.org). The launchpad is the only recommended way to become a validator on mainnet. You can explore validator rewards/penalties via Bitfly's block explorer: [beaconcha.in](https://beaconcha.in), and follow the latest blocks added to the chain on [beaconscan](https://beaconscan.com).
See the [Changelog](https://github.com/prysmaticlabs/prysm/releases) for details of the latest releases and upcoming breaking changes.
---
## 🚀 Getting Started
A detailed set of installation and usage instructions as well as breakdowns of each individual component are available in the **[official documentation portal](https://docs.prylabs.network)**.
💬 **Need help?** Join our **[Discord Community](https://discord.gg/prysmaticlabs)** for support.
---
## 🏆 Staking on Mainnet
To participate in staking, you can join the **[official Ethereum launchpad](https://launchpad.ethereum.org)**. The launchpad is the **only recommended** way to become a validator on mainnet.
🔍 Explore validator rewards/penalties:
- **[beaconcha.in](https://beaconcha.in)**
- **[beaconscan](https://beaconscan.com)**
---
## 🤝 Contributing
### 🔥 Branches
## Contributing
### Branches
Prysm maintains two permanent branches:
* [master](https://github.com/prysmaticlabs/prysm/tree/master): This points to the latest stable release. It is ideal for most users.
* [develop](https://github.com/prysmaticlabs/prysm/tree/develop): This is used for development, it contains the latest PRs. Developers should base their PRs on this branch.
- **[`master`](https://github.com/prysmaticlabs/prysm/tree/master)** - This points to the latest stable release. It is ideal for most users.
- **[`develop`](https://github.com/prysmaticlabs/prysm/tree/develop)** - This is used for development and contains the latest PRs. Developers should base their PRs on this branch.
### Guide
Want to get involved? Check out our [Contribution Guide](https://docs.prylabs.network/docs/contribute/contribution-guidelines/) to learn more!
### 🛠 Contribution Guide
## License
Want to get involved? Check out our **[Contribution Guide](https://docs.prylabs.network/docs/contribute/contribution-guidelines/)** to learn more!
[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html)
---
## Legal Disclaimer
## 📜 License
[Terms of Use](/TERMS_OF_SERVICE.md)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
This project is licensed under the **GNU General Public License v3.0**.
---
## ⚖️ Legal Disclaimer
📜 [Terms of Use](/TERMS_OF_SERVICE.md)

View File

@@ -255,7 +255,7 @@ filegroup(
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
)
consensus_spec_version = "v1.5.0-beta.3"
consensus_spec_version = "v1.5.0-beta.4"
bls_test_version = "v0.1.1"
@@ -271,7 +271,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-z+j0BEJuXMBKbGL+7jq35zddzZMW1je8/uvTz5+wboQ=",
integrity = "sha256-QG0NUqaCvP5lKaKKwF/fmeICZVjONMlb7EE+MtYl0C0=",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
)
@@ -287,7 +287,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-5/YUOXH65CmM1plZ8twJ3BQxwM51jgSpOB8/VSBI19k=",
integrity = "sha256-8NQngTSSqzW/j3tOUi3r5h+94ChRbLNWTt7BOGqr4+E=",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
)
@@ -303,7 +303,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-iZ2eNhwRnbxrjR+5gMBUYakaCXicvPChwFUkZtQUbbI=",
integrity = "sha256-gFqxbaBnJ7dtdoj0zFbVrtlHv/bLNuWjrTHkyCAjFjI=",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
)
@@ -318,7 +318,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-inAXV7xNM5J1aUdP7JNXFO2iFFZ7dth38Ji+mJW50Ts=",
integrity = "sha256-9paalF0POULpP2ga+4ouHSETKYrWNCUCZoJHPuFw06E=",
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
)

19
api/apiutil/BUILD.bazel Normal file
View File

@@ -0,0 +1,19 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["common.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/api/apiutil",
visibility = ["//visibility:public"],
deps = ["//consensus-types/primitives:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["common_test.go"],
embed = [":go_default_library"],
deps = [
"//consensus-types/primitives:go_default_library",
"//testing/assert:go_default_library",
],
)

23
api/apiutil/common.go Normal file
View File

@@ -0,0 +1,23 @@
package apiutil
import (
"fmt"
neturl "net/url"
"strconv"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)
// Uint64ToString is a util function that will convert uints to string
func Uint64ToString[T uint64 | primitives.Slot | primitives.ValidatorIndex | primitives.CommitteeIndex | primitives.Epoch](val T) string {
return strconv.FormatUint(uint64(val), 10)
}
// BuildURL is a util function that assists with adding query parameters to the url
func BuildURL(path string, queryParams ...neturl.Values) string {
if len(queryParams) == 0 {
return path
}
return fmt.Sprintf("%s?%s", path, queryParams[0].Encode())
}

View File

@@ -0,0 +1,37 @@
package apiutil
import (
"net/url"
"testing"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
)
func TestBeaconApiHelpers_TestUint64ToString(t *testing.T) {
const expectedResult = "1234"
const val = uint64(1234)
assert.Equal(t, expectedResult, Uint64ToString(val))
assert.Equal(t, expectedResult, Uint64ToString(primitives.Slot(val)))
assert.Equal(t, expectedResult, Uint64ToString(primitives.ValidatorIndex(val)))
assert.Equal(t, expectedResult, Uint64ToString(primitives.CommitteeIndex(val)))
assert.Equal(t, expectedResult, Uint64ToString(primitives.Epoch(val)))
}
func TestBuildURL_NoParams(t *testing.T) {
wanted := "/aaa/bbb/ccc"
actual := BuildURL("/aaa/bbb/ccc")
assert.Equal(t, wanted, actual)
}
func TestBuildURL_WithParams(t *testing.T) {
params := url.Values{}
params.Add("xxxx", "1")
params.Add("yyyy", "2")
params.Add("zzzz", "3")
wanted := "/aaa/bbb/ccc?xxxx=1&yyyy=2&zzzz=3"
actual := BuildURL("/aaa/bbb/ccc", params)
assert.Equal(t, wanted, actual)
}

View File

@@ -5,7 +5,6 @@ go_library(
srcs = [
"client.go",
"doc.go",
"health.go",
"log.go",
"template.go",
],
@@ -13,7 +12,6 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//api/client:go_default_library",
"//api/client/beacon/iface:go_default_library",
"//api/server:go_default_library",
"//api/server/structs:go_default_library",
"//consensus-types/primitives:go_default_library",
@@ -28,15 +26,10 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"client_test.go",
"health_test.go",
],
srcs = ["client_test.go"],
embed = [":go_default_library"],
deps = [
"//api/client:go_default_library",
"//api/client/beacon/testing:go_default_library",
"//testing/require:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",
],
)

View File

@@ -0,0 +1,20 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"health.go",
"interfaces.go",
"mock.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon/health",
visibility = ["//visibility:public"],
deps = ["@org_uber_go_mock//gomock:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["health_test.go"],
embed = [":go_default_library"],
deps = ["@org_uber_go_mock//gomock:go_default_library"],
)

View File

@@ -1,20 +1,18 @@
package beacon
package health
import (
"context"
"sync"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon/iface"
)
type NodeHealthTracker struct {
isHealthy *bool
healthChan chan bool
node iface.HealthNode
node Node
sync.RWMutex
}
func NewNodeHealthTracker(node iface.HealthNode) *NodeHealthTracker {
func NewTracker(node Node) Tracker {
return &NodeHealthTracker{
node: node,
healthChan: make(chan bool, 1),
@@ -26,7 +24,7 @@ func (n *NodeHealthTracker) HealthUpdates() <-chan bool {
return n.healthChan
}
func (n *NodeHealthTracker) IsHealthy() bool {
func (n *NodeHealthTracker) IsHealthy(_ context.Context) bool {
n.RLock()
defer n.RUnlock()
if n.isHealthy == nil {

View File

@@ -1,11 +1,10 @@
package beacon
package health
import (
"context"
"sync"
"testing"
healthTesting "github.com/prysmaticlabs/prysm/v5/api/client/beacon/testing"
"go.uber.org/mock/gomock"
)
@@ -24,7 +23,7 @@ func TestNodeHealth_IsHealthy(t *testing.T) {
isHealthy: &tt.isHealthy,
healthChan: make(chan bool, 1),
}
if got := n.IsHealthy(); got != tt.want {
if got := n.IsHealthy(context.Background()); got != tt.want {
t.Errorf("IsHealthy() = %v, want %v", got, tt.want)
}
})
@@ -47,7 +46,7 @@ func TestNodeHealth_UpdateNodeHealth(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := healthTesting.NewMockHealthClient(ctrl)
client := NewMockHealthClient(ctrl)
client.EXPECT().IsHealthy(gomock.Any()).Return(tt.newStatus)
n := &NodeHealthTracker{
isHealthy: &tt.initial,
@@ -80,8 +79,8 @@ func TestNodeHealth_UpdateNodeHealth(t *testing.T) {
func TestNodeHealth_Concurrency(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
client := healthTesting.NewMockHealthClient(ctrl)
n := NewNodeHealthTracker(client)
client := NewMockHealthClient(ctrl)
n := NewTracker(client)
var wg sync.WaitGroup
// Number of goroutines to spawn for both reading and writing
@@ -104,7 +103,7 @@ func TestNodeHealth_Concurrency(t *testing.T) {
for i := 0; i < numGoroutines; i++ {
go func() {
defer wg.Done()
_ = n.IsHealthy() // Just read the value
_ = n.IsHealthy(context.Background()) // Just read the value
}()
}

View File

@@ -1,13 +1,13 @@
package iface
package health
import "context"
type HealthTracker interface {
type Tracker interface {
HealthUpdates() <-chan bool
IsHealthy() bool
CheckHealth(ctx context.Context) bool
Node
}
type HealthNode interface {
type Node interface {
IsHealthy(ctx context.Context) bool
}

View File

@@ -1,16 +1,15 @@
package testing
package health
import (
"context"
"reflect"
"sync"
"github.com/prysmaticlabs/prysm/v5/api/client/beacon/iface"
"go.uber.org/mock/gomock"
)
var (
_ = iface.HealthNode(&MockHealthClient{})
_ = Node(&MockHealthClient{})
)
// MockHealthClient is a mock of HealthClient interface.

View File

@@ -1,8 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["health.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon/iface",
visibility = ["//visibility:public"],
)

View File

@@ -1,12 +0,0 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["mock.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/api/client/beacon/testing",
visibility = ["//visibility:public"],
deps = [
"//api/client/beacon/iface:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",
],
)

View File

@@ -100,6 +100,12 @@ type GetValidatorBalancesResponse struct {
Data []*ValidatorBalance `json:"data"`
}
type GetValidatorIdentitiesResponse struct {
ExecutionOptimistic bool `json:"execution_optimistic"`
Finalized bool `json:"finalized"`
Data []*ValidatorIdentity `json:"data"`
}
type ValidatorContainer struct {
Index string `json:"index"`
Balance string `json:"balance"`
@@ -112,6 +118,12 @@ type ValidatorBalance struct {
Balance string `json:"balance"`
}
type ValidatorIdentity struct {
Index string `json:"index"`
Pubkey string `json:"pubkey"`
ActivationEpoch string `json:"activation_epoch"`
}
type GetBlockResponse struct {
Data *SignedBlock `json:"data"`
}

View File

@@ -51,6 +51,7 @@ type ForkchoiceFetcher interface {
ProposerBoost() [32]byte
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
DependentRoot(primitives.Epoch) ([32]byte, error)
}
// TimeFetcher retrieves the Ethereum consensus data that's related to time.

View File

@@ -126,3 +126,10 @@ func (s *Service) hashForGenesisBlock(ctx context.Context, root [32]byte) ([]byt
}
return bytesutil.SafeCopyBytes(header.BlockHash()), nil
}
// DependentRoot wraps the corresponding method in forkchoice
func (s *Service) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.DependentRoot(epoch)
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed"
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/features"
@@ -328,34 +327,22 @@ func (s *Service) notifyNewHeadEvent(
newHeadStateRoot,
newHeadRoot []byte,
) error {
previousDutyDependentRoot := s.originBlockRoot[:]
currentDutyDependentRoot := s.originBlockRoot[:]
currEpoch := slots.ToEpoch(newHeadSlot)
currentDutyDependentRoot, err := s.DependentRoot(currEpoch)
if err != nil {
return errors.Wrap(err, "could not get duty dependent root")
}
if currentDutyDependentRoot == [32]byte{} {
currentDutyDependentRoot = s.originBlockRoot
}
previousDutyDependentRoot := currentDutyDependentRoot
if currEpoch > 0 {
previousDutyDependentRoot, err = s.DependentRoot(currEpoch.Sub(1))
if err != nil {
return errors.Wrap(err, "could not get duty dependent root")
}
}
var previousDutyEpoch primitives.Epoch
currentDutyEpoch := slots.ToEpoch(newHeadSlot)
if currentDutyEpoch > 0 {
previousDutyEpoch = currentDutyEpoch.Sub(1)
}
currentDutySlot, err := slots.EpochStart(currentDutyEpoch)
if err != nil {
return errors.Wrap(err, "could not get duty slot")
}
previousDutySlot, err := slots.EpochStart(previousDutyEpoch)
if err != nil {
return errors.Wrap(err, "could not get duty slot")
}
if currentDutySlot > 0 {
currentDutyDependentRoot, err = helpers.BlockRootAtSlot(newHeadState, currentDutySlot-1)
if err != nil {
return errors.Wrap(err, "could not get duty dependent root")
}
}
if previousDutySlot > 0 {
previousDutyDependentRoot, err = helpers.BlockRootAtSlot(newHeadState, previousDutySlot-1)
if err != nil {
return errors.Wrap(err, "could not get duty dependent root")
}
}
isOptimistic, err := s.IsOptimistic(ctx)
if err != nil {
return errors.Wrap(err, "could not check if node is optimistically synced")
@@ -367,8 +354,8 @@ func (s *Service) notifyNewHeadEvent(
Block: newHeadRoot,
State: newHeadStateRoot,
EpochTransition: slots.IsEpochStart(newHeadSlot),
PreviousDutyDependentRoot: previousDutyDependentRoot,
CurrentDutyDependentRoot: currentDutyDependentRoot,
PreviousDutyDependentRoot: previousDutyDependentRoot[:],
CurrentDutyDependentRoot: currentDutyDependentRoot[:],
ExecutionOptimistic: isOptimistic,
},
})

View File

@@ -9,6 +9,7 @@ import (
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
testDB "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/blstoexec"
"github.com/prysmaticlabs/prysm/v5/config/params"
@@ -156,14 +157,17 @@ func Test_notifyNewHeadEvent(t *testing.T) {
notifier := &mock.MockStateNotifier{RecordEvents: true}
srv := &Service{
cfg: &config{
StateNotifier: notifier,
StateNotifier: notifier,
ForkChoiceStore: doublylinkedtree.New(),
},
originBlockRoot: [32]byte{1},
}
st, blk, err := prepareForkchoiceState(context.Background(), 0, [32]byte{}, [32]byte{}, [32]byte{}, &ethpb.Checkpoint{}, &ethpb.Checkpoint{})
require.NoError(t, err)
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(context.Background(), st, blk))
newHeadStateRoot := [32]byte{2}
newHeadRoot := [32]byte{3}
err := srv.notifyNewHeadEvent(context.Background(), 1, bState, newHeadStateRoot[:], newHeadRoot[:])
require.NoError(t, err)
require.NoError(t, srv.notifyNewHeadEvent(context.Background(), 1, bState, newHeadStateRoot[:], newHeadRoot[:]))
events := notifier.ReceivedEvents()
require.Equal(t, 1, len(events))
@@ -185,10 +189,14 @@ func Test_notifyNewHeadEvent(t *testing.T) {
genesisRoot := [32]byte{1}
srv := &Service{
cfg: &config{
StateNotifier: notifier,
StateNotifier: notifier,
ForkChoiceStore: doublylinkedtree.New(),
},
originBlockRoot: genesisRoot,
}
st, blk, err := prepareForkchoiceState(context.Background(), 0, [32]byte{}, [32]byte{}, [32]byte{}, &ethpb.Checkpoint{}, &ethpb.Checkpoint{})
require.NoError(t, err)
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(context.Background(), st, blk))
epoch1Start, err := slots.EpochStart(1)
require.NoError(t, err)
epoch2Start, err := slots.EpochStart(1)
@@ -209,8 +217,8 @@ func Test_notifyNewHeadEvent(t *testing.T) {
Block: newHeadRoot[:],
State: newHeadStateRoot[:],
EpochTransition: true,
PreviousDutyDependentRoot: genesisRoot[:],
CurrentDutyDependentRoot: make([]byte, 32),
PreviousDutyDependentRoot: make([]byte, 32),
CurrentDutyDependentRoot: srv.originBlockRoot[:],
}
require.DeepSSZEqual(t, wanted, eventHead)
})

View File

@@ -18,6 +18,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// The caller of this function must have a lock on forkchoice.
func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) state.ReadOnlyBeaconState {
headEpoch := slots.ToEpoch(s.HeadSlot())
if c.Epoch < headEpoch {
@@ -27,13 +28,6 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st
return nil
}
if c.Epoch == headEpoch {
targetSlot, err := s.cfg.ForkChoiceStore.Slot([32]byte(c.Root))
if err != nil {
return nil
}
if slots.ToEpoch(targetSlot)+1 < headEpoch {
return nil
}
st, err := s.HeadStateReadOnly(ctx)
if err != nil {
return nil
@@ -65,12 +59,13 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st
return nil
}
if err := s.checkpointStateCache.AddCheckpointState(c, st); err != nil {
return nil
log.WithError(err).Error("Could not save checkpoint state to cache")
}
return st
}
// getAttPreState retrieves the att pre state by either from the cache or the DB.
// The caller of this function must have a lock on forkchoice.
func (s *Service) getAttPreState(ctx context.Context, c *ethpb.Checkpoint) (state.ReadOnlyBeaconState, error) {
// If the attestation is recent and canonical we can use the head state to compute the shuffling.
if st := s.getRecentPreState(ctx, c); st != nil {

View File

@@ -2653,7 +2653,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
t.Run("Altair", func(t *testing.T) {
t.Run("No old update", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -2699,7 +2699,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
})
t.Run("New update is better", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -2751,7 +2751,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
})
t.Run("Old update is better", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, false)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -2812,7 +2812,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
t.Run("Capella", func(t *testing.T) {
t.Run("No old update", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -2857,7 +2857,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
})
t.Run("New update is better", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -2909,7 +2909,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
})
t.Run("Old update is better", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, false)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -2970,7 +2970,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
t.Run("Deneb", func(t *testing.T) {
t.Run("No old update", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -3015,7 +3015,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
})
t.Run("New update is better", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -3067,7 +3067,7 @@ func TestSaveLightClientUpdate(t *testing.T) {
})
t.Run("Old update is better", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, false)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -3138,7 +3138,7 @@ func TestSaveLightClientBootstrap(t *testing.T) {
ctx := tr.ctx
t.Run("Altair", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().AltairForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -3173,7 +3173,7 @@ func TestSaveLightClientBootstrap(t *testing.T) {
})
t.Run("Capella", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().CapellaForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)
@@ -3208,7 +3208,7 @@ func TestSaveLightClientBootstrap(t *testing.T) {
})
t.Run("Deneb", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
s.genesisTime = time.Unix(time.Now().Unix()-(int64(params.BeaconConfig().DenebForkEpoch)*int64(params.BeaconConfig().SlotsPerEpoch)*int64(params.BeaconConfig().SecondsPerSlot)), 0)

View File

@@ -75,6 +75,7 @@ type ChainService struct {
SyncingRoot [32]byte
Blobs []blocks.VerifiedROBlob
TargetRoot [32]byte
MockHeadSlot *primitives.Slot
}
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
@@ -334,6 +335,9 @@ func (s *ChainService) ReceiveBlock(ctx context.Context, block interfaces.ReadOn
// HeadSlot mocks HeadSlot method in chain service.
func (s *ChainService) HeadSlot() primitives.Slot {
if s.MockHeadSlot != nil {
return *s.MockHeadSlot
}
if s.State == nil {
return 0
}
@@ -444,6 +448,11 @@ func (s *ChainService) IsCanonical(_ context.Context, r [32]byte) (bool, error)
return true, nil
}
// DependentRoot mocks the base method in the chain service.
func (*ChainService) DependentRoot(_ primitives.Epoch) ([32]byte, error) {
return [32]byte{}, nil
}
// HasBlock mocks the same method in the chain service.
func (s *ChainService) HasBlock(ctx context.Context, rt [32]byte) bool {
if s.DB == nil {

View File

@@ -165,7 +165,7 @@ func AddValidatorFlag(flag, flagPosition uint8) (uint8, error) {
// if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index):
// epoch_participation[index] = add_flag(epoch_participation[index], flag_index)
// proposer_reward_numerator += get_base_reward(state, index) * weight
func EpochParticipation(beaconState state.BeaconState, indices []uint64, epochParticipation []byte, participatedFlags map[uint8]bool, totalBalance uint64) (uint64, []byte, error) {
func EpochParticipation(beaconState state.ReadOnlyBeaconState, indices []uint64, epochParticipation []byte, participatedFlags map[uint8]bool, totalBalance uint64) (uint64, []byte, error) {
cfg := params.BeaconConfig()
sourceFlagIndex := cfg.TimelySourceFlagIndex
targetFlagIndex := cfg.TimelyTargetFlagIndex
@@ -267,7 +267,7 @@ func RewardProposer(ctx context.Context, beaconState state.BeaconState, proposer
// participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX)
//
// return participation_flag_indices
func AttestationParticipationFlagIndices(beaconState state.BeaconState, data *ethpb.AttestationData, delay primitives.Slot) (map[uint8]bool, error) {
func AttestationParticipationFlagIndices(beaconState state.ReadOnlyBeaconState, data *ethpb.AttestationData, delay primitives.Slot) (map[uint8]bool, error) {
currEpoch := time.CurrentEpoch(beaconState)
var justifiedCheckpt *ethpb.Checkpoint
if data.Target.Epoch == currEpoch {
@@ -312,7 +312,7 @@ func AttestationParticipationFlagIndices(beaconState state.BeaconState, data *et
// is_matching_source = data.source == justified_checkpoint
// is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch)
// is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot)
func MatchingStatus(beaconState state.BeaconState, data *ethpb.AttestationData, cp *ethpb.Checkpoint) (matchedSrc, matchedTgt, matchedHead bool, err error) {
func MatchingStatus(beaconState state.ReadOnlyBeaconState, data *ethpb.AttestationData, cp *ethpb.Checkpoint) (matchedSrc, matchedTgt, matchedHead bool, err error) {
matchedSrc = attestation.CheckPointIsEqual(data.Source, cp)
r, err := helpers.BlockRoot(beaconState, data.Target.Epoch)

View File

@@ -29,6 +29,7 @@ go_library(
"//beacon-chain/core/validators:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//beacon-chain/state/state-native/custom-types:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
@@ -39,6 +40,7 @@ go_library(
"//monitoring/tracing/trace:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/attestation:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",

View File

@@ -1,7 +1,94 @@
package electra
import "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
import (
"context"
"fmt"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
customtypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/custom-types"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/attestation"
)
var (
ProcessAttestationsNoVerifySignature = altair.ProcessAttestationsNoVerifySignature
)
// GetProposerRewardNumerator returns the numerator of the proposer reward for an attestation.
func GetProposerRewardNumerator(
ctx context.Context,
st state.ReadOnlyBeaconState,
att ethpb.Att,
totalBalance uint64,
) (uint64, error) {
data := att.GetData()
delay, err := st.Slot().SafeSubSlot(data.Slot)
if err != nil {
return 0, fmt.Errorf("attestation slot %d exceeds state slot %d", data.Slot, st.Slot())
}
flags, err := altair.AttestationParticipationFlagIndices(st, data, delay)
if err != nil {
return 0, err
}
committees, err := helpers.AttestationCommitteesFromState(ctx, st, att)
if err != nil {
return 0, err
}
indices, err := attestation.AttestingIndices(att, committees...)
if err != nil {
return 0, err
}
var participation customtypes.ReadOnlyParticipation
if data.Target.Epoch == time.CurrentEpoch(st) {
participation, err = st.CurrentEpochParticipationReadOnly()
} else {
participation, err = st.PreviousEpochParticipationReadOnly()
}
if err != nil {
return 0, err
}
cfg := params.BeaconConfig()
var rewardNumerator uint64
for _, index := range indices {
if index >= uint64(participation.Len()) {
return 0, fmt.Errorf("index %d exceeds participation length %d", index, participation.Len())
}
br, err := altair.BaseRewardWithTotalBalance(st, primitives.ValidatorIndex(index), totalBalance)
if err != nil {
return 0, err
}
for _, entry := range []struct {
flagIndex uint8
weight uint64
}{
{cfg.TimelySourceFlagIndex, cfg.TimelySourceWeight},
{cfg.TimelyTargetFlagIndex, cfg.TimelyTargetWeight},
{cfg.TimelyHeadFlagIndex, cfg.TimelyHeadWeight},
} {
if flags[entry.flagIndex] { // If set, the validator voted correctly for the attestation given flag index.
hasVoted, err := altair.HasValidatorFlag(participation.At(index), entry.flagIndex)
if err != nil {
return 0, err
}
if !hasVoted { // If set, the validator has already voted in the beacon state so we don't double count.
rewardNumerator += br * entry.weight
}
}
}
}
return rewardNumerator, nil
}

View File

@@ -211,7 +211,7 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
if npc, err := st.NumPendingConsolidations(); err != nil {
return fmt.Errorf("failed to fetch number of pending consolidations: %w", err) // This should never happen.
} else if npc >= pcLimit {
return nil
continue
}
activeBal, err := helpers.TotalActiveBalance(st)
@@ -220,7 +220,7 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
}
churnLimit := helpers.ConsolidationChurnLimit(primitives.Gwei(activeBal))
if churnLimit <= primitives.Gwei(params.BeaconConfig().MinActivationBalance) {
return nil
continue
}
srcIdx, ok := st.ValidatorIndexByPubkey(sourcePubkey)

View File

@@ -415,6 +415,44 @@ func TestProcessConsolidationRequests(t *testing.T) {
require.Equal(t, params.BeaconConfig().FarFutureEpoch, src.WithdrawableEpoch, "source validator withdrawable epoch should not be updated")
},
},
{
name: "pending consolidations limit reached and compounded consolidation after",
state: func() state.BeaconState {
st := &eth.BeaconStateElectra{
Slot: params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().ShardCommitteePeriod)),
Validators: createValidatorsWithTotalActiveBalance(32000000000000000), // 32M ETH
PendingConsolidations: make([]*eth.PendingConsolidation, params.BeaconConfig().PendingConsolidationsLimit),
}
// To allow compounding consolidation requests.
st.Validators[3].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
s, err := state_native.InitializeFromProtoElectra(st)
require.NoError(t, err)
return s
}(),
reqs: []*enginev1.ConsolidationRequest{
{
SourceAddress: append(bytesutil.PadTo(nil, 19), byte(1)),
SourcePubkey: []byte("val_1"),
TargetPubkey: []byte("val_2"),
},
{
SourceAddress: append(bytesutil.PadTo(nil, 19), byte(3)),
SourcePubkey: []byte("val_3"),
TargetPubkey: []byte("val_3"),
},
},
validate: func(t *testing.T, st state.BeaconState) {
// Verify a pending consolidation is created.
numPC, err := st.NumPendingConsolidations()
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().PendingConsolidationsLimit, numPC)
// Verify that the last consolidation was included
src, err := st.ValidatorAtIndex(3)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().CompoundingWithdrawalPrefixByte, src.WithdrawalCredentials[0], "source validator was not compounded")
},
},
}
for _, tt := range tests {

View File

@@ -307,7 +307,7 @@ func TestProcessPendingDeposits(t *testing.T) {
}
func TestBatchProcessNewPendingDeposits(t *testing.T) {
t.Run("invalid batch initiates correct individual validation", func(t *testing.T) {
t.Run("one valid deposit one garbage deposit", func(t *testing.T) {
st := stateWithActiveBalanceETH(t, 0)
require.Equal(t, 0, len(st.Validators()))
require.Equal(t, 0, len(st.Balances()))
@@ -318,13 +318,47 @@ func TestBatchProcessNewPendingDeposits(t *testing.T) {
wc[31] = byte(0)
validDep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
invalidDep := &eth.PendingDeposit{PublicKey: make([]byte, 48)}
// have a combination of valid and invalid deposits
deps := []*eth.PendingDeposit{validDep, invalidDep}
require.NoError(t, electra.BatchProcessNewPendingDeposits(context.Background(), st, deps))
// successfully added to register
require.Equal(t, 1, len(st.Validators()))
require.Equal(t, 1, len(st.Balances()))
})
t.Run("two valid deposits from same key", func(t *testing.T) {
st := stateWithActiveBalanceETH(t, 0)
require.Equal(t, 0, len(st.Validators()))
require.Equal(t, 0, len(st.Balances()))
sk, err := bls.RandKey()
require.NoError(t, err)
wc := make([]byte, 32)
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
wc[31] = byte(0)
validDep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
deps := []*eth.PendingDeposit{validDep, validDep}
require.NoError(t, electra.BatchProcessNewPendingDeposits(context.Background(), st, deps))
require.Equal(t, 1, len(st.Validators()))
require.Equal(t, 1, len(st.Balances()))
require.Equal(t, params.BeaconConfig().MinActivationBalance*2, st.Balances()[0])
})
t.Run("one valid one with invalid signature deposit", func(t *testing.T) {
st := stateWithActiveBalanceETH(t, 0)
require.Equal(t, 0, len(st.Validators()))
require.Equal(t, 0, len(st.Balances()))
sk, err := bls.RandKey()
require.NoError(t, err)
wc := make([]byte, 32)
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
wc[31] = byte(0)
validDep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
invalidSigDep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
invalidSigDep.Signature = make([]byte, 96)
deps := []*eth.PendingDeposit{validDep, invalidSigDep}
require.NoError(t, electra.BatchProcessNewPendingDeposits(context.Background(), st, deps))
require.Equal(t, 1, len(st.Validators()))
require.Equal(t, 1, len(st.Balances()))
require.Equal(t, 2*params.BeaconConfig().MinActivationBalance, st.Balances()[0])
})
}
func TestProcessDepositRequests(t *testing.T) {

View File

@@ -10,7 +10,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
prysmTime "github.com/prysmaticlabs/prysm/v5/time"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
@@ -86,15 +85,7 @@ func IsAggregator(committeeCount uint64, slotSig []byte) (bool, error) {
//
// return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT)
func ComputeSubnetForAttestation(activeValCount uint64, att ethpb.Att) uint64 {
if att.Version() >= version.Electra {
committeeIndex := 0
committeeIndices := att.CommitteeBitsVal().BitIndices()
if len(committeeIndices) > 0 {
committeeIndex = committeeIndices[0]
}
return ComputeSubnetFromCommitteeAndSlot(activeValCount, primitives.CommitteeIndex(committeeIndex), att.GetData().Slot)
}
return ComputeSubnetFromCommitteeAndSlot(activeValCount, att.GetData().CommitteeIndex, att.GetData().Slot)
return ComputeSubnetFromCommitteeAndSlot(activeValCount, att.GetCommitteeIndex(), att.GetData().Slot)
}
// ComputeSubnetFromCommitteeAndSlot is a flattened version of ComputeSubnetForAttestation where we only pass in

View File

@@ -2,7 +2,10 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["lightclient.go"],
srcs = [
"lightclient.go",
"store.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client",
visibility = ["//visibility:public"],
deps = [
@@ -27,7 +30,10 @@ go_library(
go_test(
name = "go_default_test",
srcs = ["lightclient_test.go"],
srcs = [
"lightclient_test.go",
"store_test.go",
],
deps = [
":go_default_library",
"//config/fieldparams:go_default_library",

View File

@@ -32,7 +32,7 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T)
params.OverrideBeaconConfig(cfg)
t.Run("Altair", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
@@ -44,7 +44,7 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T)
})
t.Run("Capella", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
@@ -57,7 +57,7 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T)
})
t.Run("Deneb", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
@@ -70,7 +70,7 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T)
})
t.Run("Electra", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestElectra(false)
l := util.NewTestLightClient(t).SetupTestElectra(false, 0, true)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
@@ -94,7 +94,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
params.OverrideBeaconConfig(cfg)
t.Run("Altair", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
@@ -131,7 +131,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
t.Run("Capella", func(t *testing.T) {
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
@@ -238,7 +238,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
t.Run("Deneb", func(t *testing.T) {
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
@@ -390,7 +390,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
t.Run("Electra", func(t *testing.T) {
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestElectra(false)
l := util.NewTestLightClient(t).SetupTestElectra(false, 0, true)
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
@@ -542,7 +542,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
func TestLightClient_BlockToLightClientHeader(t *testing.T) {
t.Run("Altair", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -565,7 +565,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Bellatrix", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestBellatrix()
l := util.NewTestLightClient(t).SetupTestBellatrix(0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -589,7 +589,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
t.Run("Capella", func(t *testing.T) {
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -650,7 +650,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(true)
l := util.NewTestLightClient(t).SetupTestCapella(true, 0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -713,7 +713,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
t.Run("Deneb", func(t *testing.T) {
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false)
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -782,7 +782,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(true)
l := util.NewTestLightClient(t).SetupTestDeneb(true, 0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -853,7 +853,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
t.Run("Electra", func(t *testing.T) {
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestElectra(false)
l := util.NewTestLightClient(t).SetupTestElectra(false, 0, true)
header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block)
require.NoError(t, err)
@@ -918,7 +918,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestElectra(true)
l := util.NewTestLightClient(t).SetupTestElectra(true, 0, true)
header, err := lightClient.BlockToLightClientHeader(l.Ctx, l.State.Slot(), l.Block)
require.NoError(t, err)
@@ -984,7 +984,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Capella fork with Altair block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -1006,7 +1006,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Deneb fork with Altair block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -1029,7 +1029,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
t.Run("Deneb fork with Capella block", func(t *testing.T) {
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false)
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,
@@ -1089,7 +1089,7 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
t.Run("Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(true)
l := util.NewTestLightClient(t).SetupTestCapella(true, 0, true)
header, err := lightClient.BlockToLightClientHeader(
l.Ctx,

View File

@@ -0,0 +1,38 @@
package light_client
import (
"sync"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
)
type Store struct {
mu sync.RWMutex
lastFinalityUpdate interfaces.LightClientFinalityUpdate
lastOptimisticUpdate interfaces.LightClientOptimisticUpdate
}
func (s *Store) SetLastFinalityUpdate(update interfaces.LightClientFinalityUpdate) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastFinalityUpdate = update
}
func (s *Store) LastFinalityUpdate() interfaces.LightClientFinalityUpdate {
s.mu.RLock()
defer s.mu.RUnlock()
return s.lastFinalityUpdate
}
func (s *Store) SetLastOptimisticUpdate(update interfaces.LightClientOptimisticUpdate) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastOptimisticUpdate = update
}
func (s *Store) LastOptimisticUpdate() interfaces.LightClientOptimisticUpdate {
s.mu.RLock()
defer s.mu.RUnlock()
return s.lastOptimisticUpdate
}

View File

@@ -0,0 +1,67 @@
package light_client_test
import (
"testing"
lightClient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
)
func TestLightClientStore(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.AltairForkEpoch = 1
cfg.BellatrixForkEpoch = 2
cfg.CapellaForkEpoch = 3
cfg.DenebForkEpoch = 4
cfg.ElectraForkEpoch = 5
params.OverrideBeaconConfig(cfg)
// Initialize the light client store
lcStore := &lightClient.Store{}
// Create test light client updates for Capella and Deneb
lCapella := util.NewTestLightClient(t).SetupTestCapella(false, 0, true)
opUpdateCapella, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(lCapella.Ctx, lCapella.State.Slot(), lCapella.State, lCapella.Block, lCapella.AttestedState, lCapella.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, opUpdateCapella, "OptimisticUpdateCapella is nil")
finUpdateCapella, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(lCapella.Ctx, lCapella.State.Slot(), lCapella.State, lCapella.Block, lCapella.AttestedState, lCapella.AttestedBlock, lCapella.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, finUpdateCapella, "FinalityUpdateCapella is nil")
lDeneb := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true)
opUpdateDeneb, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(lDeneb.Ctx, lDeneb.State.Slot(), lDeneb.State, lDeneb.Block, lDeneb.AttestedState, lDeneb.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, opUpdateDeneb, "OptimisticUpdateDeneb is nil")
finUpdateDeneb, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(lDeneb.Ctx, lDeneb.State.Slot(), lDeneb.State, lDeneb.Block, lDeneb.AttestedState, lDeneb.AttestedBlock, lDeneb.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, finUpdateDeneb, "FinalityUpdateDeneb is nil")
// Initially the store should have nil values for both updates
require.IsNil(t, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should be nil")
require.IsNil(t, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate should be nil")
// Set and get finality with Capella update. Optimistic update should be nil
lcStore.SetLastFinalityUpdate(finUpdateCapella)
require.Equal(t, finUpdateCapella, lcStore.LastFinalityUpdate(), "lastFinalityUpdate is wrong")
require.IsNil(t, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate should be nil")
// Set and get optimistic with Capella update. Finality update should be Capella
lcStore.SetLastOptimisticUpdate(opUpdateCapella)
require.Equal(t, opUpdateCapella, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate is wrong")
require.Equal(t, finUpdateCapella, lcStore.LastFinalityUpdate(), "lastFinalityUpdate is wrong")
// Set and get finality and optimistic with Deneb update
lcStore.SetLastFinalityUpdate(finUpdateDeneb)
lcStore.SetLastOptimisticUpdate(opUpdateDeneb)
require.Equal(t, finUpdateDeneb, lcStore.LastFinalityUpdate(), "lastFinalityUpdate is wrong")
require.Equal(t, opUpdateDeneb, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate is wrong")
// Set and get finality and optimistic with nil update
lcStore.SetLastFinalityUpdate(nil)
lcStore.SetLastOptimisticUpdate(nil)
require.IsNil(t, lcStore.LastFinalityUpdate(), "lastFinalityUpdate should be nil")
require.IsNil(t, lcStore.LastOptimisticUpdate(), "lastOptimisticUpdate should be nil")
}

View File

@@ -415,11 +415,15 @@ func VerifyOperationLengths(_ context.Context, state state.BeaconState, b interf
)
}
if uint64(len(body.Attestations())) > params.BeaconConfig().MaxAttestations {
maxAttestations := params.BeaconConfig().MaxAttestations
if body.Version() >= version.Electra {
maxAttestations = params.BeaconConfig().MaxAttestationsElectra
}
if uint64(len(body.Attestations())) > maxAttestations {
return nil, fmt.Errorf(
"number of attestations (%d) in block body exceeds allowed threshold of %d",
len(body.Attestations()),
params.BeaconConfig().MaxAttestations,
maxAttestations,
)
}

View File

@@ -474,6 +474,24 @@ func TestProcessBlock_OverMaxAttestations(t *testing.T) {
assert.ErrorContains(t, want, err)
}
func TestProcessBlock_OverMaxAttestationsElectra(t *testing.T) {
b := &ethpb.SignedBeaconBlockElectra{
Block: &ethpb.BeaconBlockElectra{
Body: &ethpb.BeaconBlockBodyElectra{
Attestations: make([]*ethpb.AttestationElectra, params.BeaconConfig().MaxAttestationsElectra+1),
},
},
}
want := fmt.Sprintf("number of attestations (%d) in block body exceeds allowed threshold of %d",
len(b.Block.Body.Attestations), params.BeaconConfig().MaxAttestationsElectra)
s, err := state_native.InitializeFromProtoUnsafeElectra(&ethpb.BeaconStateElectra{})
require.NoError(t, err)
wsb, err := consensusblocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
_, err = transition.VerifyOperationLengths(context.Background(), s, wsb.Block())
assert.ErrorContains(t, want, err)
}
func TestProcessBlock_OverMaxVoluntaryExits(t *testing.T) {
maxExits := params.BeaconConfig().MaxVoluntaryExits
b := &ethpb.SignedBeaconBlock{

View File

@@ -2,13 +2,19 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["filter.go"],
srcs = [
"errors.go",
"filter.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filters",
visibility = [
"//beacon-chain:__subpackages__",
"//tools:__subpackages__",
],
deps = ["//consensus-types/primitives:go_default_library"],
deps = [
"//consensus-types/primitives:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)
go_test(
@@ -18,5 +24,6 @@ go_test(
deps = [
"//consensus-types/primitives:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
],
)

View File

@@ -0,0 +1,9 @@
package filters
import "errors"
var (
ErrIncompatibleFilters = errors.New("combination of filters is not valid")
ErrNotSet = errors.New("filter was not set")
ErrInvalidQuery = errors.New("invalid query")
)

View File

@@ -14,7 +14,10 @@
// }
package filters
import primitives "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)
// FilterType defines an enum which is used as the keys in a map that tracks
// set attribute filters for data as part of the `FilterQuery` struct type.
@@ -45,10 +48,34 @@ const (
SlotStep
)
// SlotRoot is the slot and root of a single block.
type SlotRoot struct {
Slot primitives.Slot
Root [32]byte
}
// AncestryQuery is a special query that describes a chain of blocks that satisfies the invariant of:
// blocks[n].parent_root == blocks[n-1].root.
type AncestryQuery struct {
// Slot of oldest to return.
Earliest primitives.Slot
// Descendent that all ancestors in chain must descend from.
Descendent SlotRoot
set bool
}
func (aq AncestryQuery) Span() primitives.Slot {
if aq.Earliest > aq.Descendent.Slot {
return 0
}
return (aq.Descendent.Slot - aq.Earliest) + 1 // +1 to include upper bound
}
// QueryFilter defines a generic interface for type-asserting
// specific filters to use in querying DB objects.
type QueryFilter struct {
queries map[FilterType]interface{}
queries map[FilterType]interface{}
ancestry AncestryQuery
}
// NewFilter instantiates a new QueryFilter type used to build filters for
@@ -132,3 +159,42 @@ func (q *QueryFilter) SetSlotStep(val uint64) *QueryFilter {
q.queries[SlotStep] = val
return q
}
// SimpleSlotRange returns the start and end slot of a query filter if it is a simple slot range query.
// A simple slot range query is one where the filter only contains a start slot and an end slot.
// If the query is not a simple slot range query, the bool return value will be false.
func (q *QueryFilter) SimpleSlotRange() (primitives.Slot, primitives.Slot, bool) {
if len(q.queries) != 2 || q.queries[StartSlot] == nil || q.queries[EndSlot] == nil {
return 0, 0, false
}
start, ok := q.queries[StartSlot].(primitives.Slot)
if !ok {
return 0, 0, false
}
end, ok := q.queries[EndSlot].(primitives.Slot)
if !ok {
return 0, 0, false
}
return start, end, true
}
// SetAncestryQuery sets the filter to be an ancestryQuery. Note that this filter type is exclusive with
// other filters, so call ing GetAncestryQuery will return an error if other values are set.
func (q *QueryFilter) SetAncestryQuery(aq AncestryQuery) *QueryFilter {
aq.set = true
q.ancestry = aq
return q
}
func (q *QueryFilter) GetAncestryQuery() (AncestryQuery, error) {
if !q.ancestry.set {
return q.ancestry, ErrNotSet
}
if len(q.queries) > 0 {
return q.ancestry, errors.Wrap(ErrIncompatibleFilters, "AncestryQuery cannot be combined with other filters")
}
if q.ancestry.Earliest > q.ancestry.Descendent.Slot {
return q.ancestry, errors.Wrap(ErrInvalidQuery, "descendent slot must come after earliest slot")
}
return q.ancestry, nil
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestQueryFilter_ChainsCorrectly(t *testing.T) {
@@ -28,3 +29,64 @@ func TestQueryFilter_ChainsCorrectly(t *testing.T) {
}
}
}
func TestSimpleSlotRange(t *testing.T) {
type tc struct {
name string
applFilters []func(*QueryFilter) *QueryFilter
isSimple bool
start primitives.Slot
end primitives.Slot
}
cases := []tc{
{
name: "no filters",
applFilters: []func(*QueryFilter) *QueryFilter{},
isSimple: false,
},
{
name: "start slot",
applFilters: []func(*QueryFilter) *QueryFilter{
func(f *QueryFilter) *QueryFilter {
return f.SetStartSlot(3)
},
},
isSimple: false,
},
{
name: "end slot",
applFilters: []func(*QueryFilter) *QueryFilter{
func(f *QueryFilter) *QueryFilter {
return f.SetEndSlot(3)
},
},
isSimple: false,
},
{
name: "end slot",
applFilters: []func(*QueryFilter) *QueryFilter{
func(f *QueryFilter) *QueryFilter {
return f.SetStartSlot(3)
},
func(f *QueryFilter) *QueryFilter {
return f.SetEndSlot(7)
},
},
start: 3,
end: 7,
isSimple: true,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
f := NewFilter()
for _, filt := range c.applFilters {
f = filt(f)
}
start, end, isSimple := f.SimpleSlotRange()
require.Equal(t, c.isSimple, isSimple)
require.Equal(t, c.start, start)
require.Equal(t, c.end, end)
})
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"slices"
"github.com/ethereum/go-ethereum/common"
"github.com/golang/snappy"
@@ -117,13 +118,95 @@ func (s *Store) HeadBlock(ctx context.Context) (interfaces.ReadOnlySignedBeaconB
return headBlock, err
}
// blocksAncestryQuery returns all blocks *before* the descendent block;
// that is: inclusive of q.Earliest, exclusive of q.Descendent.Slot.
func (s *Store) blocksAncestryQuery(ctx context.Context, q filters.AncestryQuery) ([]interfaces.ReadOnlySignedBeaconBlock, [][32]byte, error) {
// Save resources if no blocks will be found by the query.
if q.Span() < 1 {
return nil, nil, filters.ErrInvalidQuery
}
blocks := make([]interfaces.ReadOnlySignedBeaconBlock, 0, q.Span())
roots := make([][32]byte, 0, q.Span())
// Handle edge case where start and end are equal; slotRootsInRange would see end < start and err.
// So, just grab the descendent in its own tx and stop there.
if q.Span() == 1 {
err := s.db.View(func(tx *bolt.Tx) error {
descendent, err := s.getBlock(ctx, q.Descendent.Root, tx)
if err != nil {
return errors.Wrap(err, "descendent block not in db")
}
blocks = append(blocks, descendent)
roots = append(roots, q.Descendent.Root)
return nil
})
return blocks, roots, err
}
// stop before the descendent slot since it is determined by the query
sr, err := s.slotRootsInRange(ctx, q.Earliest, q.Descendent.Slot-1, -1)
if err != nil {
return nil, nil, err
}
err = s.db.View(func(tx *bolt.Tx) error {
descendent, err := s.getBlock(ctx, q.Descendent.Root, tx)
if err != nil {
return errors.Wrap(err, "descendent block not in db")
}
proot := descendent.Block().ParentRoot()
lowest := descendent.Block().Slot()
blocks = append(blocks, descendent)
roots = append(roots, q.Descendent.Root)
// slotRootsInRange returns the roots in descending order
for _, prev := range sr {
if prev.slot < q.Earliest {
return nil
}
if prev.slot >= lowest {
continue
}
if prev.root == proot {
p, err := s.getBlock(ctx, prev.root, tx)
if err != nil {
return err
}
roots = append(roots, prev.root)
blocks = append(blocks, p)
proot = p.Block().ParentRoot()
lowest = p.Block().Slot()
}
}
return nil
})
if err != nil {
return nil, nil, err
}
slices.Reverse(roots)
slices.Reverse(blocks)
return blocks, roots, err
}
// Blocks retrieves a list of beacon blocks and its respective roots by filter criteria.
func (s *Store) Blocks(ctx context.Context, f *filters.QueryFilter) ([]interfaces.ReadOnlySignedBeaconBlock, [][32]byte, error) {
ctx, span := trace.StartSpan(ctx, "BeaconDB.Blocks")
defer span.End()
if q, err := f.GetAncestryQuery(); err == nil {
return s.blocksAncestryQuery(ctx, q)
} else {
if !errors.Is(err, filters.ErrNotSet) {
return nil, nil, err
}
}
blocks := make([]interfaces.ReadOnlySignedBeaconBlock, 0)
blockRoots := make([][32]byte, 0)
if start, end, isSimple := f.SimpleSlotRange(); isSimple {
return s.blocksForSlotRange(ctx, start, end)
}
err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(blocksBucket)
@@ -146,6 +229,69 @@ func (s *Store) Blocks(ctx context.Context, f *filters.QueryFilter) ([]interface
return blocks, blockRoots, err
}
// cleanupMissingBlockIndices cleans up the slot->root mapping, and the parent root index pointing
// from each of these blocks to each of their children. Since we don't have the blocks themselves,
// we don't know their parent root to efficiently clean the index going the other direction.
func (s *Store) cleanupMissingBlockIndices(ctx context.Context, badBlocks []slotRoot) {
errs := make([]error, 0)
err := s.db.Update(func(tx *bolt.Tx) error {
for _, sr := range badBlocks {
log.WithField("root", fmt.Sprintf("%#x", sr.root)).WithField("slot", sr.slot).Warn("Cleaning up indices for missing block")
if err := s.deleteSlotIndexEntry(tx, sr.slot, sr.root); err != nil {
errs = append(errs, errors.Wrapf(err, "failed to clean up slot index entry for root %#x and slot %d", sr.root, sr.slot))
}
if err := tx.Bucket(blockParentRootIndicesBucket).Delete(sr.root[:]); err != nil {
errs = append(errs, errors.Wrapf(err, "failed to clean up block parent index for root %#x", sr.root))
}
}
return nil
})
if err != nil {
errs = append(errs, err)
}
for _, err := range errs {
log.WithError(err).Error("Failed to clean up indices for missing block")
}
}
// blocksForSlotRange gets all blocks and roots for a given slot range.
// This function uses the slot->root index, which can contain multiple entries for the same slot
// in case of forks. It will return all blocks for the given slot range, and the roots of those blocks.
// The [i]th element of the blocks slice corresponds to the [i]th element of the roots slice.
// If a block is not found, it will be added to a slice of missing blocks, which will have their indices cleaned
// in a separate Update transaction before the method returns. This is done to compensate for a previous bug where
// block deletions left danging index entries.
func (s *Store) blocksForSlotRange(ctx context.Context, startSlot, endSlot primitives.Slot) ([]interfaces.ReadOnlySignedBeaconBlock, [][32]byte, error) {
slotRootPairs, err := s.slotRootsInRange(ctx, startSlot, endSlot, -1) // set batch size to zero to retrieve all
if err != nil {
return nil, nil, err
}
slices.Reverse(slotRootPairs)
badBlocks := make([]slotRoot, 0)
defer func() { s.cleanupMissingBlockIndices(ctx, badBlocks) }()
roots := make([][32]byte, 0, len(slotRootPairs))
blks := make([]interfaces.ReadOnlySignedBeaconBlock, 0, len(slotRootPairs))
err = s.db.View(func(tx *bolt.Tx) error {
for _, sr := range slotRootPairs {
blk, err := s.getBlock(ctx, sr.root, tx)
if err != nil {
if errors.Is(err, ErrNotFound) {
badBlocks = append(badBlocks, sr)
continue
}
return err
}
roots = append(roots, sr.root)
blks = append(blks, blk)
}
return nil
})
if err != nil {
return nil, nil, err
}
return blks, roots, nil
}
// BlockRoots retrieves a list of beacon block roots by filter criteria. If the caller
// requires both the blocks and the block roots for a certain filter they should instead
// use the Blocks function rather than use BlockRoots. During periods of non finality
@@ -720,6 +866,7 @@ type slotRoot struct {
}
// slotRootsInRange returns slot and block root pairs of length min(batchSize, end-slot)
// If batchSize < 0, the limit check will be skipped entirely.
func (s *Store) slotRootsInRange(ctx context.Context, start, end primitives.Slot, batchSize int) ([]slotRoot, error) {
_, span := trace.StartSpan(ctx, "BeaconDB.slotRootsInRange")
defer span.End()
@@ -729,10 +876,26 @@ func (s *Store) slotRootsInRange(ctx context.Context, start, end primitives.Slot
var pairs []slotRoot
key := bytesutil.SlotToBytesBigEndian(end)
edge := false // used to detect whether we are at the very beginning or end of the index
err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(blockSlotIndicesBucket)
c := bkt.Cursor()
for k, v := c.Seek(key); k != nil; k, v = c.Prev() {
for k, v := c.Seek(key); ; /* rely on internal checks to exit */ k, v = c.Prev() {
if len(k) == 0 && len(v) == 0 {
// The `edge`` variable and this `if` deal with 2 edge cases:
// - Seeking past the end of the bucket (the `end` param is higher than the highest slot).
// - Seeking before the beginning of the bucket (the `start` param is lower than the lowest slot).
// In both of these cases k,v will be nil and we can handle the same way using `edge` to
// - continue to the next iteration. If the following Prev() key/value is nil, Prev has gone past the beginning.
// - Otherwise, iterate as usual.
if edge {
return nil
}
edge = true
continue
}
edge = false
slot := bytesutil.BytesToSlotBigEndian(k)
if slot > end {
continue // Seek will seek to the next key *after* the given one if not present
@@ -747,11 +910,13 @@ func (s *Store) slotRootsInRange(ctx context.Context, start, end primitives.Slot
for _, r := range roots {
pairs = append(pairs, slotRoot{slot: slot, root: r})
}
if batchSize < 0 {
continue
}
if len(pairs) >= batchSize {
return nil // allows code to easily cap the number of items pruned
}
}
return nil
})
return pairs, err

View File

@@ -24,9 +24,11 @@ import (
"google.golang.org/protobuf/proto"
)
type testNewBlockFunc func(primitives.Slot, []byte) (interfaces.ReadOnlySignedBeaconBlock, error)
var blockTests = []struct {
name string
newBlock func(primitives.Slot, []byte) (interfaces.ReadOnlySignedBeaconBlock, error)
newBlock testNewBlockFunc
}{
{
name: "phase0",
@@ -742,6 +744,120 @@ func TestStore_Blocks_FiltersCorrectly(t *testing.T) {
}
}
func testBlockChain(t *testing.T, nb testNewBlockFunc, slots []primitives.Slot, parent []byte) []interfaces.ReadOnlySignedBeaconBlock {
if len(parent) < 32 {
var zero [32]byte
copy(parent, zero[:])
}
chain := make([]interfaces.ReadOnlySignedBeaconBlock, 0, len(slots))
for _, slot := range slots {
pr := make([]byte, 32)
copy(pr, parent)
b, err := nb(slot, pr)
require.NoError(t, err)
chain = append(chain, b)
npr, err := b.Block().HashTreeRoot()
parent = npr[:]
require.NoError(t, err)
}
return chain
}
func testSlotSlice(start, end primitives.Slot) []primitives.Slot {
end += 1 // add 1 to make the range inclusive
slots := make([]primitives.Slot, 0, end-start)
for ; start < end; start++ {
slots = append(slots, start)
}
return slots
}
func TestCleanupMissingBlockIndices(t *testing.T) {
for _, tt := range blockTests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
db := setupDB(t)
chain := testBlockChain(t, tt.newBlock, testSlotSlice(1, 10), nil)
require.NoError(t, db.SaveBlocks(ctx, chain))
corrupt, err := blocks.NewROBlock(chain[5])
require.NoError(t, err)
cr := corrupt.Root()
require.NoError(t, db.db.Update(func(tx *bolt.Tx) error {
return tx.Bucket(blocksBucket).Delete(cr[:])
}))
// Need to also delete it from the cache!!
db.blockCache.Del(string(cr[:]))
res, roots, err := db.Blocks(ctx, filters.NewFilter().SetEndSlot(10).SetStartSlot(1))
require.NoError(t, err)
require.Equal(t, 9, len(roots))
require.Equal(t, len(res), len(roots))
require.NoError(t, db.db.View(func(tx *bolt.Tx) error {
encSlot := bytesutil.SlotToBytesBigEndian(corrupt.Block().Slot())
// make sure slot->root index is cleaned up
require.Equal(t, 0, len(tx.Bucket(blockSlotIndicesBucket).Get(encSlot)))
require.Equal(t, 0, len(tx.Bucket(blockParentRootIndicesBucket).Get(cr[:])))
return nil
}))
})
}
}
func TestCleanupMissingForkedBlockIndices(t *testing.T) {
for _, tt := range blockTests[0:1] {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
db := setupDB(t)
chain := testBlockChain(t, tt.newBlock, testSlotSlice(1, 10), nil)
require.NoError(t, db.SaveBlocks(ctx, chain))
// forkChain should skip the slot at skipBlock, and have the same parent
skipBlockParent := chain[4].Block().ParentRoot()
// It should start at the same slot as missingBlock, which comes one slot after the skip slot,
// so there are 2 blocks in that slot
missingBlock, err := blocks.NewROBlock(chain[5])
require.NoError(t, err)
// missingBlock will be deleted in the main chain, but there will be a block at that slot in the fork chain
forkChain := testBlockChain(t, tt.newBlock, testSlotSlice(missingBlock.Block().Slot(), 10), skipBlockParent[:])
require.NoError(t, db.SaveBlocks(ctx, forkChain))
forkChainStart, err := blocks.NewROBlock(forkChain[0])
require.NoError(t, err)
encMissingSlot := bytesutil.SlotToBytesBigEndian(missingBlock.Block().Slot())
require.NoError(t, db.db.View(func(tx *bolt.Tx) error {
require.Equal(t, 32, len(tx.Bucket(blockParentRootIndicesBucket).Get(missingBlock.RootSlice())))
// There are 2 block roots packed in this slot, so it is 64 bytes long
require.Equal(t, 64, len(tx.Bucket(blockSlotIndicesBucket).Get(encMissingSlot)))
// skipBlockParent should also have 2 entries and be 64 bytes, since the forkChain is based on the same parent as the skip block
childRoots := tx.Bucket(blockParentRootIndicesBucket).Get(skipBlockParent[:])
require.Equal(t, 64, len(childRoots))
return nil
}))
require.NoError(t, db.db.Update(func(tx *bolt.Tx) error {
return tx.Bucket(blocksBucket).Delete(missingBlock.RootSlice())
}))
// Need to also delete it from the cache!!
db.blockCache.Del(string(missingBlock.RootSlice()))
// Blocks should give us blocks from all chains.
res, roots, err := db.Blocks(ctx, filters.NewFilter().SetEndSlot(10).SetStartSlot(1))
require.NoError(t, err)
require.Equal(t, (len(chain)-1)+len(forkChain), len(roots))
require.Equal(t, len(res), len(roots))
require.NoError(t, db.db.View(func(tx *bolt.Tx) error {
// There should now be 32 bytes in this index - one root from the forked chain
slotIdxVal := tx.Bucket(blockSlotIndicesBucket).Get(encMissingSlot)
require.Equal(t, forkChainStart.Root(), [32]byte(slotIdxVal))
require.Equal(t, 0, len(tx.Bucket(blockParentRootIndicesBucket).Get(missingBlock.RootSlice())))
forkChildRoot := tx.Bucket(blockParentRootIndicesBucket).Get(skipBlockParent[:])
require.Equal(t, 64, len(forkChildRoot))
return nil
}))
})
}
}
func TestStore_Blocks_VerifyBlockRoots(t *testing.T) {
for _, tt := range blockTests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -587,7 +587,7 @@ func BenchmarkHighestAttestations(b *testing.B) {
beaconDB := setupDB(b)
require.NoError(b, beaconDB.SaveAttestationRecordsForValidators(ctx, atts))
allIndices := make([]primitives.ValidatorIndex, valsPerAtt*count)
allIndices := make([]primitives.ValidatorIndex, 0, valsPerAtt*count)
for i := 0; i < count; i++ {
indicesForAtt := make([]primitives.ValidatorIndex, valsPerAtt)
for r := 0; r < valsPerAtt; r++ {

View File

@@ -622,6 +622,25 @@ func (f *ForkChoice) Slot(root [32]byte) (primitives.Slot, error) {
return n.slot, nil
}
// DependentRoot returns the last root of the epoch prior to the requested ecoch in the canonical chain.
func (f *ForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
tr, err := f.TargetRootForEpoch(f.CachedHeadRoot(), epoch)
if err != nil {
return [32]byte{}, err
}
if tr == [32]byte{} {
return [32]byte{}, nil
}
n, ok := f.store.nodeByRoot[tr]
if !ok || n == nil {
return [32]byte{}, ErrNilNode
}
if slots.ToEpoch(n.slot) == epoch && n.parent != nil {
n = n.parent
}
return n.root, nil
}
// TargetRootForEpoch returns the root of the target block for a given epoch.
// The epoch parameter is crucial to identify the correct target root. For example:
// When inserting a block at slot 63 with block root 0xA and target root 0xB (pointing to the block at slot 32),

View File

@@ -30,7 +30,7 @@ func TestLastRoot(t *testing.T) {
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'}]
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))

View File

@@ -471,53 +471,101 @@ func TestStore_TargetRootForEpoch(t *testing.T) {
target, err := f.TargetRootForEpoch(blk.Root(), 1)
require.NoError(t, err)
require.Equal(t, target, blk.Root())
dependent, err := f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
state, blk1, err := prepareForkchoiceState(ctx, params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'b'}, blk.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk1))
headRoot, err := f.Head(ctx) // To cache the head root
require.NoError(t, err)
require.Equal(t, headRoot, blk1.Root())
target, err = f.TargetRootForEpoch(blk1.Root(), 1)
require.NoError(t, err)
require.Equal(t, target, blk.Root())
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
// Insert a block for the next epoch (missed slot 0)
state, blk2, err := prepareForkchoiceState(ctx, 2*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'c'}, blk1.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk2))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk2.Root())
target, err = f.TargetRootForEpoch(blk2.Root(), 2)
require.NoError(t, err)
require.Equal(t, target, blk1.Root())
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk2.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
state, blk3, err := prepareForkchoiceState(ctx, 2*params.BeaconConfig().SlotsPerEpoch+2, [32]byte{'d'}, blk2.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk3))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk3.Root())
target, err = f.TargetRootForEpoch(blk2.Root(), 2)
require.NoError(t, err)
require.Equal(t, target, blk1.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
// Prune finalization
s := f.store
s.finalizedCheckpoint.Root = blk1.Root()
s.justifiedCheckpoint.Root = blk1.Root()
require.NoError(t, s.prune(ctx))
target, err = f.TargetRootForEpoch(blk1.Root(), 1)
require.NoError(t, err)
require.Equal(t, [32]byte{}, target)
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, [32]byte{}, dependent)
// Insert a block for next epoch (slot 0 present)
state, blk4, err := prepareForkchoiceState(ctx, 3*params.BeaconConfig().SlotsPerEpoch, [32]byte{'e'}, blk1.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk4))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk4.Root())
target, err = f.TargetRootForEpoch(blk4.Root(), 3)
require.NoError(t, err)
require.Equal(t, target, blk4.Root())
dependent, err = f.DependentRoot(3)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
state, blk5, err := prepareForkchoiceState(ctx, 3*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'f'}, blk4.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk5))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk5.Root())
target, err = f.TargetRootForEpoch(blk5.Root(), 3)
require.NoError(t, err)
require.Equal(t, target, blk4.Root())
dependent, err = f.DependentRoot(3)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
// Target root where the target epoch is same or ahead of the block slot
target, err = f.TargetRootForEpoch(blk5.Root(), 4)
@@ -533,9 +581,21 @@ func TestStore_TargetRootForEpoch(t *testing.T) {
state, blk6, err := prepareForkchoiceState(ctx, 4*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'g'}, blk5.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk6))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk6.Root())
target, err = f.TargetRootForEpoch(blk6.Root(), 4)
require.NoError(t, err)
require.Equal(t, target, blk5.Root())
dependent, err = f.DependentRoot(4)
require.NoError(t, err)
require.Equal(t, dependent, blk5.Root())
dependent, err = f.DependentRoot(3)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
target, err = f.TargetRootForEpoch(blk6.Root(), 2)
require.NoError(t, err)
require.Equal(t, target, blk1.Root())

View File

@@ -79,6 +79,7 @@ type FastGetter interface {
ReceivedBlocksLastEpoch() (uint64, error)
ShouldOverrideFCU() bool
Slot([32]byte) (primitives.Slot, error)
DependentRoot(primitives.Epoch) ([32]byte, error)
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
UnrealizedJustifiedPayloadBlockHash() [32]byte
Weight(root [32]byte) (uint64, error)

View File

@@ -170,6 +170,13 @@ func (ro *ROForkChoice) LastRoot(e primitives.Epoch) [32]byte {
return ro.getter.LastRoot(e)
}
// DependentRoot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.DependentRoot(epoch)
}
// TargetRootForEpoch delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock()

View File

@@ -39,6 +39,7 @@ const (
lastRootCalled
targetRootForEpochCalled
parentRootCalled
dependentRootCalled
)
func _discard(t *testing.T, e error) {
@@ -156,6 +157,11 @@ func TestROLocking(t *testing.T) {
call: targetRootForEpochCalled,
cb: func(g FastGetter) { _, err := g.TargetRootForEpoch([32]byte{}, 0); _discard(t, err) },
},
{
name: "dependentRootCalled",
call: dependentRootCalled,
cb: func(g FastGetter) { _, err := g.DependentRoot(0); _discard(t, err) },
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -293,6 +299,12 @@ func (ro *mockROForkchoice) LastRoot(_ primitives.Epoch) [32]byte {
return [32]byte{}
}
// DependentRoot impoements FastGetter.
func (ro *mockROForkchoice) DependentRoot(_ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, dependentRootCalled)
return [32]byte{}, nil
}
// TargetRootForEpoch implements FastGetter.
func (ro *mockROForkchoice) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, targetRootForEpochCalled)

View File

@@ -22,6 +22,7 @@ go_library(
"//beacon-chain/builder:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/cache/depositsnapshot:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/db/filesystem:go_default_library",
"//beacon-chain/db/kv:go_default_library",
@@ -61,7 +62,6 @@ go_library(
"//monitoring/prometheus:go_default_library",
"//monitoring/tracing:go_default_library",
"//runtime:go_default_library",
"//runtime/debug:go_default_library",
"//runtime/prereqs:go_default_library",
"//runtime/version:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",

View File

@@ -26,6 +26,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/builder"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache/depositsnapshot"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filesystem"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/kv"
@@ -64,7 +65,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/prometheus"
"github.com/prysmaticlabs/prysm/v5/runtime"
"github.com/prysmaticlabs/prysm/v5/runtime/debug"
"github.com/prysmaticlabs/prysm/v5/runtime/prereqs"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/sirupsen/logrus"
@@ -123,6 +123,7 @@ type BeaconNode struct {
verifyInitWaiter *verification.InitializerWaiter
syncChecker *initialsync.SyncChecker
slasherEnabled bool
lcStore *lightclient.Store
}
// New creates a new node instance, sets up configuration options, and registers
@@ -161,6 +162,7 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
initialSyncComplete: make(chan struct{}),
syncChecker: &initialsync.SyncChecker{},
slasherEnabled: cliCtx.Bool(flags.SlasherFlag.Name),
lcStore: &lightclient.Store{},
}
for _, opt := range opts {
@@ -432,7 +434,6 @@ func (b *BeaconNode) Start() {
defer signal.Stop(sigc)
<-sigc
log.Info("Got interrupt, shutting down...")
debug.Exit(b.cliCtx) // Ensure trace and CPU profile data are flushed.
go b.Close()
for i := 10; i > 0; i-- {
<-sigc

View File

@@ -17,6 +17,7 @@ go_library(
"//validator/client:__pkg__",
],
deps = [
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",

View File

@@ -9,12 +9,11 @@ import (
"github.com/pkg/errors"
ssz "github.com/prysmaticlabs/fastssz"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
const rootLength = 32
const maxErrorLength = 256
// SSZBytes is a bytes slice that satisfies the fast-ssz interface.
@@ -34,7 +33,7 @@ func (b *SSZBytes) HashTreeRootWith(hh *ssz.Hasher) error {
}
// BeaconBlockByRootsReq specifies the block by roots request type.
type BeaconBlockByRootsReq [][rootLength]byte
type BeaconBlockByRootsReq [][fieldparams.RootLength]byte
// MarshalSSZTo marshals the block by roots request with the provided byte slice.
func (r *BeaconBlockByRootsReq) MarshalSSZTo(dst []byte) ([]byte, error) {
@@ -59,25 +58,25 @@ func (r *BeaconBlockByRootsReq) MarshalSSZ() ([]byte, error) {
// SizeSSZ returns the size of the serialized representation.
func (r *BeaconBlockByRootsReq) SizeSSZ() int {
return len(*r) * rootLength
return len(*r) * fieldparams.RootLength
}
// UnmarshalSSZ unmarshals the provided bytes buffer into the
// block by roots request object.
func (r *BeaconBlockByRootsReq) UnmarshalSSZ(buf []byte) error {
bufLen := len(buf)
maxLength := int(params.BeaconConfig().MaxRequestBlocks * rootLength)
maxLength := int(params.BeaconConfig().MaxRequestBlocks * fieldparams.RootLength)
if bufLen > maxLength {
return errors.Errorf("expected buffer with length of up to %d but received length %d", maxLength, bufLen)
}
if bufLen%rootLength != 0 {
if bufLen%fieldparams.RootLength != 0 {
return ssz.ErrIncorrectByteSize
}
numOfRoots := bufLen / rootLength
roots := make([][rootLength]byte, 0, numOfRoots)
numOfRoots := bufLen / fieldparams.RootLength
roots := make([][fieldparams.RootLength]byte, 0, numOfRoots)
for i := 0; i < numOfRoots; i++ {
var rt [rootLength]byte
copy(rt[:], buf[i*rootLength:(i+1)*rootLength])
var rt [fieldparams.RootLength]byte
copy(rt[:], buf[i*fieldparams.RootLength:(i+1)*fieldparams.RootLength])
roots = append(roots, rt)
}
*r = roots

View File

@@ -883,6 +883,16 @@ func (s *Service) beaconEndpoints(
handler: server.GetValidatorBalances,
methods: []string{http.MethodGet, http.MethodPost},
},
{
template: "/eth/v1/beacon/states/{state_id}/validator_identities",
name: namespace + ".GetValidatorIdentities",
middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
},
handler: server.GetValidatorIdentities,
methods: []string{http.MethodPost},
},
{
// Deprecated: no longer needed post Electra
template: "/eth/v1/beacon/deposit_snapshot",

View File

@@ -24,6 +24,7 @@ func Test_endpoints(t *testing.T) {
"/eth/v1/beacon/states/{state_id}/validators": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/states/{state_id}/validators/{validator_id}": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/validator_balances": {http.MethodGet, http.MethodPost},
"/eth/v1/beacon/states/{state_id}/validator_identities": {http.MethodPost},
"/eth/v1/beacon/states/{state_id}/committees": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/sync_committees": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/randao": {http.MethodGet},
@@ -150,7 +151,14 @@ func Test_endpoints(t *testing.T) {
actualRoutes[e.template] = e.methods
}
}
expectedRoutes := combineMaps(beaconRoutes, builderRoutes, configRoutes, debugRoutes, eventsRoutes, nodeRoutes, validatorRoutes, rewardsRoutes, lightClientRoutes, blobRoutes, prysmValidatorRoutes, prysmNodeRoutes, prysmBeaconRoutes)
expectedRoutes := make(map[string][]string)
for _, m := range []map[string][]string{
beaconRoutes, builderRoutes, configRoutes, debugRoutes, eventsRoutes,
nodeRoutes, validatorRoutes, rewardsRoutes, lightClientRoutes, blobRoutes,
prysmValidatorRoutes, prysmNodeRoutes, prysmBeaconRoutes,
} {
maps.Copy(expectedRoutes, m)
}
assert.Equal(t, true, maps.EqualFunc(expectedRoutes, actualRoutes, func(actualMethods []string, expectedMethods []string) bool {
return slices.Equal(expectedMethods, actualMethods)

View File

@@ -219,7 +219,7 @@ func (s *Server) getBlockV2Ssz(w http.ResponseWriter, blk interfaces.ReadOnlySig
return
}
w.Header().Set(api.VersionHeader, version.String(blk.Version()))
httputil.WriteSsz(w, result, "beacon_block.ssz")
httputil.WriteSsz(w, result)
}
func (*Server) getBlockResponseBodySsz(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) {
@@ -1568,7 +1568,7 @@ func (s *Server) GetDepositSnapshot(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, "Could not marshal deposit snapshot into SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszData, "deposit_snapshot.ssz")
httputil.WriteSsz(w, sszData)
return
}
httputil.WriteJson(
@@ -1646,7 +1646,7 @@ func (s *Server) GetPendingDeposits(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, "Failed to serialize pending deposits: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszData, "pending_deposits.ssz")
httputil.WriteSsz(w, sszData)
} else {
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
if err != nil {
@@ -1702,7 +1702,7 @@ func (s *Server) GetPendingPartialWithdrawals(w http.ResponseWriter, r *http.Req
httputil.HandleError(w, "Failed to serialize pending partial withdrawals: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszData, "pending_partial_withdrawals.ssz")
httputil.WriteSsz(w, sszData)
} else {
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
if err != nil {

View File

@@ -1,6 +1,7 @@
package beacon
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -21,6 +22,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
@@ -324,6 +326,152 @@ func (s *Server) GetValidatorBalances(w http.ResponseWriter, r *http.Request) {
httputil.WriteJson(w, resp)
}
// GetValidatorIdentities returns a filterable list of validators identities.
func (s *Server) GetValidatorIdentities(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetValidatorIdentities")
defer span.End()
stateId := r.PathValue("state_id")
if stateId == "" {
httputil.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
}
var rawIds []string
err = json.NewDecoder(r.Body).Decode(&rawIds)
switch {
case errors.Is(err, io.EOF):
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
return
case err != nil:
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
ids, ok := decodeIds(w, st, rawIds, true /* ignore unknown */)
if !ok {
return
}
if httputil.RespondWithSsz(r) {
s.getValidatorIdentitiesSSZ(w, st, rawIds, ids)
} else {
s.getValidatorIdentitiesJSON(r.Context(), w, st, stateId, rawIds, ids)
}
}
func (s *Server) getValidatorIdentitiesSSZ(w http.ResponseWriter, st state.BeaconState, rawIds []string, ids []primitives.ValidatorIndex) {
// return no data if all IDs are ignored
if len(rawIds) > 0 && len(ids) == 0 {
httputil.WriteSsz(w, []byte{})
return
}
vals := st.ValidatorsReadOnly()
var identities []*eth.ValidatorIdentity
if len(ids) == 0 {
identities = make([]*eth.ValidatorIdentity, len(vals))
for i, v := range vals {
pubkey := v.PublicKey()
identities[i] = &eth.ValidatorIdentity{
Index: primitives.ValidatorIndex(i),
Pubkey: pubkey[:],
ActivationEpoch: v.ActivationEpoch(),
}
}
} else {
identities = make([]*eth.ValidatorIdentity, len(ids))
for i, id := range ids {
pubkey := vals[id].PublicKey()
identities[i] = &eth.ValidatorIdentity{
Index: id,
Pubkey: pubkey[:],
ActivationEpoch: vals[id].ActivationEpoch(),
}
}
}
sszLen := (&eth.ValidatorIdentity{}).SizeSSZ()
resp := make([]byte, len(identities)*sszLen)
for i, vi := range identities {
ssz, err := vi.MarshalSSZ()
if err != nil {
httputil.HandleError(w, "Could not marshal validator identity to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
copy(resp[i*sszLen:(i+1)*sszLen], ssz)
}
httputil.WriteSsz(w, resp)
}
func (s *Server) getValidatorIdentitiesJSON(
ctx context.Context,
w http.ResponseWriter,
st state.BeaconState,
stateId string,
rawIds []string,
ids []primitives.ValidatorIndex,
) {
isOptimistic, err := helpers.IsOptimistic(ctx, []byte(stateId), s.OptimisticModeFetcher, s.Stater, s.ChainInfoFetcher, s.BeaconDB)
if err != nil {
httputil.HandleError(w, "Could not check optimistic status: "+err.Error(), http.StatusInternalServerError)
return
}
blockRoot, err := st.LatestBlockHeader().HashTreeRoot()
if err != nil {
httputil.HandleError(w, "Could not calculate root of latest block header: "+err.Error(), http.StatusInternalServerError)
return
}
isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot)
// return no data if all IDs are ignored
if len(rawIds) > 0 && len(ids) == 0 {
resp := &structs.GetValidatorIdentitiesResponse{
Data: []*structs.ValidatorIdentity{},
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
httputil.WriteJson(w, resp)
return
}
vals := st.ValidatorsReadOnly()
var identities []*structs.ValidatorIdentity
if len(ids) == 0 {
identities = make([]*structs.ValidatorIdentity, len(vals))
for i, v := range vals {
pubkey := v.PublicKey()
identities[i] = &structs.ValidatorIdentity{
Index: strconv.FormatUint(uint64(i), 10),
Pubkey: hexutil.Encode(pubkey[:]),
ActivationEpoch: strconv.FormatUint(uint64(v.ActivationEpoch()), 10),
}
}
} else {
identities = make([]*structs.ValidatorIdentity, len(ids))
for i, id := range ids {
pubkey := vals[id].PublicKey()
identities[i] = &structs.ValidatorIdentity{
Index: strconv.FormatUint(uint64(id), 10),
Pubkey: hexutil.Encode(pubkey[:]),
ActivationEpoch: strconv.FormatUint(uint64(vals[id].ActivationEpoch()), 10),
}
}
}
resp := &structs.GetValidatorIdentitiesResponse{
Data: identities,
ExecutionOptimistic: isOptimistic,
Finalized: isFinalized,
}
httputil.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) {

View File

@@ -11,6 +11,7 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
@@ -946,16 +947,16 @@ func TestGetValidatorBalances(t *testing.T) {
hexPubkey := hexutil.Encode(pubkey[:])
request := httptest.NewRequest(
http.MethodGet,
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validators?id=%s&id=1", hexPubkey),
fmt.Sprintf("http://example.com/eth/v1/beacon/states/{state_id}/validator_balances?id=%s&id=1", hexPubkey),
nil,
)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidators(writer, request)
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorsResponse{}
resp := &structs.GetValidatorBalancesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "0", resp.Data[0].Index)
@@ -1025,7 +1026,7 @@ func TestGetValidatorBalances(t *testing.T) {
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidator(writer, request)
s.GetValidatorBalances(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
@@ -1183,3 +1184,478 @@ func TestGetValidatorBalances(t *testing.T) {
assert.StringContains(t, "Could not decode request body", e.Message)
})
}
func TestGetValidatorIdentities(t *testing.T) {
count := uint64(4)
genesisState, _ := util.DeterministicGenesisState(t, count)
st := genesisState.ToProtoUnsafe().(*eth.BeaconState)
for i := uint64(0); i < count; i++ {
st.Validators[i].ActivationEpoch = primitives.Epoch(i)
}
t.Run("json", func(t *testing.T) {
t.Run("get all", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 4, len(resp.Data))
for i := uint64(0); i < count; i++ {
assert.Equal(t, fmt.Sprintf("%d", i), resp.Data[i].Index)
assert.DeepEqual(t, hexutil.Encode(st.Validators[i].PublicKey), resp.Data[i].Pubkey)
assert.Equal(t, fmt.Sprintf("%d", st.Validators[i].ActivationEpoch), resp.Data[i].ActivationEpoch)
}
})
t.Run("get by index", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[\"0\",\"1\"]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "0", resp.Data[0].Index)
assert.Equal(t, "1", resp.Data[1].Index)
})
t.Run("get by pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey1 := st.Validators[0].PublicKey
pubkey2 := st.Validators[1].PublicKey
hexPubkey1 := hexutil.Encode(pubkey1)
hexPubkey2 := hexutil.Encode(pubkey2)
body := bytes.Buffer{}
_, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey1, hexPubkey2))
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "0", resp.Data[0].Index)
assert.Equal(t, "1", 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: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.Validators[0].PublicKey
hexPubkey := hexutil.Encode(pubkey)
body := bytes.Buffer{}
_, err := body.WriteString(fmt.Sprintf("[\"%s\",\"1\"]", hexPubkey))
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 2, len(resp.Data))
assert.Equal(t, "0", resp.Data[0].Index)
assert.Equal(t, "1", resp.Data[1].Index)
})
t.Run("unknown pubkey is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.Validators[1].PublicKey
hexPubkey := hexutil.Encode(pubkey)
body := bytes.Buffer{}
_, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey, hexutil.Encode([]byte(strings.Repeat("x", fieldparams.BLSPubkeyLength)))))
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
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: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[\"1\",\"99999\"]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
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: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.ExecutionOptimistic)
})
t.Run("finalized", func(t *testing.T) {
headerRoot, err := genesisState.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
chainService := &chainMock.ChainService{
FinalizedRoots: map[[32]byte]bool{
headerRoot: true,
},
}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err = body.WriteString("[]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, true, resp.Finalized)
})
})
t.Run("ssz", func(t *testing.T) {
size := uint64((&eth.ValidatorIdentity{}).SizeSSZ())
t.Run("get all", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.Header.Set("Accept", api.OctetStreamMediaType)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, size*count, uint64(len(writer.Body.Bytes())))
})
t.Run("get by index", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[\"0\",\"1\"]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.Header.Set("Accept", api.OctetStreamMediaType)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, size*2, uint64(len(writer.Body.Bytes())))
})
t.Run("get by pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey1 := st.Validators[0].PublicKey
pubkey2 := st.Validators[1].PublicKey
hexPubkey1 := hexutil.Encode(pubkey1)
hexPubkey2 := hexutil.Encode(pubkey2)
body := bytes.Buffer{}
_, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey1, hexPubkey2))
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.Header.Set("Accept", api.OctetStreamMediaType)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, size*2, uint64(len(writer.Body.Bytes())))
})
t.Run("get by both index and pubkey", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.Validators[0].PublicKey
hexPubkey := hexutil.Encode(pubkey)
body := bytes.Buffer{}
_, err := body.WriteString(fmt.Sprintf("[\"%s\",\"1\"]", hexPubkey))
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.Header.Set("Accept", api.OctetStreamMediaType)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, size*2, uint64(len(writer.Body.Bytes())))
})
t.Run("unknown pubkey is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
pubkey := st.Validators[1].PublicKey
hexPubkey := hexutil.Encode(pubkey)
body := bytes.Buffer{}
_, err := body.WriteString(fmt.Sprintf("[\"%s\",\"%s\"]", hexPubkey, hexutil.Encode([]byte(strings.Repeat("x", fieldparams.BLSPubkeyLength)))))
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.Header.Set("Accept", api.OctetStreamMediaType)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, size, uint64(len(writer.Body.Bytes())))
})
t.Run("unknown index is ignored", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("[\"1\",\"99999\"]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.Header.Set("Accept", api.OctetStreamMediaType)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, size, uint64(len(writer.Body.Bytes())))
})
})
t.Run("errors", func(t *testing.T) {
t.Run("state ID required", func(t *testing.T) {
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: &chainMock.ChainService{},
}
body := bytes.Buffer{}
_, err := body.WriteString("[]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
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("empty body", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", nil)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "No data submitted", e.Message)
})
t.Run("invalid body", func(t *testing.T) {
chainService := &chainMock.ChainService{}
s := Server{
Stater: &testutil.MockStater{
BeaconState: genesisState,
},
HeadFetcher: chainService,
OptimisticModeFetcher: chainService,
FinalizationFetcher: chainService,
}
body := bytes.Buffer{}
_, err := body.WriteString("foo")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodPost, "http://example.com/eth/v1/beacon/states/{state_id}/validator_identities", &body)
request.SetPathValue("state_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetValidatorIdentities(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
e := &httputil.DefaultJsonError{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
assert.Equal(t, http.StatusBadRequest, e.Code)
assert.StringContains(t, "Could not decode request body", e.Message)
})
})
}

View File

@@ -70,7 +70,7 @@ func (s *Server) Blobs(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set(api.VersionHeader, version.String(blk.Version()))
httputil.WriteSsz(w, sszResp, "blob_sidecars.ssz")
httputil.WriteSsz(w, sszResp)
return
}

View File

@@ -134,7 +134,7 @@ func (s *Server) getBeaconStateSSZV2(ctx context.Context, w http.ResponseWriter,
return
}
w.Header().Set(api.VersionHeader, version.String(st.Version()))
httputil.WriteSsz(w, sszState, "beacon_state.ssz")
httputil.WriteSsz(w, sszState)
}
// GetForkChoiceHeadsV2 retrieves the leaves of the current fork choice tree.

View File

@@ -53,7 +53,7 @@ func IsOptimistic(
}
return optimisticModeFetcher.IsOptimisticForRoot(ctx, bytesutil.ToBytes32(jcp.Root))
default:
if len(stateIdString) >= 2 && stateIdString[:2] == "0x" {
if bytesutil.IsHex(stateId) {
id, err := hexutil.Decode(stateIdString)
if err != nil {
return false, err

View File

@@ -13,18 +13,22 @@ go_library(
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/db:go_default_library",
"//beacon-chain/rpc/eth/shared:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//network/forks:go_default_library",
"//network/httputil:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_wealdtech_go_bytesutil//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
],
)
@@ -56,5 +60,6 @@ go_test(
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
],
)

View File

@@ -8,17 +8,21 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
ssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
"github.com/prysmaticlabs/prysm/v5/network/forks"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/wealdtech/go-bytesutil"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// GetLightClientBootstrap - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/bootstrap.yaml
@@ -58,7 +62,7 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques
httputil.HandleError(w, "Could not marshal bootstrap to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, ssz, "light_client_bootstrap.ssz")
httputil.WriteSsz(w, ssz)
} else {
data, err := structs.LightClientBootstrapFromConsensus(bootstrap)
if err != nil {
@@ -111,27 +115,79 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
return
}
updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap))
if httputil.RespondWithSsz(req) {
w.Header().Set("Content-Type", "application/octet-stream")
for i := startPeriod; i <= endPeriod; i++ {
update, ok := updatesMap[i]
if !ok {
// Only return the first contiguous range of updates
break
for i := startPeriod; i <= endPeriod; i++ {
if ctx.Err() != nil {
httputil.HandleError(w, "Context error: "+ctx.Err().Error(), http.StatusInternalServerError)
}
update, ok := updatesMap[i]
if !ok {
// Only return the first contiguous range of updates
break
}
updateSlot := update.AttestedHeader().Beacon().Slot
updateEpoch := slots.ToEpoch(updateSlot)
updateFork, err := forks.Fork(updateEpoch)
if err != nil {
httputil.HandleError(w, "Could not get fork Version: "+err.Error(), http.StatusInternalServerError)
return
}
forkDigest, err := signing.ComputeForkDigest(updateFork.CurrentVersion, params.BeaconConfig().GenesisValidatorsRoot[:])
if err != nil {
httputil.HandleError(w, "Could not compute fork digest: "+err.Error(), http.StatusInternalServerError)
return
}
updateSSZ, err := update.MarshalSSZ()
if err != nil {
httputil.HandleError(w, "Could not marshal update to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
var chunkLength []byte
chunkLength = ssz.MarshalUint64(chunkLength, uint64(len(updateSSZ)+4))
if _, err := w.Write(chunkLength); err != nil {
httputil.HandleError(w, "Could not write chunk length: "+err.Error(), http.StatusInternalServerError)
}
if _, err := w.Write(forkDigest[:]); err != nil {
httputil.HandleError(w, "Could not write fork digest: "+err.Error(), http.StatusInternalServerError)
}
if _, err := w.Write(updateSSZ); err != nil {
httputil.HandleError(w, "Could not write update SSZ: "+err.Error(), http.StatusInternalServerError)
}
}
} else {
updates := make([]*structs.LightClientUpdateResponse, 0, len(updatesMap))
for i := startPeriod; i <= endPeriod; i++ {
if ctx.Err() != nil {
httputil.HandleError(w, "Context error: "+ctx.Err().Error(), http.StatusInternalServerError)
}
update, ok := updatesMap[i]
if !ok {
// Only return the first contiguous range of updates
break
}
updateJson, err := structs.LightClientUpdateFromConsensus(update)
if err != nil {
httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError)
return
}
updateResponse := &structs.LightClientUpdateResponse{
Version: version.String(update.Version()),
Data: updateJson,
}
updates = append(updates, updateResponse)
}
updateJson, err := structs.LightClientUpdateFromConsensus(update)
if err != nil {
httputil.HandleError(w, "Could not convert light client update: "+err.Error(), http.StatusInternalServerError)
return
}
updateResponse := &structs.LightClientUpdateResponse{
Version: version.String(update.Version()),
Data: updateJson,
}
updates = append(updates, updateResponse)
httputil.WriteJson(w, updates)
}
httputil.WriteJson(w, updates)
}
// GetLightClientFinalityUpdate - implements https://github.com/ethereum/beacon-APIs/blob/263f4ed6c263c967f13279c7a9f5629b51c5fc55/apis/beacon/light_client/finality_update.yaml
@@ -195,7 +251,7 @@ func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.R
httputil.HandleError(w, "Could not marshal finality update to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, ssz, "light_client_finality_update.ssz")
httputil.WriteSsz(w, ssz)
} else {
updateStruct, err := structs.LightClientFinalityUpdateFromConsensus(update)
if err != nil {
@@ -258,7 +314,7 @@ func (s *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http
httputil.HandleError(w, "Could not marshal optimistic update to SSZ: "+err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, ssz, "light_client_optimistic_update.ssz")
httputil.WriteSsz(w, ssz)
} else {
updateStruct, err := structs.LightClientOptimisticUpdateFromConsensus(update)
if err != nil {

View File

@@ -12,6 +12,7 @@ import (
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
ssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
@@ -50,7 +51,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
params.OverrideBeaconConfig(cfg)
t.Run("altair", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -92,7 +93,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
})
t.Run("altairSSZ", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -126,7 +127,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.CurrentSyncCommitteeBranch)
})
t.Run("altair - no bootstrap found", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestAltair()
l := util.NewTestLightClient(t).SetupTestAltair(0, true)
slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -152,7 +153,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.Equal(t, http.StatusNotFound, writer.Code)
})
t.Run("bellatrix", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestBellatrix()
l := util.NewTestLightClient(t).SetupTestBellatrix(0, true)
slot := primitives.Slot(params.BeaconConfig().BellatrixForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -193,7 +194,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
})
t.Run("bellatrixSSZ", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestBellatrix()
l := util.NewTestLightClient(t).SetupTestBellatrix(0, true)
slot := primitives.Slot(params.BeaconConfig().BellatrixForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -227,7 +228,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.CurrentSyncCommitteeBranch)
})
t.Run("capella", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true) // result is same for true and false
slot := primitives.Slot(params.BeaconConfig().CapellaForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -268,7 +269,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
})
t.Run("capellaSSZ", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false
l := util.NewTestLightClient(t).SetupTestCapella(false, 0, true) // result is same for true and false
slot := primitives.Slot(params.BeaconConfig().CapellaForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -302,7 +303,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.CurrentSyncCommitteeBranch)
})
t.Run("deneb", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true) // result is same for true and false
slot := primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -343,7 +344,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
})
t.Run("denebSSZ", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false
l := util.NewTestLightClient(t).SetupTestDeneb(false, 0, true) // result is same for true and false
slot := primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -377,7 +378,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.CurrentSyncCommitteeBranch)
})
t.Run("electra", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestElectra(false) // result is same for true and false
l := util.NewTestLightClient(t).SetupTestElectra(false, 0, true) // result is same for true and false
slot := primitives.Slot(params.BeaconConfig().ElectraForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -418,7 +419,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
})
t.Run("electraSSZ", func(t *testing.T) {
l := util.NewTestLightClient(t).SetupTestElectra(false) // result is same for true and false
l := util.NewTestLightClient(t).SetupTestElectra(false, 0, true) // result is same for true and false
slot := primitives.Slot(params.BeaconConfig().ElectraForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
blockRoot, err := l.Block.Block().HashTreeRoot()
@@ -511,6 +512,44 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
require.DeepEqual(t, updateJson, resp.Updates[0].Data)
})
t.Run("altair ssz", func(t *testing.T) {
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)))
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, updatePeriod, update)
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp pb.LightClientUpdateAltair
err = resp.UnmarshalSSZ(writer.Body.Bytes()[12:]) // skip the length and fork digest prefixes
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, update.AttestedHeader().Proto())
})
t.Run("capella", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -553,6 +592,45 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
require.DeepEqual(t, updateJson, resp.Updates[0].Data)
})
t.Run("capella ssz", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateCapella()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)))
update, err := createUpdate(t, version.Capella)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, updatePeriod, update)
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp pb.LightClientUpdateCapella
err = resp.UnmarshalSSZ(writer.Body.Bytes()[12:]) // skip the length and fork digest prefixes
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, update.AttestedHeader().Proto())
})
t.Run("deneb", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -594,6 +672,44 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
require.DeepEqual(t, updateJson, resp.Updates[0].Data)
})
t.Run("deneb ssz", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateDeneb()
require.NoError(t, err)
err = st.SetSlot(slot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := uint64(slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch)))
update, err := createUpdate(t, version.Deneb)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, updatePeriod, update)
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
url := fmt.Sprintf("http://foo.com/?count=1&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
var resp pb.LightClientUpdateDeneb
err = resp.UnmarshalSSZ(writer.Body.Bytes()[12:]) // skip the length and fork digest prefixes
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, update.AttestedHeader().Proto())
})
t.Run("altair Multiple", func(t *testing.T) {
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -646,6 +762,60 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("altair Multiple ssz", func(t *testing.T) {
slot := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates := make([]interfaces.LightClientUpdate, 0)
for i := 1; i <= 2; i++ {
update, err := createUpdate(t, version.Altair)
require.NoError(t, err)
updates = append(updates, update)
}
for _, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update)
require.NoError(t, err)
updatePeriod++
}
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
for i := 0; offset < writer.Body.Len(); i++ {
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateAltair
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[i].AttestedHeader().Proto())
offset += updateLen
}
})
t.Run("capella Multiple", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -698,6 +868,60 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("capella Multiple ssz", func(t *testing.T) {
slot := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod)) // 2 periods
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates := make([]interfaces.LightClientUpdate, 0)
for i := 0; i < 2; i++ {
update, err := createUpdate(t, version.Capella)
require.NoError(t, err)
updates = append(updates, update)
}
for _, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update)
require.NoError(t, err)
updatePeriod++
}
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
for i := 0; offset < writer.Body.Len(); i++ {
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateCapella
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[i].AttestedHeader().Proto())
offset += updateLen
}
})
t.Run("deneb Multiple", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -749,6 +973,59 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("deneb Multiple", func(t *testing.T) {
slot := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slot.Add(2 * uint64(config.SlotsPerEpoch) * uint64(config.EpochsPerSyncCommitteePeriod))
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates := make([]interfaces.LightClientUpdate, 0)
for i := 0; i < 2; i++ {
update, err := createUpdate(t, version.Deneb)
require.NoError(t, err)
updates = append(updates, update)
}
for _, update := range updates {
err := db.SaveLightClientUpdate(ctx, uint64(updatePeriod), update)
require.NoError(t, err)
updatePeriod++
}
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := slot.Sub(1).Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
for i := 0; offset < writer.Body.Len(); i++ {
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateDeneb
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[i].AttestedHeader().Proto())
offset += updateLen
}
})
t.Run("multiple forks - altair, capella", func(t *testing.T) {
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotAltair := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -809,6 +1086,68 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("multiple forks - altair, capella - ssz", func(t *testing.T) {
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotAltair := primitives.Slot(config.AltairForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slotCapella.Add(1)
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updates := make([]interfaces.LightClientUpdate, 2)
updatePeriod := slotAltair.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[0], err = createUpdate(t, version.Altair)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0])
require.NoError(t, err)
updatePeriod = slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[1], err = createUpdate(t, version.Capella)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1])
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := 0
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateAltair
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[0].AttestedHeader().Proto())
offset += updateLen
updateLen = int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp1 pb.LightClientUpdateCapella
err = resp1.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp1.AttestedHeader, updates[1].AttestedHeader().Proto())
})
t.Run("multiple forks - capella, deneb", func(t *testing.T) {
slotDeneb := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -869,6 +1208,68 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
}
})
t.Run("multiple forks - capella, deneb - ssz", func(t *testing.T) {
slotDeneb := primitives.Slot(config.DenebForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
slotCapella := primitives.Slot(config.CapellaForkEpoch * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
st, err := util.NewBeaconStateAltair()
require.NoError(t, err)
headSlot := slotDeneb.Add(1)
err = st.SetSlot(headSlot)
require.NoError(t, err)
db := dbtesting.SetupDB(t)
updates := make([]interfaces.LightClientUpdate, 2)
updatePeriod := slotCapella.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[0], err = createUpdate(t, version.Capella)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[0])
require.NoError(t, err)
updatePeriod = slotDeneb.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
updates[1], err = createUpdate(t, version.Deneb)
require.NoError(t, err)
err = db.SaveLightClientUpdate(ctx, uint64(updatePeriod), updates[1])
require.NoError(t, err)
mockChainService := &mock.ChainService{State: st}
s := &Server{
HeadFetcher: mockChainService,
BeaconDB: db,
}
startPeriod := 1
url := fmt.Sprintf("http://foo.com/?count=100&start_period=%d", startPeriod)
request := httptest.NewRequest("GET", url, nil)
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetLightClientUpdatesByRange(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
offset := 0
updateLen := int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp pb.LightClientUpdateCapella
err = resp.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp.AttestedHeader, updates[0].AttestedHeader().Proto())
offset += updateLen
updateLen = int(ssz.UnmarshallUint64(writer.Body.Bytes()[offset:offset+8]) - 4)
offset += 12
var resp1 pb.LightClientUpdateDeneb
err = resp1.UnmarshalSSZ(writer.Body.Bytes()[offset : offset+updateLen])
require.NoError(t, err)
require.DeepEqual(t, resp1.AttestedHeader, updates[1].AttestedHeader().Proto())
})
t.Run("count bigger than limit", func(t *testing.T) {
config.MaxRequestLightClientUpdates = 2
params.OverrideBeaconConfig(config)

View File

@@ -355,7 +355,7 @@ func handleProducePhase0V3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "phase0Block.ssz")
httputil.WriteSsz(w, sszResp)
return
}
jsonBytes, err := json.Marshal(structs.BeaconBlockFromConsensus(blk.Phase0))
@@ -385,7 +385,7 @@ func handleProduceAltairV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "altairBlock.ssz")
httputil.WriteSsz(w, sszResp)
return
}
jsonBytes, err := json.Marshal(structs.BeaconBlockAltairFromConsensus(blk.Altair))
@@ -415,7 +415,7 @@ func handleProduceBellatrixV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "bellatrixBlock.ssz")
httputil.WriteSsz(w, sszResp)
return
}
block, err := structs.BeaconBlockBellatrixFromConsensus(blk.Bellatrix)
@@ -450,7 +450,7 @@ func handleProduceBlindedBellatrixV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "blindedBellatrixBlock.ssz")
httputil.WriteSsz(w, sszResp)
return
}
block, err := structs.BlindedBeaconBlockBellatrixFromConsensus(blk.BlindedBellatrix)
@@ -485,7 +485,7 @@ func handleProduceBlindedCapellaV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "blindedCapellaBlock.ssz")
httputil.WriteSsz(w, sszResp)
return
}
block, err := structs.BlindedBeaconBlockCapellaFromConsensus(blk.BlindedCapella)
@@ -520,7 +520,7 @@ func handleProduceCapellaV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "capellaBlock.ssz")
httputil.WriteSsz(w, sszResp)
return
}
block, err := structs.BeaconBlockCapellaFromConsensus(blk.Capella)
@@ -555,7 +555,7 @@ func handleProduceBlindedDenebV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "blindedDenebBlockContents.ssz")
httputil.WriteSsz(w, sszResp)
return
}
blindedBlock, err := structs.BlindedBeaconBlockDenebFromConsensus(blk.BlindedDeneb)
@@ -590,7 +590,7 @@ func handleProduceDenebV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "denebBlockContents.ssz")
httputil.WriteSsz(w, sszResp)
return
}
@@ -626,7 +626,7 @@ func handleProduceBlindedElectraV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "blindedElectraBlockContents.ssz")
httputil.WriteSsz(w, sszResp)
return
}
blindedBlock, err := structs.BlindedBeaconBlockElectraFromConsensus(blk.BlindedElectra)
@@ -661,7 +661,7 @@ func handleProduceElectraV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "electraBlockContents.ssz")
httputil.WriteSsz(w, sszResp)
return
}
@@ -697,7 +697,7 @@ func handleProduceBlindedFuluV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "blindedFuluBlockContents.ssz")
httputil.WriteSsz(w, sszResp)
return
}
blindedBlock, err := structs.BlindedBeaconBlockFuluFromConsensus(blk.BlindedFulu)
@@ -732,7 +732,7 @@ func handleProduceFuluV3(
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp, "fuluBlockContents.ssz")
httputil.WriteSsz(w, sszResp)
return
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
@@ -82,8 +81,7 @@ func (p *BeaconDbBlocker) Block(ctx context.Context, id []byte) (interfaces.Read
return nil, errors.Wrap(err, "could not retrieve genesis block")
}
default:
stringId := strings.ToLower(string(id))
if len(stringId) >= 2 && stringId[:2] == "0x" {
if bytesutil.IsHex(id) {
decoded, err := hexutil.Decode(string(id))
if err != nil {
e := NewBlockIdParseError(err)

View File

@@ -143,7 +143,7 @@ func (p *BeaconDbStater) State(ctx context.Context, stateId []byte) (state.Beaco
return nil, errors.Wrap(err, "could not get justified state")
}
default:
if len(stateIdString) >= 2 && stateIdString[:2] == "0x" {
if bytesutil.IsHex(stateId) {
decoded, parseErr := hexutil.Decode(string(stateId))
if parseErr != nil {
e := NewStateIdParseError(parseErr)
@@ -185,7 +185,15 @@ func (p *BeaconDbStater) StateRoot(ctx context.Context, stateId []byte) (root []
case "justified":
root, err = p.justifiedStateRoot(ctx)
default:
if len(stateId) == 32 {
if bytesutil.IsHex(stateId) {
var decoded []byte
decoded, err = hexutil.Decode(string(stateId))
if err != nil {
e := NewStateIdParseError(err)
return nil, &e
}
root, err = p.stateRootByRoot(ctx, decoded)
} else if len(stateId) == 32 {
root, err = p.stateRootByRoot(ctx, stateId)
} else {
slotNumber, parseErr := strconv.ParseUint(stateIdString, 10, 64)

View File

@@ -241,7 +241,6 @@ func TestGetStateRoot(t *testing.T) {
require.NoError(t, err)
assert.DeepEqual(t, stateRoot[:], s)
})
t.Run("genesis", func(t *testing.T) {
db := testDB.SetupDB(t)
b := util.NewBeaconBlock()
@@ -270,7 +269,6 @@ func TestGetStateRoot(t *testing.T) {
sr := genesisBlock.Block().StateRoot()
assert.DeepEqual(t, sr[:], s)
})
t.Run("finalized", func(t *testing.T) {
db := testDB.SetupDB(t)
genesis := bytesutil.ToBytes32([]byte("genesis"))
@@ -301,7 +299,6 @@ func TestGetStateRoot(t *testing.T) {
require.NoError(t, err)
assert.DeepEqual(t, blk.Block.StateRoot, s)
})
t.Run("justified", func(t *testing.T) {
db := testDB.SetupDB(t)
genesis := bytesutil.ToBytes32([]byte("genesis"))
@@ -332,30 +329,52 @@ func TestGetStateRoot(t *testing.T) {
require.NoError(t, err)
assert.DeepEqual(t, blk.Block.StateRoot, s)
})
t.Run("hex_root", func(t *testing.T) {
stateId, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
t.Run("hex", func(t *testing.T) {
hex := "0x" + strings.Repeat("0", 63) + "1"
p := BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState},
}
s, err := p.StateRoot(ctx, stateId)
s, err := p.StateRoot(ctx, []byte(hex))
require.NoError(t, err)
assert.DeepEqual(t, stateId, s)
expected, err := hexutil.Decode(hex)
require.NoError(t, err)
assert.DeepEqual(t, expected, s)
})
t.Run("hex not found", func(t *testing.T) {
hex := "0x" + strings.Repeat("f", 64)
t.Run("hex_root_not_found", func(t *testing.T) {
p := BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState},
}
stateId, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
_, err = p.StateRoot(ctx, stateId)
_, err = p.StateRoot(ctx, []byte(hex))
require.ErrorContains(t, "state root not found in the last 8192 state roots", err)
})
t.Run("bytes", func(t *testing.T) {
root, err := hexutil.Decode("0x" + strings.Repeat("0", 63) + "1")
require.NoError(t, err)
p := BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState},
}
s, err := p.StateRoot(ctx, root)
require.NoError(t, err)
assert.DeepEqual(t, root, s)
})
t.Run("bytes not found", func(t *testing.T) {
root, err := hexutil.Decode("0x" + strings.Repeat("f", 64))
require.NoError(t, err)
p := BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{State: newBeaconState},
}
_, err = p.StateRoot(ctx, root)
require.ErrorContains(t, "state root not found in the last 8192 state roots", err)
})
t.Run("slot", func(t *testing.T) {
db := testDB.SetupDB(t)
genesis := bytesutil.ToBytes32([]byte("genesis"))
@@ -382,8 +401,7 @@ func TestGetStateRoot(t *testing.T) {
require.NoError(t, err)
assert.DeepEqual(t, blk.Block.StateRoot, s)
})
t.Run("slot_too_big", func(t *testing.T) {
t.Run("slot too big", func(t *testing.T) {
p := BeaconDbStater{
GenesisTimeFetcher: &chainMock.ChainService{
Genesis: time.Now(),
@@ -393,7 +411,7 @@ func TestGetStateRoot(t *testing.T) {
assert.ErrorContains(t, "slot cannot be in the future", err)
})
t.Run("invalid_state", func(t *testing.T) {
t.Run("invalid state", func(t *testing.T) {
p := BeaconDbStater{}
_, err := p.StateRoot(ctx, []byte("foo"))
require.ErrorContains(t, "could not parse state ID", err)

View File

@@ -40,6 +40,7 @@ go_library(
"//beacon-chain/cache:go_default_library",
"//beacon-chain/cache/depositsnapshot:go_default_library",
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/core/electra:go_default_library",
"//beacon-chain/core/feed:go_default_library",
"//beacon-chain/core/feed/block:go_default_library",
"//beacon-chain/core/feed/operation:go_default_library",
@@ -62,6 +63,7 @@ go_library(
"//beacon-chain/startup:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//beacon-chain/sync:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",

View File

@@ -10,6 +10,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -47,7 +48,7 @@ func (vs *Server) StreamBlocksAltair(req *ethpb.StreamBlocksRequest, stream ethp
}
}
// StreamSlots sends a block's slot to clients every single time a block is received by the beacon node.
// StreamSlots sends a the block's slot and dependent roots to clients every single time a block is received by the beacon node.
func (vs *Server) StreamSlots(req *ethpb.StreamSlotsRequest, stream ethpb.BeaconNodeValidator_StreamSlotsServer) error {
ch := make(chan *feed.Event, 1)
var sub event.Subscription
@@ -81,7 +82,24 @@ func (vs *Server) StreamSlots(req *ethpb.StreamSlotsRequest, stream ethpb.Beacon
}
s = data.SignedBlock.Block().Slot()
}
if err := stream.Send(&ethpb.StreamSlotsResponse{Slot: s}); err != nil {
currEpoch := slots.ToEpoch(s)
currDepRoot, err := vs.ForkchoiceFetcher.DependentRoot(currEpoch)
if err != nil {
return status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
}
prevDepRoot := currDepRoot
if currEpoch > 0 {
prevDepRoot, err = vs.ForkchoiceFetcher.DependentRoot(currEpoch - 1)
if err != nil {
return status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
}
}
if err := stream.Send(
&ethpb.StreamSlotsResponse{
Slot: s,
PreviousDutyDependentRoot: prevDepRoot[:],
CurrentDutyDependentRoot: currDepRoot[:],
}); err != nil {
return status.Errorf(codes.Unavailable, "Could not send over stream: %v", err)
}
case <-sub.Err():

View File

@@ -157,9 +157,22 @@ func (vs *Server) duties(ctx context.Context, req *ethpb.DutiesRequest) (*ethpb.
validatorAssignments = append(validatorAssignments, assignment)
nextValidatorAssignments = append(nextValidatorAssignments, nextAssignment)
}
currDependentRoot, err := vs.ForkchoiceFetcher.DependentRoot(currentEpoch)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get dependent root: %v", err)
}
prevDependentRoot := currDependentRoot
if currDependentRoot != [32]byte{} && currentEpoch > 0 {
prevDependentRoot, err = vs.ForkchoiceFetcher.DependentRoot(currentEpoch - 1)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not get previous dependent root: %v", err)
}
}
return &ethpb.DutiesResponse{
CurrentEpochDuties: validatorAssignments,
NextEpochDuties: nextValidatorAssignments,
PreviousDutyDependentRoot: prevDependentRoot[:],
CurrentDutyDependentRoot: currDependentRoot[:],
CurrentEpochDuties: validatorAssignments,
NextEpochDuties: nextValidatorAssignments,
}, nil
}

View File

@@ -11,6 +11,8 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
@@ -110,7 +112,7 @@ func (vs *Server) packAttestations(ctx context.Context, latestState state.Beacon
var sorted proposerAtts
if postElectra {
sorted, err = deduped.sortOnChainAggregates()
sorted, err = deduped.sortOnChainAggregates(ctx, latestState)
if err != nil {
return nil, err
}
@@ -271,16 +273,36 @@ func (a proposerAtts) sort() (proposerAtts, error) {
return a.sortBySlotAndCommittee()
}
func (a proposerAtts) sortOnChainAggregates() (proposerAtts, error) {
func (a proposerAtts) sortOnChainAggregates(ctx context.Context, st state.ReadOnlyBeaconState) (proposerAtts, error) {
if len(a) < 2 {
return a, nil
}
// Sort by slot first, then by bit count.
totalBalance, err := helpers.TotalActiveBalance(st)
if err != nil {
return nil, err
}
// Sort attestation by proposer reward numerator using a cache.
cache := make(map[ethpb.Att]uint64)
getCachedReward := func(att ethpb.Att) uint64 {
if val, ok := cache[att]; ok {
return val
}
r, err := electra.GetProposerRewardNumerator(ctx, st, att, totalBalance)
if err != nil {
log.WithError(err).Debug("Failed to get proposer reward numerator")
return 0
}
cache[att] = r
return r
}
slices.SortFunc(a, func(a, b ethpb.Att) int {
return cmp.Or(
-cmp.Compare(a.GetData().Slot, b.GetData().Slot),
-cmp.Compare(a.GetAggregationBits().Count(), b.GetAggregationBits().Count()))
r1 := getCachedReward(a)
r2 := getCachedReward(b)
return cmp.Compare(r2, r1)
})
return a, nil

View File

@@ -1,10 +1,10 @@
package validator
import (
"cmp"
"slices"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -58,11 +58,11 @@ func computeOnChainAggregate(aggregates []ethpb.Att) ([]ethpb.Att, error) {
for _, aggs := range aggsByDataRoot {
slices.SortFunc(aggs, func(a, b ethpb.Att) int {
return a.CommitteeBitsVal().BitIndices()[0] - b.CommitteeBitsVal().BitIndices()[0]
return cmp.Compare(a.GetCommitteeIndex(), b.GetCommitteeIndex())
})
sigs := make([]bls.Signature, len(aggs))
committeeIndices := make([]primitives.CommitteeIndex, len(aggs))
cb := primitives.NewAttestationCommitteeBits()
aggBitsIndices := make([]uint64, 0)
aggBitsOffset := uint64(0)
var err error
@@ -74,7 +74,7 @@ func computeOnChainAggregate(aggregates []ethpb.Att) ([]ethpb.Att, error) {
if err != nil {
return nil, err
}
committeeIndices[i] = helpers.CommitteeIndices(a.CommitteeBitsVal())[0]
cb.SetBitAt(uint64(a.GetCommitteeIndex()), true)
aggBitsOffset += a.GetAggregationBits().Len()
}
@@ -84,16 +84,12 @@ func computeOnChainAggregate(aggregates []ethpb.Att) ([]ethpb.Att, error) {
aggregationBits.SetBitAt(bi, true)
}
cb := primitives.NewAttestationCommitteeBits()
att := &ethpb.AttestationElectra{
AggregationBits: aggregationBits,
Data: aggs[0].GetData(),
CommitteeBits: cb,
Signature: bls.AggregateSignatures(sigs).Marshal(),
}
for _, ci := range committeeIndices {
att.CommitteeBits.SetBitAt(uint64(ci), true)
}
result = append(result, att)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/prysmaticlabs/go-bitfield"
chainMock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/attestations/mock"
@@ -685,7 +686,7 @@ func Test_packAttestations(t *testing.T) {
})
}
func Test_packAttestations_ElectraOnChainAggregates(t *testing.T) {
func TestPackAttestations_ElectraOnChainAggregates(t *testing.T) {
ctx := context.Background()
params.SetupTestConfigCleanup(t)
@@ -702,125 +703,141 @@ func Test_packAttestations_ElectraOnChainAggregates(t *testing.T) {
cb1 := primitives.NewAttestationCommitteeBits()
cb1.SetBitAt(1, true)
data0 := util.HydrateAttestationData(&ethpb.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32)})
data1 := util.HydrateAttestationData(&ethpb.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte{'1'}, 32)})
data0 := util.HydrateAttestationData(&ethpb.AttestationData{
BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32),
})
data1 := util.HydrateAttestationData(&ethpb.AttestationData{
BeaconBlockRoot: bytesutil.PadTo([]byte{'1'}, 32),
})
att := func(bits byte, cb []byte, data *ethpb.AttestationData) *ethpb.AttestationElectra {
return &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{bits},
CommitteeBits: cb,
Data: util.HydrateAttestationData(data),
Signature: sig.Marshal(),
}
}
// Glossary:
// - Single Aggregate: aggregate with exactly one committee bit set, from which an On-Chain Aggregate is constructed
// - On-Chain Aggregate: final aggregate packed into a block
//
// We construct the following number of single aggregates:
// - data_root_0 and committee_index_0: 3 single aggregates
// - data_root_0 and committee_index_1: 2 single aggregates
// - data_root_1 and committee_index_0: 1 single aggregate
// - data_root_1 and committee_index_1: 3 single aggregates
//
// Because the function tries to aggregate attestations, we have to create attestations which are not aggregatable
// and are not redundant when using MaxCover.
// The function should also sort attestation by ID before computing the On-Chain Aggregate, so we want unsorted aggregation bits
// to test the sorting part.
//
// The result should be the following six on-chain aggregates:
// - for data_root_0 combining the most profitable aggregate for each committee
// - for data_root_0 combining the second most profitable aggregate for each committee
// - for data_root_0 constructed from the single aggregate at index 2 for committee_index_0
// - for data_root_1 combining the most profitable aggregate for each committee
// - for data_root_1 constructed from the single aggregate at index 1 for committee_index_1
// - for data_root_1 constructed from the single aggregate at index 2 for committee_index_1
// - Single Aggregate: one committee bit set, becomes an On-Chain Aggregate
// - On-Chain Aggregate: final packed aggregate in block
d0_c0_a1 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1000011},
CommitteeBits: cb0,
Data: data0,
Signature: sig.Marshal(),
}
d0_c0_a2 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1100101},
CommitteeBits: cb0,
Data: data0,
Signature: sig.Marshal(),
}
d0_c0_a3 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1111000},
CommitteeBits: cb0,
Data: data0,
Signature: sig.Marshal(),
}
d0_c1_a1 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1111100},
CommitteeBits: cb1,
Data: data0,
Signature: sig.Marshal(),
}
d0_c1_a2 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1001111},
CommitteeBits: cb1,
Data: data0,
Signature: sig.Marshal(),
}
d1_c0_a1 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1111111},
CommitteeBits: cb0,
Data: data1,
Signature: sig.Marshal(),
}
d1_c1_a1 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1000011},
CommitteeBits: cb1,
Data: data1,
Signature: sig.Marshal(),
}
d1_c1_a2 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1100101},
CommitteeBits: cb1,
Data: data1,
Signature: sig.Marshal(),
}
d1_c1_a3 := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1111000},
CommitteeBits: cb1,
Data: data1,
Signature: sig.Marshal(),
aggregates := []*ethpb.AttestationElectra{
att(0b1000011, cb0, data0), // d0_c0_a1
att(0b1100101, cb0, data0), // d0_c0_a2
att(0b1111000, cb0, data0), // d0_c0_a3
att(0b1111100, cb1, data0), // d0_c1_a1
att(0b1001111, cb1, data0), // d0_c1_a2
att(0b1111111, cb0, data1), // d1_c0_a1
att(0b1000011, cb1, data1), // d1_c1_a1
att(0b1100101, cb1, data1), // d1_c1_a2
att(0b1111000, cb1, data1), // d1_c1_a3
}
pool := &mock.PoolMock{}
require.NoError(t, pool.SaveAggregatedAttestations([]ethpb.Att{d0_c0_a1, d0_c0_a2, d0_c0_a3, d0_c1_a1, d0_c1_a2, d1_c0_a1, d1_c1_a1, d1_c1_a2, d1_c1_a3}))
slot := primitives.Slot(1)
s := &Server{AttPool: pool, HeadFetcher: &chainMock.ChainService{}, TimeFetcher: &chainMock.ChainService{Slot: &slot}}
require.NoError(t, pool.SaveAggregatedAttestations(sliceCast(aggregates)))
// We need the correct number of validators so that there are at least 2 committees per slot
// and each committee has exactly 6 validators (this is because we have 6 aggregation bits).
// 192 validators → 2 committees per slot with 6 validators each
st, _ := util.DeterministicGenesisStateElectra(t, 192)
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch+1))
slot := primitives.Slot(1)
headSlot := primitives.Slot(0)
s := &Server{
AttPool: pool,
HeadFetcher: &chainMock.ChainService{State: st, MockHeadSlot: &headSlot},
TimeFetcher: &chainMock.ChainService{Slot: &slot},
}
t.Run("ok", func(t *testing.T) {
atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch)
require.NoError(t, err)
require.Equal(t, 6, len(atts))
assert.Equal(t, true,
atts[0].GetAggregationBits().Count() >= atts[1].GetAggregationBits().Count() &&
atts[1].GetAggregationBits().Count() >= atts[2].GetAggregationBits().Count() &&
atts[2].GetAggregationBits().Count() >= atts[3].GetAggregationBits().Count() &&
atts[3].GetAggregationBits().Count() >= atts[4].GetAggregationBits().Count() &&
atts[4].GetAggregationBits().Count() >= atts[5].GetAggregationBits().Count(),
"on-chain aggregates are not sorted by aggregation bit count",
)
totalBalance, err := helpers.TotalActiveBalance(st)
require.NoError(t, err)
expected := []uint64{
193332672,
150369856,
150369856,
64444224,
42962816,
42962816,
}
for i, want := range expected {
got, err := electra.GetProposerRewardNumerator(ctx, st, atts[i], totalBalance)
require.NoError(t, err)
require.Equal(t, want, got)
}
})
t.Run("slot takes precedence", func(t *testing.T) {
moreRecentAtt := &ethpb.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b1100000}, // we set only one bit for committee_index_0
CommitteeBits: cb1,
Data: util.HydrateAttestationData(&ethpb.AttestationData{Slot: 1, BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32)}),
Signature: sig.Marshal(),
}
require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpb.Att{moreRecentAtt}))
t.Run("reward takes precedence", func(t *testing.T) {
moreRecent := att(0b1100000, cb1, &ethpb.AttestationData{
Slot: 1,
BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32),
})
require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpb.Att{moreRecent}))
atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch)
require.NoError(t, err)
require.Equal(t, 7, len(atts))
assert.Equal(t, true, atts[0].GetData().Slot == 1)
totalBalance, err := helpers.TotalActiveBalance(st)
require.NoError(t, err)
got, err := electra.GetProposerRewardNumerator(ctx, st, atts[6], totalBalance)
require.NoError(t, err)
require.Equal(t, uint64(21481408), got)
require.Equal(t, primitives.Slot(1), atts[6].GetData().Slot)
})
t.Run("use latest state", func(t *testing.T) {
moreRecent := att(0b1100000, cb1, &ethpb.AttestationData{
Slot: 1,
BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32),
})
require.NoError(t, pool.SaveUnaggregatedAttestations([]ethpb.Att{moreRecent}))
copiedState := st.Copy()
// Setting head state validator set to empty, but it shouldn't matter as pack attestation should be using latest state.
require.NoError(t, copiedState.SetValidators([]*ethpb.Validator{}))
s := &Server{
AttPool: pool,
HeadFetcher: &chainMock.ChainService{State: copiedState, MockHeadSlot: &headSlot},
TimeFetcher: &chainMock.ChainService{Slot: &slot},
}
atts, err := s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch)
require.NoError(t, err)
require.Equal(t, 7, len(atts))
totalBalance, err := helpers.TotalActiveBalance(st)
require.NoError(t, err)
// The reward numerator should be the same as the previous test.
expected := []uint64{
193332672,
150369856,
150369856,
64444224,
42962816,
42962816,
}
for i, want := range expected {
got, err := electra.GetProposerRewardNumerator(ctx, st, atts[i], totalBalance)
require.NoError(t, err)
require.Equal(t, want, got)
}
})
}
func sliceCast(atts []*ethpb.AttestationElectra) []ethpb.Att {
res := make([]ethpb.Att, len(atts))
for i, att := range atts {
res[i] = att
}
return res
}
func Benchmark_packAttestations_Electra(b *testing.B) {

View File

@@ -23,18 +23,6 @@ func init() {
logrus.SetOutput(io.Discard)
}
func combineMaps(maps ...map[string][]string) map[string][]string {
combinedMap := make(map[string][]string)
for _, m := range maps {
for k, v := range m {
combinedMap[k] = v
}
}
return combinedMap
}
func TestLifecycle_OK(t *testing.T) {
hook := logTest.NewGlobal()
chainService := &mock.ChainService{

View File

@@ -10,6 +10,7 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/state",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/state/state-native/custom-types:go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"github.com/prysmaticlabs/go-bitfield"
customtypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/custom-types"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
@@ -210,6 +211,8 @@ type ReadOnlyWithdrawals interface {
type ReadOnlyParticipation interface {
CurrentEpochParticipation() ([]byte, error)
PreviousEpochParticipation() ([]byte, error)
CurrentEpochParticipationReadOnly() (customtypes.ReadOnlyParticipation, error)
PreviousEpochParticipationReadOnly() (customtypes.ReadOnlyParticipation, error)
}
// ReadOnlyInactivity defines a struct which only has read access to inactivity methods.

View File

@@ -5,6 +5,7 @@ go_library(
srcs = [
"block_roots.go",
"historical_roots.go",
"participation.go",
"randao_mixes.go",
"state_roots.go",
],

View File

@@ -0,0 +1,17 @@
package customtypes
type ReadOnlyParticipation struct {
p []byte
}
func NewReadOnlyParticipation(p []byte) ReadOnlyParticipation {
return ReadOnlyParticipation{p}
}
func (r ReadOnlyParticipation) At(i uint64) byte {
return r.p[i]
}
func (r ReadOnlyParticipation) Len() int {
return len(r.p)
}

View File

@@ -2,6 +2,7 @@ package state_native
import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
customtypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/custom-types"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
@@ -39,6 +40,30 @@ func (b *BeaconState) PreviousEpochParticipation() ([]byte, error) {
return b.previousEpochParticipationVal(), nil
}
// CurrentEpochParticipationReadOnly corresponding to participation bits on the beacon chain without copying the data.
func (b *BeaconState) CurrentEpochParticipationReadOnly() (customtypes.ReadOnlyParticipation, error) {
if b.version == version.Phase0 {
return customtypes.ReadOnlyParticipation{}, errNotSupported("CurrentEpochParticipation", b.version)
}
b.lock.RLock()
defer b.lock.RUnlock()
return customtypes.NewReadOnlyParticipation(b.currentEpochParticipation), nil
}
// PreviousEpochParticipationReadOnly corresponding to participation bits on the beacon chain without copying the data.
func (b *BeaconState) PreviousEpochParticipationReadOnly() (customtypes.ReadOnlyParticipation, error) {
if b.version == version.Phase0 {
return customtypes.ReadOnlyParticipation{}, errNotSupported("PreviousEpochParticipation", b.version)
}
b.lock.RLock()
defer b.lock.RUnlock()
return customtypes.NewReadOnlyParticipation(b.previousEpochParticipation), nil
}
// UnrealizedCheckpointBalances returns the total balances: active, target attested in
// current epoch and target attested in previous epoch. This function is used to
// compute the "unrealized justification" that a synced Beacon Block will have.

View File

@@ -486,7 +486,7 @@ func (b *BeaconState) HasPendingBalanceToWithdraw(idx primitives.ValidatorIndex)
// MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD per slot. A more optimized storage indexing such as a
// lookup map could be used to reduce the complexity marginally.
for _, w := range b.pendingPartialWithdrawals {
if w.Index == idx {
if w.Index == idx && w.Amount > 0 {
return true, nil
}
}

View File

@@ -121,6 +121,10 @@ func TestHasPendingBalanceToWithdraw(t *testing.T) {
Amount: 300,
Index: 3,
},
{
Amount: 0,
Index: 4,
},
},
}
state, err := statenative.InitializeFromProtoUnsafeElectra(pb)
@@ -133,4 +137,9 @@ func TestHasPendingBalanceToWithdraw(t *testing.T) {
ok, err = state.HasPendingBalanceToWithdraw(5)
require.NoError(t, err)
require.Equal(t, false, ok)
// Handle 0 amount case.
ok, err = state.HasPendingBalanceToWithdraw(4)
require.NoError(t, err)
require.Equal(t, false, ok)
}

View File

@@ -121,6 +121,11 @@ func (s *State) StateByRootInitialSync(ctx context.Context, blockRoot [32]byte)
return cachedInfo.state, nil
}
if s.beaconDB.HasState(ctx, blockRoot) {
s, err := s.beaconDB.State(ctx, blockRoot)
return s, errors.Wrap(err, "failed to retrieve init-sync state from db")
}
startState, err := s.latestAncestor(ctx, blockRoot)
if err != nil {
return nil, errors.Wrap(err, "could not get ancestor state")

View File

@@ -2,12 +2,16 @@ package stategen
import (
"context"
"fmt"
"testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
testDB "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/params"
blt "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -110,65 +114,6 @@ func TestStateByRootIfCachedNoCopy_ColdState(t *testing.T) {
require.Equal(t, loadedState, nil)
}
func TestStateByRoot_HotStateUsingEpochBoundaryCacheNoReplay(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
require.NoError(t, beaconState.SetSlot(10))
blk := util.NewBeaconBlock()
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Root: blkRoot[:]}))
require.NoError(t, service.epochBoundaryStateCache.put(blkRoot, beaconState))
loadedState, err := service.StateByRoot(ctx, blkRoot)
require.NoError(t, err)
assert.Equal(t, primitives.Slot(10), loadedState.Slot(), "Did not correctly load state")
}
func TestStateByRoot_HotStateUsingEpochBoundaryCacheWithReplay(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
blk := util.NewBeaconBlock()
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, service.epochBoundaryStateCache.put(blkRoot, beaconState))
targetSlot := primitives.Slot(10)
targetBlock := util.NewBeaconBlock()
targetBlock.Block.Slot = 11
targetBlock.Block.ParentRoot = blkRoot[:]
targetBlock.Block.ProposerIndex = 8
util.SaveBlock(t, ctx, service.beaconDB, targetBlock)
targetRoot, err := targetBlock.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Slot: targetSlot, Root: targetRoot[:]}))
loadedState, err := service.StateByRoot(ctx, targetRoot)
require.NoError(t, err)
assert.Equal(t, targetSlot, loadedState.Slot(), "Did not correctly load state")
}
func TestStateByRoot_HotStateCached(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
r := [32]byte{'A'}
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Root: r[:]}))
service.hotStateCache.put(r, beaconState)
loadedState, err := service.StateByRoot(ctx, r)
require.NoError(t, err)
require.DeepSSZEqual(t, loadedState.ToProtoUnsafe(), beaconState.ToProtoUnsafe())
}
func TestDeleteStateFromCaches(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
@@ -198,173 +143,349 @@ func TestDeleteStateFromCaches(t *testing.T) {
require.Equal(t, false, has)
}
func TestStateByRoot_StateByRootInitialSync(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
b := util.NewBeaconBlock()
bRoot, err := b.Block.HashTreeRoot()
require.NoError(t, err)
beaconState, _ := util.DeterministicGenesisState(t, 32)
require.NoError(t, service.beaconDB.SaveState(ctx, beaconState, bRoot))
util.SaveBlock(t, ctx, service.beaconDB, b)
require.NoError(t, service.beaconDB.SaveGenesisBlockRoot(ctx, bRoot))
loadedState, err := service.StateByRootInitialSync(ctx, params.BeaconConfig().ZeroHash) // Zero hash is genesis state root.
require.NoError(t, err)
require.DeepSSZEqual(t, loadedState.ToProtoUnsafe(), beaconState.ToProtoUnsafe())
// testChainSlot represents one slot of the test chain
type testChainSlot struct {
st state.BeaconState
root [32]byte
blk blt.ROBlock
}
func TestStateByRootInitialSync_UseEpochStateCache(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
targetSlot := primitives.Slot(10)
require.NoError(t, beaconState.SetSlot(targetSlot))
blk := util.NewBeaconBlock()
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
require.NoError(t, service.epochBoundaryStateCache.put(blkRoot, beaconState))
loadedState, err := service.StateByRootInitialSync(ctx, blkRoot)
require.NoError(t, err)
assert.Equal(t, targetSlot, loadedState.Slot(), "Did not correctly load state")
// testChain represents the test block chain that is written to the DB / cache.
// Used to test the StateByRoot, StateByRootInitSync and loadStateByRoot methods.
type testChain struct {
t *testing.T
ctx context.Context
d db.Database
srv *State
cslots map[primitives.Slot]testChainSlot
}
func TestStateByRootInitialSync_UseCache(t *testing.T) {
// the following are helpers used in the test cases and helpers to concisely get the different
// components of the chain by slot.
func (c testChain) cslot(t *testing.T, s primitives.Slot) testChainSlot {
cs, ok := c.cslots[s]
require.Equal(t, true, ok, fmt.Sprintf("state not found for slot %d", s))
return cs
}
func (c testChain) state(t *testing.T, s primitives.Slot) state.BeaconState {
return c.cslot(t, s).st
}
func (c testChain) blockRoot(t *testing.T, s primitives.Slot) [32]byte {
return c.cslot(t, s).root
}
func (c testChain) block(t *testing.T, s primitives.Slot) blt.ROBlock {
return c.cslot(t, s).blk
}
type testSetupSlots struct {
stateAt primitives.Slot
lastblock primitives.Slot
}
func TestLoadStateByRoot(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
r := [32]byte{'A'}
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Root: r[:]}))
service.hotStateCache.put(r, beaconState)
loadedState, err := service.StateByRootInitialSync(ctx, r)
require.NoError(t, err)
require.DeepSSZEqual(t, loadedState.ToProtoUnsafe(), beaconState.ToProtoUnsafe())
if service.hotStateCache.has(r) {
t.Error("Hot state cache was not invalidated")
persistEpochBoundary := func(r testChain, slot primitives.Slot) {
require.NoError(t, r.srv.epochBoundaryStateCache.put(r.blockRoot(r.t, slot), r.state(t, slot)))
}
persistHotStateCache := func(r testChain, slot primitives.Slot) {
r.srv.hotStateCache.put(r.blockRoot(t, slot), r.state(t, slot))
}
persistDB := func(r testChain, slot primitives.Slot) {
require.NoError(r.t, r.d.SaveState(r.ctx, r.state(t, slot), r.blockRoot(t, slot)))
}
persistFinalizedStruct := func(r testChain, slot primitives.Slot) {
st := r.state(t, slot)
r.srv.finalizedInfo.state = st
r.srv.finalizedInfo.slot = st.Slot()
r.srv.finalizedInfo.root = r.blockRoot(t, slot)
}
}
func TestStateByRootInitialSync_CanProcessUpTo(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
type testLoader func(r testChain) (state.BeaconState, error)
lsbr := func(slot primitives.Slot) testLoader {
return func(tc testChain) (state.BeaconState, error) {
return tc.srv.loadStateByRoot(tc.ctx, tc.blockRoot(t, slot))
}
}
sbrInit := func(slot primitives.Slot) testLoader {
return func(tc testChain) (state.BeaconState, error) {
return tc.srv.StateByRootInitialSync(tc.ctx, tc.blockRoot(t, slot))
}
}
sbr := func(slot primitives.Slot) testLoader {
return func(tc testChain) (state.BeaconState, error) {
return tc.srv.StateByRoot(tc.ctx, tc.blockRoot(t, slot))
}
}
beaconState, _ := util.DeterministicGenesisState(t, 32)
blk := util.NewBeaconBlock()
blkRoot, err := blk.Block.HashTreeRoot()
cases := []struct {
name string
slots testSetupSlots
persistState func(r testChain, s primitives.Slot)
loader testLoader // ie loadStateByRoot; StateByRootInitialSync; StateByRoot
}{
// loadStateByRoot tests
{
name: "loadStateByRoot - using epoch boundary cache",
persistState: persistEpochBoundary,
loader: lsbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "loadStateByRoot - with replay - using epoch boundary cache",
persistState: persistEpochBoundary,
loader: lsbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "loadStateByRoot - using hot state cache",
persistState: persistHotStateCache,
loader: lsbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "loadStateByRoot - with replay - using hot state cache",
persistState: persistHotStateCache,
loader: lsbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "loadStateByRoot - using db",
persistState: persistDB,
loader: lsbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "loadStateByRoot - with replay - using db",
persistState: persistDB,
loader: lsbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "loadStateByRoot - using finalizedInfo struct field",
persistState: persistFinalizedStruct,
loader: lsbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "loadStateByRoot - with replay - using finalizedInfo struct field",
persistState: persistFinalizedStruct,
loader: lsbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
// StateByRootInitSync tests
{
name: "StateByRootInitSync - using epoch boundary cache",
persistState: persistEpochBoundary,
loader: sbrInit(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRootInitSync - with replay - using epoch boundary cache",
persistState: persistEpochBoundary,
loader: sbrInit(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "StateByRootInitSync - using hot state cache",
persistState: persistHotStateCache,
loader: sbrInit(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRootInitSync - with replay - using hot state cache",
persistState: persistHotStateCache,
loader: sbrInit(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "StateByRootInitSync - using db",
persistState: persistDB,
loader: sbrInit(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRootInitSync - with replay - using db",
persistState: persistDB,
loader: sbrInit(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "StateByRootInitSync - using finalizedInfo struct field",
persistState: persistFinalizedStruct,
loader: sbrInit(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRootInitSync - with replay - using finalizedInfo struct field",
persistState: persistFinalizedStruct,
loader: sbrInit(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
// StateByRoot tests
{
name: "StateByRoot - using epoch boundary cache",
persistState: persistEpochBoundary,
loader: sbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRoot - with replay - using epoch boundary cache",
persistState: persistEpochBoundary,
loader: sbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "StateByRoot - using hot state cache",
persistState: persistHotStateCache,
loader: sbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRoot - with replay - using hot state cache",
persistState: persistHotStateCache,
loader: sbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "StateByRoot - using db",
persistState: persistDB,
loader: sbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRoot - with replay - using db",
persistState: persistDB,
loader: sbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
{
name: "StateByRoot - using finalizedInfo struct field",
persistState: persistFinalizedStruct,
loader: sbr(10),
slots: testSetupSlots{stateAt: 9, lastblock: 10},
},
{
name: "StateByRoot - with replay - using finalizedInfo struct field",
persistState: persistFinalizedStruct,
loader: sbr(11),
slots: testSetupSlots{stateAt: 9, lastblock: 11},
},
}
// Do all the state setup just once
// generate state and wind up to slot 9
st9, _ := util.DeterministicGenesisState(t, 32)
st9, err := ReplayProcessSlots(ctx, st9, 9)
require.NoError(t, err)
require.NoError(t, service.epochBoundaryStateCache.put(blkRoot, beaconState))
targetSlot := primitives.Slot(10)
targetBlk := util.NewBeaconBlock()
targetBlk.Block.Slot = 11
targetBlk.Block.ParentRoot = blkRoot[:]
targetRoot, err := targetBlk.Block.HashTreeRoot()
// take latest block header at slot 9 as parent root for block at slot 10
hdr := st9.LatestBlockHeader()
hdrRoot, err := hdr.HashTreeRoot()
require.NoError(t, err)
util.SaveBlock(t, ctx, service.beaconDB, targetBlk)
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Slot: targetSlot, Root: targetRoot[:]}))
loadedState, err := service.StateByRootInitialSync(ctx, targetRoot)
st10 := st9.Copy()
// set up block at slot 10, pointed to the latest block header as parent root
// at slot 10
// using correctly computed proposer index
blk10 := util.NewBeaconBlock()
blk10.Block.Slot = 10
blk10.Block.ParentRoot = hdrRoot[:]
idx10, err := helpers.BeaconProposerIndexAtSlot(ctx, st10, blk10.Block.Slot)
require.NoError(t, err)
assert.Equal(t, targetSlot, loadedState.Slot(), "Did not correctly load state")
}
func TestLoadeStateByRoot_Cached(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
r := [32]byte{'A'}
service.hotStateCache.put(r, beaconState)
// This tests where hot state was already cached.
loadedState, err := service.loadStateByRoot(ctx, r)
blk10.Block.ProposerIndex = idx10
// modernized block types for slot 10
ib10, err := blt.NewSignedBeaconBlock(blk10)
require.NoError(t, err)
require.DeepSSZEqual(t, loadedState.ToProtoUnsafe(), beaconState.ToProtoUnsafe())
}
func TestLoadeStateByRoot_FinalizedState(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
genesisStateRoot, err := beaconState.HashTreeRoot(ctx)
// make state at slot 10 by transitioning a copy of st9 with ib10 (aka blk10)
st10, err = executeStateTransitionStateGen(context.Background(), st10, ib10)
require.NoError(t, err)
genesis := blocks.NewGenesisBlock(genesisStateRoot[:])
util.SaveBlock(t, ctx, beaconDB, genesis)
gRoot, err := genesis.Block.HashTreeRoot()
st10Root, err := st10.HashTreeRoot(context.Background())
require.NoError(t, err)
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Slot: 0, Root: gRoot[:]}))
service.finalizedInfo.state = beaconState
service.finalizedInfo.slot = beaconState.Slot()
service.finalizedInfo.root = gRoot
// This tests where hot state was already cached.
loadedState, err := service.loadStateByRoot(ctx, gRoot)
// update state root for block 10 now that its been through stf
blk10.Block.StateRoot = st10Root[:]
ib10, err = blt.NewSignedBeaconBlock(blk10)
require.NoError(t, err)
require.DeepSSZEqual(t, loadedState.ToProtoUnsafe(), beaconState.ToProtoUnsafe())
}
func TestLoadeStateByRoot_EpochBoundaryStateCanProcess(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
gBlk := util.NewBeaconBlock()
gBlkRoot, err := gBlk.Block.HashTreeRoot()
rob10, err := blt.NewROBlock(ib10)
require.NoError(t, err)
require.NoError(t, service.epochBoundaryStateCache.put(gBlkRoot, beaconState))
blk := util.NewBeaconBlock()
blk.Block.Slot = 11
blk.Block.ProposerIndex = 8
blk.Block.ParentRoot = gBlkRoot[:]
util.SaveBlock(t, ctx, service.beaconDB, blk)
blkRoot, err := blk.Block.HashTreeRoot()
// same series of steps for block at slot 11 - pointing to block 10 as parent
blk11 := util.NewBeaconBlock()
blk11.Block.Slot = 11
blk11.Block.ParentRoot = rob10.RootSlice()
idx11, err := helpers.BeaconProposerIndexAtSlot(context.Background(), st10, blk11.Block.Slot)
require.NoError(t, err)
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Slot: 10, Root: blkRoot[:]}))
// This tests where hot state was not cached and needs processing.
loadedState, err := service.loadStateByRoot(ctx, blkRoot)
blk11.Block.ProposerIndex = idx11
ib11, err := blt.NewSignedBeaconBlock(blk11)
require.NoError(t, err)
assert.Equal(t, primitives.Slot(10), loadedState.Slot(), "Did not correctly load state")
}
func TestLoadeStateByRoot_FromDBBoundaryCase(t *testing.T) {
ctx := context.Background()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
beaconState, _ := util.DeterministicGenesisState(t, 32)
gBlk := util.NewBeaconBlock()
gBlkRoot, err := gBlk.Block.HashTreeRoot()
// same steps as 9->10; stf 10->11, then block update
st11 := st10.Copy()
st11, err = executeStateTransitionStateGen(context.Background(), st11, ib11)
require.NoError(t, err)
require.NoError(t, service.epochBoundaryStateCache.put(gBlkRoot, beaconState))
blk := util.NewBeaconBlock()
blk.Block.Slot = 11
blk.Block.ProposerIndex = 8
blk.Block.ParentRoot = gBlkRoot[:]
util.SaveBlock(t, ctx, service.beaconDB, blk)
blkRoot, err := blk.Block.HashTreeRoot()
st11Root, err := st11.HashTreeRoot(context.Background())
require.NoError(t, err)
require.NoError(t, service.beaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{Slot: 10, Root: blkRoot[:]}))
// This tests where hot state was not cached and needs processing.
loadedState, err := service.loadStateByRoot(ctx, blkRoot)
// update state root for block 11 now that its been through stf
blk11.Block.StateRoot = st11Root[:]
ib11, err = blt.NewSignedBeaconBlock(blk11)
require.NoError(t, err)
assert.Equal(t, primitives.Slot(10), loadedState.Slot(), "Did not correctly load state")
rob11, err := blt.NewROBlock(ib11)
require.NoError(t, err)
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
helpers.ClearCache()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
r := testChain{
t: t,
ctx: ctx,
d: beaconDB,
srv: service,
cslots: map[primitives.Slot]testChainSlot{
9: testChainSlot{
// note blk is nil for slot 9
st: st9.Copy(),
root: hdrRoot,
},
10: testChainSlot{
st: st10.Copy(),
blk: rob10,
root: rob10.Root(),
},
11: testChainSlot{
st: st11.Copy(),
blk: rob11,
root: rob11.Root(),
},
},
}
slots := c.slots
sumRoot := r.blockRoot(t, slots.stateAt)
require.NoError(t, r.srv.beaconDB.SaveStateSummary(r.ctx, &ethpb.StateSummary{Slot: slots.stateAt, Root: sumRoot[:]}))
// Second param controls the highest slot that we save blocks for, save all blocks <= that slot
for _, ut := range []primitives.Slot{10, 11} {
if ut <= slots.lastblock {
require.NoError(t, r.d.SaveBlock(r.ctx, r.block(t, ut)))
}
}
c.persistState(r, slots.stateAt)
// DeepSSZEqual spams full state diffs on failures, so try to fail faster with more specific assertions.
expect := r.state(t, slots.lastblock)
got, err := c.loader(r)
require.NoError(t, err)
require.Equal(t, slots.lastblock, got.Slot())
lbrE, err := expect.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
lbrG, err := got.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
require.Equal(t, lbrE, lbrG)
require.DeepSSZEqual(t, expect.ToProtoUnsafe(), got.ToProtoUnsafe())
})
}
}
func TestLastAncestorState_CanGetUsingDB(t *testing.T) {

View File

@@ -17,8 +17,6 @@ import (
)
// ReplayBlocks replays the input blocks on the input state until the target slot is reached.
//
// WARNING Blocks passed to the function must be in decreasing slots order.
func (*State) replayBlocks(
ctx context.Context,
state state.BeaconState,
@@ -37,22 +35,20 @@ func (*State) replayBlocks(
})
rLog.Debug("Replaying state")
// The input block list is sorted in decreasing slots order.
if len(signed) > 0 {
for i := len(signed) - 1; i >= 0; i-- {
if ctx.Err() != nil {
return nil, ctx.Err()
}
if state.Slot() >= targetSlot {
break
}
// A node shouldn't process the block if the block slot is lower than the state slot.
if state.Slot() >= signed[i].Block().Slot() {
continue
}
state, err = executeStateTransitionStateGen(ctx, state, signed[i])
if err != nil {
return nil, err
}
for _, blk := range signed {
if ctx.Err() != nil {
return nil, ctx.Err()
}
if state.Slot() >= targetSlot {
break
}
// A node shouldn't process the block if the block slot is lower than the state slot.
if state.Slot() >= blk.Block().Slot() {
continue
}
state, err = executeStateTransitionStateGen(ctx, state, blk)
if err != nil {
return nil, err
}
}
@@ -81,53 +77,13 @@ func (s *State) loadBlocks(ctx context.Context, startSlot, endSlot primitives.Sl
if startSlot > endSlot {
return nil, fmt.Errorf("start slot %d > end slot %d", startSlot, endSlot)
}
filter := filters.NewFilter().SetStartSlot(startSlot).SetEndSlot(endSlot)
blocks, blockRoots, err := s.beaconDB.Blocks(ctx, filter)
query := filters.AncestryQuery{Earliest: startSlot, Descendent: filters.SlotRoot{Slot: endSlot, Root: endBlockRoot}}
filter := filters.NewFilter().SetAncestryQuery(query)
blocks, _, err := s.beaconDB.Blocks(ctx, filter)
if err != nil {
return nil, err
}
// The retrieved blocks and block roots have to be in the same length given same filter.
if len(blocks) != len(blockRoots) {
return nil, errors.New("length of blocks and roots don't match")
}
// Return early if there's no block given the input.
length := len(blocks)
if length == 0 {
return nil, nil
}
// The last retrieved block root has to match input end block root.
// Covers the edge case if there's multiple blocks on the same end slot,
// the end root may not be the last index in `blockRoots`.
for length >= 3 && blocks[length-1].Block().Slot() == blocks[length-2].Block().Slot() && blockRoots[length-1] != endBlockRoot {
if ctx.Err() != nil {
return nil, ctx.Err()
}
length--
if blockRoots[length-2] == endBlockRoot {
length--
break
}
}
if blockRoots[length-1] != endBlockRoot {
return nil, errors.New("end block roots don't match")
}
filteredBlocks := []interfaces.ReadOnlySignedBeaconBlock{blocks[length-1]}
// Starting from second to last index because the last block is already in the filtered block list.
for i := length - 2; i >= 0; i-- {
if ctx.Err() != nil {
return nil, ctx.Err()
}
b := filteredBlocks[len(filteredBlocks)-1]
if b.Block().ParentRoot() != blockRoots[i] {
continue
}
filteredBlocks = append(filteredBlocks, blocks[i])
}
return filteredBlocks, nil
return blocks, nil
}
// executeStateTransitionStateGen applies state transition on input historical state and block for state gen usages.

View File

@@ -280,13 +280,14 @@ func TestLoadBlocks_FirstBranch(t *testing.T) {
require.NoError(t, err)
wanted := []*ethpb.SignedBeaconBlock{
savedBlocks[8],
savedBlocks[6],
savedBlocks[4],
savedBlocks[2],
savedBlocks[1],
savedBlocks[0],
savedBlocks[1],
savedBlocks[2],
savedBlocks[4],
savedBlocks[6],
savedBlocks[8],
}
require.Equal(t, len(wanted), len(filteredBlocks))
for i, block := range wanted {
filteredBlocksPb, err := filteredBlocks[i].Proto()
@@ -311,10 +312,10 @@ func TestLoadBlocks_SecondBranch(t *testing.T) {
require.NoError(t, err)
wanted := []*ethpb.SignedBeaconBlock{
savedBlocks[5],
savedBlocks[3],
savedBlocks[1],
savedBlocks[0],
savedBlocks[1],
savedBlocks[3],
savedBlocks[5],
}
for i, block := range wanted {
@@ -340,14 +341,16 @@ func TestLoadBlocks_ThirdBranch(t *testing.T) {
require.NoError(t, err)
wanted := []*ethpb.SignedBeaconBlock{
savedBlocks[7],
savedBlocks[6],
savedBlocks[4],
savedBlocks[2],
savedBlocks[1],
savedBlocks[0],
savedBlocks[1],
savedBlocks[2],
savedBlocks[4],
savedBlocks[6],
savedBlocks[7],
}
require.Equal(t, len(wanted), len(filteredBlocks))
for i, block := range wanted {
filteredBlocksPb, err := filteredBlocks[i].Proto()
require.NoError(t, err)
@@ -371,11 +374,12 @@ func TestLoadBlocks_SameSlots(t *testing.T) {
require.NoError(t, err)
wanted := []*ethpb.SignedBeaconBlock{
savedBlocks[6],
savedBlocks[5],
savedBlocks[1],
savedBlocks[0],
savedBlocks[1],
savedBlocks[5],
savedBlocks[6],
}
require.Equal(t, len(wanted), len(filteredBlocks))
for i, block := range wanted {
filteredBlocksPb, err := filteredBlocks[i].Proto()
@@ -400,10 +404,11 @@ func TestLoadBlocks_SameEndSlots(t *testing.T) {
require.NoError(t, err)
wanted := []*ethpb.SignedBeaconBlock{
savedBlocks[2],
savedBlocks[1],
savedBlocks[0],
savedBlocks[1],
savedBlocks[2],
}
require.Equal(t, len(wanted), len(filteredBlocks))
for i, block := range wanted {
filteredBlocksPb, err := filteredBlocks[i].Proto()
@@ -428,9 +433,10 @@ func TestLoadBlocks_SameEndSlotsWith2blocks(t *testing.T) {
require.NoError(t, err)
wanted := []*ethpb.SignedBeaconBlock{
savedBlocks[1],
savedBlocks[0],
savedBlocks[1],
}
require.Equal(t, len(wanted), len(filteredBlocks))
for i, block := range wanted {
filteredBlocksPb, err := filteredBlocks[i].Proto()
@@ -441,19 +447,6 @@ func TestLoadBlocks_SameEndSlotsWith2blocks(t *testing.T) {
}
}
func TestLoadBlocks_BadStart(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
s := &State{
beaconDB: beaconDB,
}
roots, _, err := tree1(t, beaconDB, bytesutil.PadTo([]byte{'A'}, 32))
require.NoError(t, err)
_, err = s.loadBlocks(ctx, 0, 5, roots[8])
assert.ErrorContains(t, "end block roots don't match", err)
}
// tree1 constructs the following tree:
// B0 - B1 - - B3 -- B5
//

View File

@@ -1,6 +1,8 @@
package sync
import (
"io"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/pkg/errors"
@@ -40,7 +42,7 @@ func readContextFromStream(stream network.Stream) ([]byte, error) {
}
// Read context (fork-digest) from stream
b := make([]byte, forkDigestLength)
if _, err := stream.Read(b); err != nil {
if _, err := io.ReadFull(stream, b); err != nil {
return nil, err
}
return b, nil

View File

@@ -0,0 +1,3 @@
### Added
- Add light client ssz types to the spec test

View File

@@ -0,0 +1,3 @@
### Added
- Add light client store object to the beacon node object.

View File

@@ -0,0 +1,3 @@
### Ignored
- add two parameters `increaseAttestedSlotBy` and `supermajority` to the lc test utils.

View File

@@ -0,0 +1,3 @@
### Added
- Add SSZ support to light client updates by range API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15082)

View File

@@ -0,0 +1,3 @@
### Changed
- Remove the header `Content-Disposition` from the `httputil.WriteSSZ` function. No `filename` parameter is needed anymore.

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixed to use io stream instead of stream read

View File

@@ -0,0 +1,3 @@
### Ignored
- moves some beacon api helper functions to apiutil and removes the string implementation of isvalidroot for the byte validation.

View File

@@ -1,3 +0,0 @@
### Added
- enable SSZ for builder API calls

View File

@@ -0,0 +1,3 @@
### Fixed
- The `--rpc` flag will now properly enable the keymanager APIs without web. The `--web` will enable both validator api endpoints and web.

View File

@@ -0,0 +1,3 @@
### Deprecated
- deprecates and removes usage of the `--trace` flag and`--cpuprofile` flag in favor of just using `--pprof`

View File

@@ -0,0 +1,3 @@
### Ignored
- fixes health interface usage, we should be using the interface instead of using a pointer in places.

View File

@@ -1,3 +0,0 @@
### Fixed
- cosmetic fix for calling `/prysm/validators/performance` when connecting to non prysm beacon nodes and removing the 404 error log.

View File

@@ -1,3 +0,0 @@
### Changed
- changed request object for `POST /eth/v1/beacon/states/head/validators` to omit the field if empty for satisfying other clients.

View File

@@ -0,0 +1,3 @@
### Ignored
- small optimization on ComputeSubnetForAttestation to simplify for readability by using the GetCommitteeIndex function

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