Compare commits

...

19 Commits

Author SHA1 Message Date
potuz
aa06ce5e0e don't panic on underflow 2025-04-25 12:36:25 -03:00
james-prysm
e5d7888e5c Merge branch 'develop' into fix_deadlines 2025-04-25 09:26:03 -05:00
Preston Van Loon
f7eddedd1d update geth v1.15.9 (#15216)
* Update go-ethereum to v1.15.9

* Fix go-ethereum secp256k1 build after https://github.com/ethereum/go-ethereum/pull/31242

* Fix Ping API change

* Changelog fragment
2025-04-25 12:40:19 +00:00
kira
7887ebbc4a Broadcast Proposer Slashing on equivocation (#14693)
* Add equivocation detection logic; broadcast slashing immediately on equivocation

* nit: comments

* move equivocation detection to validateBeaconBlockPubSub

* include broadcasting logic within the helper function

* fix lint

* Add unit tests for equivocation detection

* remove comment that are not required

* Add changelog file

* Add descriptive comment for detectAndBroadcastEquivocation

* use head block instead of block cache for equivocation detection

* add more equivocation unit tests; update a mock to include HeadState error

* update the order of the checks

* move slashing before state fetch; update Tests

* update changelog

* use verifyProposerSlashing to verify and reject block; remove verifySlashableBlock; update tests

* Update changelog

* nit: cleaner error check

* nit: clean up

* revert code logic; update string check; add a unit test

* improve errors; merge tests

* Update a unit test

* fix lint

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-24 20:27:34 +00:00
potuz
4ea3699c51 make all duties deadlines to end of epoch 2025-04-24 12:31:27 -03:00
potuz
06a5a16007 review 2 2025-04-23 18:27:20 -03:00
potuz
fb2eeb5ce9 review 2025-04-23 18:16:29 -03:00
potuz
b526d99a55 Fix deadlines 2025-04-23 17:50:07 -03:00
Nishant Das
1b13520270 Fix Unmarshalling of BlobSidecarsByRoot Requests (#15209)
* Handle Electra Lists

* Changelog
2025-04-23 14:04:46 +00:00
Tronica
0936628b72 refactor(sync): rename reValidateSubscriptions to pruneSubscriptions (#15160)
* Update subscriber.go

* Update subscriber_test.go

* Create pronoss_refactor_subscriber_rename.md

* Update pronoss_refactor_subscriber_rename.md

* Update pronoss_refactor_subscriber_rename.md

* Update pronoss_refactor_subscriber_rename.md

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-22 13:26:57 +00:00
Nishant Das
478ae81ed1 Update Insecure Dependencies (#15204) 2025-04-22 09:10:03 +00:00
Nishant Das
93276150e7 Fix Generated SSZ Files (#15199)
* Regenerate Files

* Changelog
2025-04-22 04:52:27 +00:00
Preston Van Loon
83460c9956 Changelog v6 (#15203)
* Run unclog for v6.0.0

* Add changelog note for v6 release

* Changelog fragment
2025-04-22 01:03:42 +00:00
Bastin
d30bb63d94 Add lc p2p broadcaster functions (#15175)
* add lc boradcasters

* gazelle

* changelog entry

* remove subnet

* implement and use IsNil()

* address comments

* Apply suggestions from code review

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

* address comments

* address comments

* deps

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
2025-04-18 12:28:12 +00:00
Manu NALEPA
ab5505e13e Implement all needed KZG wrappers for peerDAS in the kzg package. (#15186)
* Implement all needed KZG wrappers for peerDAS in the `kzg` package.

This way, If we need to change the KZG backend, the only package to
modify is the `kzg` package.

* `.bazelrc`: Add `build --compilation_mode=opt`

* Remove --compilation_mode=opt, use supranational blst headers.

* Fix Terence's comment.

* Fix Terence`s comments.

---------

Co-authored-by: Preston Van Loon <preston@pvl.dev>
2025-04-17 22:43:41 +00:00
james-prysm
9c00b06966 fix expected withdrawals (#15191)
* fixed underflow with expected withdrawals

* update comment

* Revert "update comment"

This reverts commit e07da541ac.

* attempting to fix comment indents

* fixing another missed tab in comments

* trying tabs one more time for fixing tabs

* adding undeflow safety

* fixing error typo

* missed wrapping the error
2025-04-17 18:17:53 +00:00
Manu NALEPA
167f719860 Upgrade to fulu: Fix and add spectests (#15190)
* `UpgradeToFulu`: Fix.

* `UpgradeToFulu`: Add spectests.
2025-04-17 14:13:32 +00:00
mmsqe
d4469d17b7 Problem: nondeterministic default fork value when generate genesis (#15151)
* Problem: nondeterministic default fork value when generate genesis

add sort versions

* add doc

* Apply suggestions from code review

* lint

---------

Co-authored-by: Bastin <43618253+Inspector-Butters@users.noreply.github.com>
2025-04-16 16:33:48 +00:00
kasey
8418157f8a improve peer scoring code in range sync (#15173)
* separate block/blob peer scoring

* Preston's test coverage feedback

* test to ensure we don't combine distinct errors

---------

Co-authored-by: Kasey <kasey@users.noreply.github.com>
2025-04-16 13:47:58 +00:00
132 changed files with 5915 additions and 544 deletions

View File

@@ -4,6 +4,64 @@ All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
## [v6.0.0](https://github.com/prysmaticlabs/prysm/compare/v5.3.2...v6.0.0) - 2025-04-21
This release introduces Mainnet support for the upcoming Electra + Prague (Pectra) fork. The fork is scheduled for mainnet epoch 364032 (May 7, 2025, 10:05:11 UTC). You MUST update Prysm Beacon Node, Prysm Validator Client, and your execution layer client to the Pectra ready release prior to the fork to stay on the correct chain.
Besides Pectra, we have more light client API support, cleanups, and a few bugfixes. Please review the changelog below and update your client as soon as practical before May 7.
This release is **mandatory** for all operators before May 7.
### Added
- Implemented validator identities Beacon API endpoint. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15086)
- Add SSZ support to light client updates by range API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15082)
- Add light client ssz types to the spec test. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15097)
- Added the ability for execution requests to be tested in e2e with electra. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14971)
- Add warning messages for gas limit ranges that might be problematic. Low gas limits (≤10% of default) may cause transactions to fail, while high gas limits (>150% of default) could lead to block propagation issues. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15078)
- Add light client store object to the beacon node object. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15120)
- prysmctl option in wrapper script to generate devnet ssz. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15145)
- Add support for Electra fork epoch. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15132)
### Changed
- The validator client will no longer use the full list of committee values but instead use the committee length and validator committee index. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15039)
- Remove the header `Content-Disposition` from the `httputil.WriteSSZ` function. No `filename` parameter is needed anymore. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15092)
- Sort attestations in proposer block by reward. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15093)
- More efficient query method for stategen to retrieve blocks between a given state and the replay target block. This avoids attempting to look up blocks that are not needed for head replay queries, which may be missing due to a previous rollback bug. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15063)
- removed old web3signer metrics in favor for a universal one. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14920)
- Deprecated everything related with the gRPC API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14944)
- Migrate Prysm repo to Offchain Labs organization ahead of Pectra upgrade v6. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15140)
### Deprecated
- deprecates and removes usage of the `--trace` flag and`--cpuprofile` flag in favor of just using `--pprof`. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15083)
### Removed
- Remove /eth/v1/beacon/states/head/committees call when getting duties. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15039)
- Removed unused hack scripts. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15157)
- Remove `disable-committee-aware-packing` flag. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15162)
- Remove deprecated flags for the major release. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15165)
- Removed Beacon API endpoints which have been deprecated at the Deneb fork. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15166)
### Fixed
- The `--rpc` flag will now properly enable the keymanager APIs without web. The `--web` will enable both validator api endpoints and web. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15080)
- Use latest state to pack attestation. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15113)
- Clean up dangling block index entries for blocks that were previously deleted by incomplete cleanup code. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15040)
- Fixed to use io stream instead of stream read. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15089)
- When using a DV, send all aggregations for a slot and committee. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15110)
- Fixed a bug in consolidation request processing. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15122)
- Fix State Getter for pending withdrawal balance. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15123)
- Fixed a bug in checking for attestation lengths in our block. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15134)
- Fix Committee Index Check For Aggregates. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15146)
- Fix filtering by committee index post-Electra in `ListAttestationsV2`. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15148)
- Peers giving invalid data in range syncing are now downscored. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15149)
- Adding fork guard to attestation api endpoints so that it doesn't accidentally include wrong attestation types in the pool. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15161)
- fixed underflow with balances in leaking edge case with expected withdrawals. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15191)
- Attribute block and blob issues to correct peers during range syncing. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15173)
## [v5.3.2](https://github.com/prysmaticlabs/prysm/compare/v5.3.1...v5.3.2) - 2025-03-25
This release introduces support for the `Hoodi` testnet.
@@ -3255,4 +3313,4 @@ There are no security updates in this release.
# Older than v2.0.0
For changelog history for releases older than v2.0.0, please refer to https://github.com/prysmaticlabs/prysm/releases
For changelog history for releases older than v2.0.0, please refer to https://github.com/prysmaticlabs/prysm/releases

View File

@@ -3,15 +3,19 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"kzg.go",
"trusted_setup.go",
"validation.go",
],
embedsrcs = ["trusted_setup.json"],
embedsrcs = ["trusted_setup_4096.json"],
importpath = "github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain/kzg",
visibility = ["//visibility:public"],
deps = [
"//consensus-types/blocks:go_default_library",
"@com_github_crate_crypto_go_kzg_4844//:go_default_library",
"@com_github_ethereum_c_kzg_4844//bindings/go:go_default_library",
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
"@com_github_ethereum_go_ethereum//crypto/kzg4844:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)

View File

@@ -0,0 +1,143 @@
package kzg
import (
"github.com/pkg/errors"
ckzg4844 "github.com/ethereum/c-kzg-4844/v2/bindings/go"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
)
// BytesPerBlob is the number of bytes in a single blob.
const BytesPerBlob = ckzg4844.BytesPerBlob
// Blob represents a serialized chunk of data.
type Blob [BytesPerBlob]byte
// BytesPerCell is the number of bytes in a single cell.
const BytesPerCell = ckzg4844.BytesPerCell
// Cell represents a chunk of an encoded Blob.
type Cell [BytesPerCell]byte
// Commitment represent a KZG commitment to a Blob.
type Commitment [48]byte
// Proof represents a KZG proof that attests to the validity of a Blob or parts of it.
type Proof [48]byte
// Bytes48 is a 48-byte array.
type Bytes48 = ckzg4844.Bytes48
// Bytes32 is a 32-byte array.
type Bytes32 = ckzg4844.Bytes32
// CellsAndProofs represents the Cells and Proofs corresponding to a single blob.
type CellsAndProofs struct {
Cells []Cell
Proofs []Proof
}
// BlobToKZGCommitment computes a KZG commitment from a given blob.
func BlobToKZGCommitment(blob *Blob) (Commitment, error) {
var kzgBlob kzg4844.Blob
copy(kzgBlob[:], blob[:])
commitment, err := kzg4844.BlobToCommitment(&kzgBlob)
if err != nil {
return Commitment{}, err
}
return Commitment(commitment), nil
}
// ComputeCells computes the (extended) cells from a given blob.
func ComputeCells(blob *Blob) ([]Cell, error) {
var ckzgBlob ckzg4844.Blob
copy(ckzgBlob[:], blob[:])
ckzgCells, err := ckzg4844.ComputeCells(&ckzgBlob)
if err != nil {
return nil, errors.Wrap(err, "compute cells")
}
cells := make([]Cell, len(ckzgCells))
for i := range ckzgCells {
cells[i] = Cell(ckzgCells[i])
}
return cells, nil
}
// ComputeBlobKZGProof computes the blob KZG proof from a given blob and its commitment.
func ComputeBlobKZGProof(blob *Blob, commitment Commitment) (Proof, error) {
var kzgBlob kzg4844.Blob
copy(kzgBlob[:], blob[:])
proof, err := kzg4844.ComputeBlobProof(&kzgBlob, kzg4844.Commitment(commitment))
if err != nil {
return [48]byte{}, err
}
return Proof(proof), nil
}
// ComputeCellsAndKZGProofs computes the cells and cells KZG proofs from a given blob.
func ComputeCellsAndKZGProofs(blob *Blob) (CellsAndProofs, error) {
var ckzgBlob ckzg4844.Blob
copy(ckzgBlob[:], blob[:])
ckzgCells, ckzgProofs, err := ckzg4844.ComputeCellsAndKZGProofs(&ckzgBlob)
if err != nil {
return CellsAndProofs{}, err
}
return makeCellsAndProofs(ckzgCells[:], ckzgProofs[:])
}
// VerifyCellKZGProofBatch verifies the KZG proofs for a given slice of commitments, cells indices, cells and proofs.
// Note: It is way more efficient to call once this function with big slices than calling it multiple times with small slices.
func VerifyCellKZGProofBatch(commitmentsBytes []Bytes48, cellIndices []uint64, cells []Cell, proofsBytes []Bytes48) (bool, error) {
// Convert `Cell` type to `ckzg4844.Cell`
ckzgCells := make([]ckzg4844.Cell, len(cells))
for i := range cells {
ckzgCells[i] = ckzg4844.Cell(cells[i])
}
return ckzg4844.VerifyCellKZGProofBatch(commitmentsBytes, cellIndices, ckzgCells, proofsBytes)
}
// RecoverCellsAndKZGProofs recovers the complete cells and KZG proofs from a given set of cell indices and partial cells.
func RecoverCellsAndKZGProofs(cellIndices []uint64, partialCells []Cell) (CellsAndProofs, error) {
// Convert `Cell` type to `ckzg4844.Cell`
ckzgPartialCells := make([]ckzg4844.Cell, len(partialCells))
for i := range partialCells {
ckzgPartialCells[i] = ckzg4844.Cell(partialCells[i])
}
ckzgCells, ckzgProofs, err := ckzg4844.RecoverCellsAndKZGProofs(cellIndices, ckzgPartialCells)
if err != nil {
return CellsAndProofs{}, errors.Wrap(err, "recover cells and KZG proofs")
}
return makeCellsAndProofs(ckzgCells[:], ckzgProofs[:])
}
// makeCellsAndProofs converts cells/proofs to the CellsAndProofs type defined in this package.
func makeCellsAndProofs(ckzgCells []ckzg4844.Cell, ckzgProofs []ckzg4844.KZGProof) (CellsAndProofs, error) {
if len(ckzgCells) != len(ckzgProofs) {
return CellsAndProofs{}, errors.New("different number of cells/proofs")
}
cells := make([]Cell, 0, len(ckzgCells))
proofs := make([]Proof, 0, len(ckzgProofs))
for i := range ckzgCells {
cells = append(cells, Cell(ckzgCells[i]))
proofs = append(proofs, Proof(ckzgProofs[i]))
}
return CellsAndProofs{
Cells: cells,
Proofs: proofs,
}, nil
}

View File

@@ -5,24 +5,69 @@ import (
"encoding/json"
GoKZG "github.com/crate-crypto/go-kzg-4844"
CKZG "github.com/ethereum/c-kzg-4844/v2/bindings/go"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
)
var (
//go:embed trusted_setup.json
// https://github.com/ethereum/consensus-specs/blob/dev/presets/mainnet/trusted_setups/trusted_setup_4096.json
//go:embed trusted_setup_4096.json
embeddedTrustedSetup []byte // 1.2Mb
kzgContext *GoKZG.Context
kzgLoaded bool
)
type TrustedSetup struct {
G1Monomial [GoKZG.ScalarsPerBlob]GoKZG.G1CompressedHexStr `json:"g1_monomial"`
G1Lagrange [GoKZG.ScalarsPerBlob]GoKZG.G1CompressedHexStr `json:"g1_lagrange"`
G2Monomial [65]GoKZG.G2CompressedHexStr `json:"g2_monomial"`
}
func Start() error {
parsedSetup := GoKZG.JSONTrustedSetup{}
err := json.Unmarshal(embeddedTrustedSetup, &parsedSetup)
trustedSetup := &TrustedSetup{}
err := json.Unmarshal(embeddedTrustedSetup, trustedSetup)
if err != nil {
return errors.Wrap(err, "could not parse trusted setup JSON")
}
kzgContext, err = GoKZG.NewContext4096(&parsedSetup)
kzgContext, err = GoKZG.NewContext4096(&GoKZG.JSONTrustedSetup{
SetupG2: trustedSetup.G2Monomial[:],
SetupG1Lagrange: trustedSetup.G1Lagrange,
})
if err != nil {
return errors.Wrap(err, "could not initialize go-kzg context")
}
// Length of a G1 point, converted from hex to binary.
g1MonomialBytes := make([]byte, len(trustedSetup.G1Monomial)*(len(trustedSetup.G1Monomial[0])-2)/2)
for i, g1 := range &trustedSetup.G1Monomial {
copy(g1MonomialBytes[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
// Length of a G1 point, converted from hex to binary.
g1LagrangeBytes := make([]byte, len(trustedSetup.G1Lagrange)*(len(trustedSetup.G1Lagrange[0])-2)/2)
for i, g1 := range &trustedSetup.G1Lagrange {
copy(g1LagrangeBytes[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
// Length of a G2 point, converted from hex to binary.
g2MonomialBytes := make([]byte, len(trustedSetup.G2Monomial)*(len(trustedSetup.G2Monomial[0])-2)/2)
for i, g2 := range &trustedSetup.G2Monomial {
copy(g2MonomialBytes[i*(len(g2)-2)/2:], hexutil.MustDecode(g2))
}
if !kzgLoaded {
const precompute uint = 8
kzgLoaded = true
// Free the current trusted setup before running this method.
// CKZG panics if the same setup is run multiple times.
if err = CKZG.LoadTrustedSetup(g1MonomialBytes, g1LagrangeBytes, g2MonomialBytes, precompute); err != nil {
return errors.Wrap(err, "load trust setup")
}
}
return nil
}

View File

@@ -53,6 +53,7 @@ type ChainService struct {
InitSyncBlockRoots map[[32]byte]bool
DB db.Database
State state.BeaconState
HeadStateErr error
Block interfaces.ReadOnlySignedBeaconBlock
VerifyBlkDescendantErr error
stateNotifier statefeed.Notifier
@@ -364,6 +365,9 @@ func (s *ChainService) HeadState(context.Context) (state.BeaconState, error) {
// HeadStateReadOnly mocks HeadStateReadOnly method in chain service.
func (s *ChainService) HeadStateReadOnly(context.Context) (state.ReadOnlyBeaconState, error) {
if s.HeadStateErr != nil {
return nil, s.HeadStateErr
}
return s.State, nil
}

View File

@@ -16,6 +16,9 @@ import (
"google.golang.org/protobuf/proto"
)
// ErrCouldNotVerifyBlockHeader is returned when a block header's signature cannot be verified.
var ErrCouldNotVerifyBlockHeader = errors.New("could not verify beacon block header")
type slashValidatorFunc func(
ctx context.Context,
st state.BeaconState,
@@ -114,7 +117,7 @@ func VerifyProposerSlashing(
for _, header := range headers {
if err := signing.ComputeDomainVerifySigningRoot(beaconState, pIdx, slots.ToEpoch(hSlot),
header.Header, params.BeaconConfig().DomainBeaconProposer, header.Signature); err != nil {
return errors.Wrap(err, "could not verify beacon block header")
return errors.Wrap(ErrCouldNotVerifyBlockHeader, err.Error())
}
}
return nil

View File

@@ -11,7 +11,7 @@ import (
)
// UpgradeToFulu updates inputs a generic state to return the version Fulu state.
// https://github.com/ethereum/consensus-specs/blob/dev/specs/fulu/fork.md#upgrading-the-state
// https://github.com/ethereum/consensus-specs/blob/v1.5.0-beta.5/specs/fulu/fork.md#upgrading-the-state
func UpgradeToFulu(beaconState state.BeaconState) (state.BeaconState, error) {
currentSyncCommittee, err := beaconState.CurrentSyncCommittee()
if err != nil {
@@ -69,6 +69,10 @@ func UpgradeToFulu(beaconState state.BeaconState) (state.BeaconState, error) {
if err != nil {
return nil, err
}
depositRequestsStartIndex, err := beaconState.DepositRequestsStartIndex()
if err != nil {
return nil, err
}
depositBalanceToConsume, err := beaconState.DepositBalanceToConsume()
if err != nil {
return nil, err
@@ -154,7 +158,7 @@ func UpgradeToFulu(beaconState state.BeaconState) (state.BeaconState, error) {
NextWithdrawalValidatorIndex: vi,
HistoricalSummaries: summaries,
DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex,
DepositRequestsStartIndex: depositRequestsStartIndex,
DepositBalanceToConsume: depositBalanceToConsume,
ExitBalanceToConsume: exitBalanceToConsume,
EarliestExitEpoch: earliestExitEpoch,

View File

@@ -56,6 +56,7 @@ go_library(
"//cmd/beacon-chain/flags:go_default_library",
"//config/features:go_default_library",
"//config/params:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//consensus-types/wrapper:go_default_library",
"//container/leaky-bucket:go_default_library",
@@ -140,6 +141,7 @@ go_test(
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/light-client:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/p2p/encoder:go_default_library",
@@ -152,6 +154,7 @@ go_test(
"//cmd/beacon-chain/flags: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",
"//consensus-types/wrapper:go_default_library",
"//container/leaky-bucket:go_default_library",
@@ -163,10 +166,12 @@ go_test(
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/testing:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/discover:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",

View File

@@ -10,9 +10,11 @@ import (
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/altair"
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v6/crypto/hash"
"github.com/OffchainLabs/prysm/v6/monitoring/tracing"
"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v6/network/forks"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/pkg/errors"
@@ -268,6 +270,58 @@ func (s *Service) internalBroadcastBlob(ctx context.Context, subnet uint64, blob
}
}
func (s *Service) BroadcastLightClientOptimisticUpdate(ctx context.Context, update interfaces.LightClientOptimisticUpdate) error {
ctx, span := trace.StartSpan(ctx, "p2p.BroadcastLightClientOptimisticUpdate")
defer span.End()
if update == nil || update.IsNil() {
return errors.New("attempted to broadcast nil light client optimistic update")
}
forkDigest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), s.genesisValidatorsRoot)
if err != nil {
err := errors.Wrap(err, "could not retrieve fork digest")
tracing.AnnotateError(span, err)
return err
}
// TODO: should we check if the update is too early or too late to broadcast?
if err := s.broadcastObject(ctx, update, lcOptimisticToTopic(forkDigest)); err != nil {
err := errors.Wrap(err, "could not publish message")
tracing.AnnotateError(span, err)
return err
}
return nil
}
func (s *Service) BroadcastLightClientFinalityUpdate(ctx context.Context, update interfaces.LightClientFinalityUpdate) error {
ctx, span := trace.StartSpan(ctx, "p2p.BroadcastLightClientFinalityUpdate")
defer span.End()
if update == nil || update.IsNil() {
return errors.New("attempted to broadcast nil light client finality update")
}
forkDigest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(update.AttestedHeader().Beacon().Slot), s.genesisValidatorsRoot)
if err != nil {
err := errors.Wrap(err, "could not retrieve fork digest")
tracing.AnnotateError(span, err)
return err
}
// TODO: should we check if the update is too early or too late to broadcast?
if err := s.broadcastObject(ctx, update, lcFinalityToTopic(forkDigest)); err != nil {
err := errors.Wrap(err, "could not publish message")
tracing.AnnotateError(span, err)
return err
}
return nil
}
// method to broadcast messages to other peers in our gossip mesh.
func (s *Service) broadcastObject(ctx context.Context, obj ssz.Marshaler, topic string) error {
ctx, span := trace.StartSpan(ctx, "p2p.broadcastObject")
@@ -308,3 +362,11 @@ func syncCommitteeToTopic(subnet uint64, forkDigest [4]byte) string {
func blobSubnetToTopic(subnet uint64, forkDigest [4]byte) string {
return fmt.Sprintf(BlobSubnetTopicFormat, forkDigest, subnet)
}
func lcOptimisticToTopic(forkDigest [4]byte) string {
return fmt.Sprintf(LightClientOptimisticUpdateTopicFormat, forkDigest)
}
func lcFinalityToTopic(forkDigest [4]byte) string {
return fmt.Sprintf(LightClientFinalityUpdateTopicFormat, forkDigest)
}

View File

@@ -10,17 +10,22 @@ import (
"time"
"github.com/OffchainLabs/prysm/v6/beacon-chain/core/helpers"
lightClient "github.com/OffchainLabs/prysm/v6/beacon-chain/core/light-client"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/peers"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/peers/scorers"
p2ptest "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/testing"
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v6/consensus-types/wrapper"
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v6/network/forks"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
testpb "github.com/OffchainLabs/prysm/v6/proto/testing"
"github.com/OffchainLabs/prysm/v6/runtime/version"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/OffchainLabs/prysm/v6/testing/util"
"github.com/OffchainLabs/prysm/v6/time/slots"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/host"
"github.com/prysmaticlabs/go-bitfield"
@@ -516,3 +521,137 @@ func TestService_BroadcastBlob(t *testing.T) {
require.NoError(t, p.BroadcastBlob(ctx, subnet, blobSidecar))
require.Equal(t, false, util.WaitTimeout(&wg, 1*time.Second), "Failed to receive pubsub within 1s")
}
func TestService_BroadcastLightClientOptimisticUpdate(t *testing.T) {
p1 := p2ptest.NewTestP2P(t)
p2 := p2ptest.NewTestP2P(t)
p1.Connect(p2)
require.NotEqual(t, 0, len(p1.BHost.Network().Peers()))
p := &Service{
host: p1.BHost,
pubsub: p1.PubSub(),
joinedTopics: map[string]*pubsub.Topic{},
cfg: &Config{},
genesisTime: time.Now(),
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
subnetsLock: make(map[uint64]*sync.RWMutex),
subnetsLockLock: sync.Mutex{},
peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
ScorerParams: &scorers.Config{},
}),
}
l := util.NewTestLightClient(t, version.Altair)
msg, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
GossipTypeMapping[reflect.TypeOf(msg)] = LightClientOptimisticUpdateTopicFormat
digest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(msg.AttestedHeader().Beacon().Slot), p.genesisValidatorsRoot)
require.NoError(t, err)
topic := fmt.Sprintf(LightClientOptimisticUpdateTopicFormat, digest)
// External peer subscribes to the topic.
topic += p.Encoding().ProtocolSuffix()
sub, err := p2.SubscribeToTopic(topic)
require.NoError(t, err)
time.Sleep(50 * time.Millisecond) // libp2p fails without this delay...
// Async listen for the pubsub, must be before the broadcast.
var wg sync.WaitGroup
wg.Add(1)
go func(tt *testing.T) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
incomingMessage, err := sub.Next(ctx)
require.NoError(t, err)
result := &ethpb.LightClientOptimisticUpdateAltair{}
require.NoError(t, p.Encoding().DecodeGossip(incomingMessage.Data, result))
if !proto.Equal(result, msg.Proto()) {
tt.Errorf("Did not receive expected message, got %+v, wanted %+v", result, msg)
}
}(t)
// Broadcasting nil should fail.
ctx := context.Background()
require.ErrorContains(t, "attempted to broadcast nil", p.BroadcastLightClientOptimisticUpdate(ctx, nil))
var nilUpdate interfaces.LightClientOptimisticUpdate
require.ErrorContains(t, "attempted to broadcast nil", p.BroadcastLightClientOptimisticUpdate(ctx, nilUpdate))
// Broadcast to peers and wait.
require.NoError(t, p.BroadcastLightClientOptimisticUpdate(ctx, msg))
if util.WaitTimeout(&wg, 1*time.Second) {
t.Error("Failed to receive pubsub within 1s")
}
}
func TestService_BroadcastLightClientFinalityUpdate(t *testing.T) {
p1 := p2ptest.NewTestP2P(t)
p2 := p2ptest.NewTestP2P(t)
p1.Connect(p2)
require.NotEqual(t, 0, len(p1.BHost.Network().Peers()))
p := &Service{
host: p1.BHost,
pubsub: p1.PubSub(),
joinedTopics: map[string]*pubsub.Topic{},
cfg: &Config{},
genesisTime: time.Now(),
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
subnetsLock: make(map[uint64]*sync.RWMutex),
subnetsLockLock: sync.Mutex{},
peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
ScorerParams: &scorers.Config{},
}),
}
l := util.NewTestLightClient(t, version.Altair)
msg, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State.Slot(), l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
GossipTypeMapping[reflect.TypeOf(msg)] = LightClientFinalityUpdateTopicFormat
digest, err := forks.ForkDigestFromEpoch(slots.ToEpoch(msg.AttestedHeader().Beacon().Slot), p.genesisValidatorsRoot)
require.NoError(t, err)
topic := fmt.Sprintf(LightClientFinalityUpdateTopicFormat, digest)
// External peer subscribes to the topic.
topic += p.Encoding().ProtocolSuffix()
sub, err := p2.SubscribeToTopic(topic)
require.NoError(t, err)
time.Sleep(50 * time.Millisecond) // libp2p fails without this delay...
// Async listen for the pubsub, must be before the broadcast.
var wg sync.WaitGroup
wg.Add(1)
go func(tt *testing.T) {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
incomingMessage, err := sub.Next(ctx)
require.NoError(t, err)
result := &ethpb.LightClientFinalityUpdateAltair{}
require.NoError(t, p.Encoding().DecodeGossip(incomingMessage.Data, result))
if !proto.Equal(result, msg.Proto()) {
tt.Errorf("Did not receive expected message, got %+v, wanted %+v", result, msg)
}
}(t)
// Broadcasting nil should fail.
ctx := context.Background()
require.ErrorContains(t, "attempted to broadcast nil", p.BroadcastLightClientFinalityUpdate(ctx, nil))
var nilUpdate interfaces.LightClientFinalityUpdate
require.ErrorContains(t, "attempted to broadcast nil", p.BroadcastLightClientFinalityUpdate(ctx, nilUpdate))
// Broadcast to peers and wait.
require.NoError(t, p.BroadcastLightClientFinalityUpdate(ctx, msg))
if util.WaitTimeout(&wg, 1*time.Second) {
t.Error("Failed to receive pubsub within 1s")
}
}

View File

@@ -105,7 +105,8 @@ func (l *listenerWrapper) RandomNodes() enode.Iterator {
func (l *listenerWrapper) Ping(node *enode.Node) error {
l.mu.RLock()
defer l.mu.RUnlock()
return l.listener.Ping(node)
_, err := l.listener.Ping(node)
return err
}
func (l *listenerWrapper) RequestENR(node *enode.Node) (*enode.Node, error) {

View File

@@ -22,6 +22,8 @@ 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{} },
LightClientOptimisticUpdateTopicFormat: func() proto.Message { return &ethpb.LightClientOptimisticUpdateAltair{} },
LightClientFinalityUpdateTopicFormat: func() proto.Message { return &ethpb.LightClientFinalityUpdateAltair{} },
}
// GossipTopicMappings is a function to return the assigned data type
@@ -63,6 +65,25 @@ func GossipTopicMappings(topic string, epoch primitives.Epoch) proto.Message {
return &ethpb.SignedAggregateAttestationAndProofElectra{}
}
return gossipMessage(topic)
case LightClientOptimisticUpdateTopicFormat:
if epoch >= params.BeaconConfig().DenebForkEpoch {
return &ethpb.LightClientOptimisticUpdateDeneb{}
}
if epoch >= params.BeaconConfig().CapellaForkEpoch {
return &ethpb.LightClientOptimisticUpdateCapella{}
}
return gossipMessage(topic)
case LightClientFinalityUpdateTopicFormat:
if epoch >= params.BeaconConfig().ElectraForkEpoch {
return &ethpb.LightClientFinalityUpdateElectra{}
}
if epoch >= params.BeaconConfig().DenebForkEpoch {
return &ethpb.LightClientFinalityUpdateDeneb{}
}
if epoch >= params.BeaconConfig().CapellaForkEpoch {
return &ethpb.LightClientFinalityUpdateCapella{}
}
return gossipMessage(topic)
default:
return gossipMessage(topic)
}
@@ -97,21 +118,28 @@ func init() {
// Specially handle Altair objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockAltair{})] = BlockSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientFinalityUpdateAltair{})] = LightClientFinalityUpdateTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientOptimisticUpdateAltair{})] = LightClientOptimisticUpdateTopicFormat
// Specially handle Bellatrix objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockBellatrix{})] = BlockSubnetTopicFormat
// Specially handle Capella objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockCapella{})] = BlockSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientOptimisticUpdateCapella{})] = LightClientOptimisticUpdateTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientFinalityUpdateCapella{})] = LightClientFinalityUpdateTopicFormat
// Specially handle Deneb objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockDeneb{})] = BlockSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientOptimisticUpdateDeneb{})] = LightClientOptimisticUpdateTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientFinalityUpdateDeneb{})] = LightClientFinalityUpdateTopicFormat
// Specially handle Electra objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockElectra{})] = BlockSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.SingleAttestation{})] = AttestationSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.AttesterSlashingElectra{})] = AttesterSlashingSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedAggregateAttestationAndProofElectra{})] = AggregateAndProofSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.LightClientFinalityUpdateElectra{})] = LightClientFinalityUpdateTopicFormat
// Specially handle Fulu objects.
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedBeaconBlockFulu{})] = BlockSubnetTopicFormat

View File

@@ -102,6 +102,9 @@ func (s *BadResponsesScorer) countNoLock(pid peer.ID) (int, error) {
// Increment increments the number of bad responses we have received from the given remote peer.
// If peer doesn't exist this method is no-op.
func (s *BadResponsesScorer) Increment(pid peer.ID) {
if pid == "" {
return
}
s.store.Lock()
defer s.store.Unlock()

View File

@@ -124,6 +124,9 @@ func (s *BlockProviderScorer) Params() *BlockProviderScorerConfig {
// IncrementProcessedBlocks increments the number of blocks that have been successfully processed.
func (s *BlockProviderScorer) IncrementProcessedBlocks(pid peer.ID, cnt uint64) {
if pid == "" {
return
}
s.store.Lock()
defer s.store.Unlock()
defer s.touchNoLock(pid)

View File

@@ -30,6 +30,10 @@ const (
GossipBlsToExecutionChangeMessage = "bls_to_execution_change"
// GossipBlobSidecarMessage is the name for the blob sidecar message type.
GossipBlobSidecarMessage = "blob_sidecar"
// GossipLightClientFinalityUpdateMessage is the name for the light client finality update message type.
GossipLightClientFinalityUpdateMessage = "light_client_finality_update"
// GossipLightClientOptimisticUpdateMessage is the name for the light client optimistic update message type.
GossipLightClientOptimisticUpdateMessage = "light_client_optimistic_update"
// Topic Formats
//
// AttestationSubnetTopicFormat is the topic format for the attestation subnet.
@@ -52,4 +56,8 @@ const (
BlsToExecutionChangeSubnetTopicFormat = GossipProtocolAndDigest + GossipBlsToExecutionChangeMessage
// BlobSubnetTopicFormat is the topic format for the blob subnet.
BlobSubnetTopicFormat = GossipProtocolAndDigest + GossipBlobSidecarMessage + "_%d"
// LightClientFinalityUpdateTopicFormat is the topic format for the light client finality update subnet.
LightClientFinalityUpdateTopicFormat = GossipProtocolAndDigest + GossipLightClientFinalityUpdateMessage
// LightClientOptimisticUpdateTopicFormat is the topic format for the light client optimistic update subnet.
LightClientOptimisticUpdateTopicFormat = GossipProtocolAndDigest + GossipLightClientOptimisticUpdateMessage
)

View File

@@ -162,9 +162,9 @@ func (b *BlobSidecarsByRootReq) MarshalSSZ() ([]byte, error) {
// BlobSidecarsByRootReq value.
func (b *BlobSidecarsByRootReq) UnmarshalSSZ(buf []byte) error {
bufLen := len(buf)
maxLength := int(params.BeaconConfig().MaxRequestBlobSidecars) * blobIdSize
maxLength := int(params.BeaconConfig().MaxRequestBlobSidecarsElectra) * blobIdSize
if bufLen > maxLength {
return errors.Errorf("expected buffer with length of up to %d but received length %d", maxLength, bufLen)
return errors.Wrapf(ssz.ErrIncorrectListSize, "expected buffer with length of up to %d but received length %d", maxLength, bufLen)
}
if bufLen%blobIdSize != 0 {
return errors.Wrapf(ssz.ErrIncorrectByteSize, "size=%d", bufLen)

View File

@@ -43,6 +43,15 @@ func TestBlobSidecarsByRootReq_MarshalSSZ(t *testing.T) {
name: "10 item list",
ids: generateBlobIdentifiers(10),
},
{
name: "max list",
ids: generateBlobIdentifiers(int(params.BeaconConfig().MaxRequestBlobSidecarsElectra)),
},
{
name: "beyond max list",
ids: generateBlobIdentifiers(int(params.BeaconConfig().MaxRequestBlobSidecarsElectra) + 1),
unmarshalErr: ssz.ErrIncorrectListSize,
},
{
name: "wonky unmarshal size",
ids: generateBlobIdentifiers(10),

View File

@@ -82,7 +82,7 @@ func TestProposeAttestation(t *testing.T) {
config := params.BeaconConfig()
config.ElectraForkEpoch = 0
params.OverrideBeaconConfig(config)
state, err := util.NewBeaconState()
require.NoError(t, err)
require.NoError(t, state.SetSlot(params.BeaconConfig().SlotsPerEpoch+1))

View File

@@ -62,9 +62,11 @@ func (b *BeaconState) NextWithdrawalValidatorIndex() (primitives.ValidatorIndex,
//
// validator = state.validators[withdrawal.index]
// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE
// has_excess_balance = state.balances[withdrawal.index] > MIN_ACTIVATION_BALANCE
// total_withdrawn = sum(w.amount for w in withdrawals if w.validator_index == withdrawal.validator_index)
// balance = state.balances[withdrawal.validator_index] - total_withdrawn
// has_excess_balance = balance > MIN_ACTIVATION_BALANCE
// if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance:
// withdrawable_balance = min(state.balances[withdrawal.index] - MIN_ACTIVATION_BALANCE, withdrawal.amount)
// withdrawable_balance = min(balance - MIN_ACTIVATION_BALANCE, withdrawal.amount)
// withdrawals.append(Withdrawal(
// index=withdrawal_index,
// validator_index=withdrawal.index,
@@ -132,9 +134,19 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
return nil, 0, fmt.Errorf("could not retrieve balance at index %d: %w", w.Index, err)
}
hasSufficientEffectiveBalance := v.EffectiveBalance() >= params.BeaconConfig().MinActivationBalance
hasExcessBalance := vBal > params.BeaconConfig().MinActivationBalance
var totalWithdrawn uint64
for _, wi := range withdrawals {
if wi.ValidatorIndex == w.Index {
totalWithdrawn += wi.Amount
}
}
balance, err := mathutil.Sub64(vBal, totalWithdrawn)
if err != nil {
return nil, 0, errors.Wrapf(err, "failed to subtract balance %d with total withdrawn %d", vBal, totalWithdrawn)
}
hasExcessBalance := balance > params.BeaconConfig().MinActivationBalance
if v.ExitEpoch() == params.BeaconConfig().FarFutureEpoch && hasSufficientEffectiveBalance && hasExcessBalance {
amount := min(vBal-params.BeaconConfig().MinActivationBalance, w.Amount)
amount := min(balance-params.BeaconConfig().MinActivationBalance, w.Amount)
withdrawals = append(withdrawals, &enginev1.Withdrawal{
Index: withdrawalIndex,
ValidatorIndex: w.Index,
@@ -165,7 +177,10 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
partiallyWithdrawnBalance += w.Amount
}
}
balance = balance - partiallyWithdrawnBalance
balance, err = mathutil.Sub64(balance, partiallyWithdrawnBalance)
if err != nil {
return nil, 0, errors.Wrapf(err, "could not subtract balance %d with partial withdrawn balance %d", balance, partiallyWithdrawnBalance)
}
}
if helpers.IsFullyWithdrawableValidator(val, balance, epoch, b.version) {
withdrawals = append(withdrawals, &enginev1.Withdrawal{

View File

@@ -416,3 +416,37 @@ func TestExpectedWithdrawals(t *testing.T) {
require.DeepEqual(t, withdrawalFull, expected[1])
})
}
func TestExpectedWithdrawals_underflow_electra(t *testing.T) {
s, err := state_native.InitializeFromProtoUnsafeElectra(&ethpb.BeaconStateElectra{})
require.NoError(t, err)
vals := make([]*ethpb.Validator, 1)
balances := make([]uint64, 1)
balances[0] = 2015_000_000_000 //Validator A begins leaking ETH due to inactivity, and over time, its balance decreases to 2,015 ETH
val := &ethpb.Validator{
WithdrawalCredentials: make([]byte, 32),
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra,
WithdrawableEpoch: primitives.Epoch(0),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
val.WithdrawalCredentials[0] = params.BeaconConfig().CompoundingWithdrawalPrefixByte
val.WithdrawalCredentials[31] = byte(0)
vals[0] = val
require.NoError(t, s.SetValidators(vals))
require.NoError(t, s.SetBalances(balances))
require.NoError(t, s.AppendPendingPartialWithdrawal(&ethpb.PendingPartialWithdrawal{
Amount: 1008_000_000_000,
WithdrawableEpoch: primitives.Epoch(0),
}))
require.NoError(t, s.AppendPendingPartialWithdrawal(&ethpb.PendingPartialWithdrawal{
Amount: 1008_000_000_000,
WithdrawableEpoch: primitives.Epoch(0),
}))
expected, _, err := s.ExpectedWithdrawals()
require.NoError(t, err)
require.Equal(t, 3, len(expected)) // is a fully withdrawable validator
require.Equal(t, uint64(1008_000_000_000), expected[0].Amount)
require.Equal(t, uint64(975_000_000_000), expected[1].Amount)
require.Equal(t, uint64(32_000_000_000), expected[2].Amount)
}

View File

@@ -211,6 +211,7 @@ go_test(
"//beacon-chain/operations/attestations:go_default_library",
"//beacon-chain/operations/blstoexec:go_default_library",
"//beacon-chain/operations/slashings:go_default_library",
"//beacon-chain/operations/slashings/mock:go_default_library",
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/p2p/encoder:go_default_library",
"//beacon-chain/p2p/peers:go_default_library",

View File

@@ -60,6 +60,7 @@ go_test(
"blocks_fetcher_test.go",
"blocks_fetcher_utils_test.go",
"blocks_queue_test.go",
"downscore_test.go",
"fsm_benchmark_test.go",
"fsm_test.go",
"initial_sync_test.go",
@@ -70,6 +71,7 @@ go_test(
tags = ["CI_race_detection"],
deps = [
"//async/abool:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/das:go_default_library",
"//beacon-chain/db:go_default_library",
@@ -78,6 +80,7 @@ go_test(
"//beacon-chain/db/testing:go_default_library",
"//beacon-chain/p2p:go_default_library",
"//beacon-chain/p2p/peers:go_default_library",
"//beacon-chain/p2p/peers/peerdata:go_default_library",
"//beacon-chain/p2p/peers/scorers:go_default_library",
"//beacon-chain/p2p/testing:go_default_library",
"//beacon-chain/p2p/types:go_default_library",
@@ -105,6 +108,7 @@ go_test(
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
"@com_github_paulbellamy_ratecounter//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
],

View File

@@ -120,11 +120,20 @@ type fetchRequestParams struct {
// fetchRequestResponse is a combined type to hold results of both successful executions and errors.
// Valid usage pattern will be to check whether result's `err` is nil, before using `blocks`.
type fetchRequestResponse struct {
pid peer.ID
start primitives.Slot
count uint64
bwb []blocks.BlockWithROBlobs
err error
blocksFrom peer.ID
blobsFrom peer.ID
start primitives.Slot
count uint64
bwb []blocks.BlockWithROBlobs
err error
}
func (r *fetchRequestResponse) blocksQueueFetchedData() *blocksQueueFetchedData {
return &blocksQueueFetchedData{
blocksFrom: r.blocksFrom,
blobsFrom: r.blobsFrom,
bwb: r.bwb,
}
}
// newBlocksFetcher creates ready to use fetcher.
@@ -314,13 +323,14 @@ func (f *blocksFetcher) handleRequest(ctx context.Context, start primitives.Slot
}
}
response.bwb, response.pid, response.err = f.fetchBlocksFromPeer(ctx, start, count, peers)
response.bwb, response.blocksFrom, response.err = f.fetchBlocksFromPeer(ctx, start, count, peers)
if response.err == nil {
bwb, err := f.fetchBlobsFromPeer(ctx, response.bwb, response.pid, peers)
pid, bwb, err := f.fetchBlobsFromPeer(ctx, response.bwb, response.blocksFrom, peers)
if err != nil {
response.err = err
}
response.bwb = bwb
response.blobsFrom = pid
}
return response
}
@@ -537,20 +547,20 @@ func missingCommitError(root [32]byte, slot primitives.Slot, missing [][]byte) e
}
// fetchBlobsFromPeer fetches blocks from a single randomly selected peer.
func (f *blocksFetcher) fetchBlobsFromPeer(ctx context.Context, bwb []blocks.BlockWithROBlobs, pid peer.ID, peers []peer.ID) ([]blocks.BlockWithROBlobs, error) {
func (f *blocksFetcher) fetchBlobsFromPeer(ctx context.Context, bwb []blocks.BlockWithROBlobs, pid peer.ID, peers []peer.ID) (peer.ID, []blocks.BlockWithROBlobs, error) {
ctx, span := trace.StartSpan(ctx, "initialsync.fetchBlobsFromPeer")
defer span.End()
if slots.ToEpoch(f.clock.CurrentSlot()) < params.BeaconConfig().DenebForkEpoch {
return bwb, nil
return "", bwb, nil
}
blobWindowStart, err := prysmsync.BlobRPCMinValidSlot(f.clock.CurrentSlot())
if err != nil {
return nil, err
return "", nil, err
}
// Construct request message based on observed interval of blocks in need of blobs.
req := countCommitments(bwb, blobWindowStart).blobRange(f.bs).Request()
if req == nil {
return bwb, nil
return "", bwb, nil
}
peers = f.filterPeers(ctx, peers, peersPercentagePerRequest)
// We dial the initial peer first to ensure that we get the desired set of blobs.
@@ -573,9 +583,9 @@ func (f *blocksFetcher) fetchBlobsFromPeer(ctx context.Context, bwb []blocks.Blo
log.WithField("peer", p).WithError(err).Debug("Invalid BeaconBlobsByRange response")
continue
}
return robs, err
return p, robs, err
}
return nil, errNoPeersAvailable
return "", nil, errNoPeersAvailable
}
// requestBlocks is a wrapper for handling BeaconBlocksByRangeRequest requests/streams.

View File

@@ -22,8 +22,9 @@ import (
// Blocks are stored in an ascending slot order. The first block is guaranteed to have parent
// either in DB or initial sync cache.
type forkData struct {
peer peer.ID
bwb []blocks.BlockWithROBlobs
blocksFrom peer.ID
blobsFrom peer.ID
bwb []blocks.BlockWithROBlobs
}
// nonSkippedSlotAfter checks slots after the given one in an attempt to find a non-empty future slot.
@@ -280,13 +281,13 @@ func (f *blocksFetcher) findForkWithPeer(ctx context.Context, pid peer.ID, slot
}
// We need to fetch the blobs for the given alt-chain if any exist, so that we can try to verify and import
// the blocks.
bwb, err := f.fetchBlobsFromPeer(ctx, altBlocks, pid, []peer.ID{pid})
bpid, bwb, err := f.fetchBlobsFromPeer(ctx, altBlocks, pid, []peer.ID{pid})
if err != nil {
return nil, errors.Wrap(err, "unable to retrieve blobs for blocks found in findForkWithPeer")
}
// The caller will use the BlocksWith VerifiedBlobs in bwb as the starting point for
// round-robin syncing the alternate chain.
return &forkData{peer: pid, bwb: bwb}, nil
return &forkData{blocksFrom: pid, blobsFrom: bpid, bwb: bwb}, nil
}
return nil, errNoAlternateBlocks
}
@@ -302,13 +303,15 @@ func (f *blocksFetcher) findAncestor(ctx context.Context, pid peer.ID, b interfa
if err != nil {
return nil, errors.Wrap(err, "received invalid blocks in findAncestor")
}
bwb, err = f.fetchBlobsFromPeer(ctx, bwb, pid, []peer.ID{pid})
var bpid peer.ID
bpid, bwb, err = f.fetchBlobsFromPeer(ctx, bwb, pid, []peer.ID{pid})
if err != nil {
return nil, errors.Wrap(err, "unable to retrieve blobs for blocks found in findAncestor")
}
return &forkData{
peer: pid,
bwb: bwb,
blocksFrom: pid,
bwb: bwb,
blobsFrom: bpid,
}, nil
}
// Request block's parent.

View File

@@ -263,7 +263,7 @@ func TestBlocksFetcher_findFork(t *testing.T) {
reqEnd := testForkStartSlot(t, 251) + primitives.Slot(findForkReqRangeSize())
require.Equal(t, primitives.Slot(len(chain1)), fork.bwb[0].Block.Block().Slot())
require.Equal(t, int(reqEnd-forkSlot1b), len(fork.bwb))
require.Equal(t, curForkMoreBlocksPeer, fork.peer)
require.Equal(t, curForkMoreBlocksPeer, fork.blocksFrom)
// Save all chain1b blocks (so that they do not interfere with alternative fork)
for _, blk := range chain1b {
util.SaveBlock(t, ctx, beaconDB, blk)
@@ -283,7 +283,7 @@ func TestBlocksFetcher_findFork(t *testing.T) {
alternativePeer := connectPeerHavingBlocks(t, p2p, chain2, finalizedSlot, p2p.Peers())
fork, err = fetcher.findFork(ctx, 251)
require.NoError(t, err)
assert.Equal(t, alternativePeer, fork.peer)
assert.Equal(t, alternativePeer, fork.blocksFrom)
assert.Equal(t, 65, len(fork.bwb))
ind := forkSlot
for _, blk := range fork.bwb {

View File

@@ -10,6 +10,7 @@ import (
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p"
"github.com/OffchainLabs/prysm/v6/beacon-chain/startup"
beaconsync "github.com/OffchainLabs/prysm/v6/beacon-chain/sync"
"github.com/OffchainLabs/prysm/v6/beacon-chain/verification"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v6/time/slots"
@@ -93,8 +94,9 @@ type blocksQueue struct {
// blocksQueueFetchedData is a data container that is returned from a queue on each step.
type blocksQueueFetchedData struct {
pid peer.ID
bwb []blocks.BlockWithROBlobs
blocksFrom peer.ID
blobsFrom peer.ID
bwb []blocks.BlockWithROBlobs
}
// newBlocksQueue creates initialized priority queue.
@@ -337,13 +339,15 @@ func (q *blocksQueue) onDataReceivedEvent(ctx context.Context) eventHandlerFn {
}
if errors.Is(response.err, beaconsync.ErrInvalidFetchedData) {
// Peer returned invalid data, penalize.
q.blocksFetcher.p2p.Peers().Scorers().BadResponsesScorer().Increment(m.pid)
log.WithField("pid", response.pid).Debug("Peer is penalized for invalid blocks")
q.blocksFetcher.p2p.Peers().Scorers().BadResponsesScorer().Increment(response.blocksFrom)
log.WithField("pid", response.blocksFrom).Debug("Peer is penalized for invalid blocks")
} else if errors.Is(response.err, verification.ErrBlobInvalid) {
q.blocksFetcher.p2p.Peers().Scorers().BadResponsesScorer().Increment(response.blobsFrom)
log.WithField("pid", response.blobsFrom).Debug("Peer is penalized for invalid blob response")
}
return m.state, response.err
}
m.pid = response.pid
m.bwb = response.bwb
m.fetched = *response
return stateDataParsed, nil
}
}
@@ -358,19 +362,15 @@ func (q *blocksQueue) onReadyToSendEvent(ctx context.Context) eventHandlerFn {
return m.state, errInvalidInitialState
}
if len(m.bwb) == 0 {
if m.numFetched() == 0 {
return stateSkipped, nil
}
send := func() (stateID, error) {
data := &blocksQueueFetchedData{
pid: m.pid,
bwb: m.bwb,
}
select {
case <-ctx.Done():
return m.state, ctx.Err()
case q.fetchedData <- data:
case q.fetchedData <- m.fetched.blocksQueueFetchedData():
}
return stateSent, nil
}

View File

@@ -472,8 +472,8 @@ func TestBlocksQueue_onDataReceivedEvent(t *testing.T) {
updatedState, err := handlerFn(&stateMachine{
state: stateScheduled,
}, &fetchRequestResponse{
pid: "abc",
err: errSlotIsTooHigh,
blocksFrom: "abc",
err: errSlotIsTooHigh,
})
assert.ErrorContains(t, errSlotIsTooHigh.Error(), err)
assert.Equal(t, stateScheduled, updatedState)
@@ -495,9 +495,9 @@ func TestBlocksQueue_onDataReceivedEvent(t *testing.T) {
updatedState, err := handlerFn(&stateMachine{
state: stateScheduled,
}, &fetchRequestResponse{
pid: "abc",
err: errSlotIsTooHigh,
start: 256,
blocksFrom: "abc",
err: errSlotIsTooHigh,
start: 256,
})
assert.ErrorContains(t, errSlotIsTooHigh.Error(), err)
assert.Equal(t, stateScheduled, updatedState)
@@ -517,8 +517,8 @@ func TestBlocksQueue_onDataReceivedEvent(t *testing.T) {
updatedState, err := handlerFn(&stateMachine{
state: stateScheduled,
}, &fetchRequestResponse{
pid: "abc",
err: beaconsync.ErrInvalidFetchedData,
blocksFrom: "abc",
err: beaconsync.ErrInvalidFetchedData,
})
assert.ErrorContains(t, beaconsync.ErrInvalidFetchedData.Error(), err)
assert.Equal(t, stateScheduled, updatedState)
@@ -537,7 +537,7 @@ func TestBlocksQueue_onDataReceivedEvent(t *testing.T) {
wsbCopy, err := wsb.Copy()
require.NoError(t, err)
response := &fetchRequestResponse{
pid: "abc",
blocksFrom: "abc",
bwb: []blocks.BlockWithROBlobs{
{Block: blocks.ROBlock{ReadOnlySignedBeaconBlock: wsb}},
{Block: blocks.ROBlock{ReadOnlySignedBeaconBlock: wsbCopy}},
@@ -546,13 +546,15 @@ func TestBlocksQueue_onDataReceivedEvent(t *testing.T) {
fsm := &stateMachine{
state: stateScheduled,
}
assert.Equal(t, peer.ID(""), fsm.pid)
assert.Equal(t, 0, len(fsm.bwb))
assert.Equal(t, peer.ID(""), fsm.fetched.blocksFrom)
assert.Equal(t, peer.ID(""), fsm.fetched.blobsFrom)
assert.Equal(t, 0, fsm.numFetched())
updatedState, err := handlerFn(fsm, response)
assert.NoError(t, err)
assert.Equal(t, stateDataParsed, updatedState)
assert.Equal(t, response.pid, fsm.pid)
assert.DeepSSZEqual(t, response.bwb, fsm.bwb)
assert.Equal(t, response.blocksFrom, fsm.fetched.blocksFrom)
assert.Equal(t, response.blobsFrom, fsm.fetched.blobsFrom)
assert.DeepSSZEqual(t, response.bwb, fsm.fetched.bwb)
})
}
@@ -635,10 +637,10 @@ func TestBlocksQueue_onReadyToSendEvent(t *testing.T) {
queue.smm.addStateMachine(256)
queue.smm.addStateMachine(320)
queue.smm.machines[256].state = stateDataParsed
queue.smm.machines[256].pid = pidDataParsed
queue.smm.machines[256].fetched.blocksFrom = pidDataParsed
rwsb, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
queue.smm.machines[256].bwb = []blocks.BlockWithROBlobs{
queue.smm.machines[256].fetched.bwb = []blocks.BlockWithROBlobs{
{Block: rwsb},
}
@@ -669,10 +671,10 @@ func TestBlocksQueue_onReadyToSendEvent(t *testing.T) {
queue.smm.machines[256].state = stateDataParsed
queue.smm.addStateMachine(320)
queue.smm.machines[320].state = stateDataParsed
queue.smm.machines[320].pid = pidDataParsed
queue.smm.machines[320].fetched.blocksFrom = pidDataParsed
rwsb, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
queue.smm.machines[320].bwb = []blocks.BlockWithROBlobs{
queue.smm.machines[320].fetched.bwb = []blocks.BlockWithROBlobs{
{Block: rwsb},
}
@@ -700,10 +702,10 @@ func TestBlocksQueue_onReadyToSendEvent(t *testing.T) {
queue.smm.machines[256].state = stateSkipped
queue.smm.addStateMachine(320)
queue.smm.machines[320].state = stateDataParsed
queue.smm.machines[320].pid = pidDataParsed
queue.smm.machines[320].fetched.blocksFrom = pidDataParsed
rwsb, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
queue.smm.machines[320].bwb = []blocks.BlockWithROBlobs{
queue.smm.machines[320].fetched.bwb = []blocks.BlockWithROBlobs{
{Block: rwsb},
}
@@ -1199,17 +1201,17 @@ func TestBlocksQueue_stuckInUnfavourableFork(t *testing.T) {
firstFSM, ok := queue.smm.findStateMachine(forkedSlot)
require.Equal(t, true, ok)
require.Equal(t, stateDataParsed, firstFSM.state)
require.Equal(t, forkedPeer, firstFSM.pid)
require.Equal(t, forkedPeer, firstFSM.fetched.blocksFrom)
reqEnd := testForkStartSlot(t, 251) + primitives.Slot(findForkReqRangeSize())
require.Equal(t, int(reqEnd-forkedSlot), len(firstFSM.bwb))
require.Equal(t, forkedSlot, firstFSM.bwb[0].Block.Block().Slot())
require.Equal(t, int(reqEnd-forkedSlot), len(firstFSM.fetched.bwb))
require.Equal(t, forkedSlot, firstFSM.fetched.bwb[0].Block.Block().Slot())
// Assert that forked data from chain2 is available (within 64 fetched blocks).
for i, blk := range chain2[forkedSlot:] {
if i >= len(firstFSM.bwb) {
if i >= len(firstFSM.fetched.bwb) {
break
}
rootFromFSM := firstFSM.bwb[i].Block.Root()
rootFromFSM := firstFSM.fetched.bwb[i].Block.Root()
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
assert.Equal(t, blkRoot, rootFromFSM)
@@ -1217,7 +1219,7 @@ func TestBlocksQueue_stuckInUnfavourableFork(t *testing.T) {
// Assert that machines are in the expected state.
startSlot = forkedEpochStartSlot.Add(1 + blocksPerRequest)
require.Equal(t, int(blocksPerRequest)-int(forkedSlot-(forkedEpochStartSlot+1)), len(firstFSM.bwb))
require.Equal(t, int(blocksPerRequest)-int(forkedSlot-(forkedEpochStartSlot+1)), len(firstFSM.fetched.bwb))
for i := startSlot; i < startSlot.Add(blocksPerRequest*(lookaheadSteps-1)); i += primitives.Slot(blocksPerRequest) {
fsm, ok := queue.smm.findStateMachine(i)
require.Equal(t, true, ok)

View File

@@ -24,8 +24,8 @@ func (q *blocksQueue) resetFromFork(fork *forkData) error {
return err
}
fsm := q.smm.addStateMachine(firstBlock.Slot())
fsm.pid = fork.peer
fsm.bwb = fork.bwb
fsm.fetched.bwb = fork.bwb
fsm.fetched.blocksFrom, fsm.fetched.blobsFrom = fork.blocksFrom, fork.blobsFrom
fsm.state = stateDataParsed
// The rest of machines are in skipped state.

View File

@@ -0,0 +1,219 @@
package initialsync
import (
"context"
"testing"
"time"
"github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain"
mock "github.com/OffchainLabs/prysm/v6/beacon-chain/blockchain/testing"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/peers/peerdata"
p2pt "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/testing"
"github.com/OffchainLabs/prysm/v6/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v6/beacon-chain/sync"
"github.com/OffchainLabs/prysm/v6/beacon-chain/verification"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v6/testing/assert"
"github.com/OffchainLabs/prysm/v6/testing/require"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/pkg/errors"
)
type testDownscorePeer int
const (
testDownscoreNeither testDownscorePeer = iota
testDownscoreBlock
testDownscoreBlob
)
func peerIDForTestDownscore(w testDownscorePeer, name string) peer.ID {
switch w {
case testDownscoreBlock:
return peer.ID("block" + name)
case testDownscoreBlob:
return peer.ID("blob" + name)
default:
return ""
}
}
func TestUpdatePeerScorerStats(t *testing.T) {
cases := []struct {
name string
err error
processed uint64
downPeer testDownscorePeer
}{
{
name: "invalid block",
err: blockchain.ErrInvalidPayload,
downPeer: testDownscoreBlock,
processed: 10,
},
{
name: "invalid blob",
err: verification.ErrBlobIndexInvalid,
downPeer: testDownscoreBlob,
processed: 3,
},
{
name: "not validity error",
err: errors.New("test"),
processed: 32,
},
{
name: "no error",
processed: 32,
},
}
s := &Service{
cfg: &Config{
P2P: p2pt.NewTestP2P(t),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
data := &blocksQueueFetchedData{
blocksFrom: peerIDForTestDownscore(testDownscoreBlock, c.name),
blobsFrom: peerIDForTestDownscore(testDownscoreBlob, c.name),
}
s.updatePeerScorerStats(data, c.processed, c.err)
if c.err != nil && c.downPeer != testDownscoreNeither {
switch c.downPeer {
case testDownscoreBlock:
// block should be downscored
blocksCount, err := s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Count(data.blocksFrom)
require.NoError(t, err)
require.Equal(t, 1, blocksCount)
// blob should not be downscored - also we expect a not found error since peer scoring did not interact with blobs
blobCount, err := s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Count(data.blobsFrom)
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blobCount)
case testDownscoreBlob:
// block should not be downscored - also we expect a not found error since peer scoring did not interact with blocks
blocksCount, err := s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Count(data.blocksFrom)
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blocksCount)
// blob should be downscored
blobCount, err := s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Count(data.blobsFrom)
require.NoError(t, err)
require.Equal(t, 1, blobCount)
}
assert.Equal(t, uint64(0), s.cfg.P2P.Peers().Scorers().BlockProviderScorer().ProcessedBlocks(data.blocksFrom))
return
}
// block should not be downscored - also we expect a not found error since peer scoring did not interact with blocks
blocksCount, err := s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Count(data.blocksFrom)
// The scorer will know about the the block peer because it will have a processed blocks count
require.NoError(t, err)
require.Equal(t, 0, blocksCount)
// no downscore, so scorer doesn't know the peer
blobCount, err := s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Count(data.blobsFrom)
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blobCount)
assert.Equal(t, c.processed, s.cfg.P2P.Peers().Scorers().BlockProviderScorer().ProcessedBlocks(data.blocksFrom))
})
}
}
func TestOnDataReceivedDownscore(t *testing.T) {
cases := []struct {
name string
err error
downPeer testDownscorePeer
}{
{
name: "invalid block",
err: sync.ErrInvalidFetchedData,
downPeer: testDownscoreBlock,
},
{
name: "invalid blob",
err: errors.Wrap(verification.ErrBlobInvalid, "test"),
downPeer: testDownscoreBlob,
},
{
name: "not validity error",
err: errors.New("test"),
},
{
name: "no error",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
data := &fetchRequestResponse{
blocksFrom: peerIDForTestDownscore(testDownscoreBlock, c.name),
blobsFrom: peerIDForTestDownscore(testDownscoreBlob, c.name),
err: c.err,
}
if c.downPeer == testDownscoreBlob {
require.Equal(t, true, verification.IsBlobValidationFailure(c.err))
}
ctx := context.Background()
p2p := p2pt.NewTestP2P(t)
mc := &mock.ChainService{Genesis: time.Now(), ValidatorsRoot: [32]byte{}}
fetcher := newBlocksFetcher(ctx, &blocksFetcherConfig{
chain: mc,
p2p: p2p,
clock: startup.NewClock(mc.Genesis, mc.ValidatorsRoot),
})
q := newBlocksQueue(ctx, &blocksQueueConfig{
p2p: p2p,
blocksFetcher: fetcher,
highestExpectedSlot: primitives.Slot(32),
chain: mc})
sm := q.smm.addStateMachine(0)
sm.state = stateScheduled
handle := q.onDataReceivedEvent(context.Background())
endState, err := handle(sm, data)
if c.err != nil {
require.ErrorIs(t, err, c.err)
} else {
require.NoError(t, err)
}
// state machine should stay in "scheduled" if there's an error
// and transition to "data parsed" if there's no error
if c.err != nil {
require.Equal(t, stateScheduled, endState)
} else {
require.Equal(t, stateDataParsed, endState)
}
if c.err != nil && c.downPeer != testDownscoreNeither {
switch c.downPeer {
case testDownscoreBlock:
// block should be downscored
blocksCount, err := p2p.Peers().Scorers().BadResponsesScorer().Count(data.blocksFrom)
require.NoError(t, err)
require.Equal(t, 1, blocksCount)
// blob should not be downscored - also we expect a not found error since peer scoring did not interact with blobs
blobCount, err := p2p.Peers().Scorers().BadResponsesScorer().Count(data.blobsFrom)
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blobCount)
case testDownscoreBlob:
// block should not be downscored - also we expect a not found error since peer scoring did not interact with blocks
blocksCount, err := p2p.Peers().Scorers().BadResponsesScorer().Count(data.blocksFrom)
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blocksCount)
// blob should be downscored
blobCount, err := p2p.Peers().Scorers().BadResponsesScorer().Count(data.blobsFrom)
require.NoError(t, err)
require.Equal(t, 1, blobCount)
}
assert.Equal(t, uint64(0), p2p.Peers().Scorers().BlockProviderScorer().ProcessedBlocks(data.blocksFrom))
return
}
// block should not be downscored - also we expect a not found error since peer scoring did not interact with blocks
blocksCount, err := p2p.Peers().Scorers().BadResponsesScorer().Count(data.blocksFrom)
// no downscore, so scorer doesn't know the peer
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blocksCount)
blobCount, err := p2p.Peers().Scorers().BadResponsesScorer().Count(data.blobsFrom)
// no downscore, so scorer doesn't know the peer
require.ErrorIs(t, err, peerdata.ErrPeerUnknown)
require.Equal(t, -1, blobCount)
})
}
}

View File

@@ -6,11 +6,9 @@ import (
"sort"
"time"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
prysmTime "github.com/OffchainLabs/prysm/v6/time"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/libp2p/go-libp2p/core/peer"
)
const (
@@ -45,8 +43,7 @@ type stateMachine struct {
smm *stateMachineManager
start primitives.Slot
state stateID
pid peer.ID
bwb []blocks.BlockWithROBlobs
fetched fetchRequestResponse
updated time.Time
}
@@ -78,7 +75,7 @@ func (smm *stateMachineManager) addStateMachine(startSlot primitives.Slot) *stat
smm: smm,
start: startSlot,
state: stateNew,
bwb: []blocks.BlockWithROBlobs{},
fetched: fetchRequestResponse{},
updated: prysmTime.Now(),
}
smm.recalculateMachineAttribs()
@@ -90,7 +87,7 @@ func (smm *stateMachineManager) removeStateMachine(startSlot primitives.Slot) er
if _, ok := smm.machines[startSlot]; !ok {
return fmt.Errorf("state for machine %v is not found", startSlot)
}
smm.machines[startSlot].bwb = nil
smm.machines[startSlot].fetched = fetchRequestResponse{}
delete(smm.machines, startSlot)
smm.recalculateMachineAttribs()
return nil
@@ -187,6 +184,10 @@ func (m *stateMachine) isLast() bool {
return m.start == m.smm.keys[len(m.smm.keys)-1]
}
func (m *stateMachine) numFetched() int {
return len(m.fetched.bwb)
}
// String returns human-readable representation of a FSM state.
func (m *stateMachine) String() string {
return fmt.Sprintf("{%d:%s}", slots.ToEpoch(m.start), m.state)

View File

@@ -14,7 +14,6 @@ import (
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/paulbellamy/ratecounter"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -127,7 +126,7 @@ func (s *Service) syncToNonFinalizedEpoch(ctx context.Context) error {
}
for data := range queue.fetchedData {
count, err := s.processFetchedDataRegSync(ctx, data)
s.updatePeerScorerStats(data.pid, count, err)
s.updatePeerScorerStats(data, count, err)
}
log.WithFields(logrus.Fields{
"syncedSlot": s.cfg.Chain.HeadSlot(),
@@ -147,7 +146,7 @@ func (s *Service) processFetchedData(ctx context.Context, data *blocksQueueFetch
if err != nil {
log.WithError(err).Warn("Skip processing batched blocks")
}
s.updatePeerScorerStats(data.pid, count, err)
s.updatePeerScorerStats(data, count, err)
}
// processFetchedDataRegSync processes data received from queue.
@@ -339,18 +338,19 @@ func isPunishableError(err error) bool {
}
// updatePeerScorerStats adjusts monitored metrics for a peer.
func (s *Service) updatePeerScorerStats(pid peer.ID, count uint64, err error) {
if pid == "" {
return
}
func (s *Service) updatePeerScorerStats(data *blocksQueueFetchedData, count uint64, err error) {
if isPunishableError(err) {
log.WithError(err).WithField("peer_id", pid).Warn("Incrementing peers bad response count")
s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(pid)
if verification.IsBlobValidationFailure(err) {
log.WithError(err).WithField("peer_id", data.blobsFrom).Warn("Downscoring peer for invalid blobs")
s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(data.blobsFrom)
} else {
log.WithError(err).WithField("peer_id", data.blocksFrom).Warn("Downscoring peer for invalid blocks")
s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(data.blocksFrom)
}
// If the error is punishable, exit here so that we don't give them credit for providing bad blocks.
return
}
scorer := s.cfg.P2P.Peers().Scorers().BlockProviderScorer()
scorer.IncrementProcessedBlocks(pid, count)
s.cfg.P2P.Peers().Scorers().BlockProviderScorer().IncrementProcessedBlocks(data.blocksFrom, count)
}
// isProcessedBlock checks DB and local cache for presence of a given block, to avoid duplicates.

View File

@@ -156,7 +156,7 @@ func readChunkEncodedBlobsLowMax(t *testing.T, s *Service, expect []*expectedBlo
}
return func(stream network.Stream) {
_, err := readChunkEncodedBlobs(stream, encoding, ctxMap, vf, 1)
require.ErrorIs(t, err, ErrInvalidFetchedData)
require.ErrorIs(t, err, errMaxRequestBlobSidecarsExceeded)
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/encoder"
p2ptypes "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/types"
"github.com/OffchainLabs/prysm/v6/beacon-chain/verification"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v6/consensus-types/interfaces"
@@ -30,14 +31,14 @@ var errBlobUnmarshal = errors.New("Could not unmarshal chunk-encoded blob")
var (
// ErrInvalidFetchedData is used to signal that an error occurred which should result in peer downscoring.
ErrInvalidFetchedData = errors.New("invalid data returned from peer")
errBlobIndexOutOfBounds = errors.Wrap(ErrInvalidFetchedData, "blob index out of range")
errMaxRequestBlobSidecarsExceeded = errors.Wrap(ErrInvalidFetchedData, "peer exceeded req blob chunk tx limit")
errChunkResponseSlotNotAsc = errors.Wrap(ErrInvalidFetchedData, "blob slot not higher than previous block root")
errChunkResponseIndexNotAsc = errors.Wrap(ErrInvalidFetchedData, "blob indices for a block must start at 0 and increase by 1")
errUnrequested = errors.Wrap(ErrInvalidFetchedData, "received BlobSidecar in response that was not requested")
errBlobResponseOutOfBounds = errors.Wrap(ErrInvalidFetchedData, "received BlobSidecar with slot outside BlobSidecarsByRangeRequest bounds")
errChunkResponseBlockMismatch = errors.Wrap(ErrInvalidFetchedData, "blob block details do not match")
errChunkResponseParentMismatch = errors.Wrap(ErrInvalidFetchedData, "parent root for response element doesn't match previous element root")
errBlobIndexOutOfBounds = errors.Wrap(verification.ErrBlobInvalid, "blob index out of range")
errMaxRequestBlobSidecarsExceeded = errors.Wrap(verification.ErrBlobInvalid, "peer exceeded req blob chunk tx limit")
errChunkResponseSlotNotAsc = errors.Wrap(verification.ErrBlobInvalid, "blob slot not higher than previous block root")
errChunkResponseIndexNotAsc = errors.Wrap(verification.ErrBlobInvalid, "blob indices for a block must start at 0 and increase by 1")
errUnrequested = errors.Wrap(verification.ErrBlobInvalid, "received BlobSidecar in response that was not requested")
errBlobResponseOutOfBounds = errors.Wrap(verification.ErrBlobInvalid, "received BlobSidecar with slot outside BlobSidecarsByRangeRequest bounds")
errChunkResponseBlockMismatch = errors.Wrap(verification.ErrBlobInvalid, "blob block details do not match")
errChunkResponseParentMismatch = errors.Wrap(verification.ErrBlobInvalid, "parent root for response element doesn't match previous element root")
)
// BeaconBlockProcessor defines a block processing function, which allows to start utilizing

View File

@@ -12,6 +12,7 @@ import (
p2ptest "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/testing"
p2pTypes "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/types"
"github.com/OffchainLabs/prysm/v6/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v6/beacon-chain/verification"
fieldparams "github.com/OffchainLabs/prysm/v6/config/fieldparams"
"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
@@ -877,3 +878,7 @@ func TestSendBlobsByRangeRequest(t *testing.T) {
assert.Equal(t, int(totalElectraBlobs), len(blobs))
})
}
func TestErrInvalidFetchedDataDistinction(t *testing.T) {
require.Equal(t, false, errors.Is(ErrInvalidFetchedData, verification.ErrBlobInvalid))
}

View File

@@ -350,10 +350,9 @@ func (s *Service) wrapAndReportValidation(topic string, v wrappedVal) (string, p
}
}
// reValidateSubscriptions unsubscribe from topics we are currently subscribed to but that are
// pruneSubscriptions unsubscribe from topics we are currently subscribed to but that are
// not in the list of wanted subnets.
// TODO: Rename this functions as it does not only revalidate subscriptions.
func (s *Service) reValidateSubscriptions(
func (s *Service) pruneSubscriptions(
subscriptions map[uint64]*pubsub.Subscription,
wantedSubs []uint64,
topicFormat string,
@@ -452,7 +451,7 @@ func (s *Service) subscribeToSubnets(
"digest": fmt.Sprintf("%#x", digest),
"subnets": description,
}).Debug("Subnets with this digest are no longer valid, unsubscribing from all of them")
s.reValidateSubscriptions(subscriptions, []uint64{}, topicFormat, digest)
s.pruneSubscriptions(subscriptions, []uint64{}, topicFormat, digest)
return false
}
@@ -460,7 +459,7 @@ func (s *Service) subscribeToSubnets(
subnetsToSubscribeIndex := getSubnetsToSubscribe(currentSlot)
// Remove subscriptions that are no longer wanted.
s.reValidateSubscriptions(subscriptions, subnetsToSubscribeIndex, topicFormat, digest)
s.pruneSubscriptions(subscriptions, subnetsToSubscribeIndex, topicFormat, digest)
// Subscribe to wanted subnets.
for _, subnetIndex := range subnetsToSubscribeIndex {

View File

@@ -308,7 +308,7 @@ func TestRevalidateSubscription_CorrectlyFormatsTopic(t *testing.T) {
subscriptions[2], err = r.cfg.p2p.SubscribeToTopic(fullTopic)
require.NoError(t, err)
r.reValidateSubscriptions(subscriptions, []uint64{2}, defaultTopic, digest)
r.pruneSubscriptions(subscriptions, []uint64{2}, defaultTopic, digest)
require.LogsDoNotContain(t, hook, "Could not unregister topic validator")
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v6/monitoring/tracing"
"github.com/OffchainLabs/prysm/v6/monitoring/tracing/trace"
ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v6/runtime/version"
prysmTime "github.com/OffchainLabs/prysm/v6/time"
"github.com/OffchainLabs/prysm/v6/time/slots"
@@ -31,8 +32,9 @@ import (
)
var (
ErrOptimisticParent = errors.New("parent of the block is optimistic")
errRejectCommitmentLen = errors.New("[REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer")
ErrOptimisticParent = errors.New("parent of the block is optimistic")
errRejectCommitmentLen = errors.New("[REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer")
ErrSlashingSignatureFailure = errors.New("proposer slashing signature verification failed")
)
// validateBeaconBlockPubSub checks that the incoming block has a valid BLS signature.
@@ -109,6 +111,16 @@ func (s *Service) validateBeaconBlockPubSub(ctx context.Context, pid peer.ID, ms
// Verify the block is the first block received for the proposer for the slot.
if s.hasSeenBlockIndexSlot(blk.Block().Slot(), blk.Block().ProposerIndex()) {
// Attempt to detect and broadcast equivocation before ignoring
err = s.detectAndBroadcastEquivocation(ctx, blk)
if err != nil {
// If signature verification fails, reject the block
if errors.Is(err, ErrSlashingSignatureFailure) {
return pubsub.ValidationReject, err
}
// In case there is some other error log but don't reject
log.WithError(err).Debug("Could not detect/broadcast equivocation")
}
return pubsub.ValidationIgnore, nil
}
@@ -469,3 +481,74 @@ func getBlockFields(b interfaces.ReadOnlySignedBeaconBlock) logrus.Fields {
"version": b.Block().Version(),
}
}
// detectAndBroadcastEquivocation checks if the given block is an equivocating block by comparing it with
// the head block. If the blocks are from the same slot and proposer but have different signatures,
// it creates and broadcasts a proposer slashing object after verification.
func (s *Service) detectAndBroadcastEquivocation(ctx context.Context, blk interfaces.ReadOnlySignedBeaconBlock) error {
slot := blk.Block().Slot()
proposerIndex := blk.Block().ProposerIndex()
// Get head block for comparison
headBlock, err := s.cfg.chain.HeadBlock(ctx)
if err != nil {
return errors.Wrap(err, "could not get head block")
}
// Only proceed if this block is from same slot and proposer as head
if headBlock.Block().Slot() != slot || headBlock.Block().ProposerIndex() != proposerIndex {
return nil
}
// Compare signatures
sig1 := blk.Signature()
sig2 := headBlock.Signature()
// If signatures match, these are the same block
if sig1 == sig2 {
return nil
}
// Extract headers for slashing
header1, err := blk.Header()
if err != nil {
return errors.Wrap(err, "could not get header from new block")
}
header2, err := headBlock.Header()
if err != nil {
return errors.Wrap(err, "could not get header from head block")
}
slashing := &ethpb.ProposerSlashing{
Header_1: header1,
Header_2: header2,
}
// Get state for verification
headState, err := s.cfg.chain.HeadStateReadOnly(ctx)
if err != nil {
return errors.Wrap(err, "could not get head state")
}
// Verify the slashing against current state
if err := blocks.VerifyProposerSlashing(headState, slashing); err != nil {
if errors.Is(err, blocks.ErrCouldNotVerifyBlockHeader) {
return errors.Wrap(ErrSlashingSignatureFailure, err.Error())
}
return errors.Wrap(err, "could not verify proposer slashing")
}
// Broadcast if verification passes
if !features.Get().DisableBroadcastSlashings {
if err := s.cfg.p2p.Broadcast(ctx, slashing); err != nil {
return errors.Wrap(err, "could not broadcast slashing object")
}
}
// Insert into slashing pool
if err := s.cfg.slashingPool.InsertProposerSlashing(ctx, headState, slashing); err != nil {
return errors.Wrap(err, "could not insert proposer slashing into pool")
}
return nil
}

View File

@@ -18,6 +18,7 @@ import (
dbtest "github.com/OffchainLabs/prysm/v6/beacon-chain/db/testing"
doublylinkedtree "github.com/OffchainLabs/prysm/v6/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/OffchainLabs/prysm/v6/beacon-chain/operations/attestations"
slashingsmock "github.com/OffchainLabs/prysm/v6/beacon-chain/operations/slashings/mock"
"github.com/OffchainLabs/prysm/v6/beacon-chain/p2p"
p2ptest "github.com/OffchainLabs/prysm/v6/beacon-chain/p2p/testing"
"github.com/OffchainLabs/prysm/v6/beacon-chain/startup"
@@ -713,8 +714,21 @@ func TestValidateBeaconBlockPubSub_SeenProposerSlot(t *testing.T) {
msg.Signature, err = signing.ComputeDomainAndSign(beaconState, 0, msg.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[proposerIdx])
require.NoError(t, err)
chainService := &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0),
State: beaconState,
// Create a clone of the same block (same signature, not an equivocation)
msgClone := util.NewBeaconBlock()
msgClone.Block.Slot = 1
msgClone.Block.ProposerIndex = proposerIdx
msgClone.Block.ParentRoot = bRoot[:]
msgClone.Signature = msg.Signature // Use the same signature
signedBlock, err := blocks.NewSignedBeaconBlock(msg)
require.NoError(t, err)
slashingPool := &slashingsmock.PoolMock{}
chainService := &mock.ChainService{
Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0),
State: beaconState,
Block: signedBlock, // Set the first block as the head block
FinalizedCheckPoint: &ethpb.Checkpoint{
Epoch: 0,
Root: make([]byte, 32),
@@ -728,6 +742,7 @@ func TestValidateBeaconBlockPubSub_SeenProposerSlot(t *testing.T) {
chain: chainService,
clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot),
blockNotifier: chainService.BlockNotifier(),
slashingPool: slashingPool,
},
seenBlockCache: lruwrpr.New(10),
badBlockCache: lruwrpr.New(10),
@@ -735,10 +750,15 @@ func TestValidateBeaconBlockPubSub_SeenProposerSlot(t *testing.T) {
seenPendingBlocks: make(map[[32]byte]bool),
}
// Mark the proposer/slot as seen
r.setSeenBlockIndexSlot(msg.Block.Slot, msg.Block.ProposerIndex)
time.Sleep(10 * time.Millisecond) // Wait for cached value to pass through buffers
// Prepare and validate the second message (clone)
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
_, err = p.Encoding().EncodeGossip(buf, msgClone)
require.NoError(t, err)
topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
topic := p2p.GossipTypeMapping[reflect.TypeOf(msgClone)]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -748,11 +768,14 @@ func TestValidateBeaconBlockPubSub_SeenProposerSlot(t *testing.T) {
Topic: &topic,
},
}
r.setSeenBlockIndexSlot(msg.Block.Slot, msg.Block.ProposerIndex)
time.Sleep(10 * time.Millisecond) // Wait for cached value to pass through buffers.
// Since this is not an equivocation (same signature), it should be ignored
res, err := r.validateBeaconBlockPubSub(ctx, "", m)
assert.NoError(t, err)
assert.Equal(t, res, pubsub.ValidationIgnore, "seen proposer block should be ignored")
assert.Equal(t, pubsub.ValidationIgnore, res, "block with same signature should be ignored")
// Verify no slashings were created
assert.Equal(t, 0, len(slashingPool.PendingPropSlashings), "Expected no slashings for same signature")
}
func TestValidateBeaconBlockPubSub_FilterByFinalizedEpoch(t *testing.T) {
@@ -1495,3 +1518,218 @@ func Test_validateDenebBeaconBlock(t *testing.T) {
require.NoError(t, err)
require.ErrorIs(t, validateDenebBeaconBlock(bdb.Block()), errRejectCommitmentLen)
}
func TestDetectAndBroadcastEquivocation(t *testing.T) {
ctx := context.Background()
p := p2ptest.NewTestP2P(t)
beaconState, privKeys := util.DeterministicGenesisState(t, 100)
t.Run("no equivocation", func(t *testing.T) {
block := util.NewBeaconBlock()
block.Block.Slot = 1
block.Block.ProposerIndex = 0
sig, err := signing.ComputeDomainAndSign(beaconState, 0, block.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
block.Signature = sig
// Create head block with different slot/proposer
headBlock := util.NewBeaconBlock()
headBlock.Block.Slot = 2 // Different slot
headBlock.Block.ProposerIndex = 1 // Different proposer
signedHeadBlock, err := blocks.NewSignedBeaconBlock(headBlock)
require.NoError(t, err)
chainService := &mock.ChainService{
State: beaconState,
Genesis: time.Now(),
Block: signedHeadBlock,
}
slashingPool := &slashingsmock.PoolMock{}
r := &Service{
cfg: &config{
p2p: p,
chain: chainService,
slashingPool: slashingPool,
},
seenBlockCache: lruwrpr.New(10),
}
signedBlock, err := blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)
err = r.detectAndBroadcastEquivocation(ctx, signedBlock)
require.NoError(t, err)
assert.Equal(t, 0, len(slashingPool.PendingPropSlashings), "Expected no slashings")
})
t.Run("equivocation detected", func(t *testing.T) {
// Create head block
headBlock := util.NewBeaconBlock()
headBlock.Block.Slot = 1
headBlock.Block.ProposerIndex = 0
headBlock.Block.ParentRoot = bytesutil.PadTo([]byte("parent1"), 32)
sig1, err := signing.ComputeDomainAndSign(beaconState, 0, headBlock.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
headBlock.Signature = sig1
// Create second block with same slot/proposer but different contents
newBlock := util.NewBeaconBlock()
newBlock.Block.Slot = 1
newBlock.Block.ProposerIndex = 0
newBlock.Block.ParentRoot = bytesutil.PadTo([]byte("parent2"), 32)
sig2, err := signing.ComputeDomainAndSign(beaconState, 0, newBlock.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
newBlock.Signature = sig2
signedHeadBlock, err := blocks.NewSignedBeaconBlock(headBlock)
require.NoError(t, err)
slashingPool := &slashingsmock.PoolMock{}
chainService := &mock.ChainService{
State: beaconState,
Genesis: time.Now(),
Block: signedHeadBlock,
}
r := &Service{
cfg: &config{
p2p: p,
chain: chainService,
slashingPool: slashingPool,
},
seenBlockCache: lruwrpr.New(10),
}
signedNewBlock, err := blocks.NewSignedBeaconBlock(newBlock)
require.NoError(t, err)
err = r.detectAndBroadcastEquivocation(ctx, signedNewBlock)
require.NoError(t, err)
// Verify slashing was inserted
require.Equal(t, 1, len(slashingPool.PendingPropSlashings), "Expected a slashing to be inserted")
slashing := slashingPool.PendingPropSlashings[0]
assert.Equal(t, primitives.ValidatorIndex(0), slashing.Header_1.Header.ProposerIndex, "Wrong proposer index")
assert.Equal(t, primitives.Slot(1), slashing.Header_1.Header.Slot, "Wrong slot")
})
t.Run("same signature", func(t *testing.T) {
// Create block
block := util.NewBeaconBlock()
block.Block.Slot = 1
block.Block.ProposerIndex = 0
sig, err := signing.ComputeDomainAndSign(beaconState, 0, block.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
block.Signature = sig
signedBlock, err := blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)
slashingPool := &slashingsmock.PoolMock{}
chainService := &mock.ChainService{
State: beaconState,
Genesis: time.Now(),
Block: signedBlock,
}
r := &Service{
cfg: &config{
p2p: p,
chain: chainService,
slashingPool: slashingPool,
},
seenBlockCache: lruwrpr.New(10),
}
err = r.detectAndBroadcastEquivocation(ctx, signedBlock)
require.NoError(t, err)
assert.Equal(t, 0, len(slashingPool.PendingPropSlashings), "Expected no slashings for same signature")
})
t.Run("head state error", func(t *testing.T) {
block := util.NewBeaconBlock()
block.Block.Slot = 1
block.Block.ProposerIndex = 0
block.Block.ParentRoot = bytesutil.PadTo([]byte("parent1"), 32)
sig1, err := signing.ComputeDomainAndSign(beaconState, 0, block.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
block.Signature = sig1
headBlock := util.NewBeaconBlock()
headBlock.Block.Slot = 1 // Same slot
headBlock.Block.ProposerIndex = 0 // Same proposer
headBlock.Block.ParentRoot = bytesutil.PadTo([]byte("parent2"), 32) // Different parent root
sig2, err := signing.ComputeDomainAndSign(beaconState, 0, headBlock.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
headBlock.Signature = sig2
signedBlock, err := blocks.NewSignedBeaconBlock(block)
require.NoError(t, err)
signedHeadBlock, err := blocks.NewSignedBeaconBlock(headBlock)
require.NoError(t, err)
chainService := &mock.ChainService{
State: nil,
Block: signedHeadBlock,
HeadStateErr: errors.New("could not get head state"),
}
r := &Service{
cfg: &config{
p2p: p,
chain: chainService,
slashingPool: &slashingsmock.PoolMock{},
},
seenBlockCache: lruwrpr.New(10),
}
err = r.detectAndBroadcastEquivocation(ctx, signedBlock)
require.ErrorContains(t, "could not get head state", err)
})
t.Run("signature verification failure", func(t *testing.T) {
// Create head block
headBlock := util.NewBeaconBlock()
headBlock.Block.Slot = 1
headBlock.Block.ProposerIndex = 0
sig1, err := signing.ComputeDomainAndSign(beaconState, 0, headBlock.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[0])
require.NoError(t, err)
headBlock.Signature = sig1
// Create test block with invalid signature
newBlock := util.NewBeaconBlock()
newBlock.Block.Slot = 1
newBlock.Block.ProposerIndex = 0
newBlock.Block.ParentRoot = bytesutil.PadTo([]byte("different"), 32)
// generate invalid signature
invalidSig := make([]byte, 96)
copy(invalidSig, []byte("invalid signature"))
newBlock.Signature = invalidSig
signedHeadBlock, err := blocks.NewSignedBeaconBlock(headBlock)
require.NoError(t, err)
signedNewBlock, err := blocks.NewSignedBeaconBlock(newBlock)
require.NoError(t, err)
slashingPool := &slashingsmock.PoolMock{}
chainService := &mock.ChainService{
State: beaconState,
Genesis: time.Now(),
Block: signedHeadBlock,
}
r := &Service{
cfg: &config{
p2p: p,
chain: chainService,
slashingPool: slashingPool,
},
seenBlockCache: lruwrpr.New(10),
}
err = r.detectAndBroadcastEquivocation(ctx, signedNewBlock)
require.ErrorIs(t, err, ErrSlashingSignatureFailure)
})
}

View File

@@ -16,6 +16,11 @@ func AsVerificationFailure(err error) error {
return errors.Join(ErrInvalid, err)
}
// IsBlobValidationFailure checks if the given error is a blob validation failure.
func IsBlobValidationFailure(err error) bool {
return errors.Is(err, ErrBlobInvalid)
}
var (
// ErrBlobInvalid is joined with all other blob verification errors. This enables other packages to check for any sort of
// verification error at one point, like sync code checking for peer scoring purposes.

View File

@@ -0,0 +1,3 @@
### Added
- Add light client p2p broadcaster functions.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
### Fixed
- Adding fork guard to attestation api endpoints so that it doesn't accidentally include wrong attestation types in the pool.

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +0,0 @@
### Changed
- The validator client will no longer use the full list of committee values but instead use the committee length and validator committee index.
### Removed
- Remove /eth/v1/beacon/states/head/committees call when getting duties.

View File

@@ -1,3 +0,0 @@
### Changed
- removed old web3signer metrics in favor for a universal one

View File

@@ -1,3 +0,0 @@
### Ignored
- removed redundant mock validator for unit tests, we should just have 1 implementation.

View File

@@ -1,3 +0,0 @@
### Ignored
- small optimization on the computeOnChainAggregate function

View File

@@ -1,3 +0,0 @@
### Fixed
- When using a DV, send all aggregations for a slot and committee.

View File

@@ -1,2 +0,0 @@
### Changed
- More efficient query method for stategen to retrieve blocks between a given state and the replay target block. This avoids attempting to look up blocks that are not needed for head replay queries, which may be missing due to a previous rollback bug.

View File

@@ -1,2 +0,0 @@
### Fixed
- Peers giving invalid data in range syncing are now downscored.

View File

@@ -1,2 +0,0 @@
### Fixed
- Clean up dangling block index entries for blocks that were previously deleted by incomplete cleanup code.

View File

@@ -1,2 +0,0 @@
### Ignored
- consolidate several statagen tests into a comprehensive/consistent table-drive test.

View File

@@ -0,0 +1,3 @@
### Added
- Added immediate broadcasting of proposer slashings when equivocating blocks are detected during block processing.
- Added 2 new errors: `HeadStateErr` and `ErrCouldNotVerifyBlockHeader`

3
changelog/manu-kzg.md Normal file
View File

@@ -0,0 +1,3 @@
### Added
- PeerDAS related KZG wrappers.

View File

@@ -0,0 +1,5 @@
### Added
- `UpgradeToFulu` spectests.
### Fixed
- `UpgradeToFulu`

View File

@@ -1,3 +0,0 @@
### Added
- prysmctl option in wrapper script to generate devnet ssz. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15145)

View File

@@ -0,0 +1,3 @@
### Fixed
- avoid nondeterministic default fork value when generate genesis. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15151)

View File

@@ -1,3 +0,0 @@
### Fixed
- Fix Committee Index Check For Aggregates.

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixes our generated ssz files to have the correct package name.

View File

@@ -1,3 +0,0 @@
### Fixed
- Fixed a bug in checking for attestation lengths in our block.

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixes our blob sidecar by root request lists for electra.

View File

@@ -0,0 +1,6 @@
### Security
- Fix CVE-2025-22869
- Fix CVE-2025-22870
- Fix CVE-2025-22872
- Fix CVE-2025-30204

View File

@@ -1,3 +0,0 @@
### Fixed
- Fix State Getter for pending withdrawal balance.

View File

@@ -1,3 +0,0 @@
### Fixed
- Fixed a bug in consolidation request processing.

View File

@@ -1,3 +0,0 @@
### Ignored
- Revert execution requests in E2E.

View File

@@ -1,3 +0,0 @@
### Ignored
- Add helper for dependent root in forkchoice.

View File

@@ -1,3 +0,0 @@
### Ignored
- Use forkchoice dependent root helper in new head event feed.

View File

@@ -1,3 +0,0 @@
### Ignored
- Use headstate to validate canonical attestations with an old target

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixed deadlines in update duties.

View File

@@ -0,0 +1,2 @@
### Changed
- Refactored internal function `reValidateSubscriptions` to `pruneSubscriptions` in `beacon-chain/sync/subscriber.go` for improved clarity, addressing a TODO comment.

View File

@@ -0,0 +1,3 @@
### Changed
- Updated geth to v1.15.9

View File

@@ -0,0 +1,3 @@
### Ignored
- Updated changelog for v6.0.0 release

View File

@@ -1,3 +0,0 @@
#### Ignored
- Updated changelog for v5.3.2

View File

@@ -1,3 +0,0 @@
### Added
- Implemented validator identities Beacon API endpoint.

View File

@@ -1,3 +0,0 @@
### Fixed
- Fix filtering by committee index post-Electra in `ListAttestationsV2`.

View File

@@ -1,3 +0,0 @@
### Changed
- Deprecated everything related with the gRPC API.

View File

@@ -1,3 +0,0 @@
#### Ignored
- Allow hex strings in `/eth/v1/beacon/states/{state_id}/root` endpoint.

View File

@@ -1,3 +0,0 @@
### Removed
- Remove `disable-committee-aware-packing` flag.

View File

@@ -1,3 +0,0 @@
### Removed
- Remove deprecated flags for the major release.

View File

@@ -1,3 +0,0 @@
### Removed
- Removed Beacon API endpoints which have been deprecated at the Deneb fork.

View File

@@ -1,3 +0,0 @@
### Ignored
- Use the built-in min to simplify the code

View File

@@ -1,3 +0,0 @@
### Fixed
- Use latest state to pack attestation.

View File

@@ -1,3 +0,0 @@
### Ignored
- Update spec tests to v1.5.0-beta.4 version

View File

@@ -1,3 +0,0 @@
### Changed
- Migrate Prysm repo to Offchain Labs organization ahead of Pectra upgrade v6

View File

@@ -1,3 +0,0 @@
### Removed
- Removed unused hack scripts.

View File

@@ -1,3 +0,0 @@
### Ignored
- Mainnet config returns copied config instead of assume caller to copy.

View File

@@ -1,3 +0,0 @@
### Added
- Add support for Electra fork epoch

View File

@@ -1,3 +0,0 @@
### Ignored
- Add more tests to process pending deposits.

View File

@@ -1,3 +0,0 @@
### Changed
- Sort attestations in proposer block by reward.

View File

@@ -1,3 +0,0 @@
### Added
- Add warning messages for gas limit ranges that might be problematic. Low gas limits (≤10% of default) may cause transactions to fail, while high gas limits (>150% of default) could lead to block propagation issues.

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