mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Compare commits
19 Commits
peerdas-sa
...
fix_deadli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa06ce5e0e | ||
|
|
e5d7888e5c | ||
|
|
f7eddedd1d | ||
|
|
7887ebbc4a | ||
|
|
4ea3699c51 | ||
|
|
06a5a16007 | ||
|
|
fb2eeb5ce9 | ||
|
|
b526d99a55 | ||
|
|
1b13520270 | ||
|
|
0936628b72 | ||
|
|
478ae81ed1 | ||
|
|
93276150e7 | ||
|
|
83460c9956 | ||
|
|
d30bb63d94 | ||
|
|
ab5505e13e | ||
|
|
9c00b06966 | ||
|
|
167f719860 | ||
|
|
d4469d17b7 | ||
|
|
8418157f8a |
60
CHANGELOG.md
60
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
143
beacon-chain/blockchain/kzg/kzg.go
Normal file
143
beacon-chain/blockchain/kzg/kzg.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -22,6 +22,8 @@ var gossipTopicMappings = map[string]func() proto.Message{
|
||||
SyncCommitteeSubnetTopicFormat: func() proto.Message { return ðpb.SyncCommitteeMessage{} },
|
||||
BlsToExecutionChangeSubnetTopicFormat: func() proto.Message { return ðpb.SignedBLSToExecutionChange{} },
|
||||
BlobSubnetTopicFormat: func() proto.Message { return ðpb.BlobSidecar{} },
|
||||
LightClientOptimisticUpdateTopicFormat: func() proto.Message { return ðpb.LightClientOptimisticUpdateAltair{} },
|
||||
LightClientFinalityUpdateTopicFormat: func() proto.Message { return ðpb.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 ðpb.SignedAggregateAttestationAndProofElectra{}
|
||||
}
|
||||
return gossipMessage(topic)
|
||||
case LightClientOptimisticUpdateTopicFormat:
|
||||
if epoch >= params.BeaconConfig().DenebForkEpoch {
|
||||
return ðpb.LightClientOptimisticUpdateDeneb{}
|
||||
}
|
||||
if epoch >= params.BeaconConfig().CapellaForkEpoch {
|
||||
return ðpb.LightClientOptimisticUpdateCapella{}
|
||||
}
|
||||
return gossipMessage(topic)
|
||||
case LightClientFinalityUpdateTopicFormat:
|
||||
if epoch >= params.BeaconConfig().ElectraForkEpoch {
|
||||
return ðpb.LightClientFinalityUpdateElectra{}
|
||||
}
|
||||
if epoch >= params.BeaconConfig().DenebForkEpoch {
|
||||
return ðpb.LightClientFinalityUpdateDeneb{}
|
||||
}
|
||||
if epoch >= params.BeaconConfig().CapellaForkEpoch {
|
||||
return ðpb.LightClientFinalityUpdateCapella{}
|
||||
}
|
||||
return gossipMessage(topic)
|
||||
default:
|
||||
return gossipMessage(topic)
|
||||
}
|
||||
@@ -97,21 +118,28 @@ func init() {
|
||||
|
||||
// Specially handle Altair objects.
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockAltair{})] = BlockSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateAltair{})] = LightClientFinalityUpdateTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateAltair{})] = LightClientOptimisticUpdateTopicFormat
|
||||
|
||||
// Specially handle Bellatrix objects.
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockBellatrix{})] = BlockSubnetTopicFormat
|
||||
|
||||
// Specially handle Capella objects.
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockCapella{})] = BlockSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateCapella{})] = LightClientOptimisticUpdateTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateCapella{})] = LightClientFinalityUpdateTopicFormat
|
||||
|
||||
// Specially handle Deneb objects.
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockDeneb{})] = BlockSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateDeneb{})] = LightClientOptimisticUpdateTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateDeneb{})] = LightClientFinalityUpdateTopicFormat
|
||||
|
||||
// Specially handle Electra objects.
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockElectra{})] = BlockSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SingleAttestation{})] = AttestationSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.AttesterSlashingElectra{})] = AttesterSlashingSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedAggregateAttestationAndProofElectra{})] = AggregateAndProofSubnetTopicFormat
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateElectra{})] = LightClientFinalityUpdateTopicFormat
|
||||
|
||||
// Specially handle Fulu objects.
|
||||
GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockFulu{})] = BlockSubnetTopicFormat
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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(ðpb.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 := ðpb.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(ðpb.PendingPartialWithdrawal{
|
||||
Amount: 1008_000_000_000,
|
||||
WithdrawableEpoch: primitives.Epoch(0),
|
||||
}))
|
||||
require.NoError(t, s.AppendPendingPartialWithdrawal(ðpb.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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
219
beacon-chain/sync/initial-sync/downscore_test.go
Normal file
219
beacon-chain/sync/initial-sync/downscore_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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 := ðpb.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
|
||||
}
|
||||
|
||||
@@ -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: ðpb.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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
3
changelog/bastin_add-lc-p2p-broadcasters.md
Normal file
3
changelog/bastin_add-lc-p2p-broadcasters.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Add light client p2p broadcaster functions.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- Add light client ssz types to the spec test
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- Add light client store object to the beacon node object.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- add two parameters `increaseAttestedSlotBy` and `supermajority` to the lc test utils.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- Add SSZ support to light client updates by range API. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15082)
|
||||
@@ -1,3 +0,0 @@
|
||||
### Changed
|
||||
|
||||
- Remove the header `Content-Disposition` from the `httputil.WriteSSZ` function. No `filename` parameter is needed anymore.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed to use io stream instead of stream read
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- moves some beacon api helper functions to apiutil and removes the string implementation of isvalidroot for the byte validation.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Deprecated
|
||||
|
||||
- deprecates and removes usage of the `--trace` flag and`--cpuprofile` flag in favor of just using `--pprof`
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- fixes health interface usage, we should be using the interface instead of using a pointer in places.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- small optimization on ComputeSubnetForAttestation to simplify for readability by using the GetCommitteeIndex function
|
||||
@@ -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.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Changed
|
||||
|
||||
- removed old web3signer metrics in favor for a universal one
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- removed redundant mock validator for unit tests, we should just have 1 implementation.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- small optimization on the computeOnChainAggregate function
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- When using a DV, send all aggregations for a slot and committee.
|
||||
@@ -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.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Fixed
|
||||
- Peers giving invalid data in range syncing are now downscored.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Fixed
|
||||
- Clean up dangling block index entries for blocks that were previously deleted by incomplete cleanup code.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Ignored
|
||||
- consolidate several statagen tests into a comprehensive/consistent table-drive test.
|
||||
3
changelog/kira_broadcast_slashings.md
Normal file
3
changelog/kira_broadcast_slashings.md
Normal 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
3
changelog/manu-kzg.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- PeerDAS related KZG wrappers.
|
||||
5
changelog/manu-upgrade-to-fulu.md
Normal file
5
changelog/manu-upgrade-to-fulu.md
Normal file
@@ -0,0 +1,5 @@
|
||||
### Added
|
||||
- `UpgradeToFulu` spectests.
|
||||
|
||||
### Fixed
|
||||
- `UpgradeToFulu`
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- prysmctl option in wrapper script to generate devnet ssz. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15145)
|
||||
3
changelog/mmsqe_fix_fork.md
Normal file
3
changelog/mmsqe_fix_fork.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- avoid nondeterministic default fork value when generate genesis. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15151)
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Fix Committee Index Check For Aggregates.
|
||||
3
changelog/nisdas_fix_generated_ssz.md
Normal file
3
changelog/nisdas_fix_generated_ssz.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Fixes our generated ssz files to have the correct package name.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed a bug in checking for attestation lengths in our block.
|
||||
3
changelog/nisdas_fix_p2p_req_ssz.md
Normal file
3
changelog/nisdas_fix_p2p_req_ssz.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Fixes our blob sidecar by root request lists for electra.
|
||||
6
changelog/nisdas_fix_sec_advisories.md
Normal file
6
changelog/nisdas_fix_sec_advisories.md
Normal file
@@ -0,0 +1,6 @@
|
||||
### Security
|
||||
|
||||
- Fix CVE-2025-22869
|
||||
- Fix CVE-2025-22870
|
||||
- Fix CVE-2025-22872
|
||||
- Fix CVE-2025-30204
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Fix State Getter for pending withdrawal balance.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed a bug in consolidation request processing.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Revert execution requests in E2E.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Add helper for dependent root in forkchoice.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Use forkchoice dependent root helper in new head event feed.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Use headstate to validate canonical attestations with an old target
|
||||
3
changelog/potuz_vc_deadlines.md
Normal file
3
changelog/potuz_vc_deadlines.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed deadlines in update duties.
|
||||
2
changelog/pronoss_refactor_subscriber_rename.md
Normal file
2
changelog/pronoss_refactor_subscriber_rename.md
Normal 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.
|
||||
3
changelog/pvl_geth_update.md
Normal file
3
changelog/pvl_geth_update.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Changed
|
||||
|
||||
- Updated geth to v1.15.9
|
||||
3
changelog/pvl_v6_changelog.md
Normal file
3
changelog/pvl_v6_changelog.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Ignored
|
||||
|
||||
- Updated changelog for v6.0.0 release
|
||||
@@ -1,3 +0,0 @@
|
||||
#### Ignored
|
||||
|
||||
- Updated changelog for v5.3.2
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- Implemented validator identities Beacon API endpoint.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Fix filtering by committee index post-Electra in `ListAttestationsV2`.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Changed
|
||||
|
||||
- Deprecated everything related with the gRPC API.
|
||||
@@ -1,3 +0,0 @@
|
||||
#### Ignored
|
||||
|
||||
- Allow hex strings in `/eth/v1/beacon/states/{state_id}/root` endpoint.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Removed
|
||||
|
||||
- Remove `disable-committee-aware-packing` flag.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Removed
|
||||
|
||||
- Remove deprecated flags for the major release.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Removed
|
||||
|
||||
- Removed Beacon API endpoints which have been deprecated at the Deneb fork.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Use the built-in min to simplify the code
|
||||
@@ -1,3 +0,0 @@
|
||||
### Fixed
|
||||
|
||||
- Use latest state to pack attestation.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Update spec tests to v1.5.0-beta.4 version
|
||||
@@ -1,3 +0,0 @@
|
||||
### Changed
|
||||
|
||||
- Migrate Prysm repo to Offchain Labs organization ahead of Pectra upgrade v6
|
||||
@@ -1,3 +0,0 @@
|
||||
### Removed
|
||||
|
||||
- Removed unused hack scripts.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Mainnet config returns copied config instead of assume caller to copy.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Added
|
||||
|
||||
- Add support for Electra fork epoch
|
||||
@@ -1,3 +0,0 @@
|
||||
### Ignored
|
||||
|
||||
- Add more tests to process pending deposits.
|
||||
@@ -1,3 +0,0 @@
|
||||
### Changed
|
||||
|
||||
- Sort attestations in proposer block by reward.
|
||||
@@ -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
Reference in New Issue
Block a user