Compare commits

...

52 Commits

Author SHA1 Message Date
Potuz
ce48bcd0fc Paralellize commitment computation
- Also fix --rlnc-block-chunks flag to pass it on the validator
- Add a feature flag --rlnc-mesh-size to configure at runtime to how
  many peers we send chunks.
2025-05-15 14:28:09 -03:00
potuz
4217fee54c add benches 2025-05-15 11:37:30 -03:00
potuz
b8ccfb6d13 Adjust meshsize as a function of chunk numbers 2025-05-14 14:20:50 -03:00
potuz
c72e3d9d67 add chunks feature flag 2025-05-13 16:22:56 -03:00
Potuz
bb4022fd01 add larger trusted setup 2025-05-13 15:37:07 -03:00
Potuz
740de753fb Fix genesis block proposal (#15084)
* Fix genesis block proposal

* fix test

* fix test 2
2025-04-24 10:07:55 -03:00
Potuz
11fd5cb40a ignore some chunks and blocks earlier 2025-04-15 14:53:44 -03:00
Potuz
f4b5b89508 delay block proposal 2025-04-15 14:29:37 -03:00
Potuz
dcb1923d0b better logs 2025-04-15 14:23:09 -03:00
Potuz
0c69601014 Add feature flag to delay block broadcast 2025-04-15 13:41:36 -03:00
Potuz
82e74cfa64 do not verify block signatures over RPC 2025-01-29 11:19:31 -03:00
Potuz
a90cdd8d7d create new context to process blocks 2025-01-29 09:56:19 -03:00
Potuz
1220e0798a send block feed when received via chunks 2025-01-29 08:48:26 -03:00
nisdas
8a6d74d636 Use SignedBeaconBlock 2025-01-28 22:41:04 +08:00
Potuz
b6f76033f1 Fix chunking 2025-01-28 08:29:44 -03:00
nisdas
cc5681b0d8 Build Reproducible Test 2025-01-28 14:28:13 +08:00
nisdas
f8ddcb8978 Fix Interface Assertion 2025-01-28 13:02:22 +08:00
Potuz
84051f43bb broadcast chunks on gossip 2025-01-27 16:26:01 -03:00
Potuz
e593797a5c add log when decoding a block 2025-01-27 16:10:22 -03:00
Potuz
51b86abd06 Broadcast chunked blocks 2025-01-27 16:05:41 -03:00
Potuz
c2073dfd3f fix block encoding when chunking 2025-01-27 12:45:42 -03:00
nisdas
ca2a20e992 Fix Validator Config 2025-01-27 21:48:16 +08:00
Nishant Das
e4075b6684 Implement RLNC Broadcasting (#14830)
Add a pubsub API to broadcast chunks
2025-01-27 10:18:54 -03:00
Potuz
50e327c7f8 Do not check blob signature 2025-01-24 06:29:28 -03:00
Potuz
2771bfc5f7 Add the signatures over the commitments to the block before processing the state transition 2025-01-24 06:29:28 -03:00
Potuz
186974fc7f Add endpoint to propose blocks
The implementation of Broadcast is blocked as it needs to be changed
2025-01-24 06:29:28 -03:00
Potuz
9ca832b4fb remove signature check 2025-01-24 06:29:28 -03:00
Potuz
7a00a9cc16 Add Ristretto trusted setup
Load the setup from a file and use it across all clients.
2025-01-24 06:29:28 -03:00
Potuz
9a3bcb876c Add chunk broadcasting
Also make `ReadOnlyBeaconBlockChunk` actually read only and copy the
return values.
2025-01-24 06:29:28 -03:00
Potuz
8bb3c388ed Handle the block when it can be recovered
Still TODO

- Handle chunks for pending blocks
- Handle state transition logic
- Handle rebroadcasting
- Handle block production
2025-01-24 06:29:28 -03:00
Potuz
044456a2c3 prune the chunk cache every 10 minutes 2025-01-24 06:29:28 -03:00
Potuz
303ac87d17 Add a cache for incoming chunks
- Finish validation of each incoming chunk including signature
  verification when needed.

Still TODO:
- Handle chunks for pending blocks
- Convert the decoded block to an actual block and send it to the
  blockchain package.
2025-01-24 06:29:28 -03:00
Potuz
d93ab44a37 Handle incoming chunks on the P2P layer
- Add hooks for the new topic "beacon_block_chunk"
- Add protos for the new messages
- Handle the sync package logic of signature validation
- Create native types for the chunks to be handled in the sync package

Still TODO:
- Handle chunks for pending blocks.
- Handle nodes with incoming chunks, avoid verifying signature twice
- Decode the block and send it to the blockchain package
2025-01-24 06:29:28 -03:00
Potuz
8b905d24a0 Add feature flag 2025-01-24 06:29:28 -03:00
Potuz
f46eaa88bf Add decoding and tests. 2025-01-24 06:29:28 -03:00
Potuz
799bd33158 Add node, receive and prepare message functions 2025-01-24 06:29:28 -03:00
Potuz
f51132fcda Add echelon form and addRow 2025-01-24 06:29:28 -03:00
Potuz
a32ed2eb98 Add chunks and message verifying 2025-01-24 06:29:28 -03:00
Potuz
7043dacaf9 add committer 2025-01-24 06:29:28 -03:00
Nishant Das
5d6a406829 Update to Go 1.23 (#14818)
* Update to Go 1.23

* Update bazel version

* Update rules_go

* Use toolchains_protoc

* Update go_honnef_go_tools

* Update golang.org/x/tools

* Fix violations of SA3000

* Update errcheck by re-exporting the upstream repo

* Remove problematic ginkgo and gomega test helpers. Rewrote tests without these test libraries.

* Update go to 1.23.5

* gofmt with go1.23.5

* Revert Patch

* Unclog

* Update for go 1.23 support

* Fix Lint Issues

* Gazelle

* Fix Build

* Fix Lint

* no lint

* Fix lint

* Fix lint

* Disable intrange

* Preston's review

---------

Co-authored-by: Preston Van Loon <preston@pvl.dev>
2025-01-24 04:53:23 +00:00
james-prysm
2c78e501b3 Builder: Electra (#14344)
* removing skip from test

* builder wip

* removing todo, it's probably ok

* adding more TODOs

* adding fromProtoElectra

* using lightclient
s branch and updating values

* minor fixes

* rolling back dependency changes

* go mod tidy

* adding space back in

* updating builder changes based on execution request changes

* update ssz

* changelog

* updating based on execution request changes

* fixing validation

* adding builder test for electra

* gaz

* attempting to fix test

* fixing ssz

* fixing build and handling develop changes

* gaz

* fixing unfinished function

* fixing test

* fixing important missed regression

* removing unneeded validations

* missed linting

* gofmt

* fixing fulu test

* fixing changelog

* Update bid.go

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

* Update bid.go

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

* Update types.go

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

* Update types.go

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

* Update james-prysm_builder-electra.md

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

* Update testing/middleware/builder/builder.go

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

* addressing review feedback and updating e2e

* fixing parsing bid version

* reversing incorrect check

* improving tests and updating more code based on review feedback

* gofmt

* fixing unit tests

* more feedback from terence

* gofmt

* Update api/client/builder/types.go

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

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

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

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

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

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

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

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

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

* Update api/client/builder/types.go

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

* addressing nitpicks

* gofmt

* radek feedback

* improves error

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-01-23 17:48:19 +00:00
Jun Song
c8cb0f37b2 fix: early return for packing local deposits when EIP-6110 is applied (#14697)
* fix: early return for gathering local deposits when EIP-6110 is applied

* Add an entry on CHANGELOG.md

* Fix weird indent at CHANGELOG.md

* Add changelog

* Fix CHANGELOG.md

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2025-01-23 16:21:57 +00:00
Manu NALEPA
78722239da nodeFilter: Add GossipBlobSidecarMessage case. (#14822)
Before this commit, this kind of logs were possible:

```
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_0/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_1/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_2/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_3/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_4/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_5/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_6/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_7/ssz_snappy
[2025-01-22 17:18:48]  DEBUG sync: Could not search for peers error=node filter: no subnet exists for provided topic: /eth2/d1f05cae/blob_sidecar_8/ssz_snappy
```

Note this bug has no real other impact than logging these errors: Since all nodes are subscribed to these subnets, as soon as some peers are found, there is no more issue.

Why not using `s.subscribe` instead of `s.subscribeWithParameters`?
Blobs subnets were before considered as static subnets. But since Electra, the number of subnets is a function of the epoch.
So it's better to use `s.subscribeWithParameters` than 2 specific but almost identic functions in `s.subscribe`.

Why `filterPeerForBlobSubnet` is the only one returning always `true`?
Because blobs subnets are actually the only subnets which are both dynamic AND which have to be subscribed by all the nodes.
So, `filterPeerForBlobSubnet` does not filter out any node.
2025-01-22 19:56:55 +00:00
Manu NALEPA
3ffef024c7 UpgradeToFulu: Respect the specification. (#14821)
188a2ff818/specs/fulu/fork.md (upgrading-the-state)

Before this commit, the `UpgradeToFulu` did not really respect the specification. This commit fixes that.

How can we be sure now the specification is really respected?

As long as the equivalent of https://github.com/ethereum/consensus-spec-tests/tree/master/tests/mainnet/electra/fork/fork/pyspec_tests are not released, we cannot be sure.

However, with this commit, Prysm and Lighthouse do agree with the post state after the Fulu fork (which is not the case without this commit).

So either both Prysm and Lighthouse are both right,
either the are both wrong (but in the exact same way, which has a pretty low likelyhood).
2025-01-22 18:39:12 +00:00
Radosław Kapka
a1eef44492 Update slasher service to Electra (#14812)
* Update slasher service to Electra

* Update beacon-chain/slasher/chunks.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* Update beacon-chain/slasher/chunks_test.go

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>

* Manu's review

* Manu's review again

---------

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
2025-01-22 17:32:19 +00:00
Radosław Kapka
2845ab9365 Update proto_test.go to Electra (#14817) 2025-01-21 20:30:52 +00:00
Radosław Kapka
4f43c15ebb Update rewards API to Electra (#14816) 2025-01-21 20:13:36 +00:00
Radosław Kapka
e473d7cc4d Use SingleAttestation for Fulu in p2p attestation map (#14809)
* Use `SingleAttestation` for Fulu in p2p attestation map.

* Fix `TestExtractDataType`.

---------

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
2025-01-20 10:35:52 +00:00
Potuz
794a05af26 Remove unused Copy() from the ReadOnlyBeaconBlock interface (#14811) 2025-01-19 19:56:14 +00:00
hidewrong
15df13c7e6 Signed-off-by: hidewrong <hidewrong@outlook.com> (#14792)
Signed-off-by: hidewrong <hidewrong@outlook.com>
2025-01-17 15:59:29 +00:00
Potuz
b76f7fed2f move credential helpers to ReadOnlyValidator methods (#14808)
* move credential helpers to ReadOnlyValidator methods

* Changelog + Gazelle

* Fix nil tests

* Fix more nil tests

* Fix another nil test
2025-01-17 12:37:08 +00:00
james-prysm
e263687ea5 Remote Signer: Electra (#14477)
* updating blockv2 to handle electra blocks

* adding aggregate attesation and proof electra

* gaz

* changelogs

* updating web3signer dependency

* test mock was flipped

* fixing hex value

* accidently checked in dependency changes

* preston feedback

* readding old metrics to not break linting

* review feedback and changelog

* gaz
2025-01-16 17:51:59 +00:00
216 changed files with 75637 additions and 5965 deletions

View File

@@ -1 +1 @@
7.1.0
7.4.1

View File

@@ -1,4 +1,4 @@
FROM golang:1.22-alpine
FROM golang:1.23-alpine
COPY entrypoint.sh /entrypoint.sh

View File

@@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.22.10'
go-version: '1.23.5'
- id: list
uses: shogo82148/actions-go-fuzz/list@v0
with:
@@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.22.10'
go-version: '1.23.5'
- uses: shogo82148/actions-go-fuzz/run@v0
with:
packages: ${{ matrix.package }}

View File

@@ -28,10 +28,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go 1.22
- name: Set up Go 1.23
uses: actions/setup-go@v4
with:
go-version: '1.22.10'
go-version: '1.23.5'
- name: Run Gosec Security Scanner
run: | # https://github.com/securego/gosec/issues/469
export PATH=$PATH:$(go env GOPATH)/bin
@@ -45,16 +45,16 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go 1.22
- name: Set up Go 1.23
uses: actions/setup-go@v4
with:
go-version: '1.22.10'
go-version: '1.23.5'
id: go
- name: Golangci-lint
uses: golangci/golangci-lint-action@v5
with:
version: v1.56.1
version: v1.63.4
args: --config=.golangci.yml --out-${NO_FUTURE}format colored-line-number
build:
@@ -64,7 +64,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v4
with:
go-version: '1.22.10'
go-version: '1.23.5'
id: go
- name: Check out code into the Go module directory

View File

@@ -1,28 +1,20 @@
run:
skip-files:
timeout: 10m
go: '1.23.5'
issues:
exclude-files:
- validator/web/site_data.go
- .*_test.go
skip-dirs:
exclude-dirs:
- proto
- tools/analyzers
timeout: 10m
go: '1.22.10'
linters:
enable-all: true
disable:
# Deprecated linters:
- deadcode
- exhaustivestruct
- golint
- govet
- ifshort
- interfacer
- maligned
- nosnakecase
- scopelint
- structcheck
- varcheck
# Disabled for now:
- asasalint
@@ -34,6 +26,8 @@ linters:
- dogsled
- dupl
- durationcheck
- errname
- err113
- exhaustive
- exhaustruct
- forbidigo
@@ -47,17 +41,17 @@ linters:
- gocyclo
- godot
- godox
- goerr113
- gofumpt
- gomnd
- gomoddirectives
- gosec
- inamedparam
- interfacebloat
- intrange
- ireturn
- lll
- maintidx
- makezero
- mnd
- musttag
- nakedret
- nestif
@@ -72,6 +66,7 @@ linters:
- predeclared
- promlinter
- protogetter
- recvcheck
- revive
- spancheck
- staticcheck

1689
MODULE.bazel.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,34 @@ load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
rules_pkg_dependencies()
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "toolchains_protoc",
sha256 = "abb1540f8a9e045422730670ebb2f25b41fa56ca5a7cf795175a110a0a68f4ad",
strip_prefix = "toolchains_protoc-0.3.6",
url = "https://github.com/aspect-build/toolchains_protoc/releases/download/v0.3.6/toolchains_protoc-v0.3.6.tar.gz",
)
load("@toolchains_protoc//protoc:repositories.bzl", "rules_protoc_dependencies")
rules_protoc_dependencies()
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies")
rules_proto_dependencies()
load("@bazel_features//:deps.bzl", "bazel_features_deps")
bazel_features_deps()
load("@toolchains_protoc//protoc:toolchain.bzl", "protoc_toolchains")
protoc_toolchains(
name = "protoc_toolchains",
version = "v25.3",
)
HERMETIC_CC_TOOLCHAIN_VERSION = "v3.0.1"
http_archive(
@@ -137,10 +165,10 @@ http_archive(
# Expose internals of go_test for custom build transitions.
"//third_party:io_bazel_rules_go_test.patch",
],
sha256 = "80a98277ad1311dacd837f9b16db62887702e9f1d1c4c9f796d0121a46c8e184",
sha256 = "b2038e2de2cace18f032249cb4bb0048abf583a36369fa98f687af1b3f880b26",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.46.0/rules_go-v0.46.0.zip",
"https://github.com/bazelbuild/rules_go/releases/download/v0.46.0/rules_go-v0.46.0.zip",
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.48.1/rules_go-v0.48.1.zip",
"https://github.com/bazelbuild/rules_go/releases/download/v0.48.1/rules_go-v0.48.1.zip",
],
)
@@ -182,7 +210,7 @@ load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_depe
go_rules_dependencies()
go_register_toolchains(
go_version = "1.22.10",
go_version = "1.23.5",
nogo = "@//:nogo",
)

View File

@@ -13,6 +13,7 @@ go_library(
deps = [
"//api:go_default_library",
"//api/client:go_default_library",
"//api/server:go_default_library",
"//api/server/structs:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
@@ -27,6 +28,7 @@ go_library(
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",

View File

@@ -1,12 +1,12 @@
package builder
import (
"github.com/pkg/errors"
ssz "github.com/prysmaticlabs/fastssz"
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
@@ -22,7 +22,6 @@ type SignedBid interface {
// Bid is an interface describing the method set of a builder bid.
type Bid interface {
Header() (interfaces.ExecutionData, error)
BlobKzgCommitments() ([][]byte, error)
Value() primitives.Wei
Pubkey() []byte
Version() int
@@ -31,6 +30,18 @@ type Bid interface {
HashTreeRootWith(hh *ssz.Hasher) error
}
// BidDeneb is an interface that exposes newly added kzg commitments on top of builder bid
type BidDeneb interface {
Bid
BlobKzgCommitments() [][]byte
}
// BidElectra is an interface that exposes the newly added execution requests on top of the builder bid
type BidElectra interface {
BidDeneb
ExecutionRequests() *v1.ExecutionRequests
}
type signedBuilderBid struct {
p *ethpb.SignedBuilderBid
}
@@ -115,11 +126,6 @@ func (b builderBid) Header() (interfaces.ExecutionData, error) {
return blocks.WrappedExecutionPayloadHeader(b.p.Header)
}
// BlobKzgCommitments --
func (b builderBid) BlobKzgCommitments() ([][]byte, error) {
return [][]byte{}, errors.New("blob kzg commitments not available before Deneb")
}
// Version --
func (b builderBid) Version() int {
return version.Bellatrix
@@ -169,11 +175,6 @@ func (b builderBidCapella) Header() (interfaces.ExecutionData, error) {
return blocks.WrappedExecutionPayloadHeaderCapella(b.p.Header)
}
// BlobKzgCommitments --
func (b builderBidCapella) BlobKzgCommitments() ([][]byte, error) {
return [][]byte{}, errors.New("blob kzg commitments not available before Deneb")
}
// Version --
func (b builderBidCapella) Version() int {
return version.Capella
@@ -254,8 +255,8 @@ func (b builderBidDeneb) Header() (interfaces.ExecutionData, error) {
}
// BlobKzgCommitments --
func (b builderBidDeneb) BlobKzgCommitments() ([][]byte, error) {
return b.p.BlobKzgCommitments, nil
func (b builderBidDeneb) BlobKzgCommitments() [][]byte {
return b.p.BlobKzgCommitments
}
type signedBuilderBidDeneb struct {
@@ -290,3 +291,95 @@ func (b signedBuilderBidDeneb) Version() int {
func (b signedBuilderBidDeneb) IsNil() bool {
return b.p == nil
}
type builderBidElectra struct {
p *ethpb.BuilderBidElectra
}
// WrappedBuilderBidElectra is a constructor which wraps a protobuf bid into an interface.
func WrappedBuilderBidElectra(p *ethpb.BuilderBidElectra) (Bid, error) {
w := builderBidElectra{p: p}
if w.IsNil() {
return nil, consensus_types.ErrNilObjectWrapped
}
return w, nil
}
// Version --
func (b builderBidElectra) Version() int {
return version.Electra
}
// Value --
func (b builderBidElectra) Value() primitives.Wei {
return primitives.LittleEndianBytesToWei(b.p.Value)
}
// Pubkey --
func (b builderBidElectra) Pubkey() []byte {
return b.p.Pubkey
}
// IsNil --
func (b builderBidElectra) IsNil() bool {
return b.p == nil
}
// HashTreeRoot --
func (b builderBidElectra) HashTreeRoot() ([32]byte, error) {
return b.p.HashTreeRoot()
}
// HashTreeRootWith --
func (b builderBidElectra) HashTreeRootWith(hh *ssz.Hasher) error {
return b.p.HashTreeRootWith(hh)
}
// Header --
func (b builderBidElectra) Header() (interfaces.ExecutionData, error) {
// We have to convert big endian to little endian because the value is coming from the execution layer.
return blocks.WrappedExecutionPayloadHeaderDeneb(b.p.Header)
}
// ExecutionRequests --
func (b builderBidElectra) ExecutionRequests() *v1.ExecutionRequests {
return b.p.ExecutionRequests // does not copy
}
// BlobKzgCommitments --
func (b builderBidElectra) BlobKzgCommitments() [][]byte {
return b.p.BlobKzgCommitments
}
type signedBuilderBidElectra struct {
p *ethpb.SignedBuilderBidElectra
}
// WrappedSignedBuilderBidElectra is a constructor which wraps a protobuf signed bit into an interface.
func WrappedSignedBuilderBidElectra(p *ethpb.SignedBuilderBidElectra) (SignedBid, error) {
w := signedBuilderBidElectra{p: p}
if w.IsNil() {
return nil, consensus_types.ErrNilObjectWrapped
}
return w, nil
}
// Message --
func (b signedBuilderBidElectra) Message() (Bid, error) {
return WrappedBuilderBidElectra(b.p.Message)
}
// Signature --
func (b signedBuilderBidElectra) Signature() []byte {
return b.p.Signature
}
// Version --
func (b signedBuilderBidElectra) Version() int {
return version.Electra
}
// IsNil --
func (b signedBuilderBidElectra) IsNil() bool {
return b.p == nil
}

View File

@@ -219,8 +219,23 @@ func (c *Client) GetHeader(ctx context.Context, slot primitives.Slot, parentHash
if err := json.Unmarshal(hb, v); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling the builder GetHeader response, using slot=%d, parentHash=%#x, pubkey=%#x", slot, parentHash, pubkey)
}
switch strings.ToLower(v.Version) {
case strings.ToLower(version.String(version.Deneb)):
ver, err := version.FromString(strings.ToLower(v.Version))
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("unsupported header version %s", strings.ToLower(v.Version)))
}
if ver >= version.Electra {
hr := &ExecHeaderResponseElectra{}
if err := json.Unmarshal(hb, hr); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling the builder GetHeader response, using slot=%d, parentHash=%#x, pubkey=%#x", slot, parentHash, pubkey)
}
p, err := hr.ToProto()
if err != nil {
return nil, errors.Wrapf(err, "could not extract proto message from header")
}
return WrappedSignedBuilderBidElectra(p)
}
if ver >= version.Deneb {
hr := &ExecHeaderResponseDeneb{}
if err := json.Unmarshal(hb, hr); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling the builder GetHeader response, using slot=%d, parentHash=%#x, pubkey=%#x", slot, parentHash, pubkey)
@@ -230,7 +245,8 @@ func (c *Client) GetHeader(ctx context.Context, slot primitives.Slot, parentHash
return nil, errors.Wrapf(err, "could not extract proto message from header")
}
return WrappedSignedBuilderBidDeneb(p)
case strings.ToLower(version.String(version.Capella)):
}
if ver >= version.Capella {
hr := &ExecHeaderResponseCapella{}
if err := json.Unmarshal(hb, hr); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling the builder GetHeader response, using slot=%d, parentHash=%#x, pubkey=%#x", slot, parentHash, pubkey)
@@ -240,7 +256,8 @@ func (c *Client) GetHeader(ctx context.Context, slot primitives.Slot, parentHash
return nil, errors.Wrapf(err, "could not extract proto message from header")
}
return WrappedSignedBuilderBidCapella(p)
case strings.ToLower(version.String(version.Bellatrix)):
}
if ver >= version.Bellatrix {
hr := &ExecHeaderResponse{}
if err := json.Unmarshal(hb, hr); err != nil {
return nil, errors.Wrapf(err, "error unmarshaling the builder GetHeader response, using slot=%d, parentHash=%#x, pubkey=%#x", slot, parentHash, pubkey)
@@ -250,9 +267,8 @@ func (c *Client) GetHeader(ctx context.Context, slot primitives.Slot, parentHash
return nil, errors.Wrap(err, "could not extract proto message from header")
}
return WrappedSignedBuilderBid(p)
default:
return nil, fmt.Errorf("unsupported header version %s", strings.ToLower(v.Version))
}
return nil, fmt.Errorf("unsupported header version %s", strings.ToLower(v.Version))
}
// RegisterValidator encodes the SignedValidatorRegistrationV1 message to json (including hex-encoding the byte

View File

@@ -266,9 +266,9 @@ func TestClient_GetHeader(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 0, value.Int.Cmp(primitives.WeiToBigInt(bid.Value())))
require.Equal(t, bidStr, primitives.WeiToBigInt(bid.Value()).String())
kcgCommitments, err := bid.BlobKzgCommitments()
require.NoError(t, err)
dbid, ok := bid.(builderBidDeneb)
require.Equal(t, true, ok)
kcgCommitments := dbid.BlobKzgCommitments()
require.Equal(t, len(kcgCommitments) > 0, true)
for i := range kcgCommitments {
require.Equal(t, len(kcgCommitments[i]) == 48, true)
@@ -292,6 +292,50 @@ func TestClient_GetHeader(t *testing.T) {
_, err := c.GetHeader(ctx, slot, bytesutil.ToBytes32(parentHash), bytesutil.ToBytes48(pubkey))
require.ErrorContains(t, "could not extract proto message from header: too many blob commitments: 7", err)
})
t.Run("electra", func(t *testing.T) {
hc := &http.Client{
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {
require.Equal(t, expectedPath, r.URL.Path)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(testExampleHeaderResponseElectra)),
Request: r.Clone(ctx),
}, nil
}),
}
c := &Client{
hc: hc,
baseURL: &url.URL{Host: "localhost:3500", Scheme: "http"},
}
h, err := c.GetHeader(ctx, slot, bytesutil.ToBytes32(parentHash), bytesutil.ToBytes48(pubkey))
require.NoError(t, err)
expectedWithdrawalsRoot := ezDecode(t, "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2")
bid, err := h.Message()
require.NoError(t, err)
bidHeader, err := bid.Header()
require.NoError(t, err)
withdrawalsRoot, err := bidHeader.WithdrawalsRoot()
require.NoError(t, err)
require.Equal(t, true, bytes.Equal(expectedWithdrawalsRoot, withdrawalsRoot))
bidStr := "652312848583266388373324160190187140051835877600158453279131187530910662656"
value, err := stringToUint256(bidStr)
require.NoError(t, err)
require.Equal(t, 0, value.Int.Cmp(primitives.WeiToBigInt(bid.Value())))
require.Equal(t, bidStr, primitives.WeiToBigInt(bid.Value()).String())
ebid, ok := bid.(builderBidElectra)
require.Equal(t, true, ok)
kcgCommitments := ebid.BlobKzgCommitments()
require.Equal(t, len(kcgCommitments) > 0, true)
for i := range kcgCommitments {
require.Equal(t, len(kcgCommitments[i]) == 48, true)
}
requests := ebid.ExecutionRequests()
require.Equal(t, 1, len(requests.Deposits))
require.Equal(t, 1, len(requests.Withdrawals))
require.Equal(t, 1, len(requests.Consolidations))
})
t.Run("unsupported version", func(t *testing.T) {
hc := &http.Client{
Transport: roundtrip(func(r *http.Request) (*http.Response, error) {

View File

@@ -5,13 +5,15 @@ import (
"fmt"
"math/big"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/api/server"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
types "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/math"
@@ -414,54 +416,10 @@ func FromProtoDeneb(payload *v1.ExecutionPayloadDeneb) (ExecutionPayloadDeneb, e
}, nil
}
var errInvalidTypeConversion = errors.New("unable to translate between api and foreign type")
// ExecutionPayloadResponseFromData converts an ExecutionData interface value to a payload response.
// This involves serializing the execution payload value so that the abstract payload envelope can be used.
func ExecutionPayloadResponseFromData(ed interfaces.ExecutionData, bundle *v1.BlobsBundle) (*ExecutionPayloadResponse, error) {
pb := ed.Proto()
var data interface{}
var err error
var ver string
switch pbStruct := pb.(type) {
case *v1.ExecutionPayload:
ver = version.String(version.Bellatrix)
data, err = FromProto(pbStruct)
if err != nil {
return nil, errors.Wrap(err, "failed to convert a Bellatrix ExecutionPayload to an API response")
}
case *v1.ExecutionPayloadCapella:
ver = version.String(version.Capella)
data, err = FromProtoCapella(pbStruct)
if err != nil {
return nil, errors.Wrap(err, "failed to convert a Capella ExecutionPayload to an API response")
}
case *v1.ExecutionPayloadDeneb:
ver = version.String(version.Deneb)
payloadStruct, err := FromProtoDeneb(pbStruct)
if err != nil {
return nil, errors.Wrap(err, "failed to convert a Deneb ExecutionPayload to an API response")
}
data = &ExecutionPayloadDenebAndBlobsBundle{
ExecutionPayload: &payloadStruct,
BlobsBundle: FromBundleProto(bundle),
}
default:
return nil, errInvalidTypeConversion
}
encoded, err := json.Marshal(data)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal execution payload version=%s", ver)
}
return &ExecutionPayloadResponse{
Version: ver,
Data: encoded,
}, nil
}
// ExecHeaderResponseCapella is the response of builder API /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey} for Capella.
type ExecHeaderResponseCapella struct {
Data struct {
Version string `json:"version"`
Data struct {
Signature hexutil.Bytes `json:"signature"`
Message *BuilderBidCapella `json:"message"`
} `json:"data"`
@@ -605,17 +563,25 @@ type BlobBundler interface {
BundleProto() (*v1.BlobsBundle, error)
}
// ParsedExecutionRequests can retrieve the underlying execution requests for the given execution payload response.
type ParsedExecutionRequests interface {
ExecutionRequestsProto() (*v1.ExecutionRequests, error)
}
func (r *ExecutionPayloadResponse) ParsePayload() (ParsedPayload, error) {
var toProto ParsedPayload
switch r.Version {
case version.String(version.Bellatrix):
toProto = &ExecutionPayload{}
case version.String(version.Capella):
toProto = &ExecutionPayloadCapella{}
case version.String(version.Deneb):
v, err := version.FromString(strings.ToLower(r.Version))
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("unsupported version %s", strings.ToLower(r.Version)))
}
if v >= version.Deneb {
toProto = &ExecutionPayloadDenebAndBlobsBundle{}
default:
return nil, consensusblocks.ErrUnsupportedVersion
} else if v >= version.Capella {
toProto = &ExecutionPayloadCapella{}
} else if v >= version.Bellatrix {
toProto = &ExecutionPayload{}
} else {
return nil, fmt.Errorf("unsupported version %s", strings.ToLower(r.Version))
}
if err := json.Unmarshal(r.Data, toProto); err != nil {
@@ -990,7 +956,8 @@ func (ch *BLSToExecutionChange) MarshalJSON() ([]byte, error) {
// ExecHeaderResponseDeneb is the header response for builder API /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}.
type ExecHeaderResponseDeneb struct {
Data struct {
Version string `json:"version"`
Data struct {
Signature hexutil.Bytes `json:"signature"`
Message *BuilderBidDeneb `json:"message"`
} `json:"data"`
@@ -1307,6 +1274,208 @@ func (p *ExecutionPayloadDeneb) ToProto() (*v1.ExecutionPayloadDeneb, error) {
}, nil
}
// ExecHeaderResponseElectra is the header response for builder API /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}.
type ExecHeaderResponseElectra struct {
Version string `json:"version"`
Data struct {
Signature hexutil.Bytes `json:"signature"`
Message *BuilderBidElectra `json:"message"`
} `json:"data"`
}
// ToProto creates a SignedBuilderBidElectra Proto from ExecHeaderResponseElectra.
func (ehr *ExecHeaderResponseElectra) ToProto() (*eth.SignedBuilderBidElectra, error) {
bb, err := ehr.Data.Message.ToProto()
if err != nil {
return nil, err
}
return &eth.SignedBuilderBidElectra{
Message: bb,
Signature: bytesutil.SafeCopyBytes(ehr.Data.Signature),
}, nil
}
// ToProto creates a BuilderBidElectra Proto from BuilderBidElectra.
func (bb *BuilderBidElectra) ToProto() (*eth.BuilderBidElectra, error) {
header, err := bb.Header.ToProto()
if err != nil {
return nil, err
}
if len(bb.BlobKzgCommitments) > params.BeaconConfig().MaxBlobsPerBlockByVersion(version.Electra) {
return nil, fmt.Errorf("blob commitment count %d exceeds the maximum %d", len(bb.BlobKzgCommitments), params.BeaconConfig().MaxBlobsPerBlockByVersion(version.Electra))
}
kzgCommitments := make([][]byte, len(bb.BlobKzgCommitments))
for i, commit := range bb.BlobKzgCommitments {
if len(commit) != fieldparams.BLSPubkeyLength {
return nil, fmt.Errorf("commitment length %d is not %d", len(commit), fieldparams.BLSPubkeyLength)
}
kzgCommitments[i] = bytesutil.SafeCopyBytes(commit)
}
// post electra execution requests should not be nil, if no requests exist use an empty request
if bb.ExecutionRequests == nil {
return nil, errors.New("bid contains nil execution requests")
}
executionRequests, err := bb.ExecutionRequests.ToProto()
if err != nil {
return nil, errors.Wrap(err, "failed to convert ExecutionRequests")
}
return &eth.BuilderBidElectra{
Header: header,
BlobKzgCommitments: kzgCommitments,
ExecutionRequests: executionRequests,
// Note that SSZBytes() reverses byte order for the little-endian representation.
// Uint256.Bytes() is big-endian, SSZBytes takes this value and reverses it.
Value: bytesutil.SafeCopyBytes(bb.Value.SSZBytes()),
Pubkey: bytesutil.SafeCopyBytes(bb.Pubkey),
}, nil
}
// ExecutionRequestsV1 is a wrapper for different execution requests
type ExecutionRequestsV1 struct {
Deposits []*DepositRequestV1 `json:"deposits"`
Withdrawals []*WithdrawalRequestV1 `json:"withdrawals"`
Consolidations []*ConsolidationRequestV1 `json:"consolidations"`
}
func (er *ExecutionRequestsV1) ToProto() (*v1.ExecutionRequests, error) {
if uint64(len(er.Deposits)) > params.BeaconConfig().MaxDepositRequestsPerPayload {
return nil, fmt.Errorf("deposit requests count %d exceeds the maximum %d", len(er.Deposits), params.BeaconConfig().MaxDepositRequestsPerPayload)
}
deposits := make([]*v1.DepositRequest, len(er.Deposits))
for i, dep := range er.Deposits {
d, err := dep.ToProto()
if err != nil {
return nil, err
}
deposits[i] = d
}
if uint64(len(er.Withdrawals)) > params.BeaconConfig().MaxWithdrawalRequestsPerPayload {
return nil, fmt.Errorf("withdrawal requests count %d exceeds the maximum %d", len(er.Withdrawals), params.BeaconConfig().MaxWithdrawalRequestsPerPayload)
}
withdrawals := make([]*v1.WithdrawalRequest, len(er.Withdrawals))
for i, wr := range er.Withdrawals {
w, err := wr.ToProto()
if err != nil {
return nil, err
}
withdrawals[i] = w
}
if uint64(len(er.Consolidations)) > params.BeaconConfig().MaxConsolidationsRequestsPerPayload {
return nil, fmt.Errorf("consolidation requests count %d exceeds the maximum %d", len(er.Consolidations), params.BeaconConfig().MaxConsolidationsRequestsPerPayload)
}
consolidations := make([]*v1.ConsolidationRequest, len(er.Consolidations))
for i, con := range er.Consolidations {
c, err := con.ToProto()
if err != nil {
return nil, err
}
consolidations[i] = c
}
return &v1.ExecutionRequests{
Deposits: deposits,
Withdrawals: withdrawals,
Consolidations: consolidations,
}, nil
}
// BuilderBidElectra is a field of ExecHeaderResponseElectra.
type BuilderBidElectra struct {
Header *ExecutionPayloadHeaderDeneb `json:"header"`
BlobKzgCommitments []hexutil.Bytes `json:"blob_kzg_commitments"`
ExecutionRequests *ExecutionRequestsV1 `json:"execution_requests"`
Value Uint256 `json:"value"`
Pubkey hexutil.Bytes `json:"pubkey"`
}
// WithdrawalRequestV1 is a field of ExecutionRequestsV1.
type WithdrawalRequestV1 struct {
SourceAddress hexutil.Bytes `json:"source_address"`
ValidatorPubkey hexutil.Bytes `json:"validator_pubkey"`
Amount Uint256 `json:"amount"`
}
func (wr *WithdrawalRequestV1) ToProto() (*v1.WithdrawalRequest, error) {
srcAddress, err := bytesutil.DecodeHexWithLength(wr.SourceAddress.String(), common.AddressLength)
if err != nil {
return nil, server.NewDecodeError(err, "source_address")
}
pubkey, err := bytesutil.DecodeHexWithLength(wr.ValidatorPubkey.String(), fieldparams.BLSPubkeyLength)
if err != nil {
return nil, server.NewDecodeError(err, "validator_pubkey")
}
return &v1.WithdrawalRequest{
SourceAddress: srcAddress,
ValidatorPubkey: pubkey,
Amount: wr.Amount.Uint64(),
}, nil
}
// DepositRequestV1 is a field of ExecutionRequestsV1.
type DepositRequestV1 struct {
PubKey hexutil.Bytes `json:"pubkey"`
// withdrawalCredentials: DATA, 32 Bytes
WithdrawalCredentials hexutil.Bytes `json:"withdrawal_credentials"`
// amount: QUANTITY, 64 Bits
Amount Uint256 `json:"amount"`
// signature: DATA, 96 Bytes
Signature hexutil.Bytes `json:"signature"`
// index: QUANTITY, 64 Bits
Index Uint256 `json:"index"`
}
func (dr *DepositRequestV1) ToProto() (*v1.DepositRequest, error) {
pubkey, err := bytesutil.DecodeHexWithLength(dr.PubKey.String(), fieldparams.BLSPubkeyLength)
if err != nil {
return nil, server.NewDecodeError(err, "pubkey")
}
wc, err := bytesutil.DecodeHexWithLength(dr.WithdrawalCredentials.String(), fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "withdrawal_credentials")
}
sig, err := bytesutil.DecodeHexWithLength(dr.Signature.String(), fieldparams.BLSSignatureLength)
if err != nil {
return nil, server.NewDecodeError(err, "signature")
}
return &v1.DepositRequest{
Pubkey: pubkey,
WithdrawalCredentials: wc,
Amount: dr.Amount.Uint64(),
Signature: sig,
Index: dr.Index.Uint64(),
}, nil
}
// ConsolidationRequestV1 is a field of ExecutionRequestsV1.
type ConsolidationRequestV1 struct {
// sourceAddress: DATA, 20 Bytes
SourceAddress hexutil.Bytes `json:"source_address"`
// sourcePubkey: DATA, 48 Bytes
SourcePubkey hexutil.Bytes `json:"source_pubkey"`
// targetPubkey: DATA, 48 Bytes
TargetPubkey hexutil.Bytes `json:"target_pubkey"`
}
func (cr *ConsolidationRequestV1) ToProto() (*v1.ConsolidationRequest, error) {
srcAddress, err := bytesutil.DecodeHexWithLength(cr.SourceAddress.String(), common.AddressLength)
if err != nil {
return nil, server.NewDecodeError(err, "source_address")
}
sourcePubkey, err := bytesutil.DecodeHexWithLength(cr.SourcePubkey.String(), fieldparams.BLSPubkeyLength)
if err != nil {
return nil, server.NewDecodeError(err, "source_pubkey")
}
targetPubkey, err := bytesutil.DecodeHexWithLength(cr.TargetPubkey.String(), fieldparams.BLSPubkeyLength)
if err != nil {
return nil, server.NewDecodeError(err, "target_pubkey")
}
return &v1.ConsolidationRequest{
SourceAddress: srcAddress,
SourcePubkey: sourcePubkey,
TargetPubkey: targetPubkey,
}, nil
}
// ErrorMessage is a JSON representation of the builder API's returned error message.
type ErrorMessage struct {
Code int `json:"code"`

View File

@@ -154,6 +154,64 @@ var testExampleHeaderResponseDeneb = `{
}
}`
var testExampleHeaderResponseElectra = `{
"version": "electra",
"data": {
"message": {
"header": {
"parent_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"fee_recipient": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"receipts_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prev_randao": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"block_number": "1",
"gas_limit": "1",
"gas_used": "1",
"timestamp": "1",
"extra_data": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"base_fee_per_gas": "1",
"blob_gas_used": "1",
"excess_blob_gas": "1",
"block_hash": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
},
"blob_kzg_commitments": [
"0xa94170080872584e54a1cf092d845703b13907f2e6b3b1c0ad573b910530499e3bcd48c6378846b80d2bfa58c81cf3d5"
],
"execution_requests": {
"deposits": [
{
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
"withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"amount": "1",
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
"index": "1"
}
],
"withdrawals": [
{
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"validator_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
"amount": "1"
}
],
"consolidations": [
{
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
"source_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
"target_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
}
]
},
"value": "652312848583266388373324160190187140051835877600158453279131187530910662656",
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
},
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
}
}`
var testExampleHeaderResponseDenebNoBundle = `{
"version": "deneb",
"data": {
@@ -1924,9 +1982,9 @@ func TestEmptyResponseBody(t *testing.T) {
emptyResponse := &ExecutionPayloadResponse{}
require.NoError(t, json.Unmarshal(empty, emptyResponse))
_, err := emptyResponse.ParsePayload()
require.ErrorIs(t, err, consensusblocks.ErrUnsupportedVersion)
require.ErrorContains(t, "unsupported version", err)
})
versions := []int{version.Bellatrix, version.Capella, version.Deneb}
versions := []int{version.Bellatrix, version.Capella, version.Deneb, version.Electra}
for i := range versions {
vstr := version.String(versions[i])
t.Run("populated version without payload"+vstr, func(t *testing.T) {

View File

@@ -74,7 +74,7 @@ func AppendHeaders(parent context.Context, headers []string) context.Context {
logrus.Warnf("Incorrect gRPC header flag format. Skipping %v", keyValue[0])
continue
}
parent = metadata.AppendToOutgoingContext(parent, keyValue[0], strings.Join(keyValue[1:], "="))
parent = metadata.AppendToOutgoingContext(parent, keyValue[0], strings.Join(keyValue[1:], "=")) // nolint:fatcontext
}
}
return parent

View File

@@ -68,6 +68,7 @@ go_library(
"//beacon-chain/startup:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync/rlnc:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",

View File

@@ -2,6 +2,7 @@ package blockchain
import (
"io"
"os"
"testing"
"github.com/sirupsen/logrus"
@@ -11,5 +12,5 @@ func TestMain(m *testing.M) {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(io.Discard)
m.Run()
os.Exit(m.Run())
}

View File

@@ -3,10 +3,13 @@ package blockchain
import (
"context"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
// CachedHeadRoot returns the corresponding value from Forkchoice
@@ -100,3 +103,26 @@ func (s *Service) ParentRoot(root [32]byte) ([32]byte, error) {
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.ParentRoot(root)
}
// hashForGenesisBlock returns the right hash for the genesis block
func (s *Service) hashForGenesisBlock(ctx context.Context, root [32]byte) ([]byte, error) {
genRoot, err := s.cfg.BeaconDB.GenesisBlockRoot(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get genesis block root")
}
if root != genRoot {
return nil, errNotGenesisRoot
}
st, err := s.cfg.BeaconDB.GenesisState(ctx)
if err != nil {
return nil, errors.Wrap(err, "could not get genesis state")
}
if st.Version() < version.Bellatrix {
return nil, nil
}
header, err := st.LatestExecutionPayloadHeader()
if err != nil {
return nil, errors.Wrap(err, "could not get latest execution payload header")
}
return bytesutil.SafeCopyBytes(header.BlockHash()), nil
}

View File

@@ -612,3 +612,20 @@ func TestService_IsFinalized(t *testing.T) {
require.Equal(t, true, c.IsFinalized(ctx, br))
require.Equal(t, false, c.IsFinalized(ctx, [32]byte{'c'}))
}
func Test_hashForGenesisRoot(t *testing.T) {
beaconDB := testDB.SetupDB(t)
ctx := context.Background()
c := setupBeaconChain(t, beaconDB)
st, _ := util.DeterministicGenesisStateElectra(t, 10)
require.NoError(t, c.cfg.BeaconDB.SaveGenesisData(ctx, st))
root, err := beaconDB.GenesisBlockRoot(ctx)
require.NoError(t, err)
genRoot, err := c.hashForGenesisBlock(ctx, [32]byte{'a'})
require.ErrorIs(t, err, errNotGenesisRoot)
require.IsNil(t, genRoot)
genRoot, err = c.hashForGenesisBlock(ctx, root)
require.NoError(t, err)
require.Equal(t, [32]byte{}, [32]byte(genRoot))
}

View File

@@ -30,6 +30,8 @@ var (
ErrNotCheckpoint = errors.New("not a checkpoint in forkchoice")
// ErrNilHead is returned when no head is present in the blockchain service.
ErrNilHead = errors.New("nil head")
// errNotGenesisRoot is returned when the root is not the genesis block root.
errNotGenesisRoot = errors.New("root is not the genesis block root")
)
var errMaxBlobsExceeded = errors.New("Expected commitments in block exceeds MAX_BLOBS_PER_BLOCK")

View File

@@ -69,6 +69,18 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
SafeBlockHash: justifiedHash[:],
FinalizedBlockHash: finalizedHash[:],
}
if len(fcs.HeadBlockHash) != 32 || [32]byte(fcs.HeadBlockHash) == [32]byte{} {
// check if we are sending FCU at genesis
hash, err := s.hashForGenesisBlock(ctx, arg.headRoot)
if errors.Is(err, errNotGenesisRoot) {
log.Error("Sending nil head block hash to execution engine")
return nil, nil
}
if err != nil {
return nil, errors.Wrap(err, "could not get head block hash")
}
fcs.HeadBlockHash = hash
}
if arg.attributes == nil {
arg.attributes = payloadattribute.EmptyWithVersion(headBlk.Version())
}
@@ -268,6 +280,9 @@ func (s *Service) notifyNewPayload(ctx context.Context, preStateVersion int,
if err != nil {
return false, errors.Wrap(err, "could not get execution requests")
}
if requests == nil {
return false, errors.New("nil execution requests")
}
}
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, parentRoot, requests)

View File

@@ -84,7 +84,7 @@ func Test_NotifyForkchoiceUpdate_GetPayloadAttrErrorCanContinue(t *testing.T) {
service.cfg.PayloadIDCache.Set(1, [32]byte{}, [8]byte{})
got, err := service.notifyForkchoiceUpdate(ctx, arg)
require.NoError(t, err)
require.DeepEqual(t, got, pid) // We still get a payload ID even though the state is bad. This means it returns until the end.
require.IsNil(t, got)
}
func Test_NotifyForkchoiceUpdate(t *testing.T) {
@@ -113,6 +113,7 @@ func Test_NotifyForkchoiceUpdate(t *testing.T) {
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
require.NoError(t, err)
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
badHash := [32]byte{'h'}
tests := []struct {
name string
@@ -210,7 +211,7 @@ func Test_NotifyForkchoiceUpdate(t *testing.T) {
blk: func() interfaces.ReadOnlySignedBeaconBlock {
b, err := consensusblocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockBellatrix{Block: &ethpb.BeaconBlockBellatrix{
Body: &ethpb.BeaconBlockBodyBellatrix{
ExecutionPayload: &v1.ExecutionPayload{},
ExecutionPayload: &v1.ExecutionPayload{BlockHash: badHash[:]},
},
}})
require.NoError(t, err)

View File

@@ -16,6 +16,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
@@ -69,6 +70,14 @@ func WithDepositCache(c cache.DepositCache) Option {
}
}
// WithChunkCommitter for chunk committer.
func WithChunkCommitter(c *rlnc.Committer) Option {
return func(s *Service) error {
s.cfg.ChunkCommitter = c
return nil
}
}
// WithPayloadIDCache for payload ID cache.
func WithPayloadIDCache(c *cache.PayloadIDCache) Option {
return func(s *Service) error {

View File

@@ -651,24 +651,6 @@ func TestOnBlock_NilBlock(t *testing.T) {
require.Equal(t, true, IsInvalidBlock(err))
}
func TestOnBlock_InvalidSignature(t *testing.T) {
service, tr := minimalTestService(t)
ctx := tr.ctx
gs, keys := util.DeterministicGenesisState(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
blk, err := util.GenerateFullBlock(gs, keys, util.DefaultBlockGenConfig(), 1)
require.NoError(t, err)
blk.Signature = []byte{'a'} // Mutate the signature.
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
preState, err := service.getBlockPreState(ctx, wsb.Block())
require.NoError(t, err)
_, err = service.validateStateTransition(ctx, preState, wsb)
require.Equal(t, true, IsInvalidBlock(err))
}
func TestOnBlock_CallNewPayloadAndForkchoiceUpdated(t *testing.T) {
params.SetupTestConfigCleanup(t)
config := params.BeaconConfig()

View File

@@ -69,6 +69,10 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
log.WithField("blockRoot", fmt.Sprintf("%#x", blockRoot)).Debug("Ignoring already synced block")
return nil
}
if s.BlockBeingSynced(blockRoot) {
log.WithField("blockRoot", fmt.Sprintf("%#x", blockRoot)).Debug("Ignoring already syncing block")
return nil
}
receivedTime := time.Now()
s.blockBeingSynced.set(blockRoot)
defer s.blockBeingSynced.unset(blockRoot)

View File

@@ -32,6 +32,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
@@ -92,6 +93,7 @@ type config struct {
FinalizedStateAtStartUp state.BeaconState
ExecutionEngineCaller execution.EngineCaller
SyncChecker Checker
ChunkCommitter *rlnc.Committer
}
// Checker is an interface used to determine if a node is in initial sync

View File

@@ -50,6 +50,11 @@ func (mb *mockBroadcaster) Broadcast(_ context.Context, _ proto.Message) error {
return nil
}
func (mb *mockBroadcaster) BroadcastBlockChunks(_ context.Context, _ []*ethpb.BeaconBlockChunk) error {
mb.broadcastCalled = true
return nil
}
func (mb *mockBroadcaster) BroadcastAttestation(_ context.Context, _ uint64, _ ethpb.Att) error {
mb.broadcastCalled = true
return nil

View File

@@ -33,6 +33,7 @@ type MockBuilderService struct {
Bid *ethpb.SignedBuilderBid
BidCapella *ethpb.SignedBuilderBidCapella
BidDeneb *ethpb.SignedBuilderBidDeneb
BidElectra *ethpb.SignedBuilderBidElectra
RegistrationCache *cache.RegistrationCache
ErrGetHeader error
ErrRegisterValidator error
@@ -59,7 +60,7 @@ func (s *MockBuilderService) SubmitBlindedBlock(_ context.Context, b interfaces.
return nil, nil, errors.Wrap(err, "could not wrap capella payload")
}
return w, nil, s.ErrSubmitBlindedBlock
case version.Deneb:
case version.Deneb, version.Electra:
w, err := blocks.WrappedExecutionPayloadDeneb(s.PayloadDeneb)
if err != nil {
return nil, nil, errors.Wrap(err, "could not wrap deneb payload")
@@ -72,6 +73,9 @@ func (s *MockBuilderService) SubmitBlindedBlock(_ context.Context, b interfaces.
// GetHeader for mocking.
func (s *MockBuilderService) GetHeader(_ context.Context, slot primitives.Slot, _ [32]byte, _ [48]byte) (builder.SignedBid, error) {
if slots.ToEpoch(slot) >= params.BeaconConfig().ElectraForkEpoch || s.BidElectra != nil {
return builder.WrappedSignedBuilderBidElectra(s.BidElectra)
}
if slots.ToEpoch(slot) >= params.BeaconConfig().DenebForkEpoch || s.BidDeneb != nil {
return builder.WrappedSignedBuilderBidDeneb(s.BidDeneb)
}

View File

@@ -1,9 +1,10 @@
package cache
import (
"os"
"testing"
)
func TestMain(m *testing.M) {
m.Run()
os.Exit(m.Run())
}

View File

@@ -291,52 +291,3 @@ func TestProcessBlockHeader_OK(t *testing.T) {
}
assert.Equal(t, true, proto.Equal(nsh, expected), "Expected %v, received %v", expected, nsh)
}
func TestBlockSignatureSet_OK(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
for i := 0; i < len(validators); i++ {
validators[i] = &ethpb.Validator{
PublicKey: make([]byte, 32),
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
Slashed: true,
}
}
state, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, state.SetValidators(validators))
require.NoError(t, state.SetSlot(10))
require.NoError(t, state.SetLatestBlockHeader(util.HydrateBeaconHeader(&ethpb.BeaconBlockHeader{
Slot: 9,
ProposerIndex: 0,
})))
latestBlockSignedRoot, err := state.LatestBlockHeader().HashTreeRoot()
require.NoError(t, err)
currentEpoch := time.CurrentEpoch(state)
priv, err := bls.RandKey()
require.NoError(t, err)
pID, err := helpers.BeaconProposerIndex(context.Background(), state)
require.NoError(t, err)
block := util.NewBeaconBlock()
block.Block.Slot = 10
block.Block.ProposerIndex = pID
block.Block.Body.RandaoReveal = bytesutil.PadTo([]byte{'A', 'B', 'C'}, 96)
block.Block.ParentRoot = latestBlockSignedRoot[:]
block.Signature, err = signing.ComputeDomainAndSign(state, currentEpoch, block.Block, params.BeaconConfig().DomainBeaconProposer, priv)
require.NoError(t, err)
proposerIdx, err := helpers.BeaconProposerIndex(context.Background(), state)
require.NoError(t, err)
validators[proposerIdx].Slashed = false
validators[proposerIdx].PublicKey = priv.PublicKey().Marshal()
err = state.UpdateValidatorAtIndex(proposerIdx, validators[proposerIdx])
require.NoError(t, err)
set, err := blocks.BlockSignatureBatch(state, block.Block.ProposerIndex, block.Signature, block.Block.HashTreeRoot)
require.NoError(t, err)
verified, err := set.Verify()
require.NoError(t, err)
assert.Equal(t, true, verified, "Block signature set returned a set which was unable to be verified")
}

View File

@@ -120,24 +120,6 @@ func VerifyBlockSignatureUsingCurrentFork(beaconState state.ReadOnlyBeaconState,
})
}
// BlockSignatureBatch retrieves the block signature batch from the provided block and its corresponding state.
func BlockSignatureBatch(beaconState state.ReadOnlyBeaconState,
proposerIndex primitives.ValidatorIndex,
sig []byte,
rootFunc func() ([32]byte, error)) (*bls.SignatureBatch, error) {
currentEpoch := slots.ToEpoch(beaconState.Slot())
domain, err := signing.Domain(beaconState.Fork(), currentEpoch, params.BeaconConfig().DomainBeaconProposer, beaconState.GenesisValidatorsRoot())
if err != nil {
return nil, err
}
proposer, err := beaconState.ValidatorAtIndex(proposerIndex)
if err != nil {
return nil, err
}
proposerPubKey := proposer.PublicKey
return signing.BlockSignatureBatch(proposerPubKey, sig, domain, rootFunc)
}
// RandaoSignatureBatch retrieves the relevant randao specific signature batch object
// from a block and its corresponding state.
func RandaoSignatureBatch(

View File

@@ -0,0 +1,17 @@
load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["signature.go"],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/chunks",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/state:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//network/forks:go_default_library",
"//time/slots:go_default_library",
],
)

View File

@@ -0,0 +1,53 @@
package chunks
import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/network/forks"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// VerifyChunkSignature verifies the proposer signature of a beacon block chunk.
func VerifyChunkSignature(beaconState state.ReadOnlyBeaconState,
proposerIndex primitives.ValidatorIndex,
sig []byte,
rootFunc func() ([32]byte, error)) error {
currentEpoch := slots.ToEpoch(beaconState.Slot())
domain, err := signing.Domain(beaconState.Fork(), currentEpoch, params.BeaconConfig().DomainBeaconProposer, beaconState.GenesisValidatorsRoot())
if err != nil {
return err
}
proposer, err := beaconState.ValidatorAtIndex(proposerIndex)
if err != nil {
return err
}
proposerPubKey := proposer.PublicKey
return signing.VerifyBlockSigningRoot(proposerPubKey, sig, domain, rootFunc)
}
// VerifyChunkSignatureUsingCurrentFork verifies the proposer signature of a beacon block chunk. This differs
// from the above method by not using fork data from the state and instead retrieving it
// via the respective epoch.
func VerifyChunkSignatureUsingCurrentFork(beaconState state.ReadOnlyBeaconState, chunk interfaces.ReadOnlyBeaconBlockChunk) error {
currentEpoch := slots.ToEpoch(chunk.Slot())
fork, err := forks.Fork(currentEpoch)
if err != nil {
return err
}
domain, err := signing.Domain(fork, currentEpoch, params.BeaconConfig().DomainBeaconProposer, beaconState.GenesisValidatorsRoot())
if err != nil {
return err
}
proposer, err := beaconState.ValidatorAtIndex(chunk.ProposerIndex())
if err != nil {
return err
}
proposerPubKey := proposer.PublicKey
sig := chunk.Signature()
return signing.VerifyBlockSigningRoot(proposerPubKey, sig[:], domain, func() ([32]byte, error) {
return chunk.HeaderRoot(), nil
})
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
@@ -233,13 +234,18 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
return fmt.Errorf("failed to fetch source validator: %w", err) // This should never happen.
}
roSrcV, err := state_native.NewValidator(srcV)
if err != nil {
return err
}
tgtV, err := st.ValidatorAtIndexReadOnly(tgtIdx)
if err != nil {
return fmt.Errorf("failed to fetch target validator: %w", err) // This should never happen.
}
// Verify source withdrawal credentials
if !helpers.HasExecutionWithdrawalCredentials(srcV) {
if !roSrcV.HasExecutionWithdrawalCredentials() {
continue
}
// Confirm source_validator.withdrawal_credentials[12:] == consolidation_request.source_address
@@ -248,7 +254,7 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
}
// Target validator must have their withdrawal credentials set appropriately.
if !helpers.HasCompoundingWithdrawalCredential(tgtV) {
if !tgtV.HasCompoundingWithdrawalCredentials() {
continue
}
@@ -364,7 +370,7 @@ func IsValidSwitchToCompoundingRequest(st state.BeaconState, req *enginev1.Conso
return false
}
if !helpers.HasETH1WithdrawalCredential(srcV) {
if !srcV.HasETH1WithdrawalCredentials() {
return false
}

View File

@@ -3,7 +3,6 @@ package electra
import (
"fmt"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/config/params"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
@@ -40,7 +39,7 @@ func ProcessEffectiveBalanceUpdates(st state.BeaconState) error {
// Update effective balances with hysteresis.
validatorFunc := func(idx int, val state.ReadOnlyValidator) (newVal *ethpb.Validator, err error) {
if val == nil {
if val.IsNil() {
return nil, fmt.Errorf("validator %d is nil in state", idx)
}
if idx >= len(bals) {
@@ -49,7 +48,7 @@ func ProcessEffectiveBalanceUpdates(st state.BeaconState) error {
balance := bals[idx]
effectiveBalanceLimit := params.BeaconConfig().MinActivationBalance
if helpers.HasCompoundingWithdrawalCredential(val) {
if val.HasCompoundingWithdrawalCredentials() {
effectiveBalanceLimit = params.BeaconConfig().MaxEffectiveBalanceElectra
}

View File

@@ -77,7 +77,7 @@ func TestProcessEffectiveBalnceUpdates(t *testing.T) {
Validators: []*eth.Validator{
{
EffectiveBalance: params.BeaconConfig().MinActivationBalance / 2,
WithdrawalCredentials: nil,
WithdrawalCredentials: make([]byte, 32),
},
},
Balances: []uint64{

View File

@@ -194,7 +194,7 @@ func UpgradeToElectra(beaconState state.BeaconState) (state.BeaconState, error)
if val.ActivationEpoch() == params.BeaconConfig().FarFutureEpoch {
preActivationIndices = append(preActivationIndices, primitives.ValidatorIndex(index))
}
if helpers.HasCompoundingWithdrawalCredential(val) {
if val.HasCompoundingWithdrawalCredentials() {
compoundWithdrawalIndices = append(compoundWithdrawalIndices, primitives.ValidatorIndex(index))
}
return nil

View File

@@ -116,7 +116,7 @@ func ProcessWithdrawalRequests(ctx context.Context, st state.BeaconState, wrs []
return nil, err
}
// Verify withdrawal credentials
hasCorrectCredential := helpers.HasExecutionWithdrawalCredentials(validator)
hasCorrectCredential := validator.HasExecutionWithdrawalCredentials()
wc := validator.GetWithdrawalCredentials()
isCorrectSourceAddress := bytes.Equal(wc[12:], wr.SourceAddress)
if !hasCorrectCredential || !isCorrectSourceAddress {
@@ -165,7 +165,7 @@ func ProcessWithdrawalRequests(ctx context.Context, st state.BeaconState, wrs []
hasExcessBalance := vBal > params.BeaconConfig().MinActivationBalance+pendingBalanceToWithdraw
// Only allow partial withdrawals with compounding withdrawal credentials
if helpers.HasCompoundingWithdrawalCredential(validator) && hasSufficientEffectiveBalance && hasExcessBalance {
if validator.HasCompoundingWithdrawalCredentials() && hasSufficientEffectiveBalance && hasExcessBalance {
// Spec definition:
// to_withdraw = min(
// state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw,

View File

@@ -6,15 +6,12 @@ go_library(
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/fulu",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)
@@ -24,7 +21,6 @@ go_test(
srcs = ["upgrade_test.go"],
deps = [
":go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
@@ -32,6 +28,5 @@ go_test(
"//proto/prysm/v1alpha1:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//time/slots:go_default_library",
],
)

View File

@@ -1,18 +1,13 @@
package fulu
import (
"sort"
"github.com/pkg/errors"
"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"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// UpgradeToFulu updates inputs a generic state to return the version Fulu state.
@@ -74,32 +69,37 @@ func UpgradeToFulu(beaconState state.BeaconState) (state.BeaconState, error) {
if err != nil {
return nil, err
}
earliestExitEpoch := helpers.ActivationExitEpoch(time.CurrentEpoch(beaconState))
preActivationIndices := make([]primitives.ValidatorIndex, 0)
compoundWithdrawalIndices := make([]primitives.ValidatorIndex, 0)
if err = beaconState.ReadFromEveryValidator(func(index int, val state.ReadOnlyValidator) error {
if val.ExitEpoch() != params.BeaconConfig().FarFutureEpoch && val.ExitEpoch() > earliestExitEpoch {
earliestExitEpoch = val.ExitEpoch()
}
if val.ActivationEpoch() == params.BeaconConfig().FarFutureEpoch {
preActivationIndices = append(preActivationIndices, primitives.ValidatorIndex(index))
}
if helpers.HasCompoundingWithdrawalCredential(val) {
compoundWithdrawalIndices = append(compoundWithdrawalIndices, primitives.ValidatorIndex(index))
}
return nil
}); err != nil {
depositBalanceToConsume, err := beaconState.DepositBalanceToConsume()
if err != nil {
return nil, err
}
earliestExitEpoch++ // Increment to find the earliest possible exit epoch
// note: should be the same in prestate and post beaconState.
// we are deviating from the specs a bit as it calls for using the post beaconState
tab, err := helpers.TotalActiveBalance(beaconState)
exitBalanceToConsume, err := beaconState.ExitBalanceToConsume()
if err != nil {
return nil, errors.Wrap(err, "failed to get total active balance")
return nil, err
}
earliestExitEpoch, err := beaconState.EarliestExitEpoch()
if err != nil {
return nil, err
}
consolidationBalanceToConsume, err := beaconState.ConsolidationBalanceToConsume()
if err != nil {
return nil, err
}
earliestConsolidationEpoch, err := beaconState.EarliestConsolidationEpoch()
if err != nil {
return nil, err
}
pendingDeposits, err := beaconState.PendingDeposits()
if err != nil {
return nil, err
}
pendingPartialWithdrawals, err := beaconState.PendingPartialWithdrawals()
if err != nil {
return nil, err
}
pendingConsolidations, err := beaconState.PendingConsolidations()
if err != nil {
return nil, err
}
s := &ethpb.BeaconStateFulu{
@@ -155,25 +155,16 @@ func UpgradeToFulu(beaconState state.BeaconState) (state.BeaconState, error) {
HistoricalSummaries: summaries,
DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex,
DepositBalanceToConsume: 0,
ExitBalanceToConsume: helpers.ActivationExitChurnLimit(primitives.Gwei(tab)),
DepositBalanceToConsume: depositBalanceToConsume,
ExitBalanceToConsume: exitBalanceToConsume,
EarliestExitEpoch: earliestExitEpoch,
ConsolidationBalanceToConsume: helpers.ConsolidationChurnLimit(primitives.Gwei(tab)),
EarliestConsolidationEpoch: helpers.ActivationExitEpoch(slots.ToEpoch(beaconState.Slot())),
PendingDeposits: make([]*ethpb.PendingDeposit, 0),
PendingPartialWithdrawals: make([]*ethpb.PendingPartialWithdrawal, 0),
PendingConsolidations: make([]*ethpb.PendingConsolidation, 0),
ConsolidationBalanceToConsume: consolidationBalanceToConsume,
EarliestConsolidationEpoch: earliestConsolidationEpoch,
PendingDeposits: pendingDeposits,
PendingPartialWithdrawals: pendingPartialWithdrawals,
PendingConsolidations: pendingConsolidations,
}
// Sorting preActivationIndices based on a custom criteria
sort.Slice(preActivationIndices, func(i, j int) bool {
// Comparing based on ActivationEligibilityEpoch and then by index if the epochs are the same
if s.Validators[preActivationIndices[i]].ActivationEligibilityEpoch == s.Validators[preActivationIndices[j]].ActivationEligibilityEpoch {
return preActivationIndices[i] < preActivationIndices[j]
}
return s.Validators[preActivationIndices[i]].ActivationEligibilityEpoch < s.Validators[preActivationIndices[j]].ActivationEligibilityEpoch
})
// Need to cast the beaconState to use in helper functions
post, err := state_native.InitializeFromProtoUnsafeFulu(s)
if err != nil {

View File

@@ -4,7 +4,6 @@ import (
"testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/fulu"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
@@ -12,7 +11,6 @@ import (
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
func TestUpgradeToFulu(t *testing.T) {
@@ -33,57 +31,6 @@ func TestUpgradeToFulu(t *testing.T) {
require.Equal(t, preForkState.GenesisTime(), mSt.GenesisTime())
require.DeepSSZEqual(t, preForkState.GenesisValidatorsRoot(), mSt.GenesisValidatorsRoot())
require.Equal(t, preForkState.Slot(), mSt.Slot())
require.DeepSSZEqual(t, preForkState.LatestBlockHeader(), mSt.LatestBlockHeader())
require.DeepSSZEqual(t, preForkState.BlockRoots(), mSt.BlockRoots())
require.DeepSSZEqual(t, preForkState.StateRoots(), mSt.StateRoots())
require.DeepSSZEqual(t, preForkState.Validators()[2:], mSt.Validators()[2:])
require.DeepSSZEqual(t, preForkState.Balances()[2:], mSt.Balances()[2:])
require.DeepSSZEqual(t, preForkState.Eth1Data(), mSt.Eth1Data())
require.DeepSSZEqual(t, preForkState.Eth1DataVotes(), mSt.Eth1DataVotes())
require.DeepSSZEqual(t, preForkState.Eth1DepositIndex(), mSt.Eth1DepositIndex())
require.DeepSSZEqual(t, preForkState.RandaoMixes(), mSt.RandaoMixes())
require.DeepSSZEqual(t, preForkState.Slashings(), mSt.Slashings())
require.DeepSSZEqual(t, preForkState.JustificationBits(), mSt.JustificationBits())
require.DeepSSZEqual(t, preForkState.PreviousJustifiedCheckpoint(), mSt.PreviousJustifiedCheckpoint())
require.DeepSSZEqual(t, preForkState.CurrentJustifiedCheckpoint(), mSt.CurrentJustifiedCheckpoint())
require.DeepSSZEqual(t, preForkState.FinalizedCheckpoint(), mSt.FinalizedCheckpoint())
require.Equal(t, len(preForkState.Validators()), len(mSt.Validators()))
preVal, err := preForkState.ValidatorAtIndex(0)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().MaxEffectiveBalance, preVal.EffectiveBalance)
preVal2, err := preForkState.ValidatorAtIndex(1)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().MaxEffectiveBalance, preVal2.EffectiveBalance)
// TODO: Fix this test
// mVal, err := mSt.ValidatorAtIndex(0)
_, err = mSt.ValidatorAtIndex(0)
require.NoError(t, err)
// require.Equal(t, uint64(0), mVal.EffectiveBalance)
mVal2, err := mSt.ValidatorAtIndex(1)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().MinActivationBalance, mVal2.EffectiveBalance)
numValidators := mSt.NumValidators()
p, err := mSt.PreviousEpochParticipation()
require.NoError(t, err)
require.DeepSSZEqual(t, make([]byte, numValidators), p)
p, err = mSt.CurrentEpochParticipation()
require.NoError(t, err)
require.DeepSSZEqual(t, make([]byte, numValidators), p)
s, err := mSt.InactivityScores()
require.NoError(t, err)
require.DeepSSZEqual(t, make([]uint64, numValidators), s)
hr1, err := preForkState.HistoricalRoots()
require.NoError(t, err)
hr2, err := mSt.HistoricalRoots()
require.NoError(t, err)
require.DeepEqual(t, hr1, hr2)
f := mSt.Fork()
require.DeepSSZEqual(t, &ethpb.Fork{
@@ -91,11 +38,50 @@ func TestUpgradeToFulu(t *testing.T) {
CurrentVersion: params.BeaconConfig().FuluForkVersion,
Epoch: time.CurrentEpoch(st),
}, f)
require.DeepSSZEqual(t, preForkState.LatestBlockHeader(), mSt.LatestBlockHeader())
require.DeepSSZEqual(t, preForkState.BlockRoots(), mSt.BlockRoots())
require.DeepSSZEqual(t, preForkState.StateRoots(), mSt.StateRoots())
hr1, err := preForkState.HistoricalRoots()
require.NoError(t, err)
hr2, err := mSt.HistoricalRoots()
require.NoError(t, err)
require.DeepEqual(t, hr1, hr2)
require.DeepSSZEqual(t, preForkState.Eth1Data(), mSt.Eth1Data())
require.DeepSSZEqual(t, preForkState.Eth1DataVotes(), mSt.Eth1DataVotes())
require.DeepSSZEqual(t, preForkState.Eth1DepositIndex(), mSt.Eth1DepositIndex())
require.DeepSSZEqual(t, preForkState.Validators(), mSt.Validators())
require.DeepSSZEqual(t, preForkState.Balances(), mSt.Balances())
require.DeepSSZEqual(t, preForkState.RandaoMixes(), mSt.RandaoMixes())
require.DeepSSZEqual(t, preForkState.Slashings(), mSt.Slashings())
numValidators := mSt.NumValidators()
p, err := mSt.PreviousEpochParticipation()
require.NoError(t, err)
require.DeepSSZEqual(t, make([]byte, numValidators), p)
p, err = mSt.CurrentEpochParticipation()
require.NoError(t, err)
require.DeepSSZEqual(t, make([]byte, numValidators), p)
require.DeepSSZEqual(t, preForkState.JustificationBits(), mSt.JustificationBits())
require.DeepSSZEqual(t, preForkState.PreviousJustifiedCheckpoint(), mSt.PreviousJustifiedCheckpoint())
require.DeepSSZEqual(t, preForkState.CurrentJustifiedCheckpoint(), mSt.CurrentJustifiedCheckpoint())
require.DeepSSZEqual(t, preForkState.FinalizedCheckpoint(), mSt.FinalizedCheckpoint())
s, err := mSt.InactivityScores()
require.NoError(t, err)
require.DeepSSZEqual(t, make([]uint64, numValidators), s)
csc, err := mSt.CurrentSyncCommittee()
require.NoError(t, err)
psc, err := preForkState.CurrentSyncCommittee()
require.NoError(t, err)
require.DeepSSZEqual(t, psc, csc)
nsc, err := mSt.NextSyncCommittee()
require.NoError(t, err)
psc, err = preForkState.NextSyncCommittee()
@@ -110,7 +96,6 @@ func TestUpgradeToFulu(t *testing.T) {
require.NoError(t, err)
txRoot, err := prevHeader.TransactionsRoot()
require.NoError(t, err)
wdRoot, err := prevHeader.WithdrawalsRoot()
require.NoError(t, err)
wanted := &enginev1.ExecutionPayloadHeaderDeneb{
@@ -144,45 +129,57 @@ func TestUpgradeToFulu(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 0, len(summaries))
startIndex, err := mSt.DepositRequestsStartIndex()
preDepositRequestsStartIndex, err := preForkState.DepositRequestsStartIndex()
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().UnsetDepositRequestsStartIndex, startIndex)
postDepositRequestsStartIndex, err := mSt.DepositRequestsStartIndex()
require.NoError(t, err)
require.Equal(t, preDepositRequestsStartIndex, postDepositRequestsStartIndex)
balance, err := mSt.DepositBalanceToConsume()
preDepositBalanceToConsume, err := preForkState.DepositBalanceToConsume()
require.NoError(t, err)
require.Equal(t, primitives.Gwei(0), balance)
postDepositBalanceToConsume, err := mSt.DepositBalanceToConsume()
require.NoError(t, err)
require.Equal(t, preDepositBalanceToConsume, postDepositBalanceToConsume)
tab, err := helpers.TotalActiveBalance(mSt)
preExitBalanceToConsume, err := preForkState.ExitBalanceToConsume()
require.NoError(t, err)
postExitBalanceToConsume, err := mSt.ExitBalanceToConsume()
require.NoError(t, err)
require.Equal(t, preExitBalanceToConsume, postExitBalanceToConsume)
ebtc, err := mSt.ExitBalanceToConsume()
preEarliestExitEpoch, err := preForkState.EarliestExitEpoch()
require.NoError(t, err)
require.Equal(t, helpers.ActivationExitChurnLimit(primitives.Gwei(tab)), ebtc)
postEarliestExitEpoch, err := mSt.EarliestExitEpoch()
require.NoError(t, err)
require.Equal(t, preEarliestExitEpoch, postEarliestExitEpoch)
eee, err := mSt.EarliestExitEpoch()
preConsolidationBalanceToConsume, err := preForkState.ConsolidationBalanceToConsume()
require.NoError(t, err)
require.Equal(t, helpers.ActivationExitEpoch(primitives.Epoch(1)), eee)
postConsolidationBalanceToConsume, err := mSt.ConsolidationBalanceToConsume()
require.NoError(t, err)
require.Equal(t, preConsolidationBalanceToConsume, postConsolidationBalanceToConsume)
cbtc, err := mSt.ConsolidationBalanceToConsume()
preEarliesConsolidationEoch, err := preForkState.EarliestConsolidationEpoch()
require.NoError(t, err)
require.Equal(t, helpers.ConsolidationChurnLimit(primitives.Gwei(tab)), cbtc)
postEarliestConsolidationEpoch, err := mSt.EarliestConsolidationEpoch()
require.NoError(t, err)
require.Equal(t, preEarliesConsolidationEoch, postEarliestConsolidationEpoch)
earliestConsolidationEpoch, err := mSt.EarliestConsolidationEpoch()
prePendingDeposits, err := preForkState.PendingDeposits()
require.NoError(t, err)
require.Equal(t, helpers.ActivationExitEpoch(slots.ToEpoch(preForkState.Slot())), earliestConsolidationEpoch)
postPendingDeposits, err := mSt.PendingDeposits()
require.NoError(t, err)
require.DeepSSZEqual(t, prePendingDeposits, postPendingDeposits)
// TODO: Fix this test
// pendingDeposits, err := mSt.PendingDeposits()
_, err = mSt.PendingDeposits()
prePendingPartialWithdrawals, err := preForkState.PendingPartialWithdrawals()
require.NoError(t, err)
// require.Equal(t, 2, len(pendingDeposits))
// require.Equal(t, uint64(1000), pendingDeposits[1].Amount)
postPendingPartialWithdrawals, err := mSt.PendingPartialWithdrawals()
require.NoError(t, err)
require.DeepSSZEqual(t, prePendingPartialWithdrawals, postPendingPartialWithdrawals)
numPendingPartialWithdrawals, err := mSt.NumPendingPartialWithdrawals()
prePendingConsolidations, err := preForkState.PendingConsolidations()
require.NoError(t, err)
require.Equal(t, uint64(0), numPendingPartialWithdrawals)
consolidations, err := mSt.PendingConsolidations()
postPendingConsolidations, err := mSt.PendingConsolidations()
require.NoError(t, err)
require.Equal(t, 0, len(consolidations))
require.DeepSSZEqual(t, prePendingConsolidations, postPendingConsolidations)
}

View File

@@ -25,7 +25,6 @@ go_library(
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/slice:go_default_library",
"//container/trie:go_default_library",

View File

@@ -14,7 +14,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
@@ -515,63 +514,6 @@ func LastActivatedValidatorIndex(ctx context.Context, st state.ReadOnlyBeaconSta
return lastActivatedvalidatorIndex, nil
}
// hasETH1WithdrawalCredential returns whether the validator has an ETH1
// Withdrawal prefix. It assumes that the caller has a lock on the state
func HasETH1WithdrawalCredential(val interfaces.WithWithdrawalCredentials) bool {
if val == nil {
return false
}
return isETH1WithdrawalCredential(val.GetWithdrawalCredentials())
}
func isETH1WithdrawalCredential(creds []byte) bool {
return bytes.HasPrefix(creds, []byte{params.BeaconConfig().ETH1AddressWithdrawalPrefixByte})
}
// HasCompoundingWithdrawalCredential checks if the validator has a compounding withdrawal credential.
// New in Electra EIP-7251: https://eips.ethereum.org/EIPS/eip-7251
//
// Spec definition:
//
// def has_compounding_withdrawal_credential(validator: Validator) -> bool:
// """
// Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential.
// """
// return is_compounding_withdrawal_credential(validator.withdrawal_credentials)
func HasCompoundingWithdrawalCredential(v interfaces.WithWithdrawalCredentials) bool {
if v == nil {
return false
}
return IsCompoundingWithdrawalCredential(v.GetWithdrawalCredentials())
}
// IsCompoundingWithdrawalCredential checks if the credentials are a compounding withdrawal credential.
//
// Spec definition:
//
// def is_compounding_withdrawal_credential(withdrawal_credentials: Bytes32) -> bool:
// return withdrawal_credentials[:1] == COMPOUNDING_WITHDRAWAL_PREFIX
func IsCompoundingWithdrawalCredential(creds []byte) bool {
return bytes.HasPrefix(creds, []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte})
}
// HasExecutionWithdrawalCredentials checks if the validator has an execution withdrawal credential or compounding credential.
// New in Electra EIP-7251: https://eips.ethereum.org/EIPS/eip-7251
//
// Spec definition:
//
// def has_execution_withdrawal_credential(validator: Validator) -> bool:
// """
// Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential.
// """
// return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator)
func HasExecutionWithdrawalCredentials(v interfaces.WithWithdrawalCredentials) bool {
if v == nil {
return false
}
return HasCompoundingWithdrawalCredential(v) || HasETH1WithdrawalCredential(v)
}
// IsSameWithdrawalCredentials returns true if both validators have the same withdrawal credentials.
//
// return a.withdrawal_credentials[12:] == b.withdrawal_credentials[12:]
@@ -606,10 +548,10 @@ func IsFullyWithdrawableValidator(val state.ReadOnlyValidator, balance uint64, e
// Electra / EIP-7251 logic
if fork >= version.Electra {
return HasExecutionWithdrawalCredentials(val) && val.WithdrawableEpoch() <= epoch
return val.HasExecutionWithdrawalCredentials() && val.WithdrawableEpoch() <= epoch
}
return HasETH1WithdrawalCredential(val) && val.WithdrawableEpoch() <= epoch
return val.HasETH1WithdrawalCredentials() && val.WithdrawableEpoch() <= epoch
}
// IsPartiallyWithdrawableValidator returns whether the validator is able to perform a
@@ -650,7 +592,7 @@ func isPartiallyWithdrawableValidatorElectra(val state.ReadOnlyValidator, balanc
hasMaxBalance := val.EffectiveBalance() == maxEB
hasExcessBalance := balance > maxEB
return HasExecutionWithdrawalCredentials(val) &&
return val.HasExecutionWithdrawalCredentials() &&
hasMaxBalance &&
hasExcessBalance
}
@@ -670,7 +612,7 @@ func isPartiallyWithdrawableValidatorElectra(val state.ReadOnlyValidator, balanc
func isPartiallyWithdrawableValidatorCapella(val state.ReadOnlyValidator, balance uint64, epoch primitives.Epoch) bool {
hasMaxBalance := val.EffectiveBalance() == params.BeaconConfig().MaxEffectiveBalance
hasExcessBalance := balance > params.BeaconConfig().MaxEffectiveBalance
return HasETH1WithdrawalCredential(val) && hasExcessBalance && hasMaxBalance
return val.HasETH1WithdrawalCredentials() && hasExcessBalance && hasMaxBalance
}
// ValidatorMaxEffectiveBalance returns the maximum effective balance for a validator.
@@ -686,7 +628,7 @@ func isPartiallyWithdrawableValidatorCapella(val state.ReadOnlyValidator, balanc
// else:
// return MIN_ACTIVATION_BALANCE
func ValidatorMaxEffectiveBalance(val state.ReadOnlyValidator) uint64 {
if HasCompoundingWithdrawalCredential(val) {
if val.HasCompoundingWithdrawalCredentials() {
return params.BeaconConfig().MaxEffectiveBalanceElectra
}
return params.BeaconConfig().MinActivationBalance

View File

@@ -910,13 +910,15 @@ func TestProposerIndexFromCheckpoint(t *testing.T) {
func TestHasETH1WithdrawalCredentials(t *testing.T) {
creds := []byte{0xFA, 0xCC}
v := &ethpb.Validator{WithdrawalCredentials: creds}
require.Equal(t, false, helpers.HasETH1WithdrawalCredential(v))
roV, err := state_native.NewValidator(v)
require.NoError(t, err)
require.Equal(t, false, roV.HasETH1WithdrawalCredentials())
creds = []byte{params.BeaconConfig().ETH1AddressWithdrawalPrefixByte, 0xCC}
v = &ethpb.Validator{WithdrawalCredentials: creds}
require.Equal(t, true, helpers.HasETH1WithdrawalCredential(v))
roV, err = state_native.NewValidator(v)
require.NoError(t, err)
require.Equal(t, true, roV.HasETH1WithdrawalCredentials())
// No Withdrawal cred
v = &ethpb.Validator{}
require.Equal(t, false, helpers.HasETH1WithdrawalCredential(v))
}
func TestHasCompoundingWithdrawalCredential(t *testing.T) {
@@ -931,11 +933,12 @@ func TestHasCompoundingWithdrawalCredential(t *testing.T) {
{"Does not have compounding withdrawal credential",
&ethpb.Validator{WithdrawalCredentials: bytesutil.PadTo([]byte{0x00}, 32)},
false},
{"Handles nil case", nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, helpers.HasCompoundingWithdrawalCredential(tt.validator))
roV, err := state_native.NewValidator(tt.validator)
require.NoError(t, err)
assert.Equal(t, tt.want, roV.HasCompoundingWithdrawalCredentials())
})
}
}
@@ -955,11 +958,12 @@ func TestHasExecutionWithdrawalCredentials(t *testing.T) {
{"Does not have compounding withdrawal credential or eth1 withdrawal credential",
&ethpb.Validator{WithdrawalCredentials: bytesutil.PadTo([]byte{0x00}, 32)},
false},
{"Handles nil case", nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, helpers.HasExecutionWithdrawalCredentials(tt.validator))
roV, err := state_native.NewValidator(tt.validator)
require.NoError(t, err)
assert.Equal(t, tt.want, roV.HasExecutionWithdrawalCredentials())
})
}
}

View File

@@ -180,12 +180,6 @@ func ProcessBlockNoVerifyAnySig(
return nil, nil, err
}
sig := signed.Signature()
bSet, err := b.BlockSignatureBatch(st, blk.ProposerIndex(), sig[:], blk.HashTreeRoot)
if err != nil {
tracing.AnnotateError(span, err)
return nil, nil, errors.Wrap(err, "could not retrieve block signature set")
}
randaoReveal := signed.Block().Body().RandaoReveal()
rSet, err := b.RandaoSignatureBatch(ctx, st, randaoReveal[:])
if err != nil {
@@ -199,7 +193,7 @@ func ProcessBlockNoVerifyAnySig(
// Merge beacon block, randao and attestations signatures into a set.
set := bls.NewSet()
set.Join(bSet).Join(rSet).Join(aSet)
set.Join(rSet).Join(aSet)
if blk.Version() >= version.Capella {
changes, err := signed.Block().Body().BLSToExecutionChanges()

View File

@@ -158,9 +158,8 @@ func TestProcessBlockNoVerify_SigSetContainsDescriptions(t *testing.T) {
set, _, err := transition.ProcessBlockNoVerifyAnySig(context.Background(), beaconState, wsb)
require.NoError(t, err)
assert.Equal(t, len(set.Signatures), len(set.Descriptions), "Signatures and descriptions do not match up")
assert.Equal(t, "block signature", set.Descriptions[0])
assert.Equal(t, "randao signature", set.Descriptions[1])
assert.Equal(t, "attestation signature", set.Descriptions[2])
assert.Equal(t, "randao signature", set.Descriptions[0])
assert.Equal(t, "attestation signature", set.Descriptions[1])
}
func TestProcessOperationsNoVerifyAttsSigs_OK(t *testing.T) {

View File

@@ -2,6 +2,7 @@ package slasherkv
import (
"io"
"os"
"testing"
"github.com/sirupsen/logrus"
@@ -10,5 +11,5 @@ import (
func TestMain(m *testing.M) {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(io.Discard)
m.Run()
os.Exit(m.Run())
}

View File

@@ -2,6 +2,7 @@ package execution
import (
"io"
"os"
"testing"
"github.com/sirupsen/logrus"
@@ -11,5 +12,5 @@ func TestMain(m *testing.M) {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(io.Discard)
m.Run()
os.Exit(m.Run())
}

View File

@@ -49,6 +49,7 @@ go_library(
"//beacon-chain/sync/checkpoint:go_default_library",
"//beacon-chain/sync/genesis:go_default_library",
"//beacon-chain/sync/initial-sync:go_default_library",
"//beacon-chain/sync/rlnc:go_default_library",
"//beacon-chain/verification:go_default_library",
"//cmd:go_default_library",
"//cmd/beacon-chain/flags:go_default_library",

View File

@@ -53,6 +53,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/checkpoint"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/genesis"
initialsync "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/initial-sync"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
"github.com/prysmaticlabs/prysm/v5/cmd"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
@@ -121,6 +122,7 @@ type BeaconNode struct {
BlobStorageOptions []filesystem.BlobStorageOption
verifyInitWaiter *verification.InitializerWaiter
syncChecker *initialsync.SyncChecker
chunkCommitter *rlnc.Committer
}
// New creates a new node instance, sets up configuration options, and registers
@@ -136,6 +138,11 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
registry := runtime.NewServiceRegistry()
ctx := cliCtx.Context
committer, err := rlnc.LoadTrustedSetup()
if err != nil {
return nil, errors.Wrap(err, "could not load the committer trusted setup")
}
beacon := &BeaconNode{
cliCtx: cliCtx,
ctx: ctx,
@@ -158,6 +165,7 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
serviceFlagOpts: &serviceFlagOpts{},
initialSyncComplete: make(chan struct{}),
syncChecker: &initialsync.SyncChecker{},
chunkCommitter: committer,
}
for _, opt := range opts {
@@ -751,6 +759,7 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st
blockchain.WithBlobStorage(b.BlobStorage),
blockchain.WithTrackedValidatorsCache(b.trackedValidatorsCache),
blockchain.WithPayloadIDCache(b.payloadIDCache),
blockchain.WithChunkCommitter(b.chunkCommitter),
blockchain.WithSyncChecker(b.syncChecker),
)
@@ -836,6 +845,7 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil
regularsync.WithBlobStorage(b.BlobStorage),
regularsync.WithVerifierWaiter(b.verifyInitWaiter),
regularsync.WithAvailableBlocker(bFillStore),
regularsync.WithChunkCommitter(b.chunkCommitter),
)
return b.services.RegisterService(rs)
}
@@ -982,6 +992,7 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
BlobStorage: b.BlobStorage,
TrackedValidatorsCache: b.trackedValidatorsCache,
PayloadIDCache: b.payloadIDCache,
ChunkCommitter: b.chunkCommitter,
})
return b.services.RegisterService(rpcService)

View File

@@ -7,6 +7,7 @@ import (
"reflect"
"time"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/pkg/errors"
ssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
@@ -228,6 +229,36 @@ func (s *Service) BroadcastBlob(ctx context.Context, subnet uint64, blob *ethpb.
return nil
}
// BroadcastBlockChunks sends the passed messages to the pubsub topic
func (s *Service) BroadcastBlockChunks(ctx context.Context, chunks []*ethpb.BeaconBlockChunk) error {
ctx, span := trace.StartSpan(ctx, "p2p.BroadcastBlob")
defer span.End()
forkDigest, err := s.currentForkDigest()
if err != nil {
err := errors.Wrap(err, "could not retrieve fork digest")
tracing.AnnotateError(span, err)
return err
}
topic := RLNCTopicFormat
topic = fmt.Sprintf(topic, forkDigest)
multipleMessages := make([][]byte, len(chunks))
for i, c := range chunks {
buf := new(bytes.Buffer)
if _, err := s.Encoding().EncodeGossip(buf, c); err != nil {
err := errors.Wrap(err, "could not encode message")
tracing.AnnotateError(span, err)
return err
}
multipleMessages[i] = buf.Bytes()
}
if err := s.PublishMultipleToTopic(ctx, topic+s.Encoding().ProtocolSuffix(), multipleMessages, pubsub.WithRandomPublishing()); err != nil {
err := errors.Wrap(err, "could not publish message")
tracing.AnnotateError(span, err)
return err
}
return nil
}
func (s *Service) internalBroadcastBlob(ctx context.Context, subnet uint64, blobSidecar *ethpb.BlobSidecar, forkDigest [4]byte) {
_, span := trace.StartSpan(ctx, "p2p.internalBroadcastBlob")
defer span.End()

View File

@@ -355,7 +355,7 @@ func TestStaticPeering_PeersAreAdded(t *testing.T) {
func TestHostIsResolved(t *testing.T) {
// ip.addr.tools - construct domain names that resolve to any given IP address
// ex: 192-0-2-1.ip.addr.tools resolves to 192.0.2.1.
// ex: 192-0-2-1.ip.addr.tools resolves to 192.0.2.1.
exampleHost := "96-7-129-13.ip.addr.tools"
exampleIP := "96.7.129.13"

View File

@@ -12,6 +12,7 @@ go_library(
visibility = [
"//beacon-chain:__subpackages__",
"//cmd:__subpackages__",
"//validator:__subpackages__",
],
deps = [
"//config/params:go_default_library",

View File

@@ -22,6 +22,7 @@ var gossipTopicMappings = map[string]func() proto.Message{
SyncCommitteeSubnetTopicFormat: func() proto.Message { return &ethpb.SyncCommitteeMessage{} },
BlsToExecutionChangeSubnetTopicFormat: func() proto.Message { return &ethpb.SignedBLSToExecutionChange{} },
BlobSubnetTopicFormat: func() proto.Message { return &ethpb.BlobSidecar{} },
RLNCTopicFormat: func() proto.Message { return &ethpb.BeaconBlockChunk{} },
}
// GossipTopicMappings is a function to return the assigned data type
@@ -112,7 +113,7 @@ func init() {
GossipTypeMapping[reflect.TypeOf(&ethpb.SingleAttestation{})] = AttestationSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.AttesterSlashingElectra{})] = AttesterSlashingSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedAggregateAttestationAndProofElectra{})] = AggregateAndProofSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.BeaconBlockChunk{})] = RLNCTopicFormat
// Specially handle Fulu objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockFulu{})] = BlockSubnetTopicFormat
}

View File

@@ -33,6 +33,7 @@ type P2P interface {
// Broadcaster broadcasts messages to peers over the p2p pubsub protocol.
type Broadcaster interface {
Broadcast(context.Context, proto.Message) error
BroadcastBlockChunks(context.Context, []*ethpb.BeaconBlockChunk) error
BroadcastAttestation(ctx context.Context, subnet uint64, att ethpb.Att) error
BroadcastSyncCommitteeMessage(ctx context.Context, subnet uint64, sMsg *ethpb.SyncCommitteeMessage) error
BroadcastBlob(ctx context.Context, subnet uint64, blob *ethpb.BlobSidecar) error

View File

@@ -2,6 +2,7 @@ package peers_test
import (
"io"
"os"
"testing"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
@@ -28,5 +29,5 @@ func TestMain(m *testing.M) {
defer func() {
flags.Init(resetFlags)
}()
m.Run()
os.Exit(m.Run())
}

View File

@@ -3,6 +3,7 @@ package scorers_test
import (
"io"
"math"
"os"
"testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/peers/scorers"
@@ -28,7 +29,7 @@ func TestMain(m *testing.M) {
defer func() {
flags.Init(resetFlags)
}()
m.Run()
os.Exit(m.Run())
}
// roundScore returns score rounded in accordance with the score manager's rounding factor.

View File

@@ -98,6 +98,26 @@ func (s *Service) PublishToTopic(ctx context.Context, topic string, data []byte,
}
}
func (s *Service) PublishMultipleToTopic(ctx context.Context, topic string, msgs [][]byte, opts ...pubsub.PubOpt) error {
topicHandle, err := s.JoinTopic(topic)
if err != nil {
return err
}
// Wait for at least 1 peer to be available to receive the published message.
for {
if len(topicHandle.ListPeers()) > 0 || flags.Get().MinimumSyncPeers == 0 {
return topicHandle.PublishMultiple(ctx, msgs, opts...)
}
select {
case <-ctx.Done():
return errors.Wrapf(ctx.Err(), "unable to find requisite number of peers for topic %s, 0 peers found to publish to", topic)
default:
time.Sleep(100 * time.Millisecond)
}
}
}
// SubscribeToTopic joins (if necessary) and subscribes to PubSub topic.
func (s *Service) SubscribeToTopic(topic string, opts ...pubsub.SubOpt) (*pubsub.Subscription, error) {
s.awaitStateInitialized() // Genesis time and genesis validators root are required to subscribe.

View File

@@ -54,6 +54,8 @@ func (s *Service) nodeFilter(topic string, index uint64) (func(node *enode.Node)
return s.filterPeerForAttSubnet(index), nil
case strings.Contains(topic, GossipSyncCommitteeMessage):
return s.filterPeerForSyncSubnet(index), nil
case strings.Contains(topic, GossipBlobSidecarMessage):
return s.filterPeerForBlobSubnet(), nil
default:
return nil, errors.Errorf("no subnet exists for provided topic: %s", topic)
}
@@ -266,6 +268,14 @@ func (s *Service) filterPeerForSyncSubnet(index uint64) func(node *enode.Node) b
}
}
// returns a method with filters peers specifically for a particular blob subnet.
// All peers are supposed to be subscribed to all blob subnets.
func (s *Service) filterPeerForBlobSubnet() func(_ *enode.Node) bool {
return func(_ *enode.Node) bool {
return true
}
}
// lower threshold to broadcast object compared to searching
// for a subnet. So that even in the event of poor peer
// connectivity, we can still broadcast an attestation.

View File

@@ -133,6 +133,10 @@ func (*FakeP2P) Broadcast(_ context.Context, _ proto.Message) error {
return nil
}
func (*FakeP2P) BroadcastBlockChunks(_ context.Context, _ []*ethpb.BeaconBlockChunk) error {
return nil
}
// BroadcastAttestation -- fake.
func (*FakeP2P) BroadcastAttestation(_ context.Context, _ uint64, _ ethpb.Att) error {
return nil

View File

@@ -27,6 +27,11 @@ func (m *MockBroadcaster) Broadcast(_ context.Context, msg proto.Message) error
return nil
}
func (m *MockBroadcaster) BroadcastBlockChunks(_ context.Context, _ []*ethpb.BeaconBlockChunk) error {
m.BroadcastCalled.Store(true)
return nil
}
// BroadcastAttestation records a broadcast occurred.
func (m *MockBroadcaster) BroadcastAttestation(_ context.Context, _ uint64, a ethpb.Att) error {
m.BroadcastCalled.Store(true)

View File

@@ -189,6 +189,11 @@ func (p *TestP2P) Broadcast(_ context.Context, _ proto.Message) error {
return nil
}
func (p *TestP2P) BroadcastBlockChunks(_ context.Context, _ []*ethpb.BeaconBlockChunk) error {
p.BroadcastCalled.Store(true)
return nil
}
// BroadcastAttestation broadcasts an attestation.
func (p *TestP2P) BroadcastAttestation(_ context.Context, _ uint64, _ ethpb.Att) error {
p.BroadcastCalled.Store(true)

View File

@@ -30,6 +30,8 @@ const (
GossipBlsToExecutionChangeMessage = "bls_to_execution_change"
// GossipBlobSidecarMessage is the name for the blob sidecar message type.
GossipBlobSidecarMessage = "blob_sidecar"
// GossipBlockChunkMessage is the name for the block chunk message type.
GossipBlockChunkMessage = "beacon_block_chunk"
// Topic Formats
//
// AttestationSubnetTopicFormat is the topic format for the attestation subnet.
@@ -52,4 +54,6 @@ const (
BlsToExecutionChangeSubnetTopicFormat = GossipProtocolAndDigest + GossipBlsToExecutionChangeMessage
// BlobSubnetTopicFormat is the topic format for the blob subnet.
BlobSubnetTopicFormat = GossipProtocolAndDigest + GossipBlobSidecarMessage + "_%d"
// RLNCTopicFormat is the topic format for the RLNC subnet.
RLNCTopicFormat = GossipProtocolAndDigest + GossipBlockChunkMessage
)

View File

@@ -123,7 +123,7 @@ func InitializeDataMaps() {
return &ethpb.SingleAttestation{}, nil
},
bytesutil.ToBytes4(params.BeaconConfig().FuluForkVersion): func() (ethpb.Att, error) {
return &ethpb.AttestationElectra{}, nil
return &ethpb.SingleAttestation{}, nil
},
}

View File

@@ -51,6 +51,7 @@ go_library(
"//beacon-chain/startup:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync:go_default_library",
"//beacon-chain/sync/rlnc:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//io/logs:go_default_library",
@@ -90,6 +91,5 @@ go_test(
"//testing/require:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_x_exp//maps:go_default_library",
],
)

View File

@@ -1,12 +1,12 @@
package rpc
import (
"maps"
"net/http"
"slices"
"testing"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"golang.org/x/exp/maps"
)
func Test_endpoints(t *testing.T) {

View File

@@ -17,6 +17,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/api"
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache/depositsnapshot"
coreblocks "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
corehelpers "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filters"
@@ -1182,6 +1183,13 @@ func (s *Server) validateConsensus(ctx context.Context, b *eth.GenericSignedBeac
if err != nil {
return errors.Wrap(err, "could not get parent state")
}
blockRoot, err := blk.Block().HashTreeRoot()
if err != nil {
return errors.Wrap(err, "could not hash block")
}
if err := coreblocks.VerifyBlockSignatureUsingCurrentFork(parentState, blk, blockRoot); err != nil {
return errors.Wrap(err, "could not verify block signature")
}
_, err = transition.ExecuteStateTransition(ctx, parentState, blk)
if err != nil {
return errors.Wrap(err, "could not execute state transition")

View File

@@ -461,7 +461,7 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
defer testSync.cleanup()
st := tc.getState()
v := &eth.Validator{ExitEpoch: math.MaxUint64, EffectiveBalance: params.BeaconConfig().MinActivationBalance}
v := &eth.Validator{ExitEpoch: math.MaxUint64, EffectiveBalance: params.BeaconConfig().MinActivationBalance, WithdrawalCredentials: make([]byte, 32)}
require.NoError(t, st.SetValidators([]*eth.Validator{v}))
currentSlot := primitives.Slot(0)
// to avoid slot processing

View File

@@ -63,6 +63,7 @@ go_test(
"//encoding/bytesutil:go_default_library",
"//network/httputil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",

View File

@@ -32,45 +32,52 @@ import (
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/network/httputil"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
)
func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, interfaces.SignedBeaconBlock, error) {
func BlockRewardTestSetup(t *testing.T, ver int) (state.BeaconState, interfaces.SignedBeaconBlock, error) {
helpers.ClearCache()
var sbb interfaces.SignedBeaconBlock
var st state.BeaconState
var err error
switch forkName {
case "phase0":
switch ver {
case version.Phase0:
return nil, nil, errors.New("phase0 not supported")
case "altair":
case version.Altair:
st, err = util.NewBeaconStateAltair()
require.NoError(t, err)
b := util.HydrateSignedBeaconBlockAltair(util.NewBeaconBlockAltair())
sbb, err = blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
case "bellatrix":
case version.Bellatrix:
st, err = util.NewBeaconStateBellatrix()
require.NoError(t, err)
b := util.HydrateSignedBeaconBlockBellatrix(util.NewBeaconBlockBellatrix())
sbb, err = blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
case "capella":
case version.Capella:
st, err = util.NewBeaconStateCapella()
require.NoError(t, err)
b := util.HydrateSignedBeaconBlockCapella(util.NewBeaconBlockCapella())
sbb, err = blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
case "deneb":
case version.Deneb:
st, err = util.NewBeaconStateDeneb()
require.NoError(t, err)
b := util.HydrateSignedBeaconBlockDeneb(util.NewBeaconBlockDeneb())
sbb, err = blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
case version.Electra:
st, err = util.NewBeaconStateElectra()
require.NoError(t, err)
b := util.HydrateSignedBeaconBlockElectra(util.NewBeaconBlockElectra())
sbb, err = blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
default:
return nil, nil, errors.New("fork is not supported")
return nil, nil, fmt.Errorf("fork %s is not supported", version.String(ver))
}
valCount := 64
require.NoError(t, st.SetSlot(1))
@@ -102,20 +109,47 @@ func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, int
require.NoError(t, st.SetBlockRoots(bRoots))
sbb.SetSlot(2)
// we have to set the proposer index to the value that will be randomly chosen (fortunately it's deterministic)
sbb.SetProposerIndex(12)
require.NoError(t, sbb.SetAttestations([]eth.Att{
&eth.Attestation{
AggregationBits: bitfield.Bitlist{0b00000111},
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
&eth.Attestation{
AggregationBits: bitfield.Bitlist{0b00000111},
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
}))
if ver >= version.Electra {
sbb.SetProposerIndex(4)
} else {
sbb.SetProposerIndex(12)
}
var atts []eth.Att
if ver >= version.Electra {
cb := primitives.NewAttestationCommitteeBits()
cb.SetBitAt(0, true)
atts = []eth.Att{
&eth.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b00000111},
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
CommitteeBits: cb,
},
&eth.AttestationElectra{
AggregationBits: bitfield.Bitlist{0b00000111},
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
CommitteeBits: cb,
},
}
} else {
atts = []eth.Att{
&eth.Attestation{
AggregationBits: bitfield.Bitlist{0b00000111},
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
&eth.Attestation{
AggregationBits: bitfield.Bitlist{0b00000111},
Data: util.HydrateAttestationData(&eth.AttestationData{}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
}
}
require.NoError(t, sbb.SetAttestations(atts))
attData1 := util.HydrateAttestationData(&eth.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32)})
attData2 := util.HydrateAttestationData(&eth.AttestationData{BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32)})
@@ -125,8 +159,23 @@ func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, int
require.NoError(t, err)
sigRoot2, err := signing.ComputeSigningRoot(attData2, domain)
require.NoError(t, err)
require.NoError(t, sbb.SetAttesterSlashings([]eth.AttSlashing{
&eth.AttesterSlashing{
var attSlashing eth.AttSlashing
if ver >= version.Electra {
attSlashing = &eth.AttesterSlashingElectra{
Attestation_1: &eth.IndexedAttestationElectra{
AttestingIndices: []uint64{0},
Data: attData1,
Signature: secretKeys[0].Sign(sigRoot1[:]).Marshal(),
},
Attestation_2: &eth.IndexedAttestationElectra{
AttestingIndices: []uint64{0},
Data: attData2,
Signature: secretKeys[0].Sign(sigRoot2[:]).Marshal(),
},
}
} else {
attSlashing = &eth.AttesterSlashing{
Attestation_1: &eth.IndexedAttestation{
AttestingIndices: []uint64{0},
Data: attData1,
@@ -137,8 +186,10 @@ func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, int
Data: attData2,
Signature: secretKeys[0].Sign(sigRoot2[:]).Marshal(),
},
},
}))
}
}
require.NoError(t, sbb.SetAttesterSlashings([]eth.AttSlashing{attSlashing}))
header1 := &eth.BeaconBlockHeader{
Slot: 0,
ProposerIndex: 1,
@@ -179,11 +230,21 @@ func BlockRewardTestSetup(t *testing.T, forkName string) (state.BeaconState, int
sszBytes := primitives.SSZBytes(slot0bRoot)
r, err := signing.ComputeSigningRoot(&sszBytes, domain)
require.NoError(t, err)
// Bits set in sync committee bits determine which validators will be treated as participating in sync committee.
// These validators have to sign the message.
sig1, err := blst.SignatureFromBytes(secretKeys[47].Sign(r[:]).Marshal())
var scValIdx1 int
var scValIdx2 int
if ver >= version.Electra {
scValIdx1 = 14
scValIdx2 = 27
} else {
scValIdx1 = 47
scValIdx2 = 19
}
sig1, err := blst.SignatureFromBytes(secretKeys[scValIdx1].Sign(r[:]).Marshal())
require.NoError(t, err)
sig2, err := blst.SignatureFromBytes(secretKeys[19].Sign(r[:]).Marshal())
sig2, err := blst.SignatureFromBytes(secretKeys[scValIdx2].Sign(r[:]).Marshal())
require.NoError(t, err)
aggSig := bls.AggregateSignatures([]bls.Signature{sig1, sig2}).Marshal()
err = sbb.SetSyncAggregate(&eth.SyncAggregate{SyncCommitteeBits: scBits, SyncCommitteeSignature: aggSig})
@@ -211,14 +272,14 @@ func TestBlockRewards(t *testing.T) {
writer.Body = &bytes.Buffer{}
s.BlockRewards(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
require.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)
require.Equal(t, http.StatusBadRequest, e.Code)
assert.Equal(t, "Block rewards are not supported for Phase 0 blocks", e.Message)
})
t.Run("altair", func(t *testing.T) {
st, sbb, err := BlockRewardTestSetup(t, "altair")
st, sbb, err := BlockRewardTestSetup(t, version.Altair)
require.NoError(t, err)
mockChainService := &mock.ChainService{Optimistic: true}
@@ -241,7 +302,7 @@ func TestBlockRewards(t *testing.T) {
writer.Body = &bytes.Buffer{}
s.BlockRewards(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.BlockRewardsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "12", resp.Data.ProposerIndex)
@@ -254,7 +315,7 @@ func TestBlockRewards(t *testing.T) {
assert.Equal(t, false, resp.Finalized)
})
t.Run("bellatrix", func(t *testing.T) {
st, sbb, err := BlockRewardTestSetup(t, "bellatrix")
st, sbb, err := BlockRewardTestSetup(t, version.Bellatrix)
require.NoError(t, err)
mockChainService := &mock.ChainService{Optimistic: true}
@@ -277,7 +338,7 @@ func TestBlockRewards(t *testing.T) {
writer.Body = &bytes.Buffer{}
s.BlockRewards(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.BlockRewardsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "12", resp.Data.ProposerIndex)
@@ -290,7 +351,7 @@ func TestBlockRewards(t *testing.T) {
assert.Equal(t, false, resp.Finalized)
})
t.Run("capella", func(t *testing.T) {
st, sbb, err := BlockRewardTestSetup(t, "capella")
st, sbb, err := BlockRewardTestSetup(t, version.Capella)
require.NoError(t, err)
mockChainService := &mock.ChainService{Optimistic: true}
@@ -313,7 +374,7 @@ func TestBlockRewards(t *testing.T) {
writer.Body = &bytes.Buffer{}
s.BlockRewards(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.BlockRewardsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "12", resp.Data.ProposerIndex)
@@ -326,7 +387,7 @@ func TestBlockRewards(t *testing.T) {
assert.Equal(t, false, resp.Finalized)
})
t.Run("deneb", func(t *testing.T) {
st, sbb, err := BlockRewardTestSetup(t, "deneb")
st, sbb, err := BlockRewardTestSetup(t, version.Deneb)
require.NoError(t, err)
mockChainService := &mock.ChainService{Optimistic: true}
@@ -349,7 +410,7 @@ func TestBlockRewards(t *testing.T) {
writer.Body = &bytes.Buffer{}
s.BlockRewards(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.BlockRewardsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "12", resp.Data.ProposerIndex)
@@ -361,6 +422,42 @@ func TestBlockRewards(t *testing.T) {
assert.Equal(t, true, resp.ExecutionOptimistic)
assert.Equal(t, false, resp.Finalized)
})
t.Run("electra", func(t *testing.T) {
st, sbb, err := BlockRewardTestSetup(t, version.Electra)
require.NoError(t, err)
mockChainService := &mock.ChainService{Optimistic: true}
s := &Server{
Blocker: &testutil.MockBlocker{SlotBlockMap: map[primitives.Slot]interfaces.ReadOnlySignedBeaconBlock{
0: phase0block,
2: sbb,
}},
OptimisticModeFetcher: mockChainService,
FinalizationFetcher: mockChainService,
BlockRewardFetcher: &BlockRewardService{
Replayer: mockstategen.NewReplayerBuilder(mockstategen.WithMockState(st)),
DB: db,
},
}
url := "http://only.the.slot.number.at.the.end.is.important/2"
request := httptest.NewRequest("GET", url, nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.BlockRewards(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.BlockRewardsResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
assert.Equal(t, "4", resp.Data.ProposerIndex)
assert.Equal(t, "15714490", resp.Data.Total)
assert.Equal(t, "89442", resp.Data.Attestations)
assert.Equal(t, "48", resp.Data.SyncAggregate)
assert.Equal(t, "7812500", resp.Data.AttesterSlashings)
assert.Equal(t, "7812500", resp.Data.ProposerSlashings)
assert.Equal(t, true, resp.ExecutionOptimistic)
assert.Equal(t, false, resp.Finalized)
})
}
func TestAttestationRewards(t *testing.T) {

View File

@@ -1,6 +1,7 @@
package beacon
import (
"os"
"testing"
"github.com/prysmaticlabs/prysm/v5/cmd/beacon-chain/flags"
@@ -21,5 +22,5 @@ func TestMain(m *testing.M) {
flags.Init(resetFlags)
}()
m.Run()
os.Exit(m.Run())
}

View File

@@ -63,6 +63,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync:go_default_library",
"//beacon-chain/sync/rlnc:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",

View File

@@ -97,7 +97,6 @@ func (vs *Server) ProposeAttestationElectra(ctx context.Context, singleAtt *ethp
}
} else {
go func() {
ctx = trace.NewContext(context.Background(), trace.FromContext(ctx))
if err := vs.AttPool.SaveUnaggregatedAttestation(att); err != nil {
log.WithError(err).Error("Could not save unaggregated attestation")
return

View File

@@ -22,6 +22,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/kv"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
@@ -269,6 +271,60 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
return vs.constructGenericBeaconBlock(sBlk, bundle, winningBid)
}
// ProposeChunkedBlock handles the proposal of chunked beacon blocks
func (vs *Server) ProposeChunkedBlock(ctx context.Context, req *ethpb.ChunkedBeaconBlock) (*ethpb.ProposeResponse, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.ProposeChunkedBlock")
defer span.End()
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
block, err := blocks.NewSignedBeaconBlock(req.Block.Block)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "%s: %v", "decode block failed", err)
}
var sidecars []*ethpb.BlobSidecar
if block.IsBlinded() {
block, sidecars, err = vs.handleBlindedBlock(ctx, block)
} else if block.Version() >= version.Deneb {
sidecars, err = vs.blobSidecarsFromUnblindedBlock(block, req.Block)
}
if err != nil {
return nil, status.Errorf(codes.Internal, "%s: %v", "handle block failed", err)
}
root, err := block.Block().HashTreeRoot()
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not hash tree root: %v", err)
}
var wg sync.WaitGroup
errChan := make(chan error, 1)
wg.Add(1)
go func() {
defer wg.Done()
if err := vs.broadcastReceiveChunkedBlock(ctx, req, root); err != nil {
errChan <- errors.Wrap(err, "broadcast/receive block failed")
return
}
errChan <- nil
}()
if err := vs.broadcastAndReceiveBlobs(ctx, sidecars, root); err != nil {
return nil, status.Errorf(codes.Internal, "Could not broadcast/receive blobs: %v", err)
}
wg.Wait()
if err := <-errChan; err != nil {
return nil, status.Errorf(codes.Internal, "Could not broadcast/receive block: %v", err)
}
return &ethpb.ProposeResponse{BlockRoot: root[:]}, nil
}
// ProposeBeaconBlock handles the proposal of beacon blocks.
func (vs *Server) ProposeBeaconBlock(ctx context.Context, req *ethpb.GenericSignedBeaconBlock) (*ethpb.ProposeResponse, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.ProposeBeaconBlock")
@@ -362,12 +418,61 @@ func (vs *Server) blobSidecarsFromUnblindedBlock(block interfaces.SignedBeaconBl
return BuildBlobSidecars(block, rawBlobs, proofs)
}
// broadcastReceiveChunkedBlock broadcasts a chunked block and handles its reception.
func (vs *Server) broadcastReceiveChunkedBlock(ctx context.Context, req *ethpb.ChunkedBeaconBlock, root [32]byte) error {
block, err := blocks.NewSignedBeaconBlock(req.Block.Block)
if err != nil {
return errors.Wrap(err, "block construction failed")
}
messages, err := vs.constructChunkMessages(req)
if err != nil {
return errors.Wrap(err, "could not construct messages")
}
if err := slots.WaitUntil(ctx, vs.TimeFetcher.GenesisTime(), block.Block().Slot(), features.Get().DelayBlockBroadcast); err != nil {
return errors.Wrap(err, "could not wait until broadcast time")
}
if err := vs.P2P.BroadcastBlockChunks(ctx, messages); err != nil {
return errors.Wrap(err, "broadcast failed")
}
vs.BlockNotifier.BlockFeed().Send(&feed.Event{
Type: blockfeed.ReceivedBlock,
Data: &blockfeed.ReceivedBlockData{SignedBlock: block},
})
return vs.BlockReceiver.ReceiveBlock(ctx, block, root, nil)
}
func (s *Server) constructChunkMessages(cBlk *ethpb.ChunkedBeaconBlock) ([]*ethpb.BeaconBlockChunk, error) {
node, err := rlnc.NewNodeFromChunkedBlock(s.ChunkCommitter, cBlk)
if err != nil {
return nil, errors.Wrap(err, "could not construct node")
}
meshSize := features.Get().RLNCMeshSize
multipleMessages := make([]*ethpb.BeaconBlockChunk, 0, meshSize)
for i := uint(0); i < meshSize; i++ {
msg, err := node.PrepareMessage()
if err != nil {
return nil, errors.Wrap(err, "could not prepare message")
}
chunk := &ethpb.BeaconBlockChunk{
Data: msg.Data(),
Coefficients: msg.Coefficients(),
Header: cBlk.Header,
Signature: cBlk.Signature,
}
multipleMessages = append(multipleMessages, chunk)
}
return multipleMessages, nil
}
// broadcastReceiveBlock broadcasts a block and handles its reception.
func (vs *Server) broadcastReceiveBlock(ctx context.Context, block interfaces.SignedBeaconBlock, root [32]byte) error {
protoBlock, err := block.Proto()
if err != nil {
return errors.Wrap(err, "protobuf conversion failed")
}
if err := slots.WaitUntil(ctx, vs.TimeFetcher.GenesisTime(), block.Block().Slot(), features.Get().DelayBlockBroadcast); err != nil {
return errors.Wrap(err, "could not wait until broadcast time")
}
if err := vs.P2P.Broadcast(ctx, protoBlock); err != nil {
return errors.Wrap(err, "broadcast failed")
}

View File

@@ -72,19 +72,11 @@ func setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, loc
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
}
var builderKzgCommitments [][]byte
builderPayload, err := bid.Header()
if err != nil {
log.WithError(err).Warn("Proposer: failed to retrieve header from BuilderBid")
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
}
//TODO: add builder execution requests here.
if bid.Version() >= version.Deneb {
builderKzgCommitments, err = bid.BlobKzgCommitments()
if err != nil {
log.WithError(err).Warn("Proposer: failed to retrieve kzg commitments from BuilderBid")
}
}
switch {
case blk.Version() >= version.Capella:
@@ -136,7 +128,28 @@ func setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, loc
// If we can't get the builder value, just use local block.
if higherValueBuilder && withdrawalsMatched { // Builder value is higher and withdrawals match.
if err := setBuilderExecution(blk, builderPayload, builderKzgCommitments); err != nil {
var builderKzgCommitments [][]byte
if bid.Version() >= version.Deneb {
bidDeneb, ok := bid.(builder.BidDeneb)
if !ok {
log.Warnf("bid type %T does not implement builder.BidDeneb", bid)
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
} else {
builderKzgCommitments = bidDeneb.BlobKzgCommitments()
}
}
var executionRequests *enginev1.ExecutionRequests
if bid.Version() >= version.Electra {
bidElectra, ok := bid.(builder.BidElectra)
if !ok {
log.Warnf("bid type %T does not implement builder.BidElectra", bid)
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
} else {
executionRequests = bidElectra.ExecutionRequests()
}
}
if err := setBuilderExecution(blk, builderPayload, builderKzgCommitments, executionRequests); err != nil {
log.WithError(err).Warn("Proposer: failed to set builder payload")
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
} else {
@@ -160,7 +173,7 @@ func setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, loc
)
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
default: // Bellatrix case.
if err := setBuilderExecution(blk, builderPayload, builderKzgCommitments); err != nil {
if err := setBuilderExecution(blk, builderPayload, nil, nil); err != nil {
log.WithError(err).Warn("Proposer: failed to set builder payload")
return local.Bid, local.BlobsBundle, setLocalExecution(blk, local)
} else {
@@ -270,23 +283,22 @@ func (vs *Server) getPayloadHeaderFromBuilder(
return nil, errors.Wrap(err, "could not validate builder signature")
}
maxBlobsPerBlock := params.BeaconConfig().MaxBlobsPerBlock(slot)
var kzgCommitments [][]byte
if bid.Version() >= version.Deneb {
kzgCommitments, err = bid.BlobKzgCommitments()
if err != nil {
return nil, errors.Wrap(err, "could not get blob kzg commitments")
}
if len(kzgCommitments) > maxBlobsPerBlock {
return nil, fmt.Errorf("builder returned too many kzg commitments: %d", len(kzgCommitments))
}
for _, c := range kzgCommitments {
if len(c) != fieldparams.BLSPubkeyLength {
return nil, fmt.Errorf("builder returned invalid kzg commitment length: %d", len(c))
}
dBid, ok := bid.(builder.BidDeneb)
if !ok {
return nil, fmt.Errorf("bid type %T does not implement builder.BidDeneb", dBid)
}
kzgCommitments = dBid.BlobKzgCommitments()
}
var executionRequests *enginev1.ExecutionRequests
if bid.Version() >= version.Electra {
eBid, ok := bid.(builder.BidElectra)
if !ok {
return nil, fmt.Errorf("bid type %T does not implement builder.BidElectra", eBid)
}
executionRequests = eBid.ExecutionRequests()
}
l := log.WithFields(logrus.Fields{
"gweiValue": primitives.WeiToGwei(v),
"builderPubKey": fmt.Sprintf("%#x", bid.Pubkey()),
@@ -298,6 +310,11 @@ func (vs *Server) getPayloadHeaderFromBuilder(
if len(kzgCommitments) > 0 {
l = l.WithField("kzgCommitmentCount", len(kzgCommitments))
}
if executionRequests != nil {
l = l.WithField("depositRequestCount", len(executionRequests.Deposits))
l = l.WithField("withdrawalRequestCount", len(executionRequests.Withdrawals))
l = l.WithField("consolidationRequestCount", len(executionRequests.Consolidations))
}
l.Info("Received header with bid")
span.SetAttributes(
@@ -366,20 +383,18 @@ func setLocalExecution(blk interfaces.SignedBeaconBlock, local *blocks.GetPayloa
return errors.Wrap(err, "could not set execution requests")
}
}
return setExecution(blk, local.ExecutionData, false, kzgCommitments)
return setExecution(blk, local.ExecutionData, false, kzgCommitments, local.ExecutionRequests)
}
// setBuilderExecution sets the execution context for a builder's beacon block.
// It delegates to setExecution for the actual work.
func setBuilderExecution(blk interfaces.SignedBeaconBlock, execution interfaces.ExecutionData, builderKzgCommitments [][]byte) error {
// TODO #14344: add execution requests for electra
return setExecution(blk, execution, true, builderKzgCommitments)
func setBuilderExecution(blk interfaces.SignedBeaconBlock, execution interfaces.ExecutionData, builderKzgCommitments [][]byte, requests *enginev1.ExecutionRequests) error {
return setExecution(blk, execution, true, builderKzgCommitments, requests)
}
// setExecution sets the execution context for a beacon block. It also sets KZG commitments based on the block version.
// The function is designed to be flexible and handle both local and builder executions.
func setExecution(blk interfaces.SignedBeaconBlock, execution interfaces.ExecutionData, isBlinded bool, kzgCommitments [][]byte) error {
func setExecution(blk interfaces.SignedBeaconBlock, execution interfaces.ExecutionData, isBlinded bool, kzgCommitments [][]byte, requests *enginev1.ExecutionRequests) error {
if execution == nil {
return errors.New("execution is nil")
}
@@ -399,14 +414,27 @@ func setExecution(blk interfaces.SignedBeaconBlock, execution interfaces.Executi
}
// Set the KZG commitments for the block
errMessage = "failed to set local kzg commitments"
kzgErr := "failed to set local kzg commitments"
if isBlinded {
errMessage = "failed to set builder kzg commitments"
kzgErr = "failed to set builder kzg commitments"
}
if err := blk.SetBlobKzgCommitments(kzgCommitments); err != nil {
return errors.Wrap(err, errMessage)
return errors.Wrap(err, kzgErr)
}
// If the block version is below Electra, no further actions are needed
if blk.Version() < version.Electra {
return nil
}
// Set the execution requests
requestsErr := "failed to set local execution requests"
if isBlinded {
requestsErr = "failed to set builder execution requests"
}
if err := blk.SetExecutionRequests(requests); err != nil {
return errors.Wrap(err, requestsErr)
}
return nil
}

View File

@@ -28,7 +28,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/time/slots"
@@ -173,11 +172,6 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
_, err = builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -250,11 +244,6 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
_, err = builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -326,11 +315,6 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
_, err = builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, math.MaxUint64)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -402,11 +386,6 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
_, err = builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, 0)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -428,11 +407,6 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
_, err = builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -460,11 +434,6 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
_, err = builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -493,13 +462,8 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
builderBid, err := vs.getBuilderPayloadAndBlobs(ctx, b.Slot(), b.ProposerIndex(), gasLimit)
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
if builderBid.Version() >= version.Deneb {
require.NoError(t, err)
}
_, err = builderBid.Header()
require.NoError(t, err)
require.DeepEqual(t, [][]uint8{}, builderKzgCommitments)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
require.NoError(t, err)
require.IsNil(t, bundle)
@@ -648,8 +612,9 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
builderPayload, err := builderBid.Header()
require.NoError(t, err)
builderKzgCommitments, err := builderBid.BlobKzgCommitments()
require.NoError(t, err)
dbid, ok := builderBid.(builder.BidDeneb)
require.Equal(t, true, ok)
builderKzgCommitments := dbid.BlobKzgCommitments()
require.DeepEqual(t, bid.BlobKzgCommitments, builderKzgCommitments)
require.Equal(t, bid.Header.BlockNumber, builderPayload.BlockNumber()) // header should be the same from block
@@ -663,6 +628,134 @@ func TestServer_setExecutionData(t *testing.T) {
require.NoError(t, err)
require.DeepEqual(t, bid.BlobKzgCommitments, got)
})
t.Run("Can get builder payload, blobs, and execution requests Electra", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.ElectraForkEpoch = 0
params.OverrideBeaconConfig(cfg)
params.SetupTestConfigCleanup(t)
blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockElectra())
require.NoError(t, err)
ti, err := slots.ToTime(uint64(time.Now().Unix()), 0)
require.NoError(t, err)
sk, err := bls.RandKey()
require.NoError(t, err)
wr, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
require.NoError(t, err)
builderValue := bytesutil.ReverseByteOrder(big.NewInt(1e9).Bytes())
requests := &v1.ExecutionRequests{
Deposits: []*v1.DepositRequest{
{
Pubkey: bytesutil.PadTo([]byte{byte('a')}, fieldparams.BLSPubkeyLength),
WithdrawalCredentials: bytesutil.PadTo([]byte{byte('b')}, fieldparams.RootLength),
Amount: params.BeaconConfig().MinActivationBalance,
Signature: bytesutil.PadTo([]byte{byte('c')}, fieldparams.BLSSignatureLength),
Index: 0,
},
},
Withdrawals: []*v1.WithdrawalRequest{
{
SourceAddress: bytesutil.PadTo([]byte{byte('d')}, common.AddressLength),
ValidatorPubkey: bytesutil.PadTo([]byte{byte('e')}, fieldparams.BLSPubkeyLength),
Amount: params.BeaconConfig().MinActivationBalance,
},
},
Consolidations: []*v1.ConsolidationRequest{
{
SourceAddress: bytesutil.PadTo([]byte{byte('f')}, common.AddressLength),
SourcePubkey: bytesutil.PadTo([]byte{byte('g')}, fieldparams.BLSPubkeyLength),
TargetPubkey: bytesutil.PadTo([]byte{byte('h')}, fieldparams.BLSPubkeyLength),
},
},
}
bid := &ethpb.BuilderBidElectra{
Header: &v1.ExecutionPayloadHeaderDeneb{
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
TransactionsRoot: bytesutil.PadTo([]byte{1}, fieldparams.RootLength),
ParentHash: params.BeaconConfig().ZeroHash[:],
Timestamp: uint64(ti.Unix()),
BlockNumber: 2,
WithdrawalsRoot: wr[:],
BlobGasUsed: 123,
ExcessBlobGas: 456,
GasLimit: gasLimit,
},
Pubkey: sk.PublicKey().Marshal(),
Value: bytesutil.PadTo(builderValue, 32),
BlobKzgCommitments: [][]byte{bytesutil.PadTo([]byte{2}, fieldparams.BLSPubkeyLength), bytesutil.PadTo([]byte{5}, fieldparams.BLSPubkeyLength)},
ExecutionRequests: requests,
}
d := params.BeaconConfig().DomainApplicationBuilder
domain, err := signing.ComputeDomain(d, nil, nil)
require.NoError(t, err)
sr, err := signing.ComputeSigningRoot(bid, domain)
require.NoError(t, err)
sBid := &ethpb.SignedBuilderBidElectra{
Message: bid,
Signature: sk.Sign(sr[:]).Marshal(),
}
vs.BlockBuilder = &builderTest.MockBuilderService{
BidElectra: sBid,
HasConfigured: true,
Cfg: &builderTest.Config{BeaconDB: beaconDB},
}
require.NoError(t, beaconDB.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{blk.Block().ProposerIndex()},
[]*ethpb.ValidatorRegistrationV1{{
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
Timestamp: uint64(time.Now().Unix()),
GasLimit: gasLimit,
Pubkey: make([]byte, fieldparams.BLSPubkeyLength)}}))
wb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockElectra())
require.NoError(t, err)
chain := &blockchainTest.ChainService{ForkChoiceStore: doublylinkedtree.New(), Genesis: time.Now(), Block: wb}
vs.ForkFetcher = chain
vs.ForkchoiceFetcher.SetForkChoiceGenesisTime(uint64(time.Now().Unix()))
vs.TimeFetcher = chain
vs.HeadFetcher = chain
ed, err := blocks.NewWrappedExecutionData(&v1.ExecutionPayloadDeneb{BlockNumber: 4, Withdrawals: withdrawals})
require.NoError(t, err)
vs.ExecutionEngineCaller = &powtesting.EngineClient{
PayloadIDBytes: id,
GetPayloadResponse: &blocks.GetPayloadResponse{ExecutionData: ed},
}
require.NoError(t, err)
blk.SetSlot(0)
require.NoError(t, err)
builderBid, err := vs.getBuilderPayloadAndBlobs(ctx, blk.Block().Slot(), blk.Block().ProposerIndex(), gasLimit)
require.NoError(t, err)
builderPayload, err := builderBid.Header()
require.NoError(t, err)
eBid, ok := builderBid.(builder.BidElectra)
require.Equal(t, true, ok)
require.DeepEqual(t, bid.BlobKzgCommitments, eBid.BlobKzgCommitments())
require.DeepEqual(t, bid.ExecutionRequests, eBid.ExecutionRequests())
require.Equal(t, bid.Header.BlockNumber, builderPayload.BlockNumber()) // header should be the same from block
res, err := vs.getLocalPayload(ctx, blk.Block(), denebTransitionState)
require.NoError(t, err)
_, bundle, err := setExecutionData(context.Background(), blk, res, builderBid, defaultBuilderBoostFactor)
require.NoError(t, err)
require.IsNil(t, bundle)
got, err := blk.Block().Body().BlobKzgCommitments()
require.NoError(t, err)
require.DeepEqual(t, bid.BlobKzgCommitments, got)
gRequests, err := blk.Block().Body().ExecutionRequests()
require.NoError(t, err)
require.DeepEqual(t, bid.ExecutionRequests, gRequests)
})
}
func TestServer_getPayloadHeader(t *testing.T) {

View File

@@ -94,6 +94,14 @@ func (vs *Server) deposits(
return nil, err
}
// In the post-electra phase, this function will usually return an empty list,
// as the legacy deposit process is deprecated. (EIP-6110)
// NOTE: During the transition period, the legacy deposit process
// may still be active and managed. This function handles that scenario.
if !isLegacyDepositProcessPeriod(beaconState, canonicalEth1Data) {
return []*ethpb.Deposit{}, nil
}
_, genesisEth1Block := vs.Eth1InfoFetcher.GenesisExecutionChainInfo()
if genesisEth1Block.Cmp(canonicalEth1DataHeight) == 0 {
return []*ethpb.Deposit{}, nil
@@ -277,3 +285,21 @@ func shouldRebuildTrie(totalDepCount, unFinalizedDeps uint64) bool {
unFinalizedCompute := unFinalizedDeps * params.BeaconConfig().DepositContractTreeDepth
return unFinalizedCompute > totalDepCount
}
// isLegacyDepositProcessPeriod determines if the current state should use the legacy deposit process.
func isLegacyDepositProcessPeriod(beaconState state.BeaconState, canonicalEth1Data *ethpb.Eth1Data) bool {
// Before the Electra upgrade, always use the legacy deposit process.
if beaconState.Version() < version.Electra {
return true
}
// Handle the transition period between the legacy and the new deposit process.
requestsStartIndex, err := beaconState.DepositRequestsStartIndex()
if err != nil {
// If we can't get the deposit requests start index,
// we should default to the legacy deposit process.
return true
}
eth1DepositIndexLimit := math.Min(canonicalEth1Data.DepositCount, requestsStartIndex)
return beaconState.Eth1DepositIndex() < eth1DepositIndexLimit
}

View File

@@ -2,12 +2,14 @@ package validator
import (
"context"
"math"
"math/big"
"testing"
mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache/depositsnapshot"
mockExecution "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/container/trie"
@@ -212,3 +214,85 @@ func TestProposer_PendingDeposits_Electra(t *testing.T) {
assert.Equal(t, 0, len(deposits), "Received unexpected number of pending deposits")
}
func TestIsLegacyDepositProcessPeriod(t *testing.T) {
tests := []struct {
name string
state state.BeaconState
canonicalEth1Data *ethpb.Eth1Data
want bool
}{
{
name: "pre-electra",
state: func() state.BeaconState {
st, err := state_native.InitializeFromProtoDeneb(&ethpb.BeaconStateDeneb{
Eth1Data: &ethpb.Eth1Data{
BlockHash: []byte("0x0"),
DepositRoot: make([]byte, 32),
DepositCount: 5,
},
Eth1DepositIndex: 1,
})
require.NoError(t, err)
return st
}(),
canonicalEth1Data: &ethpb.Eth1Data{
BlockHash: []byte("0x0"),
DepositRoot: make([]byte, 32),
DepositCount: 5,
},
want: true,
},
{
name: "post-electra, pending deposits from pre-electra",
state: func() state.BeaconState {
st, err := state_native.InitializeFromProtoElectra(&ethpb.BeaconStateElectra{
Eth1Data: &ethpb.Eth1Data{
BlockHash: []byte("0x0"),
DepositRoot: make([]byte, 32),
DepositCount: 5,
},
DepositRequestsStartIndex: math.MaxUint64,
Eth1DepositIndex: 1,
})
require.NoError(t, err)
return st
}(),
canonicalEth1Data: &ethpb.Eth1Data{
BlockHash: []byte("0x0"),
DepositRoot: make([]byte, 32),
DepositCount: 5,
},
want: true,
},
{
name: "post-electra, no pending deposits from pre-alpaca",
state: func() state.BeaconState {
st, err := state_native.InitializeFromProtoElectra(&ethpb.BeaconStateElectra{
Eth1Data: &ethpb.Eth1Data{
BlockHash: []byte("0x0"),
DepositRoot: make([]byte, 32),
DepositCount: 5,
},
DepositRequestsStartIndex: 1,
Eth1DepositIndex: 5,
})
require.NoError(t, err)
return st
}(),
canonicalEth1Data: &ethpb.Eth1Data{
BlockHash: []byte("0x0"),
DepositRoot: make([]byte, 32),
DepositCount: 5,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isLegacyDepositProcessPeriod(tt.state, tt.canonicalEth1Data); got != tt.want {
t.Errorf("isLegacyDepositProcessPeriod() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -661,8 +661,12 @@ func TestServer_GetBeaconBlock_Electra(t *testing.T) {
ed, err := blocks.NewWrappedExecutionData(payload)
require.NoError(t, err)
proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{
PayloadIDBytes: &enginev1.PayloadIDBytes{1},
GetPayloadResponse: &blocks.GetPayloadResponse{ExecutionData: ed},
PayloadIDBytes: &enginev1.PayloadIDBytes{1},
GetPayloadResponse: &blocks.GetPayloadResponse{ExecutionData: ed, ExecutionRequests: &enginev1.ExecutionRequests{
Withdrawals: wr,
Deposits: dr,
Consolidations: cr,
}},
}
randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys)
@@ -786,8 +790,12 @@ func TestServer_GetBeaconBlock_Fulu(t *testing.T) {
ed, err := blocks.NewWrappedExecutionData(payload)
require.NoError(t, err)
proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{
PayloadIDBytes: &enginev1.PayloadIDBytes{1},
GetPayloadResponse: &blocks.GetPayloadResponse{ExecutionData: ed},
PayloadIDBytes: &enginev1.PayloadIDBytes{1},
GetPayloadResponse: &blocks.GetPayloadResponse{ExecutionData: ed, ExecutionRequests: &enginev1.ExecutionRequests{
Withdrawals: wr,
Deposits: dr,
Consolidations: cr,
}},
}
randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys)

View File

@@ -27,6 +27,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/network/forks"
@@ -80,6 +81,7 @@ type Server struct {
ClockWaiter startup.ClockWaiter
CoreService *core.Service
AttestationStateFetcher blockchain.AttestationStateFetcher
ChunkCommitter *rlnc.Committer
}
// WaitForActivation checks if a validator public key exists in the active validator registry of the current

View File

@@ -2,6 +2,7 @@ package validator
import (
"io"
"os"
"testing"
"github.com/prysmaticlabs/prysm/v5/config/params"
@@ -16,5 +17,5 @@ func TestMain(m *testing.M) {
defer params.OverrideBeaconConfig(prevConfig)
params.OverrideBeaconConfig(params.MinimalSpecConfig())
m.Run()
os.Exit(m.Run())
}

View File

@@ -40,6 +40,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
chainSync "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/io/logs"
@@ -121,6 +122,7 @@ type Config struct {
BlobStorage *filesystem.BlobStorage
TrackedValidatorsCache *cache.TrackedValidatorsCache
PayloadIDCache *cache.PayloadIDCache
ChunkCommitter *rlnc.Committer
}
// NewService instantiates a new RPC service instance that will
@@ -250,6 +252,7 @@ func NewService(ctx context.Context, cfg *Config) *Service {
TrackedValidatorsCache: s.cfg.TrackedValidatorsCache,
PayloadIDCache: s.cfg.PayloadIDCache,
AttestationStateFetcher: s.cfg.AttestationReceiver,
ChunkCommitter: s.cfg.ChunkCommitter,
}
s.validatorServer = validatorServer
nodeServer := &nodev1alpha1.Server{

View File

@@ -41,13 +41,13 @@ go_library(
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//time/slots:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_x_exp//maps:go_default_library",
],
)
@@ -83,6 +83,7 @@ go_test(
"//crypto/bls/common:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",

View File

@@ -11,6 +11,7 @@ import (
slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types"
"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/sirupsen/logrus"
)
@@ -232,6 +233,43 @@ func (m *MinSpanChunksSlice) CheckSlashable(
surroundingVotesTotal.Inc()
// Both attestations should have the same type. If not, we convert both to Electra attestations.
unifyAttWrapperVersion(existingAttWrapper, incomingAttWrapper)
postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra
if postElectra {
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"existing attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
incomingAttWrapper.IndexedAttestation,
)
}
slashing := &ethpb.AttesterSlashingElectra{
Attestation_1: existing,
Attestation_2: incoming,
}
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashingElectra{
Attestation_1: incoming,
Attestation_2: existing,
}
}
return slashing, nil
}
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
@@ -328,6 +366,43 @@ func (m *MaxSpanChunksSlice) CheckSlashable(
surroundedVotesTotal.Inc()
// Both attestations should have the same type. If not, we convert the non-Electra attestation into an Electra attestation.
unifyAttWrapperVersion(existingAttWrapper, incomingAttWrapper)
postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra
if postElectra {
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"existing attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
incomingAttWrapper.IndexedAttestation,
)
}
slashing := &ethpb.AttesterSlashingElectra{
Attestation_1: existing,
Attestation_2: incoming,
}
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashingElectra{
Attestation_1: incoming,
Attestation_2: existing,
}
}
return slashing, nil
}
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(

View File

@@ -3,12 +3,14 @@ package slasher
import (
"context"
"math"
"reflect"
"testing"
dbtest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types"
"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/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
@@ -82,6 +84,99 @@ func TestMaxSpanChunksSlice_MaxChunkSpanFrom(t *testing.T) {
func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) {
ctx := context.Background()
for _, v := range []int{version.Phase0, version.Electra} {
t.Run(version.String(v), func(t *testing.T) {
slasherDB := dbtest.SetupSlasherDB(t)
params := &Parameters{
chunkSize: 3,
validatorChunkSize: 2,
historyLength: 3,
}
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(1)
target := primitives.Epoch(2)
att := createAttestationWrapperEmptySig(t, v, source, target, nil, nil)
// A faulty chunk should lead to error.
chunk := &MinSpanChunksSlice{
params: params,
data: []uint16{},
}
_, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att)
require.ErrorContains(t, "could not get min target for validator", err)
// We initialize a proper slice with 2 chunks with chunk size 3, 2 validators, and
// a history length of 3 representing a perfect attesting history.
//
// val0 val1
// { } { }
// [2, 2, 2, 2, 2, 2]
data := []uint16{2, 2, 2, 2, 2, 2}
chunk, err = MinChunkSpansSliceFrom(params, data)
require.NoError(t, err)
// An attestation with source 1 and target 2 should not be slashable
// based on our min chunk for either validator.
slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up we initialize an empty chunks slice and mark an attestation
// with (source 1, target 2) as attested.
chunk = EmptyMinSpanChunksSlice(params)
source = primitives.Epoch(1)
target = primitives.Epoch(2)
att = createAttestationWrapperEmptySig(t, v, source, target, nil, nil)
chunkIndex := uint64(0)
startEpoch := target
currentEpoch := target
_, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target)
require.NoError(t, err)
// Next up, we create a surrounding vote, but it should NOT be slashable
// because we DO NOT have an existing attestation record in our database at the min target epoch.
source = primitives.Epoch(0)
target = primitives.Epoch(3)
surroundingVote := createAttestationWrapperEmptySig(t, v, source, target, nil, nil)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up, we save the old attestation record, then check if the
// surrounding vote is indeed slashable.
attData := att.IndexedAttestation.GetData()
attRecord := createAttestationWrapperEmptySig(t, v, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1})
err = slasherDB.SaveAttestationRecordsForValidators(
ctx,
[]*slashertypes.IndexedAttestationWrapper{attRecord},
)
require.NoError(t, err)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
require.NoError(t, err)
require.Equal(t, false, reflect.ValueOf(slashing).IsNil())
// We check the attestation with the lower data root is the first attestation.
// Firstly we require the setup to have the surrounding vote as the second attestation.
// Then we modify the root of the surrounding vote and expect the vote to be the first attestation.
require.DeepEqual(t, surroundingVote.IndexedAttestation, slashing.SecondAttestation())
surroundingVote.DataRoot = [32]byte{}
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
require.NoError(t, err)
require.Equal(t, false, reflect.ValueOf(slashing).IsNil())
assert.DeepEqual(t, surroundingVote.IndexedAttestation, slashing.FirstAttestation())
})
}
}
func TestMinSpanChunksSlice_CheckSlashable_DifferentVersions(t *testing.T) {
ctx := context.Background()
slasherDB := dbtest.SetupSlasherDB(t)
params := &Parameters{
chunkSize: 3,
@@ -91,75 +186,138 @@ func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) {
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(1)
target := primitives.Epoch(2)
att := createAttestationWrapperEmptySig(t, source, target, nil, nil)
// A faulty chunk should lead to error.
chunk := &MinSpanChunksSlice{
params: params,
data: []uint16{},
}
_, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att)
require.ErrorContains(t, "could not get min target for validator", err)
// We create a vote with Phase0 version.
att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)
// We initialize a proper slice with 2 chunks with chunk size 3, 2 validators, and
// a history length of 3 representing a perfect attesting history.
//
// val0 val1
// { } { }
// [2, 2, 2, 2, 2, 2]
data := []uint16{2, 2, 2, 2, 2, 2}
chunk, err = MinChunkSpansSliceFrom(params, data)
require.NoError(t, err)
// An attestation with source 1 and target 2 should not be slashable
// based on our min chunk for either validator.
slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up we initialize an empty chunks slice and mark an attestation
// with (source 1, target 2) as attested.
chunk = EmptyMinSpanChunksSlice(params)
source = primitives.Epoch(1)
target = primitives.Epoch(2)
att = createAttestationWrapperEmptySig(t, source, target, nil, nil)
// We initialize an empty chunks slice and mark an attestation with (source 1, target 2) as attested.
chunk := EmptyMinSpanChunksSlice(params)
chunkIndex := uint64(0)
startEpoch := target
currentEpoch := target
_, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target)
_, err := chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target)
require.NoError(t, err)
// Next up, we create a surrounding vote, but it should NOT be slashable
// because we DO NOT have an existing attestation record in our database at the min target epoch.
// We create a surrounding vote with Electra version.
source = primitives.Epoch(0)
target = primitives.Epoch(3)
surroundingVote := createAttestationWrapperEmptySig(t, source, target, nil, nil)
surroundingVote := createAttestationWrapperEmptySig(t, version.Electra, source, target, nil, nil)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up, we save the old attestation record, then check if the
// surrounding vote is indeed slashable.
// We save the old attestation record, then check if the surrounding vote is indeed slashable.
attData := att.IndexedAttestation.GetData()
attRecord := createAttestationWrapperEmptySig(t, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1})
attRecord := createAttestationWrapperEmptySig(t, version.Phase0, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1})
err = slasherDB.SaveAttestationRecordsForValidators(
ctx,
[]*slashertypes.IndexedAttestationWrapper{attRecord},
)
require.NoError(t, err)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
require.NoError(t, err)
require.NotEqual(t, (*ethpb.AttesterSlashing)(nil), slashing)
// The old record should be converted to Electra and the resulting slashing should be an Electra slashing.
electraSlashing, ok := slashing.(*ethpb.AttesterSlashingElectra)
require.Equal(t, true, ok, "slashing has the wrong type")
assert.NotNil(t, electraSlashing)
}
func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
ctx := context.Background()
for _, v := range []int{version.Phase0, version.Electra} {
t.Run(version.String(v), func(t *testing.T) {
slasherDB := dbtest.SetupSlasherDB(t)
params := &Parameters{
chunkSize: 4,
validatorChunkSize: 2,
historyLength: 4,
}
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(1)
target := primitives.Epoch(2)
att := createAttestationWrapperEmptySig(t, v, source, target, nil, nil)
// A faulty chunk should lead to error.
chunk := &MaxSpanChunksSlice{
params: params,
data: []uint16{},
}
_, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att)
require.ErrorContains(t, "could not get max target for validator", err)
// We initialize a proper slice with 2 chunks with chunk size 4, 2 validators, and
// a history length of 4 representing a perfect attesting history.
//
// val0 val1
// { } { }
// [0, 0, 0, 0, 0, 0, 0, 0]
data := []uint16{0, 0, 0, 0, 0, 0, 0, 0}
chunk, err = MaxChunkSpansSliceFrom(params, data)
require.NoError(t, err)
// An attestation with source 1 and target 2 should not be slashable
// based on our max chunk for either validator.
slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up we initialize an empty chunks slice and mark an attestation
// with (source 0, target 3) as attested.
chunk = EmptyMaxSpanChunksSlice(params)
source = primitives.Epoch(0)
target = primitives.Epoch(3)
att = createAttestationWrapperEmptySig(t, v, source, target, nil, nil)
chunkIndex := uint64(0)
startEpoch := source
currentEpoch := target
_, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target)
require.NoError(t, err)
// Next up, we create a surrounded vote, but it should NOT be slashable
// because we DO NOT have an existing attestation record in our database at the max target epoch.
source = primitives.Epoch(1)
target = primitives.Epoch(2)
surroundedVote := createAttestationWrapperEmptySig(t, v, source, target, nil, nil)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up, we save the old attestation record, then check if the
// surroundedVote vote is indeed slashable.
attData := att.IndexedAttestation.GetData()
signingRoot := [32]byte{1}
attRecord := createAttestationWrapperEmptySig(
t, v, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, signingRoot[:],
)
err = slasherDB.SaveAttestationRecordsForValidators(
ctx,
[]*slashertypes.IndexedAttestationWrapper{attRecord},
)
require.NoError(t, err)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
require.NoError(t, err)
require.Equal(t, false, reflect.ValueOf(slashing).IsNil())
// We check the attestation with the lower data root is the first attestation.
// Firstly we require the setup to have the surrounded vote as the second attestation.
// Then we modify the root of the surrounded vote and expect the vote to be the first attestation.
require.DeepEqual(t, surroundedVote.IndexedAttestation, slashing.SecondAttestation())
surroundedVote.DataRoot = [32]byte{}
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
require.NoError(t, err)
require.Equal(t, false, reflect.ValueOf(slashing).IsNil())
assert.DeepEqual(t, surroundedVote.IndexedAttestation, slashing.FirstAttestation())
})
}
}
func TestMaxSpanChunksSlice_CheckSlashable_DifferentVersions(t *testing.T) {
ctx := context.Background()
slasherDB := dbtest.SetupSlasherDB(t)
params := &Parameters{
chunkSize: 4,
@@ -167,76 +325,38 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
historyLength: 4,
}
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(1)
target := primitives.Epoch(2)
att := createAttestationWrapperEmptySig(t, source, target, nil, nil)
source := primitives.Epoch(0)
target := primitives.Epoch(3)
// A faulty chunk should lead to error.
chunk := &MaxSpanChunksSlice{
params: params,
data: []uint16{},
}
_, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att)
require.ErrorContains(t, "could not get max target for validator", err)
// We create a vote with Phase0 version.
att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)
// We initialize a proper slice with 2 chunks with chunk size 4, 2 validators, and
// a history length of 4 representing a perfect attesting history.
//
// val0 val1
// { } { }
// [0, 0, 0, 0, 0, 0, 0, 0]
data := []uint16{0, 0, 0, 0, 0, 0, 0, 0}
chunk, err = MaxChunkSpansSliceFrom(params, data)
require.NoError(t, err)
// An attestation with source 1 and target 2 should not be slashable
// based on our max chunk for either validator.
slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up we initialize an empty chunks slice and mark an attestation
// with (source 0, target 3) as attested.
chunk = EmptyMaxSpanChunksSlice(params)
source = primitives.Epoch(0)
target = primitives.Epoch(3)
att = createAttestationWrapperEmptySig(t, source, target, nil, nil)
// We initialize an empty chunks slice and mark an attestation with (source 0, target 3) as attested.
chunk := EmptyMaxSpanChunksSlice(params)
chunkIndex := uint64(0)
startEpoch := source
currentEpoch := target
_, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target)
_, err := chunk.Update(chunkIndex, target, validatorIdx, source, target)
require.NoError(t, err)
// Next up, we create a surrounded vote, but it should NOT be slashable
// because we DO NOT have an existing attestation record in our database at the max target epoch.
// We create a surrounded vote with Electra version.
source = primitives.Epoch(1)
target = primitives.Epoch(2)
surroundedVote := createAttestationWrapperEmptySig(t, source, target, nil, nil)
surroundedVote := createAttestationWrapperEmptySig(t, version.Electra, source, target, nil, nil)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
require.NoError(t, err)
require.Equal(t, nil, slashing)
// Next up, we save the old attestation record, then check if the
// surroundedVote vote is indeed slashable.
// We save the old attestation record, then check if the surrounded vote is indeed slashable.
attData := att.IndexedAttestation.GetData()
signingRoot := [32]byte{1}
attRecord := createAttestationWrapperEmptySig(
t, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, signingRoot[:],
)
attRecord := createAttestationWrapperEmptySig(t, version.Phase0, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1})
err = slasherDB.SaveAttestationRecordsForValidators(
ctx,
[]*slashertypes.IndexedAttestationWrapper{attRecord},
)
require.NoError(t, err)
slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
require.NoError(t, err)
require.NotEqual(t, (*ethpb.AttesterSlashing)(nil), slashing)
// The old record should be converted to Electra and the resulting slashing should be an Electra slashing.
electraSlashing, ok := slashing.(*ethpb.AttesterSlashingElectra)
require.Equal(t, true, ok, "slashing has wrong type")
assert.NotNil(t, electraSlashing)
}
func TestMinSpanChunksSlice_Update_MultipleChunks(t *testing.T) {

View File

@@ -4,6 +4,8 @@ import (
"bytes"
"context"
"fmt"
"maps"
"slices"
"github.com/pkg/errors"
slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types"
@@ -11,7 +13,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"golang.org/x/exp/maps"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
// Takes in a list of indexed attestation wrappers and returns any
@@ -193,33 +195,69 @@ func (s *Service) checkDoubleVotes(
// This is a double vote.
doubleVotesTotal.Inc()
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"existing attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
incomingAttWrapper.IndexedAttestation,
)
}
var slashing ethpb.AttSlashing
slashing := &ethpb.AttesterSlashing{
Attestation_1: existing,
Attestation_2: incoming,
}
// Both attestations should have the same type. If not, we convert both to Electra attestations.
unifyAttWrapperVersion(existingAttWrapper, incomingAttWrapper)
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 {
postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra
if postElectra {
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"existing attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
incomingAttWrapper.IndexedAttestation,
)
}
slashing = &ethpb.AttesterSlashingElectra{
Attestation_1: existing,
Attestation_2: incoming,
}
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashingElectra{
Attestation_1: incoming,
Attestation_2: existing,
}
}
} else {
existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"existing attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
incomingAttWrapper.IndexedAttestation,
)
}
slashing = &ethpb.AttesterSlashing{
Attestation_1: incoming,
Attestation_2: existing,
Attestation_1: existing,
Attestation_2: incoming,
}
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashing{
Attestation_1: incoming,
Attestation_2: existing,
}
}
}
@@ -245,33 +283,69 @@ func (s *Service) checkDoubleVotes(
wrapper_1 := doubleVote.Wrapper_1
wrapper_2 := doubleVote.Wrapper_2
att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"first attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
wrapper_1.IndexedAttestation,
)
}
att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"second attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
wrapper_2.IndexedAttestation,
)
}
var slashing ethpb.AttSlashing
slashing := &ethpb.AttesterSlashing{
Attestation_1: att_1,
Attestation_2: att_2,
}
// Both attestations should have the same type. If not, we convert both to Electra attestations.
unifyAttWrapperVersion(wrapper_1, wrapper_2)
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 {
postElectra := wrapper_1.IndexedAttestation.Version() >= version.Electra
if postElectra {
att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"first attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
wrapper_1.IndexedAttestation,
)
}
att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"second attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
wrapper_2.IndexedAttestation,
)
}
slashing = &ethpb.AttesterSlashingElectra{
Attestation_1: att_1,
Attestation_2: att_2,
}
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashingElectra{
Attestation_1: att_2,
Attestation_2: att_1,
}
}
} else {
att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"first attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
wrapper_1.IndexedAttestation,
)
}
att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
"second attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestation{},
wrapper_2.IndexedAttestation,
)
}
slashing = &ethpb.AttesterSlashing{
Attestation_1: att_2,
Attestation_2: att_1,
Attestation_1: att_1,
Attestation_2: att_2,
}
// Ensure the attestation with the lower data root is the first attestation.
if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashing{
Attestation_1: att_2,
Attestation_2: att_1,
}
}
}
@@ -319,7 +393,7 @@ func (s *Service) updatedChunkByChunkIndex(
}
// Transform the map of needed chunk indexes to a slice.
neededChunkIndexes := maps.Keys(neededChunkIndexesMap)
neededChunkIndexes := slices.Collect(maps.Keys(neededChunkIndexesMap))
// Retrieve needed chunks from the database.
chunkByChunkIndex, err := s.loadChunksFromDisk(ctx, validatorChunkIndex, chunkKind, neededChunkIndexes)

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/container/slice"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/sirupsen/logrus"
)
@@ -249,3 +250,24 @@ func closeDB(d *slasherkv.Store) {
log.WithError(err).Error("could not close database")
}
}
// unifyAttWrapperVersion ensures that the two wrappers wrap indexed attestations of the same version.
// If versions differ, the wrapped attestation with the lower version will be converted to the higher version.
func unifyAttWrapperVersion(w1 *slashertypes.IndexedAttestationWrapper, w2 *slashertypes.IndexedAttestationWrapper) {
if w1.IndexedAttestation.Version() == w2.IndexedAttestation.Version() {
return
}
if w1.IndexedAttestation.Version() != version.Electra {
w1.IndexedAttestation = &ethpb.IndexedAttestationElectra{
AttestingIndices: w1.IndexedAttestation.GetAttestingIndices(),
Data: w1.IndexedAttestation.GetData(),
Signature: w1.IndexedAttestation.GetSignature(),
}
return
}
w2.IndexedAttestation = &ethpb.IndexedAttestationElectra{
AttestingIndices: w2.IndexedAttestation.GetAttestingIndices(),
Data: w2.IndexedAttestation.GetData(),
Signature: w2.IndexedAttestation.GetSignature(),
}
}

View File

@@ -9,6 +9,7 @@ import (
"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/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/require"
logTest "github.com/sirupsen/logrus/hooks/test"
)
@@ -32,13 +33,13 @@ func TestService_groupByValidatorChunkIndex(t *testing.T) {
validatorChunkSize: 2,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil),
},
},
},
@@ -48,17 +49,17 @@ func TestService_groupByValidatorChunkIndex(t *testing.T) {
validatorChunkSize: 2,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil),
},
1: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil),
},
2: {
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil),
},
},
},
@@ -95,13 +96,13 @@ func TestService_groupByChunkIndex(t *testing.T) {
historyLength: 3,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil),
},
},
},
@@ -112,17 +113,17 @@ func TestService_groupByChunkIndex(t *testing.T) {
historyLength: 3,
},
atts: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
createAttestationWrapperEmptySig(t, 2, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 2, 0, nil, nil),
},
want: map[uint64][]*slashertypes.IndexedAttestationWrapper{
0: {
createAttestationWrapperEmptySig(t, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, 1, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil),
},
1: {
createAttestationWrapperEmptySig(t, 2, 0, nil, nil),
createAttestationWrapperEmptySig(t, version.Phase0, 2, 0, nil, nil),
},
},
},
@@ -207,7 +208,7 @@ func TestService_filterAttestations(t *testing.T) {
{
name: "Source > target gets dropped",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 1, 0, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 0,
wantedDropped: 1,
@@ -215,33 +216,33 @@ func TestService_filterAttestations(t *testing.T) {
{
name: "Source < target is valid",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 1,
wantedValid: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)),
},
wantedDropped: 0,
},
{
name: "Source == target is valid",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 1,
wantedValid: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{1}, make([]byte, 32)),
},
wantedDropped: 0,
},
{
name: "Attestation from the future is deferred",
input: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{1}, make([]byte, 32)),
},
inputEpoch: 1,
wantedDeferred: []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{1}, make([]byte, 32)),
},
wantedDropped: 0,
},
@@ -271,22 +272,22 @@ func Test_logSlashingEvent(t *testing.T) {
{
name: "Surrounding vote",
slashing: &ethpb.AttesterSlashing{
Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_1: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
},
},
{
name: "Surrounded vote",
slashing: &ethpb.AttesterSlashing{
Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_1: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
},
},
{
name: "Double vote",
slashing: &ethpb.AttesterSlashing{
Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_1: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
Attestation_2: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation),
},
},
}

View File

@@ -5,6 +5,7 @@ import (
slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
@@ -12,8 +13,8 @@ func Test_attestationsQueue(t *testing.T) {
t.Run("push_and_dequeue", func(tt *testing.T) {
attQueue := newAttestationsQueue()
wantedAtts := []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, 1, 2, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, []uint64{1}, make([]byte, 32)),
}
attQueue.push(wantedAtts[0])
attQueue.push(wantedAtts[1])
@@ -27,8 +28,8 @@ func Test_attestationsQueue(t *testing.T) {
t.Run("extend_and_dequeue", func(tt *testing.T) {
attQueue := newAttestationsQueue()
wantedAtts := []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, 1, 2, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, []uint64{1}, make([]byte, 32)),
}
attQueue.extend(wantedAtts)
require.DeepEqual(t, 2, attQueue.size())

View File

@@ -13,6 +13,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
@@ -38,8 +39,8 @@ func TestSlasher_receiveAttestations_OK(t *testing.T) {
}()
firstIndices := []uint64{1, 2, 3}
secondIndices := []uint64{4, 5, 6}
att1 := createAttestationWrapperEmptySig(t, 1, 2, firstIndices, nil)
att2 := createAttestationWrapperEmptySig(t, 1, 2, secondIndices, nil)
att1 := createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, firstIndices, nil)
att2 := createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, secondIndices, nil)
wrappedAtt1 := &slashertypes.WrappedIndexedAtt{IndexedAtt: att1.IndexedAttestation}
wrappedAtt2 := &slashertypes.WrappedIndexedAtt{IndexedAtt: att2.IndexedAttestation}
indexedAttsChan <- wrappedAtt1
@@ -67,14 +68,14 @@ func TestService_pruneSlasherDataWithinSlidingWindow_AttestationsPruned(t *testi
// Setup attestations for 2 validators at each epoch for epochs 0, 1, 2, 3.
err := slasherDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 0, []uint64{0}, bytesutil.PadTo([]byte("0a"), 32)),
createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, bytesutil.PadTo([]byte("0b"), 32)),
createAttestationWrapperEmptySig(t, 0, 1, []uint64{0}, bytesutil.PadTo([]byte("1a"), 32)),
createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, bytesutil.PadTo([]byte("1b"), 32)),
createAttestationWrapperEmptySig(t, 0, 2, []uint64{0}, bytesutil.PadTo([]byte("2a"), 32)),
createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, bytesutil.PadTo([]byte("2b"), 32)),
createAttestationWrapperEmptySig(t, 0, 3, []uint64{0}, bytesutil.PadTo([]byte("3a"), 32)),
createAttestationWrapperEmptySig(t, 0, 3, []uint64{1}, bytesutil.PadTo([]byte("3b"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0}, bytesutil.PadTo([]byte("0a"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{1}, bytesutil.PadTo([]byte("0b"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{0}, bytesutil.PadTo([]byte("1a"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, bytesutil.PadTo([]byte("1b"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{0}, bytesutil.PadTo([]byte("2a"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{1}, bytesutil.PadTo([]byte("2b"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 3, []uint64{0}, bytesutil.PadTo([]byte("3a"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 3, []uint64{1}, bytesutil.PadTo([]byte("3b"), 32)),
})
require.NoError(t, err)
@@ -95,8 +96,8 @@ func TestService_pruneSlasherDataWithinSlidingWindow_AttestationsPruned(t *testi
// Setup attestations for 2 validators at epoch 4.
err = slasherDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{
createAttestationWrapperEmptySig(t, 0, 4, []uint64{0}, bytesutil.PadTo([]byte("4a"), 32)),
createAttestationWrapperEmptySig(t, 0, 4, []uint64{1}, bytesutil.PadTo([]byte("4b"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 4, []uint64{0}, bytesutil.PadTo([]byte("4a"), 32)),
createAttestationWrapperEmptySig(t, version.Phase0, 0, 4, []uint64{1}, bytesutil.PadTo([]byte("4b"), 32)),
})
require.NoError(t, err)
@@ -224,7 +225,7 @@ func TestSlasher_receiveAttestations_OnlyValidAttestations(t *testing.T) {
firstIndices := []uint64{1, 2, 3}
secondIndices := []uint64{4, 5, 6}
// Add a valid attestation.
validAtt := createAttestationWrapperEmptySig(t, 1, 2, firstIndices, nil)
validAtt := createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, firstIndices, nil)
wrappedValidAtt := &slashertypes.WrappedIndexedAtt{IndexedAtt: validAtt.IndexedAttestation}
indexedAttsChan <- wrappedValidAtt
// Send an invalid, bad attestation which will not

View File

@@ -3,6 +3,7 @@ package slasher
import (
"context"
"io"
"os"
"testing"
"time"
@@ -23,7 +24,7 @@ func TestMain(m *testing.M) {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(io.Discard)
m.Run()
os.Exit(m.Run())
}
func TestService_StartStop_ChainInitialized(t *testing.T) {

View File

@@ -119,6 +119,9 @@ type ReadOnlyValidator interface {
Copy() *ethpb.Validator
Slashed() bool
IsNil() bool
HasETH1WithdrawalCredentials() bool
HasCompoundingWithdrawalCredentials() bool
HasExecutionWithdrawalCredentials() bool
}
// ReadOnlyValidators defines a struct which only has read access to validators methods.

View File

@@ -4,6 +4,7 @@ import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
@@ -88,6 +89,27 @@ func (v readOnlyValidator) IsNil() bool {
return v.validator == nil
}
// HasETH1WithdrawalCredentials returns true if the validator has an ETH1 withdrawal credentials.
func (v readOnlyValidator) HasETH1WithdrawalCredentials() bool {
if v.IsNil() {
return false
}
return v.validator.WithdrawalCredentials[0] == params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
}
// HasCompoundingWithdrawalCredentials returns true if the validator has a compounding withdrawal credentials.
func (v readOnlyValidator) HasCompoundingWithdrawalCredentials() bool {
if v.IsNil() {
return false
}
return v.validator.WithdrawalCredentials[0] == params.BeaconConfig().CompoundingWithdrawalPrefixByte
}
// HasExecutionWithdrawalCredentials returns true if the validator has an execution withdrawal credentials.
func (v readOnlyValidator) HasExecutionWithdrawalCredentials() bool {
return v.HasETH1WithdrawalCredentials() || v.HasCompoundingWithdrawalCredentials()
}
// Copy returns a new validator from the read only validator
func (v readOnlyValidator) Copy() *ethpb.Validator {
pubKey := v.PublicKey()

View File

@@ -35,6 +35,7 @@ go_library(
"subscriber_beacon_aggregate_proof.go",
"subscriber_beacon_attestation.go",
"subscriber_beacon_blocks.go",
"subscriber_beacon_blocks_chunks.go",
"subscriber_blob_sidecar.go",
"subscriber_bls_to_execution_change.go",
"subscriber_handlers.go",
@@ -66,6 +67,7 @@ go_library(
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/altair:go_default_library",
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/core/chunks: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",
@@ -92,6 +94,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync/backfill/coverage:go_default_library",
"//beacon-chain/sync/rlnc:go_default_library",
"//beacon-chain/sync/verify:go_default_library",
"//beacon-chain/verification:go_default_library",
"//cache/lru:go_default_library",
@@ -100,6 +103,7 @@ go_library(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/chunks:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/wrapper:go_default_library",

View File

@@ -292,7 +292,7 @@ func TestExtractDataType(t *testing.T) {
return wsb
}(),
wantMd: wrapper.WrappedMetadataV1(&ethpb.MetaDataV1{}),
wantAtt: &ethpb.AttestationElectra{},
wantAtt: &ethpb.SingleAttestation{},
wantAggregate: &ethpb.SignedAggregateAttestationAndProofElectra{},
wantErr: false,
},

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"os"
"sync"
"testing"
"time"
@@ -70,7 +71,7 @@ func TestMain(m *testing.M) {
flags.Init(resetFlags)
}()
m.Run()
os.Exit(m.Run())
}
func initializeTestServices(t *testing.T, slots []primitives.Slot, peers []*peerData) (*mock.ChainService, *p2pt.TestP2P, db.Database) {

View File

@@ -18,6 +18,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/backfill/coverage"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
)
@@ -164,6 +165,14 @@ func WithStateNotifier(n statefeed.Notifier) Option {
}
}
// WithChunkCommitter gives the sync package direct access to the Ristretto trusted setup.
func WithChunkCommitter(c *rlnc.Committer) Option {
return func(s *Service) error {
s.cfg.chunkCommitter = c
return nil
}
}
// WithBlobStorage gives the sync package direct access to BlobStorage.
func WithBlobStorage(b *filesystem.BlobStorage) Option {
return func(s *Service) error {

View File

@@ -0,0 +1,47 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"block_chunk_cache.go",
"committer.go",
"errors.go",
"matrix.go",
"message.go",
"node.go",
"trusted_setup.go",
],
embedsrcs = ["trusted_setup.json"],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/rlnc",
visibility = ["//visibility:public"],
deps = [
"//config/features:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//crypto/rand:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"@com_github_gtank_ristretto255//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"block_chunk_cache_test.go",
"committer_test.go",
"matrix_test.go",
"message_test.go",
"node_test.go",
"trusted_setup_test.go",
],
embed = [":go_default_library"],
deps = [
"//config/features:go_default_library",
"//consensus-types/chunks:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/require:go_default_library",
"@com_github_gtank_ristretto255//:go_default_library",
],
)

View File

@@ -0,0 +1,110 @@
package rlnc
import (
"sync"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
var maxChunkSize = uint(65536) // 2MB for 10 chunks.
type BlockChunkCache struct {
sync.Mutex
committer *Committer
nodes map[primitives.Slot]map[primitives.ValidatorIndex]*Node
}
func NewBlockChunkCache(committer *Committer) *BlockChunkCache {
return &BlockChunkCache{
committer: committer,
nodes: make(map[primitives.Slot]map[primitives.ValidatorIndex]*Node),
}
}
func (b *BlockChunkCache) AddChunk(chunk interfaces.ReadOnlyBeaconBlockChunk) error {
b.Lock()
defer b.Unlock()
m, err := newMessage(chunk)
if err != nil {
return errors.Wrap(err, "failed to create new message")
}
if _, ok := b.nodes[chunk.Slot()]; !ok {
b.nodes[chunk.Slot()] = make(map[primitives.ValidatorIndex]*Node)
} else if n, ok := b.nodes[chunk.Slot()][chunk.ProposerIndex()]; ok {
return n.receive(m)
}
node := NewNode(b.committer, features.Get().RLNCNumChunks)
if err := node.receive(m); err != nil {
return errors.Wrap(err, "failed to receive message")
}
b.nodes[chunk.Slot()][chunk.ProposerIndex()] = node
return ErrSignatureNotVerified
}
// GetBlockData returns the block for the given slot and proposer index if all the chunks are present.
func (b *BlockChunkCache) GetBlockData(slot primitives.Slot, proposerIndex primitives.ValidatorIndex) ([]byte, error) {
b.Lock()
defer b.Unlock()
if _, ok := b.nodes[slot]; !ok {
return nil, ErrNoData
}
if _, ok := b.nodes[slot][proposerIndex]; !ok {
return nil, ErrNoData
}
node := b.nodes[slot][proposerIndex]
return node.decode() // Only error is ErrNoData when the node is not full.
}
// Prune removes all nodes from before the given slot.
func (b *BlockChunkCache) Prune(slot primitives.Slot) {
b.Lock()
defer b.Unlock()
for s := range b.nodes {
if s < slot {
delete(b.nodes, s)
}
}
}
// RemoveNode removes the node that has the given chunk.
func (b *BlockChunkCache) RemoveNode(chunk interfaces.ReadOnlyBeaconBlockChunk) {
b.Lock()
defer b.Unlock()
if _, ok := b.nodes[chunk.Slot()]; !ok {
return
}
delete(b.nodes[chunk.Slot()], chunk.ProposerIndex())
}
// PrepareMessage prepares a message to broadcast after receiving the given chunk.
func (b *BlockChunkCache) PrepareMessage(chunk interfaces.ReadOnlyBeaconBlockChunk) (*ethpb.BeaconBlockChunk, error) {
b.Lock()
defer b.Unlock()
if _, ok := b.nodes[chunk.Slot()]; !ok {
return nil, ErrNoData
}
if _, ok := b.nodes[chunk.Slot()][chunk.ProposerIndex()]; !ok {
return nil, ErrNoData
}
node := b.nodes[chunk.Slot()][chunk.ProposerIndex()]
msg, err := node.PrepareMessage()
if err != nil {
return nil, errors.Wrap(err, "failed to prepare message")
}
signature := chunk.Signature()
return &ethpb.BeaconBlockChunk{
Data: msg.Data(),
Coefficients: msg.Coefficients(),
Header: chunk.Header(),
Signature: signature[:],
}, nil
}

View File

@@ -0,0 +1,77 @@
package rlnc
import (
"crypto/rand"
"testing"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/consensus-types/chunks"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestBlockChunkCache(t *testing.T) {
// Create a new block chunk cache.
committer := newCommitter(10)
cache := NewBlockChunkCache(committer)
require.NotNil(t, cache)
require.Equal(t, 0, len(cache.nodes))
chunkSize := uint(4)
block := make([]byte, features.Get().RLNCNumChunks*chunkSize*31)
_, err := rand.Read(block)
require.NoError(t, err)
node, err := NewSource(committer, features.Get().RLNCNumChunks, block)
require.NoError(t, err)
// Prepare a message
msg, err := node.PrepareMessage()
require.NoError(t, err)
require.NotNil(t, msg)
chunkProto := &ethpb.BeaconBlockChunk{
Data: msg.Data(),
Coefficients: msg.Coefficients(),
Header: &ethpb.BeaconBlockChunkHeader{
Slot: 1,
ProposerIndex: 1,
ParentRoot: make([]byte, 32),
Commitments: msg.Commitments(),
},
Signature: make([]byte, 96),
}
chunk, err := chunks.NewBlockChunk(chunkProto)
require.NoError(t, err)
// Add the chunk to the cache.
require.ErrorIs(t, ErrSignatureNotVerified, cache.AddChunk(chunk))
require.Equal(t, 1, len(cache.nodes))
// Prepare a second message
msg, err = node.PrepareMessage()
require.NoError(t, err)
require.NotNil(t, msg)
chunkProto = &ethpb.BeaconBlockChunk{
Data: msg.Data(),
Coefficients: msg.Coefficients(),
Header: &ethpb.BeaconBlockChunkHeader{
Slot: 1,
ProposerIndex: 1,
ParentRoot: make([]byte, 32),
Commitments: msg.Commitments(),
},
Signature: make([]byte, 96),
}
chunk, err = chunks.NewBlockChunk(chunkProto)
require.NoError(t, err)
// Add the chunk to the cache
require.NoError(t, cache.AddChunk(chunk)) // No error this time as the signature was verified before
require.Equal(t, 1, len(cache.nodes)) // No new node, same block chunk
cachedNode := cache.nodes[1][1]
require.Equal(t, 2, len(cachedNode.chunks))
message, err := cache.PrepareMessage(chunk)
require.NoError(t, err)
require.DeepEqual(t, message.Header.Commitments, chunkProto.Header.Commitments)
}

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