mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 13:58:09 -05:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eebc2c8d20 | ||
|
|
7a47c03b60 | ||
|
|
bfd33b64ce | ||
|
|
92cf0bc0ab | ||
|
|
9a79f49514 | ||
|
|
0d7d9bd5fc | ||
|
|
a6052efefb | ||
|
|
fa5d2a88ce | ||
|
|
ff02661229 | ||
|
|
09309ab1f2 | ||
|
|
cb9621702e | ||
|
|
efba931610 | ||
|
|
4a1c627f6f | ||
|
|
0c2464c497 | ||
|
|
2cfc204e9a | ||
|
|
877d9ee948 | ||
|
|
785fefa3f1 | ||
|
|
0c22d91a55 | ||
|
|
fb60456116 | ||
|
|
be56711892 | ||
|
|
96f1ebf706 | ||
|
|
bdb12c7d2f | ||
|
|
83cf0f8658 | ||
|
|
762594a368 | ||
|
|
c1fc812a38 | ||
|
|
340935af9c | ||
|
|
17d0082c5c | ||
|
|
e998b5ec97 | ||
|
|
5d0eb3168c | ||
|
|
e0c2aa71d4 | ||
|
|
70c31949ba | ||
|
|
e38fdb09a4 | ||
|
|
a50e981c74 | ||
|
|
8be205cf3d | ||
|
|
1b65e00096 | ||
|
|
e3fb4e86ec | ||
|
|
70aaad1904 | ||
|
|
e42611ec72 | ||
|
|
a3e61275a3 | ||
|
|
e82f9ccca3 | ||
|
|
38a6a7a4ea | ||
|
|
1295c987e8 | ||
|
|
6a27c41aad | ||
|
|
98b13ea144 | ||
|
|
c735ed2e32 | ||
|
|
bd17779231 | ||
|
|
e08ed0d823 | ||
|
|
2b4d8a09ff |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -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
|
||||
63
README.md
63
README.md
@@ -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">
|
||||
|
||||
[](https://buildkite.com/prysmatic-labs/prysm)
|
||||
[](https://goreportcard.com/report/github.com/prysmaticlabs/prysm)
|
||||
[](https://github.com/ethereum/consensus-specs/tree/v1.4.0)
|
||||
@@ -7,31 +9,60 @@
|
||||
[](https://discord.gg/prysmaticlabs)
|
||||
[](https://www.gitpoap.io/gh/prysmaticlabs/prysm)
|
||||
|
||||
This is the core repository for Prysm, a [Golang](https://golang.org/) implementation of the [Ethereum Consensus](https://ethereum.org/en/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)
|
||||
[](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)
|
||||
|
||||
10
WORKSPACE
10
WORKSPACE
@@ -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
19
api/apiutil/BUILD.bazel
Normal 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
23
api/apiutil/common.go
Normal 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())
|
||||
}
|
||||
37
api/apiutil/common_test.go
Normal file
37
api/apiutil/common_test.go
Normal 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)
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
20
api/client/beacon/health/BUILD.bazel
Normal file
20
api/client/beacon/health/BUILD.bazel
Normal file
@@ -0,0 +1,20 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"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"],
|
||||
)
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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"],
|
||||
)
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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{}, ðpb.Checkpoint{}, ðpb.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{}, ðpb.Checkpoint{}, ðpb.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)
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 := ð.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 {
|
||||
|
||||
@@ -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 := ð.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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
38
beacon-chain/core/light-client/store.go
Normal file
38
beacon-chain/core/light-client/store.go
Normal 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
|
||||
}
|
||||
67
beacon-chain/core/light-client/store_test.go
Normal file
67
beacon-chain/core/light-client/store_test.go
Normal 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")
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -474,6 +474,24 @@ func TestProcessBlock_OverMaxAttestations(t *testing.T) {
|
||||
assert.ErrorContains(t, want, err)
|
||||
}
|
||||
|
||||
func TestProcessBlock_OverMaxAttestationsElectra(t *testing.T) {
|
||||
b := ðpb.SignedBeaconBlockElectra{
|
||||
Block: ðpb.BeaconBlockElectra{
|
||||
Body: ðpb.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(ðpb.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 := ðpb.SignedBeaconBlock{
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
9
beacon-chain/db/filters/errors.go
Normal file
9
beacon-chain/db/filters/errors.go
Normal 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")
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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++ {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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] = ð.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] = ð.ValidatorIdentity{
|
||||
Index: id,
|
||||
Pubkey: pubkey[:],
|
||||
ActivationEpoch: vals[id].ActivationEpoch(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sszLen := (ð.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) {
|
||||
|
||||
@@ -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((ð.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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(ðpb.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(
|
||||
ðpb.StreamSlotsResponse{
|
||||
Slot: s,
|
||||
PreviousDutyDependentRoot: prevDepRoot[:],
|
||||
CurrentDutyDependentRoot: currDepRoot[:],
|
||||
}); err != nil {
|
||||
return status.Errorf(codes.Unavailable, "Could not send over stream: %v", err)
|
||||
}
|
||||
case <-sub.Err():
|
||||
|
||||
@@ -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 ðpb.DutiesResponse{
|
||||
CurrentEpochDuties: validatorAssignments,
|
||||
NextEpochDuties: nextValidatorAssignments,
|
||||
PreviousDutyDependentRoot: prevDependentRoot[:],
|
||||
CurrentDutyDependentRoot: currDependentRoot[:],
|
||||
CurrentEpochDuties: validatorAssignments,
|
||||
NextEpochDuties: nextValidatorAssignments,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 := ðpb.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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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(ðpb.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32)})
|
||||
data1 := util.HydrateAttestationData(ðpb.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte{'1'}, 32)})
|
||||
data0 := util.HydrateAttestationData(ðpb.AttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte{'0'}, 32),
|
||||
})
|
||||
data1 := util.HydrateAttestationData(ðpb.AttestationData{
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte{'1'}, 32),
|
||||
})
|
||||
|
||||
att := func(bits byte, cb []byte, data *ethpb.AttestationData) *ethpb.AttestationElectra {
|
||||
return ðpb.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 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1000011},
|
||||
CommitteeBits: cb0,
|
||||
Data: data0,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d0_c0_a2 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1100101},
|
||||
CommitteeBits: cb0,
|
||||
Data: data0,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d0_c0_a3 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1111000},
|
||||
CommitteeBits: cb0,
|
||||
Data: data0,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d0_c1_a1 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1111100},
|
||||
CommitteeBits: cb1,
|
||||
Data: data0,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d0_c1_a2 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1001111},
|
||||
CommitteeBits: cb1,
|
||||
Data: data0,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d1_c0_a1 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1111111},
|
||||
CommitteeBits: cb0,
|
||||
Data: data1,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d1_c1_a1 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1000011},
|
||||
CommitteeBits: cb1,
|
||||
Data: data1,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d1_c1_a2 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1100101},
|
||||
CommitteeBits: cb1,
|
||||
Data: data1,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
d1_c1_a3 := ðpb.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 := ðpb.AttestationElectra{
|
||||
AggregationBits: bitfield.Bitlist{0b1100000}, // we set only one bit for committee_index_0
|
||||
CommitteeBits: cb1,
|
||||
Data: util.HydrateAttestationData(ðpb.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, ðpb.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, ðpb.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) {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"block_roots.go",
|
||||
"historical_roots.go",
|
||||
"participation.go",
|
||||
"randao_mixes.go",
|
||||
"state_roots.go",
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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, ðpb.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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
3
changelog/bastin_add-lc-types-to-spectest.md
Normal file
3
changelog/bastin_add-lc-types-to-spectest.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add light client ssz types to the spec test
|
||||
3
changelog/bastin_add-lcstore-to-beacon-node.md
Normal file
3
changelog/bastin_add-lcstore-to-beacon-node.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add light client store object to the beacon node object.
|
||||
3
changelog/bastin_add-parameters-to-lc-testutils.md
Normal file
3
changelog/bastin_add-parameters-to-lc-testutils.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- add two parameters `increaseAttestedSlotBy` and `supermajority` to the lc test utils.
|
||||
3
changelog/bastin_lightclient-updates-ssz.md
Normal file
3
changelog/bastin_lightclient-updates-ssz.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add SSZ support to light client updates by range API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15082)
|
||||
3
changelog/bastin_remove-content-disposition-writessz.md
Normal file
3
changelog/bastin_remove-content-disposition-writessz.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Remove the header `Content-Disposition` from the `httputil.WriteSSZ` function. No `filename` parameter is needed anymore.
|
||||
3
changelog/gazzua_use_io_stream.md
Normal file
3
changelog/gazzua_use_io_stream.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed to use io stream instead of stream read
|
||||
3
changelog/james-prysm_apiutil.md
Normal file
3
changelog/james-prysm_apiutil.md
Normal 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.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- enable SSZ for builder API calls
|
||||
@@ -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.
|
||||
3
changelog/james-prysm_deprecate-non-pprof.md
Normal file
3
changelog/james-prysm_deprecate-non-pprof.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Deprecated
|
||||
|
||||
- deprecates and removes usage of the `--trace` flag and`--cpuprofile` flag in favor of just using `--pprof`
|
||||
3
changelog/james-prysm_fix-health-interface.md
Normal file
3
changelog/james-prysm_fix-health-interface.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- fixes health interface usage, we should be using the interface instead of using a pointer in places.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
Reference in New Issue
Block a user