mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 05:47:59 -05:00
Compare commits
45 Commits
save-pendi
...
eip-7742
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23b4a43ff6 | ||
|
|
ffc443b5f2 | ||
|
|
d6c5692dc0 | ||
|
|
1086bdf2b3 | ||
|
|
2afa63b442 | ||
|
|
5a5193c59d | ||
|
|
c8d3ed02cb | ||
|
|
30fcf5366a | ||
|
|
f776b968ad | ||
|
|
de094b0078 | ||
|
|
0a4ed8279b | ||
|
|
f307a369a5 | ||
|
|
dc91c963b9 | ||
|
|
7238848d81 | ||
|
|
80cafaa6df | ||
|
|
8a0545c3d7 | ||
|
|
9c61117b71 | ||
|
|
6c22edeecc | ||
|
|
57cc4950c0 | ||
|
|
2c981d5564 | ||
|
|
492c8af83f | ||
|
|
e40d2cbd2c | ||
|
|
3fa6d3bd9d | ||
|
|
56f0eb1437 | ||
|
|
7fc5c714a1 | ||
|
|
cfbfccb203 | ||
|
|
884b663455 | ||
|
|
0f1d16c599 | ||
|
|
c11e3392d4 | ||
|
|
f498463843 | ||
|
|
cf4ffc97e2 | ||
|
|
3824e8a463 | ||
|
|
21ca4e008f | ||
|
|
6af44a1466 | ||
|
|
2e29164582 | ||
|
|
6d499bc9fc | ||
|
|
7786cb5684 | ||
|
|
003b70c34b | ||
|
|
71edf96c7d | ||
|
|
ddafedc268 | ||
|
|
7e5738bfcd | ||
|
|
315c05b351 | ||
|
|
3662cf6009 | ||
|
|
98d8b50b0e | ||
|
|
1a1cc25bd1 |
@@ -26,7 +26,6 @@ approval_rules:
|
||||
only_changed_files:
|
||||
paths:
|
||||
- "*pb.go"
|
||||
- "*pb.gw.go"
|
||||
- "*.bazel"
|
||||
options:
|
||||
ignore_commits_by:
|
||||
@@ -69,7 +68,6 @@ approval_rules:
|
||||
changed_files:
|
||||
ignore:
|
||||
- "*pb.go"
|
||||
- "*pb.gw.go"
|
||||
- "*.bazel"
|
||||
options:
|
||||
ignore_commits_by:
|
||||
|
||||
92
CHANGELOG.md
92
CHANGELOG.md
@@ -4,7 +4,54 @@ 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.
|
||||
|
||||
## [Unreleased](https://github.com/prysmaticlabs/prysm/compare/v5.1.0...HEAD)
|
||||
## [Unreleased](https://github.com/prysmaticlabs/prysm/compare/v5.1.1...HEAD)
|
||||
|
||||
### Added
|
||||
|
||||
- Electra EIP6110: Queue deposit [pr](https://github.com/prysmaticlabs/prysm/pull/14430)
|
||||
- Add Bellatrix tests for light client functions.
|
||||
- Add Discovery Rebooter Feature.
|
||||
- Added GetBlockAttestationsV2 endpoint.
|
||||
- Light client support: Consensus types for Electra
|
||||
- Added SubmitPoolAttesterSlashingV2 endpoint.
|
||||
|
||||
### Changed
|
||||
|
||||
- Electra EIP6110: Queue deposit requests changes from consensus spec pr #3818
|
||||
- reversed the boolean return on `BatchVerifyDepositsSignatures`, from need verification, to all keys successfully verified
|
||||
- Fix `engine_exchangeCapabilities` implementation.
|
||||
- Updated the default `scrape-interval` in `Client-stats` to 2 minutes to accommodate Beaconcha.in API rate limits.
|
||||
- Switch to compounding when consolidating with source==target.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `/eth/v1alpha1/validator/activation/stream` grpc wait for activation stream is deprecated. [pr](https://github.com/prysmaticlabs/prysm/pull/14514)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed finalized validator index cache, no longer needed.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed mesh size by appending `gParams.Dhi = gossipSubDhi`
|
||||
- Fix skipping partial withdrawals count.
|
||||
- recover from panics when writing the event stream [pr](https://github.com/prysmaticlabs/prysm/pull/14545)
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
## [v5.1.1](https://github.com/prysmaticlabs/prysm/compare/v5.1.0...v5.1.1) - 2024-10-15
|
||||
|
||||
This release has a number of features and improvements. Most notably, the feature flag
|
||||
`--enable-experimental-state` has been flipped to "opt out" via `--disable-experimental-state`.
|
||||
The experimental state management design has shown significant improvements in memory usage at
|
||||
runtime. Updates to libp2p's gossipsub have some bandwidith stability improvements with support for
|
||||
IDONTWANT control messages.
|
||||
|
||||
The gRPC gateway has been deprecated from Prysm in this release. If you need JSON data, consider the
|
||||
standardized beacon-APIs.
|
||||
|
||||
Updating to this release is recommended at your convenience.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -13,10 +60,22 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
- Light client support: Implement `ComputeFieldRootsForBlockBody`.
|
||||
- Light client support: Add light client database changes.
|
||||
- Light client support: Implement capella and deneb changes.
|
||||
- Light client support: Implement `BlockToLightClientHeaderXXX` functions upto Deneb
|
||||
- Light client support: Implement `BlockToLightClientHeader` function.
|
||||
- Light client support: Consensus types.
|
||||
- GetBeaconStateV2: add Electra case.
|
||||
- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875).
|
||||
- Tests to ensure sepolia config matches the official upstream yaml.
|
||||
- `engine_newPayloadV4`,`engine_getPayloadV4` used for electra payload communication with execution client. [pr](https://github.com/prysmaticlabs/prysm/pull/14492)
|
||||
- HTTP endpoint for PublishBlobs.
|
||||
- GetBlockV2, GetBlindedBlock, ProduceBlockV2, ProduceBlockV3: add Electra case.
|
||||
- Add Electra support and tests for light client functions.
|
||||
- fastssz version bump (better error messages).
|
||||
- SSE implementation that sheds stuck clients. [pr](https://github.com/prysmaticlabs/prysm/pull/14413)
|
||||
- Added GetPoolAttesterSlashingsV2 endpoint.
|
||||
|
||||
### Changed
|
||||
|
||||
- Electra: Updated interop genesis generator to support Electra.
|
||||
- `getLocalPayload` has been refactored to enable work in ePBS branch.
|
||||
- `TestNodeServer_GetPeer` and `TestNodeServer_ListPeers` test flakes resolved by iterating the whole peer list to find
|
||||
a match rather than taking the first peer in the map.
|
||||
@@ -37,6 +96,12 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
- Updated k8s-io/client-go to v0.30.4 and k8s-io/apimachinery to v0.30.4
|
||||
- Migrated tracing library from opencensus to opentelemetry for both the beacon node and validator.
|
||||
- Refactored light client code to make it more readable and make future PRs easier.
|
||||
- Update light client helper functions to reference `dev` branch of CL specs
|
||||
- Updated Libp2p Dependencies to allow prysm to use gossipsub v1.2 .
|
||||
- Updated Sepolia bootnodes.
|
||||
- Make committee aware packing the default by deprecating `--enable-committee-aware-packing`.
|
||||
- Moved `ConvertKzgCommitmentToVersionedHash` to the `primitives` package.
|
||||
- Updated correlation penalty for EIP-7251.
|
||||
|
||||
### Deprecated
|
||||
- `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal.
|
||||
@@ -44,9 +109,10 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
|
||||
### Removed
|
||||
|
||||
- removed gRPC Gateway
|
||||
- Removed unused blobs bundle cache
|
||||
- Removed gRPC Gateway.
|
||||
- Removed unused blobs bundle cache.
|
||||
- Removed consolidation signing domain from params. The Electra design changed such that EL handles consolidation signature verification.
|
||||
- Remove engine_getPayloadBodiesBy{Hash|Range}V2
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -58,12 +124,18 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
- validator registration log changed to debug, and the frequency of validator registration calls are reduced
|
||||
- Core: Fix process effective balance update to safe copy validator for Electra.
|
||||
- `== nil` checks before calling `IsNil()` on interfaces to prevent panics.
|
||||
- Core: Fixed slash processing causing extra hashing
|
||||
- Core: Fixed extra allocations when processing slashings
|
||||
- Core: Fixed slash processing causing extra hashing.
|
||||
- Core: Fixed extra allocations when processing slashings.
|
||||
- remove unneeded container in blob sidecar ssz response
|
||||
- Light client support: create finalized header based on finalizedBlock's version, not attestedBlock.
|
||||
- Light client support: fix light client attested header execution fields' wrong version bug.
|
||||
- Testing: added custom matcher for better push settings testing.
|
||||
- Registered `GetDepositSnapshot` Beacon API endpoint.
|
||||
|
||||
### Security
|
||||
|
||||
No notable security updates.
|
||||
|
||||
## [v5.1.0](https://github.com/prysmaticlabs/prysm/compare/v5.0.4...v5.1.0) - 2024-08-20
|
||||
|
||||
This release contains 171 new changes and many of these are related to Electra! Along side the Electra changes, there
|
||||
@@ -2673,7 +2745,7 @@ on your validators.
|
||||
**Beacon chain node**
|
||||
|
||||
| Metric | Description | References |
|
||||
|--------------------------------------------------|-------------------------------------------------------------------------------------------------------|------------|
|
||||
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ---------- |
|
||||
| `p2p_message_ignored_validation_total` | Count of messages that were ignored in validation | |
|
||||
| `beacon_current_active_validators` | Current total active validators | |
|
||||
| `beacon_processed_deposits_total` | Total number of deposits processed | |
|
||||
@@ -2724,9 +2796,9 @@ on your validators.
|
||||
#### Changed Metrics
|
||||
|
||||
**Beacon chain node**
|
||||
| Metric | Old Name | Description | References |
|
||||
|-----------------------|----------------------|------------------------------------------------------|------------|
|
||||
| `beacon_reorgs_total` | `beacon_reorg_total` | Count the number of times a beacon chain has a reorg | |
|
||||
| Metric | Old Name | Description | References |
|
||||
| --------------------- | -------------------- | ---------------------------------------------------- | ---------- |
|
||||
| `beacon_reorgs_total` | `beacon_reorg_total` | Count the number of times a beacon chain has a reorg | |
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
||||
26
WORKSPACE
26
WORKSPACE
@@ -227,7 +227,7 @@ filegroup(
|
||||
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
|
||||
)
|
||||
|
||||
consensus_spec_version = "v1.5.0-alpha.5"
|
||||
consensus_spec_version = "v1.5.0-alpha.8"
|
||||
|
||||
bls_test_version = "v0.1.1"
|
||||
|
||||
@@ -243,7 +243,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-R9vG5HEL5eGMOAmbkKfJ2jfelNqL5V0xBUPiXOiGM6U=",
|
||||
integrity = "sha256-BsGIbEyJuYrzhShGl0tHhR4lP5Qwno8R3k8a6YBR/DA=",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -259,7 +259,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-AEIiEOlf1XuxoRMCsN+kgJMo4LrS05+biTA1p/7Ro00=",
|
||||
integrity = "sha256-DkdvhPP2KiqUOpwFXQIFDCWCwsUDIC/xhTBD+TZevm0=",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -275,7 +275,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-LH/Xr20yrJRYnbpjRGupMWTIOWt3cpxZJWXgThwVDsk=",
|
||||
integrity = "sha256-vkZqV0HB8A2Uc56C1Us/p5G57iaHL+zw2No93Xt6M/4=",
|
||||
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
@@ -290,7 +290,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-mlytz4MPjKh0DwV7FMiAtnRbJw9B6o78/x66/vmnYc8=",
|
||||
integrity = "sha256-D/HPAW61lKqjoWwl7N0XvhdX+67dCEFAy8JxVzqBGtU=",
|
||||
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
|
||||
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
@@ -342,6 +342,22 @@ filegroup(
|
||||
url = "https://github.com/eth-clients/holesky/archive/874c199423ccd180607320c38cbaca05d9a1573a.tar.gz", # 2024-06-18
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "sepolia_testnet",
|
||||
build_file_content = """
|
||||
filegroup(
|
||||
name = "configs",
|
||||
srcs = [
|
||||
"metadata/config.yaml",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-cY/UgpCcYEhQf7JefD65FI8tn/A+rAvKhcm2/qiVdqY=",
|
||||
strip_prefix = "sepolia-f2c219a93c4491cee3d90c18f2f8e82aed850eab",
|
||||
url = "https://github.com/eth-clients/sepolia/archive/f2c219a93c4491cee3d90c18f2f8e82aed850eab.tar.gz", # 2024-09-19
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "com_google_protobuf",
|
||||
sha256 = "9bd87b8280ef720d3240514f884e56a712f2218f0d693b48050c836028940a42",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package api
|
||||
|
||||
import "net/http"
|
||||
|
||||
const (
|
||||
VersionHeader = "Eth-Consensus-Version"
|
||||
ExecutionPayloadBlindedHeader = "Eth-Execution-Payload-Blinded"
|
||||
@@ -10,3 +12,9 @@ const (
|
||||
EventStreamMediaType = "text/event-stream"
|
||||
KeepAlive = "keep-alive"
|
||||
)
|
||||
|
||||
// SetSSEHeaders sets the headers needed for a server-sent event response.
|
||||
func SetSSEHeaders(w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", EventStreamMediaType)
|
||||
w.Header().Set("Connection", KeepAlive)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ go_library(
|
||||
srcs = [
|
||||
"block.go",
|
||||
"conversions.go",
|
||||
"conversions_blob.go",
|
||||
"conversions_block.go",
|
||||
"conversions_lightclient.go",
|
||||
"conversions_state.go",
|
||||
|
||||
@@ -365,6 +365,7 @@ type BeaconBlockBodyElectra struct {
|
||||
ExecutionPayload *ExecutionPayloadElectra `json:"execution_payload"`
|
||||
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
|
||||
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
|
||||
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
|
||||
}
|
||||
|
||||
type BlindedBeaconBlockElectra struct {
|
||||
@@ -403,6 +404,7 @@ type BlindedBeaconBlockBodyElectra struct {
|
||||
ExecutionPayloadHeader *ExecutionPayloadHeaderElectra `json:"execution_payload_header"`
|
||||
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
|
||||
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
|
||||
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
|
||||
}
|
||||
|
||||
type SignedBeaconBlockHeaderContainer struct {
|
||||
@@ -514,6 +516,8 @@ type ExecutionPayloadDeneb struct {
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
}
|
||||
|
||||
type ExecutionPayloadElectra = ExecutionPayloadDeneb
|
||||
|
||||
type ExecutionPayloadHeaderDeneb struct {
|
||||
ParentHash string `json:"parent_hash"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
@@ -534,48 +538,10 @@ type ExecutionPayloadHeaderDeneb struct {
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
}
|
||||
|
||||
type ExecutionPayloadElectra struct {
|
||||
ParentHash string `json:"parent_hash"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
StateRoot string `json:"state_root"`
|
||||
ReceiptsRoot string `json:"receipts_root"`
|
||||
LogsBloom string `json:"logs_bloom"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
BlockNumber string `json:"block_number"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
GasUsed string `json:"gas_used"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
ExtraData string `json:"extra_data"`
|
||||
BaseFeePerGas string `json:"base_fee_per_gas"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
Transactions []string `json:"transactions"`
|
||||
Withdrawals []*Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed string `json:"blob_gas_used"`
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
DepositRequests []*DepositRequest `json:"deposit_requests"`
|
||||
WithdrawalRequests []*WithdrawalRequest `json:"withdrawal_requests"`
|
||||
ConsolidationRequests []*ConsolidationRequest `json:"consolidation_requests"`
|
||||
}
|
||||
type ExecutionPayloadHeaderElectra = ExecutionPayloadHeaderDeneb
|
||||
|
||||
type ExecutionPayloadHeaderElectra struct {
|
||||
ParentHash string `json:"parent_hash"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
StateRoot string `json:"state_root"`
|
||||
ReceiptsRoot string `json:"receipts_root"`
|
||||
LogsBloom string `json:"logs_bloom"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
BlockNumber string `json:"block_number"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
GasUsed string `json:"gas_used"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
ExtraData string `json:"extra_data"`
|
||||
BaseFeePerGas string `json:"base_fee_per_gas"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
TransactionsRoot string `json:"transactions_root"`
|
||||
WithdrawalsRoot string `json:"withdrawals_root"`
|
||||
BlobGasUsed string `json:"blob_gas_used"`
|
||||
ExcessBlobGas string `json:"excess_blob_gas"`
|
||||
DepositRequestsRoot string `json:"deposit_requests_root"`
|
||||
WithdrawalRequestsRoot string `json:"withdrawal_requests_root"`
|
||||
ConsolidationRequestsRoot string `json:"consolidation_requests_root"`
|
||||
type ExecutionRequests struct {
|
||||
Deposits []*DepositRequest `json:"deposits"`
|
||||
Withdrawals []*WithdrawalRequest `json:"withdrawals"`
|
||||
Consolidations []*ConsolidationRequest `json:"consolidations"`
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/math"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -1475,12 +1476,15 @@ func DepositSnapshotFromConsensus(ds *eth.DepositSnapshot) *DepositSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
func PendingBalanceDepositsFromConsensus(ds []*eth.PendingBalanceDeposit) []*PendingBalanceDeposit {
|
||||
deposits := make([]*PendingBalanceDeposit, len(ds))
|
||||
func PendingDepositsFromConsensus(ds []*eth.PendingDeposit) []*PendingDeposit {
|
||||
deposits := make([]*PendingDeposit, len(ds))
|
||||
for i, d := range ds {
|
||||
deposits[i] = &PendingBalanceDeposit{
|
||||
Index: fmt.Sprintf("%d", d.Index),
|
||||
Amount: fmt.Sprintf("%d", d.Amount),
|
||||
deposits[i] = &PendingDeposit{
|
||||
Pubkey: hexutil.Encode(d.PublicKey),
|
||||
WithdrawalCredentials: hexutil.Encode(d.WithdrawalCredentials),
|
||||
Amount: fmt.Sprintf("%d", d.Amount),
|
||||
Signature: hexutil.Encode(d.Signature),
|
||||
Slot: fmt.Sprintf("%d", d.Slot),
|
||||
}
|
||||
}
|
||||
return deposits
|
||||
@@ -1508,3 +1512,37 @@ func PendingConsolidationsFromConsensus(cs []*eth.PendingConsolidation) []*Pendi
|
||||
}
|
||||
return consolidations
|
||||
}
|
||||
|
||||
func HeadEventFromV1(event *ethv1.EventHead) *HeadEvent {
|
||||
return &HeadEvent{
|
||||
Slot: fmt.Sprintf("%d", event.Slot),
|
||||
Block: hexutil.Encode(event.Block),
|
||||
State: hexutil.Encode(event.State),
|
||||
EpochTransition: event.EpochTransition,
|
||||
ExecutionOptimistic: event.ExecutionOptimistic,
|
||||
PreviousDutyDependentRoot: hexutil.Encode(event.PreviousDutyDependentRoot),
|
||||
CurrentDutyDependentRoot: hexutil.Encode(event.CurrentDutyDependentRoot),
|
||||
}
|
||||
}
|
||||
|
||||
func FinalizedCheckpointEventFromV1(event *ethv1.EventFinalizedCheckpoint) *FinalizedCheckpointEvent {
|
||||
return &FinalizedCheckpointEvent{
|
||||
Block: hexutil.Encode(event.Block),
|
||||
State: hexutil.Encode(event.State),
|
||||
Epoch: fmt.Sprintf("%d", event.Epoch),
|
||||
ExecutionOptimistic: event.ExecutionOptimistic,
|
||||
}
|
||||
}
|
||||
|
||||
func EventChainReorgFromV1(event *ethv1.EventChainReorg) *ChainReorgEvent {
|
||||
return &ChainReorgEvent{
|
||||
Slot: fmt.Sprintf("%d", event.Slot),
|
||||
Depth: fmt.Sprintf("%d", event.Depth),
|
||||
OldHeadBlock: hexutil.Encode(event.OldHeadBlock),
|
||||
NewHeadBlock: hexutil.Encode(event.NewHeadBlock),
|
||||
OldHeadState: hexutil.Encode(event.OldHeadState),
|
||||
NewHeadState: hexutil.Encode(event.NewHeadState),
|
||||
Epoch: fmt.Sprintf("%d", event.Epoch),
|
||||
ExecutionOptimistic: event.ExecutionOptimistic,
|
||||
}
|
||||
}
|
||||
|
||||
61
api/server/structs/conversions_blob.go
Normal file
61
api/server/structs/conversions_blob.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
func (sc *Sidecar) ToConsensus() (*eth.BlobSidecar, error) {
|
||||
if sc == nil {
|
||||
return nil, errNilValue
|
||||
}
|
||||
|
||||
index, err := strconv.ParseUint(sc.Index, 10, 64)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Index")
|
||||
}
|
||||
|
||||
blob, err := bytesutil.DecodeHexWithLength(sc.Blob, 131072)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Blob")
|
||||
}
|
||||
|
||||
kzgCommitment, err := bytesutil.DecodeHexWithLength(sc.KzgCommitment, 48)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "KzgCommitment")
|
||||
}
|
||||
|
||||
kzgProof, err := bytesutil.DecodeHexWithLength(sc.KzgProof, 48)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "KzgProof")
|
||||
}
|
||||
|
||||
header, err := sc.SignedBeaconBlockHeader.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "SignedBeaconBlockHeader")
|
||||
}
|
||||
|
||||
// decode the commitment inclusion proof
|
||||
var commitmentInclusionProof [][]byte
|
||||
for _, proof := range sc.CommitmentInclusionProof {
|
||||
proofBytes, err := bytesutil.DecodeHexWithLength(proof, 32)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "CommitmentInclusionProof")
|
||||
}
|
||||
commitmentInclusionProof = append(commitmentInclusionProof, proofBytes)
|
||||
}
|
||||
|
||||
bsc := ð.BlobSidecar{
|
||||
Index: index,
|
||||
Blob: blob,
|
||||
KzgCommitment: kzgCommitment,
|
||||
KzgProof: kzgProof,
|
||||
SignedBlockHeader: header,
|
||||
CommitmentInclusionProof: commitmentInclusionProof,
|
||||
}
|
||||
|
||||
return bsc, nil
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import (
|
||||
var ErrUnsupportedConversion = errors.New("Could not determine api struct type to use for value")
|
||||
|
||||
func (h *SignedBeaconBlockHeader) ToConsensus() (*eth.SignedBeaconBlockHeader, error) {
|
||||
if h == nil {
|
||||
return nil, errNilValue
|
||||
}
|
||||
msg, err := h.Message.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Message")
|
||||
@@ -36,6 +39,9 @@ func (h *SignedBeaconBlockHeader) ToConsensus() (*eth.SignedBeaconBlockHeader, e
|
||||
}
|
||||
|
||||
func (h *BeaconBlockHeader) ToConsensus() (*eth.BeaconBlockHeader, error) {
|
||||
if h == nil {
|
||||
return nil, errNilValue
|
||||
}
|
||||
s, err := strconv.ParseUint(h.Slot, 10, 64)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Slot")
|
||||
@@ -2088,27 +2094,31 @@ func (b *BeaconBlockElectra) ToConsensus() (*eth.BeaconBlockElectra, error) {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayload.ExcessBlobGas")
|
||||
}
|
||||
|
||||
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionPayload.DepositRequests))
|
||||
for i, d := range b.Body.ExecutionPayload.DepositRequests {
|
||||
if b.Body.ExecutionRequests == nil {
|
||||
return nil, server.NewDecodeError(errors.New("nil execution requests"), "Body.ExequtionRequests")
|
||||
}
|
||||
|
||||
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionRequests.Deposits))
|
||||
for i, d := range b.Body.ExecutionRequests.Deposits {
|
||||
depositRequests[i], err = d.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.DepositRequests[%d]", i))
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Deposits[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionPayload.WithdrawalRequests))
|
||||
for i, w := range b.Body.ExecutionPayload.WithdrawalRequests {
|
||||
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionRequests.Withdrawals))
|
||||
for i, w := range b.Body.ExecutionRequests.Withdrawals {
|
||||
withdrawalRequests[i], err = w.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.WithdrawalRequests[%d]", i))
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Withdrawals[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionPayload.ConsolidationRequests))
|
||||
for i, c := range b.Body.ExecutionPayload.ConsolidationRequests {
|
||||
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionRequests.Consolidations))
|
||||
for i, c := range b.Body.ExecutionRequests.Consolidations {
|
||||
consolidationRequests[i], err = c.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.ConsolidationRequests[%d]", i))
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Consolidations[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2151,29 +2161,31 @@ func (b *BeaconBlockElectra) ToConsensus() (*eth.BeaconBlockElectra, error) {
|
||||
SyncCommitteeSignature: syncCommitteeSig,
|
||||
},
|
||||
ExecutionPayload: &enginev1.ExecutionPayloadElectra{
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
Transactions: txs,
|
||||
Withdrawals: withdrawals,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
DepositRequests: depositRequests,
|
||||
WithdrawalRequests: withdrawalRequests,
|
||||
ConsolidationRequests: consolidationRequests,
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
Transactions: txs,
|
||||
Withdrawals: withdrawals,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
},
|
||||
BlsToExecutionChanges: blsChanges,
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Deposits: depositRequests,
|
||||
Withdrawals: withdrawalRequests,
|
||||
Consolidations: consolidationRequests,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -2383,17 +2395,31 @@ func (b *BlindedBeaconBlockElectra) ToConsensus() (*eth.BlindedBeaconBlockElectr
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayload.ExcessBlobGas")
|
||||
}
|
||||
payloadDepositRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.DepositRequestsRoot, fieldparams.RootLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.DepositRequestsRoot")
|
||||
if b.Body.ExecutionRequests == nil {
|
||||
return nil, server.NewDecodeError(errors.New("nil execution requests"), "Body.ExecutionRequests")
|
||||
}
|
||||
payloadWithdrawalRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.WithdrawalRequestsRoot, fieldparams.RootLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.WithdrawalRequestsRoot")
|
||||
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionRequests.Deposits))
|
||||
for i, d := range b.Body.ExecutionRequests.Deposits {
|
||||
depositRequests[i], err = d.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Deposits[%d]", i))
|
||||
}
|
||||
}
|
||||
payloadConsolidationRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.ConsolidationRequestsRoot, fieldparams.RootLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.ConsolidationRequestsRoot")
|
||||
|
||||
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionRequests.Withdrawals))
|
||||
for i, w := range b.Body.ExecutionRequests.Withdrawals {
|
||||
withdrawalRequests[i], err = w.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Withdrawals[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionRequests.Consolidations))
|
||||
for i, c := range b.Body.ExecutionRequests.Consolidations {
|
||||
consolidationRequests[i], err = c.ToConsensus()
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Consolidations[%d]", i))
|
||||
}
|
||||
}
|
||||
|
||||
blsChanges, err := SignedBLSChangesToConsensus(b.Body.BLSToExecutionChanges)
|
||||
@@ -2436,29 +2462,31 @@ func (b *BlindedBeaconBlockElectra) ToConsensus() (*eth.BlindedBeaconBlockElectr
|
||||
SyncCommitteeSignature: syncCommitteeSig,
|
||||
},
|
||||
ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
TransactionsRoot: payloadTxsRoot,
|
||||
WithdrawalsRoot: payloadWithdrawalsRoot,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
DepositRequestsRoot: payloadDepositRequestsRoot,
|
||||
WithdrawalRequestsRoot: payloadWithdrawalRequestsRoot,
|
||||
ConsolidationRequestsRoot: payloadConsolidationRequestsRoot,
|
||||
ParentHash: payloadParentHash,
|
||||
FeeRecipient: payloadFeeRecipient,
|
||||
StateRoot: payloadStateRoot,
|
||||
ReceiptsRoot: payloadReceiptsRoot,
|
||||
LogsBloom: payloadLogsBloom,
|
||||
PrevRandao: payloadPrevRandao,
|
||||
BlockNumber: payloadBlockNumber,
|
||||
GasLimit: payloadGasLimit,
|
||||
GasUsed: payloadGasUsed,
|
||||
Timestamp: payloadTimestamp,
|
||||
ExtraData: payloadExtraData,
|
||||
BaseFeePerGas: payloadBaseFeePerGas,
|
||||
BlockHash: payloadBlockHash,
|
||||
TransactionsRoot: payloadTxsRoot,
|
||||
WithdrawalsRoot: payloadWithdrawalsRoot,
|
||||
BlobGasUsed: payloadBlobGasUsed,
|
||||
ExcessBlobGas: payloadExcessBlobGas,
|
||||
},
|
||||
BlsToExecutionChanges: blsChanges,
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Deposits: depositRequests,
|
||||
Withdrawals: withdrawalRequests,
|
||||
Consolidations: consolidationRequests,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -2526,6 +2554,8 @@ func SignedBeaconBlockMessageJsoner(block interfaces.ReadOnlySignedBeaconBlock)
|
||||
return SignedBlindedBeaconBlockDenebFromConsensus(pbStruct)
|
||||
case *eth.SignedBeaconBlockDeneb:
|
||||
return SignedBeaconBlockDenebFromConsensus(pbStruct)
|
||||
case *eth.SignedBlindedBeaconBlockElectra:
|
||||
return SignedBlindedBeaconBlockElectraFromConsensus(pbStruct)
|
||||
case *eth.SignedBeaconBlockElectra:
|
||||
return SignedBeaconBlockElectraFromConsensus(pbStruct)
|
||||
default:
|
||||
@@ -2963,10 +2993,19 @@ func BlindedBeaconBlockElectraFromConsensus(b *eth.BlindedBeaconBlockElectra) (*
|
||||
ExecutionPayloadHeader: payload,
|
||||
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ExecutionRequests),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExecutionRequestsFromConsensus(er *enginev1.ExecutionRequests) *ExecutionRequests {
|
||||
return &ExecutionRequests{
|
||||
Deposits: DepositRequestsFromConsensus(er.Deposits),
|
||||
Withdrawals: WithdrawalRequestsFromConsensus(er.Withdrawals),
|
||||
Consolidations: ConsolidationRequestsFromConsensus(er.Consolidations),
|
||||
}
|
||||
}
|
||||
|
||||
func SignedBlindedBeaconBlockElectraFromConsensus(b *eth.SignedBlindedBeaconBlockElectra) (*SignedBlindedBeaconBlockElectra, error) {
|
||||
block, err := BlindedBeaconBlockElectraFromConsensus(b.Message)
|
||||
if err != nil {
|
||||
@@ -3009,6 +3048,7 @@ func BeaconBlockElectraFromConsensus(b *eth.BeaconBlockElectra) (*BeaconBlockEle
|
||||
ExecutionPayload: payload,
|
||||
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
ExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ExecutionRequests),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -3112,39 +3152,7 @@ func ExecutionPayloadDenebFromConsensus(payload *enginev1.ExecutionPayloadDeneb)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExecutionPayloadElectraFromConsensus(payload *enginev1.ExecutionPayloadElectra) (*ExecutionPayloadElectra, error) {
|
||||
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transactions := make([]string, len(payload.Transactions))
|
||||
for i, tx := range payload.Transactions {
|
||||
transactions[i] = hexutil.Encode(tx)
|
||||
}
|
||||
|
||||
return &ExecutionPayloadElectra{
|
||||
ParentHash: hexutil.Encode(payload.ParentHash),
|
||||
FeeRecipient: hexutil.Encode(payload.FeeRecipient),
|
||||
StateRoot: hexutil.Encode(payload.StateRoot),
|
||||
ReceiptsRoot: hexutil.Encode(payload.ReceiptsRoot),
|
||||
LogsBloom: hexutil.Encode(payload.LogsBloom),
|
||||
PrevRandao: hexutil.Encode(payload.PrevRandao),
|
||||
BlockNumber: fmt.Sprintf("%d", payload.BlockNumber),
|
||||
GasLimit: fmt.Sprintf("%d", payload.GasLimit),
|
||||
GasUsed: fmt.Sprintf("%d", payload.GasUsed),
|
||||
Timestamp: fmt.Sprintf("%d", payload.Timestamp),
|
||||
ExtraData: hexutil.Encode(payload.ExtraData),
|
||||
BaseFeePerGas: baseFeePerGas,
|
||||
BlockHash: hexutil.Encode(payload.BlockHash),
|
||||
Transactions: transactions,
|
||||
Withdrawals: WithdrawalsFromConsensus(payload.Withdrawals),
|
||||
BlobGasUsed: fmt.Sprintf("%d", payload.BlobGasUsed),
|
||||
ExcessBlobGas: fmt.Sprintf("%d", payload.ExcessBlobGas),
|
||||
DepositRequests: DepositRequestsFromConsensus(payload.DepositRequests),
|
||||
WithdrawalRequests: WithdrawalRequestsFromConsensus(payload.WithdrawalRequests),
|
||||
ConsolidationRequests: ConsolidationRequestsFromConsensus(payload.ConsolidationRequests),
|
||||
}, nil
|
||||
}
|
||||
var ExecutionPayloadElectraFromConsensus = ExecutionPayloadDenebFromConsensus
|
||||
|
||||
func ExecutionPayloadHeaderFromConsensus(payload *enginev1.ExecutionPayloadHeader) (*ExecutionPayloadHeader, error) {
|
||||
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
|
||||
@@ -3222,32 +3230,4 @@ func ExecutionPayloadHeaderDenebFromConsensus(payload *enginev1.ExecutionPayload
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ExecutionPayloadHeaderElectraFromConsensus(payload *enginev1.ExecutionPayloadHeaderElectra) (*ExecutionPayloadHeaderElectra, error) {
|
||||
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ExecutionPayloadHeaderElectra{
|
||||
ParentHash: hexutil.Encode(payload.ParentHash),
|
||||
FeeRecipient: hexutil.Encode(payload.FeeRecipient),
|
||||
StateRoot: hexutil.Encode(payload.StateRoot),
|
||||
ReceiptsRoot: hexutil.Encode(payload.ReceiptsRoot),
|
||||
LogsBloom: hexutil.Encode(payload.LogsBloom),
|
||||
PrevRandao: hexutil.Encode(payload.PrevRandao),
|
||||
BlockNumber: fmt.Sprintf("%d", payload.BlockNumber),
|
||||
GasLimit: fmt.Sprintf("%d", payload.GasLimit),
|
||||
GasUsed: fmt.Sprintf("%d", payload.GasUsed),
|
||||
Timestamp: fmt.Sprintf("%d", payload.Timestamp),
|
||||
ExtraData: hexutil.Encode(payload.ExtraData),
|
||||
BaseFeePerGas: baseFeePerGas,
|
||||
BlockHash: hexutil.Encode(payload.BlockHash),
|
||||
TransactionsRoot: hexutil.Encode(payload.TransactionsRoot),
|
||||
WithdrawalsRoot: hexutil.Encode(payload.WithdrawalsRoot),
|
||||
BlobGasUsed: fmt.Sprintf("%d", payload.BlobGasUsed),
|
||||
ExcessBlobGas: fmt.Sprintf("%d", payload.ExcessBlobGas),
|
||||
DepositRequestsRoot: hexutil.Encode(payload.DepositRequestsRoot),
|
||||
WithdrawalRequestsRoot: hexutil.Encode(payload.WithdrawalRequestsRoot),
|
||||
ConsolidationRequestsRoot: hexutil.Encode(payload.ConsolidationRequestsRoot),
|
||||
}, nil
|
||||
}
|
||||
var ExecutionPayloadHeaderElectraFromConsensus = ExecutionPayloadHeaderDenebFromConsensus
|
||||
|
||||
@@ -84,6 +84,11 @@ func syncAggregateToJSON(input *v1.SyncAggregate) *SyncAggregate {
|
||||
}
|
||||
|
||||
func lightClientHeaderContainerToJSON(container *v2.LightClientHeaderContainer) (json.RawMessage, error) {
|
||||
// In the case that a finalizedHeader is nil.
|
||||
if container == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
beacon, err := container.GetBeacon()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get beacon block header")
|
||||
|
||||
@@ -674,7 +674,7 @@ func BeaconStateElectraFromConsensus(st beaconState.BeaconState) (*BeaconStateEl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcPayload, ok := execData.Proto().(*enginev1.ExecutionPayloadHeaderElectra)
|
||||
srcPayload, ok := execData.Proto().(*enginev1.ExecutionPayloadHeaderDeneb)
|
||||
if !ok {
|
||||
return nil, errPayloadHeaderNotFound
|
||||
}
|
||||
@@ -722,7 +722,7 @@ func BeaconStateElectraFromConsensus(st beaconState.BeaconState) (*BeaconStateEl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pbd, err := st.PendingBalanceDeposits()
|
||||
pbd, err := st.PendingDeposits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -770,7 +770,7 @@ func BeaconStateElectraFromConsensus(st beaconState.BeaconState) (*BeaconStateEl
|
||||
EarliestExitEpoch: fmt.Sprintf("%d", eee),
|
||||
ConsolidationBalanceToConsume: fmt.Sprintf("%d", cbtc),
|
||||
EarliestConsolidationEpoch: fmt.Sprintf("%d", ece),
|
||||
PendingBalanceDeposits: PendingBalanceDepositsFromConsensus(pbd),
|
||||
PendingDeposits: PendingDepositsFromConsensus(pbd),
|
||||
PendingPartialWithdrawals: PendingPartialWithdrawalsFromConsensus(ppw),
|
||||
PendingConsolidations: PendingConsolidationsFromConsensus(pc),
|
||||
}, nil
|
||||
|
||||
@@ -133,6 +133,13 @@ type GetBlockAttestationsResponse struct {
|
||||
Data []*Attestation `json:"data"`
|
||||
}
|
||||
|
||||
type GetBlockAttestationsV2Response struct {
|
||||
Version string `json:"version"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data json.RawMessage `json:"data"` // Accepts both `Attestation` and `AttestationElectra` types
|
||||
}
|
||||
|
||||
type GetStateRootResponse struct {
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
@@ -169,7 +176,8 @@ type BLSToExecutionChangesPoolResponse struct {
|
||||
}
|
||||
|
||||
type GetAttesterSlashingsResponse struct {
|
||||
Data []*AttesterSlashing `json:"data"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Data json.RawMessage `json:"data"` // Accepts both `[]*AttesterSlashing` and `[]*AttesterSlashingElectra` types
|
||||
}
|
||||
|
||||
type GetProposerSlashingsResponse struct {
|
||||
|
||||
@@ -12,3 +12,12 @@ type Sidecar struct {
|
||||
KzgProof string `json:"kzg_proof"`
|
||||
CommitmentInclusionProof []string `json:"kzg_commitment_inclusion_proof"`
|
||||
}
|
||||
|
||||
type BlobSidecars struct {
|
||||
Sidecars []*Sidecar `json:"sidecars"`
|
||||
}
|
||||
|
||||
type PublishBlobsRequest struct {
|
||||
BlobSidecars *BlobSidecars `json:"blob_sidecars"`
|
||||
BlockRoot string `json:"block_root"`
|
||||
}
|
||||
|
||||
@@ -83,6 +83,16 @@ type PayloadAttributesV3 struct {
|
||||
ParentBeaconBlockRoot string `json:"parent_beacon_block_root"`
|
||||
}
|
||||
|
||||
type PayloadAttributesV4 struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
SuggestedFeeRecipient string `json:"suggested_fee_recipient"`
|
||||
Withdrawals []*Withdrawal `json:"withdrawals"`
|
||||
ParentBeaconBlockRoot string `json:"parent_beacon_block_root"`
|
||||
TargetBlobCount string `json:"target_blob_count"`
|
||||
MaxBlobCount string `json:"maximum_blob_count"`
|
||||
}
|
||||
|
||||
type BlobSidecarEvent struct {
|
||||
BlockRoot string `json:"block_root"`
|
||||
Index string `json:"index"`
|
||||
|
||||
@@ -257,9 +257,12 @@ type ConsolidationRequest struct {
|
||||
TargetPubkey string `json:"target_pubkey"`
|
||||
}
|
||||
|
||||
type PendingBalanceDeposit struct {
|
||||
Index string `json:"index"`
|
||||
Amount string `json:"amount"`
|
||||
type PendingDeposit struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
WithdrawalCredentials string `json:"withdrawal_credentials"`
|
||||
Amount string `json:"amount"`
|
||||
Signature string `json:"signature"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
type PendingPartialWithdrawal struct {
|
||||
|
||||
@@ -176,7 +176,7 @@ type BeaconStateElectra struct {
|
||||
EarliestExitEpoch string `json:"earliest_exit_epoch"`
|
||||
ConsolidationBalanceToConsume string `json:"consolidation_balance_to_consume"`
|
||||
EarliestConsolidationEpoch string `json:"earliest_consolidation_epoch"`
|
||||
PendingBalanceDeposits []*PendingBalanceDeposit `json:"pending_balance_deposits"`
|
||||
PendingDeposits []*PendingDeposit `json:"pending_deposits"`
|
||||
PendingPartialWithdrawals []*PendingPartialWithdrawal `json:"pending_partial_withdrawals"`
|
||||
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||
}
|
||||
|
||||
@@ -4,26 +4,25 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"feed.go",
|
||||
"interface.go",
|
||||
"subscription.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/async/event",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//time/mclock:go_default_library"],
|
||||
deps = [
|
||||
"//time/mclock:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//event:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"example_feed_test.go",
|
||||
"example_scope_test.go",
|
||||
"example_subscription_test.go",
|
||||
"feed_test.go",
|
||||
"subscription_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
],
|
||||
deps = ["//testing/require:go_default_library"],
|
||||
)
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package event_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/async/event"
|
||||
)
|
||||
|
||||
func ExampleFeed_acknowledgedEvents() {
|
||||
// This example shows how the return value of Send can be used for request/reply
|
||||
// interaction between event consumers and producers.
|
||||
var feed event.Feed
|
||||
type ackedEvent struct {
|
||||
i int
|
||||
ack chan<- struct{}
|
||||
}
|
||||
|
||||
// Consumers wait for events on the feed and acknowledge processing.
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
for i := 0; i < 3; i++ {
|
||||
ch := make(chan ackedEvent, 100)
|
||||
sub := feed.Subscribe(ch)
|
||||
go func() {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case ev := <-ch:
|
||||
fmt.Println(ev.i) // "process" the event
|
||||
ev.ack <- struct{}{}
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// The producer sends values of type ackedEvent with increasing values of i.
|
||||
// It waits for all consumers to acknowledge before sending the next event.
|
||||
for i := 0; i < 3; i++ {
|
||||
acksignal := make(chan struct{})
|
||||
n := feed.Send(ackedEvent{i, acksignal})
|
||||
for ack := 0; ack < n; ack++ {
|
||||
<-acksignal
|
||||
}
|
||||
}
|
||||
// Output:
|
||||
// 0
|
||||
// 0
|
||||
// 0
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
// 2
|
||||
// 2
|
||||
// 2
|
||||
}
|
||||
@@ -14,241 +14,12 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package event contains an event feed implementation for process communication.
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
geth_event "github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
var errBadChannel = errors.New("event: Subscribe argument does not have sendable channel type")
|
||||
|
||||
// Feed implements one-to-many subscriptions where the carrier of events is a channel.
|
||||
// Values sent to a Feed are delivered to all subscribed channels simultaneously.
|
||||
//
|
||||
// Feeds can only be used with a single type. The type is determined by the first Send or
|
||||
// Subscribe operation. Subsequent calls to these methods panic if the type does not
|
||||
// match.
|
||||
//
|
||||
// The zero value is ready to use.
|
||||
type Feed struct {
|
||||
once sync.Once // ensures that init only runs once
|
||||
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
|
||||
removeSub chan interface{} // interrupts Send
|
||||
sendCases caseList // the active set of select cases used by Send
|
||||
|
||||
// The inbox holds newly subscribed channels until they are added to sendCases.
|
||||
mu sync.Mutex
|
||||
inbox caseList
|
||||
etype reflect.Type
|
||||
}
|
||||
|
||||
// This is the index of the first actual subscription channel in sendCases.
|
||||
// sendCases[0] is a SelectRecv case for the removeSub channel.
|
||||
const firstSubSendCase = 1
|
||||
|
||||
type feedTypeError struct {
|
||||
got, want reflect.Type
|
||||
op string
|
||||
}
|
||||
|
||||
func (e feedTypeError) Error() string {
|
||||
return "event: wrong type in " + e.op + " got " + e.got.String() + ", want " + e.want.String()
|
||||
}
|
||||
|
||||
func (f *Feed) init() {
|
||||
f.removeSub = make(chan interface{})
|
||||
f.sendLock = make(chan struct{}, 1)
|
||||
f.sendLock <- struct{}{}
|
||||
f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
|
||||
}
|
||||
|
||||
// Subscribe adds a channel to the feed. Future sends will be delivered on the channel
|
||||
// until the subscription is canceled. All channels added must have the same element type.
|
||||
//
|
||||
// The channel should have ample buffer space to avoid blocking other subscribers.
|
||||
// Slow subscribers are not dropped.
|
||||
func (f *Feed) Subscribe(channel interface{}) Subscription {
|
||||
f.once.Do(f.init)
|
||||
|
||||
chanval := reflect.ValueOf(channel)
|
||||
chantyp := chanval.Type()
|
||||
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 {
|
||||
panic(errBadChannel)
|
||||
}
|
||||
sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if !f.typecheck(chantyp.Elem()) {
|
||||
panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)})
|
||||
}
|
||||
// Add the select case to the inbox.
|
||||
// The next Send will add it to f.sendCases.
|
||||
cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval}
|
||||
f.inbox = append(f.inbox, cas)
|
||||
return sub
|
||||
}
|
||||
|
||||
// note: callers must hold f.mu
|
||||
func (f *Feed) typecheck(typ reflect.Type) bool {
|
||||
if f.etype == nil {
|
||||
f.etype = typ
|
||||
return true
|
||||
}
|
||||
// In the event the feed's type is an actual interface, we
|
||||
// perform an interface conformance check here.
|
||||
if f.etype.Kind() == reflect.Interface && typ.Implements(f.etype) {
|
||||
return true
|
||||
}
|
||||
return f.etype == typ
|
||||
}
|
||||
|
||||
func (f *Feed) remove(sub *feedSub) {
|
||||
// Delete from inbox first, which covers channels
|
||||
// that have not been added to f.sendCases yet.
|
||||
ch := sub.channel.Interface()
|
||||
f.mu.Lock()
|
||||
index := f.inbox.find(ch)
|
||||
if index != -1 {
|
||||
f.inbox = f.inbox.delete(index)
|
||||
f.mu.Unlock()
|
||||
return
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
select {
|
||||
case f.removeSub <- ch:
|
||||
// Send will remove the channel from f.sendCases.
|
||||
case <-f.sendLock:
|
||||
// No Send is in progress, delete the channel now that we have the send lock.
|
||||
f.sendCases = f.sendCases.delete(f.sendCases.find(ch))
|
||||
f.sendLock <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Send delivers to all subscribed channels simultaneously.
|
||||
// It returns the number of subscribers that the value was sent to.
|
||||
func (f *Feed) Send(value interface{}) (nsent int) {
|
||||
rvalue := reflect.ValueOf(value)
|
||||
|
||||
f.once.Do(f.init)
|
||||
<-f.sendLock
|
||||
|
||||
// Add new cases from the inbox after taking the send lock.
|
||||
f.mu.Lock()
|
||||
f.sendCases = append(f.sendCases, f.inbox...)
|
||||
f.inbox = nil
|
||||
|
||||
if !f.typecheck(rvalue.Type()) {
|
||||
f.sendLock <- struct{}{}
|
||||
f.mu.Unlock()
|
||||
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
// Set the sent value on all channels.
|
||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
||||
f.sendCases[i].Send = rvalue
|
||||
}
|
||||
|
||||
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
|
||||
// of sendCases. When a send succeeds, the corresponding case moves to the end of
|
||||
// 'cases' and it shrinks by one element.
|
||||
cases := f.sendCases
|
||||
for {
|
||||
// Fast path: try sending without blocking before adding to the select set.
|
||||
// This should usually succeed if subscribers are fast enough and have free
|
||||
// buffer space.
|
||||
for i := firstSubSendCase; i < len(cases); i++ {
|
||||
if cases[i].Chan.TrySend(rvalue) {
|
||||
nsent++
|
||||
cases = cases.deactivate(i)
|
||||
i--
|
||||
}
|
||||
}
|
||||
if len(cases) == firstSubSendCase {
|
||||
break
|
||||
}
|
||||
// Select on all the receivers, waiting for them to unblock.
|
||||
chosen, recv, _ := reflect.Select(cases)
|
||||
if chosen == 0 /* <-f.removeSub */ {
|
||||
index := f.sendCases.find(recv.Interface())
|
||||
f.sendCases = f.sendCases.delete(index)
|
||||
if index >= 0 && index < len(cases) {
|
||||
// Shrink 'cases' too because the removed case was still active.
|
||||
cases = f.sendCases[:len(cases)-1]
|
||||
}
|
||||
} else {
|
||||
cases = cases.deactivate(chosen)
|
||||
nsent++
|
||||
}
|
||||
}
|
||||
|
||||
// Forget about the sent value and hand off the send lock.
|
||||
for i := firstSubSendCase; i < len(f.sendCases); i++ {
|
||||
f.sendCases[i].Send = reflect.Value{}
|
||||
}
|
||||
f.sendLock <- struct{}{}
|
||||
return nsent
|
||||
}
|
||||
|
||||
type feedSub struct {
|
||||
feed *Feed
|
||||
channel reflect.Value
|
||||
errOnce sync.Once
|
||||
err chan error
|
||||
}
|
||||
|
||||
// Unsubscribe remove feed subscription.
|
||||
func (sub *feedSub) Unsubscribe() {
|
||||
sub.errOnce.Do(func() {
|
||||
sub.feed.remove(sub)
|
||||
close(sub.err)
|
||||
})
|
||||
}
|
||||
|
||||
// Err returns error channel.
|
||||
func (sub *feedSub) Err() <-chan error {
|
||||
return sub.err
|
||||
}
|
||||
|
||||
type caseList []reflect.SelectCase
|
||||
|
||||
// find returns the index of a case containing the given channel.
|
||||
func (cs caseList) find(channel interface{}) int {
|
||||
return slices.IndexFunc(cs, func(selectCase reflect.SelectCase) bool {
|
||||
return selectCase.Chan.Interface() == channel
|
||||
})
|
||||
}
|
||||
|
||||
// delete removes the given case from cs.
|
||||
func (cs caseList) delete(index int) caseList {
|
||||
return append(cs[:index], cs[index+1:]...)
|
||||
}
|
||||
|
||||
// deactivate moves the case at index into the non-accessible portion of the cs slice.
|
||||
func (cs caseList) deactivate(index int) caseList {
|
||||
last := len(cs) - 1
|
||||
cs[index], cs[last] = cs[last], cs[index]
|
||||
return cs[:last]
|
||||
}
|
||||
|
||||
// func (cs caseList) String() string {
|
||||
// s := "["
|
||||
// for i, cas := range cs {
|
||||
// if i != 0 {
|
||||
// s += ", "
|
||||
// }
|
||||
// switch cas.Dir {
|
||||
// case reflect.SelectSend:
|
||||
// s += fmt.Sprintf("%v<-", cas.Chan.Interface())
|
||||
// case reflect.SelectRecv:
|
||||
// s += fmt.Sprintf("<-%v", cas.Chan.Interface())
|
||||
// }
|
||||
// }
|
||||
// return s + "]"
|
||||
// }
|
||||
// Feed is a re-export of the go-ethereum event feed.
|
||||
type Feed = geth_event.Feed
|
||||
type Subscription = geth_event.Subscription
|
||||
|
||||
@@ -1,509 +0,0 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
)
|
||||
|
||||
func TestFeedPanics(t *testing.T) {
|
||||
{
|
||||
var f Feed
|
||||
f.Send(2)
|
||||
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
|
||||
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
|
||||
// Validate it doesn't deadlock.
|
||||
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
ch := make(chan int)
|
||||
f.Subscribe(ch)
|
||||
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
|
||||
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
f.Send(2)
|
||||
want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))}
|
||||
assert.NoError(t, checkPanic(want, func() { f.Subscribe(make(chan uint64)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
assert.NoError(t, checkPanic(errBadChannel, func() { f.Subscribe(make(<-chan int)) }))
|
||||
}
|
||||
{
|
||||
var f Feed
|
||||
assert.NoError(t, checkPanic(errBadChannel, func() { f.Subscribe(0) }))
|
||||
}
|
||||
}
|
||||
|
||||
func checkPanic(want error, fn func()) (err error) {
|
||||
defer func() {
|
||||
panicResult := recover()
|
||||
if panicResult == nil {
|
||||
err = fmt.Errorf("didn't panic")
|
||||
} else if !reflect.DeepEqual(panicResult, want) {
|
||||
err = fmt.Errorf("panicked with wrong error: got %q, want %q", panicResult, want)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFeed(t *testing.T) {
|
||||
var feed Feed
|
||||
var done, subscribed sync.WaitGroup
|
||||
subscriber := func(i int) {
|
||||
defer done.Done()
|
||||
|
||||
subchan := make(chan int)
|
||||
sub := feed.Subscribe(subchan)
|
||||
timeout := time.NewTimer(2 * time.Second)
|
||||
subscribed.Done()
|
||||
|
||||
select {
|
||||
case v := <-subchan:
|
||||
if v != 1 {
|
||||
t.Errorf("%d: received value %d, want 1", i, v)
|
||||
}
|
||||
case <-timeout.C:
|
||||
t.Errorf("%d: receive timeout", i)
|
||||
}
|
||||
|
||||
sub.Unsubscribe()
|
||||
select {
|
||||
case _, ok := <-sub.Err():
|
||||
if ok {
|
||||
t.Errorf("%d: error channel not closed after unsubscribe", i)
|
||||
}
|
||||
case <-timeout.C:
|
||||
t.Errorf("%d: unsubscribe timeout", i)
|
||||
}
|
||||
}
|
||||
|
||||
const n = 1000
|
||||
done.Add(n)
|
||||
subscribed.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go subscriber(i)
|
||||
}
|
||||
subscribed.Wait()
|
||||
if nsent := feed.Send(1); nsent != n {
|
||||
t.Errorf("first send delivered %d times, want %d", nsent, n)
|
||||
}
|
||||
if nsent := feed.Send(2); nsent != 0 {
|
||||
t.Errorf("second send delivered %d times, want 0", nsent)
|
||||
}
|
||||
done.Wait()
|
||||
}
|
||||
|
||||
func TestFeedSubscribeSameChannel(t *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
done sync.WaitGroup
|
||||
ch = make(chan int)
|
||||
sub1 = feed.Subscribe(ch)
|
||||
sub2 = feed.Subscribe(ch)
|
||||
_ = feed.Subscribe(ch)
|
||||
)
|
||||
expectSends := func(value, n int) {
|
||||
if nsent := feed.Send(value); nsent != n {
|
||||
t.Errorf("send delivered %d times, want %d", nsent, n)
|
||||
}
|
||||
done.Done()
|
||||
}
|
||||
expectRecv := func(wantValue, n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
if v := <-ch; v != wantValue {
|
||||
t.Errorf("received %d, want %d", v, wantValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done.Add(1)
|
||||
go expectSends(1, 3)
|
||||
expectRecv(1, 3)
|
||||
done.Wait()
|
||||
|
||||
sub1.Unsubscribe()
|
||||
|
||||
done.Add(1)
|
||||
go expectSends(2, 2)
|
||||
expectRecv(2, 2)
|
||||
done.Wait()
|
||||
|
||||
sub2.Unsubscribe()
|
||||
|
||||
done.Add(1)
|
||||
go expectSends(3, 1)
|
||||
expectRecv(3, 1)
|
||||
done.Wait()
|
||||
}
|
||||
|
||||
func TestFeedSubscribeBlockedPost(_ *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
nsends = 2000
|
||||
ch1 = make(chan int)
|
||||
ch2 = make(chan int)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
defer wg.Wait()
|
||||
|
||||
feed.Subscribe(ch1)
|
||||
wg.Add(nsends)
|
||||
for i := 0; i < nsends; i++ {
|
||||
go func() {
|
||||
feed.Send(99)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
sub2 := feed.Subscribe(ch2)
|
||||
defer sub2.Unsubscribe()
|
||||
|
||||
// We're done when ch1 has received N times.
|
||||
// The number of receives on ch2 depends on scheduling.
|
||||
for i := 0; i < nsends; {
|
||||
select {
|
||||
case <-ch1:
|
||||
i++
|
||||
case <-ch2:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeedUnsubscribeBlockedPost(_ *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
nsends = 200
|
||||
chans = make([]chan int, 2000)
|
||||
subs = make([]Subscription, len(chans))
|
||||
bchan = make(chan int)
|
||||
bsub = feed.Subscribe(bchan)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
for i := range chans {
|
||||
chans[i] = make(chan int, nsends)
|
||||
}
|
||||
|
||||
// Queue up some Sends. None of these can make progress while bchan isn't read.
|
||||
wg.Add(nsends)
|
||||
for i := 0; i < nsends; i++ {
|
||||
go func() {
|
||||
feed.Send(99)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
// Subscribe the other channels.
|
||||
for i, ch := range chans {
|
||||
subs[i] = feed.Subscribe(ch)
|
||||
}
|
||||
// Unsubscribe them again.
|
||||
for _, sub := range subs {
|
||||
sub.Unsubscribe()
|
||||
}
|
||||
// Unblock the Sends.
|
||||
bsub.Unsubscribe()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Checks that unsubscribing a channel during Send works even if that
|
||||
// channel has already been sent on.
|
||||
func TestFeedUnsubscribeSentChan(_ *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
ch1 = make(chan int)
|
||||
ch2 = make(chan int)
|
||||
sub1 = feed.Subscribe(ch1)
|
||||
sub2 = feed.Subscribe(ch2)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
defer sub2.Unsubscribe()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
feed.Send(0)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// Wait for the value on ch1.
|
||||
<-ch1
|
||||
// Unsubscribe ch1, removing it from the send cases.
|
||||
sub1.Unsubscribe()
|
||||
|
||||
// Receive ch2, finishing Send.
|
||||
<-ch2
|
||||
wg.Wait()
|
||||
|
||||
// Send again. This should send to ch2 only, so the wait group will unblock
|
||||
// as soon as a value is received on ch2.
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
feed.Send(0)
|
||||
wg.Done()
|
||||
}()
|
||||
<-ch2
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestFeedUnsubscribeFromInbox(t *testing.T) {
|
||||
var (
|
||||
feed Feed
|
||||
ch1 = make(chan int)
|
||||
ch2 = make(chan int)
|
||||
sub1 = feed.Subscribe(ch1)
|
||||
sub2 = feed.Subscribe(ch1)
|
||||
sub3 = feed.Subscribe(ch2)
|
||||
)
|
||||
assert.Equal(t, 3, len(feed.inbox))
|
||||
assert.Equal(t, 1, len(feed.sendCases), "sendCases is non-empty after unsubscribe")
|
||||
|
||||
sub1.Unsubscribe()
|
||||
sub2.Unsubscribe()
|
||||
sub3.Unsubscribe()
|
||||
assert.Equal(t, 0, len(feed.inbox), "Inbox is non-empty after unsubscribe")
|
||||
assert.Equal(t, 1, len(feed.sendCases), "sendCases is non-empty after unsubscribe")
|
||||
}
|
||||
|
||||
func BenchmarkFeedSend1000(b *testing.B) {
|
||||
var (
|
||||
done sync.WaitGroup
|
||||
feed Feed
|
||||
nsubs = 1000
|
||||
)
|
||||
subscriber := func(ch <-chan int) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
<-ch
|
||||
}
|
||||
done.Done()
|
||||
}
|
||||
done.Add(nsubs)
|
||||
for i := 0; i < nsubs; i++ {
|
||||
ch := make(chan int, 200)
|
||||
feed.Subscribe(ch)
|
||||
go subscriber(ch)
|
||||
}
|
||||
|
||||
// The actual benchmark.
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if feed.Send(i) != nsubs {
|
||||
panic("wrong number of sends")
|
||||
}
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
done.Wait()
|
||||
}
|
||||
|
||||
func TestFeed_Send(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
evFeed *Feed
|
||||
testSetup func(fd *Feed, t *testing.T, o interface{})
|
||||
obj interface{}
|
||||
expectPanic bool
|
||||
}{
|
||||
{
|
||||
name: "normal struct",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedWithPointer, 1)
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeedWithPointer{
|
||||
a: new(uint64),
|
||||
b: new(string),
|
||||
},
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "un-implemented interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface, 1)
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeedWithPointer{
|
||||
a: new(uint64),
|
||||
b: new(string),
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
{
|
||||
name: "semi-implemented interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface, 1)
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed2{
|
||||
a: 0,
|
||||
b: "",
|
||||
c: []byte{'A'},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
{
|
||||
name: "fully-implemented interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface)
|
||||
// Make it unbuffered to allow message to
|
||||
// pass through
|
||||
go func() {
|
||||
a := <-testChan
|
||||
if !reflect.DeepEqual(a, o) {
|
||||
t.Errorf("Got = %v, want = %v", a, o)
|
||||
}
|
||||
}()
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed{
|
||||
a: 0,
|
||||
b: "",
|
||||
},
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "fully-implemented interface with additional methods",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeedIface)
|
||||
// Make it unbuffered to allow message to
|
||||
// pass through
|
||||
go func() {
|
||||
a := <-testChan
|
||||
if !reflect.DeepEqual(a, o) {
|
||||
t.Errorf("Got = %v, want = %v", a, o)
|
||||
}
|
||||
}()
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed3{
|
||||
a: 0,
|
||||
b: "",
|
||||
c: []byte{'A'},
|
||||
d: []byte{'B'},
|
||||
},
|
||||
expectPanic: false,
|
||||
},
|
||||
{
|
||||
name: "concrete types implementing the same interface",
|
||||
evFeed: new(Feed),
|
||||
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
|
||||
testChan := make(chan testFeed, 1)
|
||||
// Make it unbuffered to allow message to
|
||||
// pass through
|
||||
go func() {
|
||||
a := <-testChan
|
||||
if !reflect.DeepEqual(a, o) {
|
||||
t.Errorf("Got = %v, want = %v", a, o)
|
||||
}
|
||||
}()
|
||||
fd.Subscribe(testChan)
|
||||
},
|
||||
obj: testFeed3{
|
||||
a: 0,
|
||||
b: "",
|
||||
c: []byte{'A'},
|
||||
d: []byte{'B'},
|
||||
},
|
||||
expectPanic: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.expectPanic {
|
||||
t.Errorf("panic triggered when unexpected: %v", r)
|
||||
}
|
||||
} else {
|
||||
if tt.expectPanic {
|
||||
t.Error("panic not triggered when expected")
|
||||
}
|
||||
}
|
||||
}()
|
||||
tt.testSetup(tt.evFeed, t, tt.obj)
|
||||
if gotNsent := tt.evFeed.Send(tt.obj); gotNsent != 1 {
|
||||
t.Errorf("Send() = %v, want %v", gotNsent, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The following objects below are a collection of different
|
||||
// struct types to test with.
|
||||
type testFeed struct {
|
||||
a uint64
|
||||
b string
|
||||
}
|
||||
|
||||
func (testFeed) method1() {
|
||||
|
||||
}
|
||||
|
||||
func (testFeed) method2() {
|
||||
|
||||
}
|
||||
|
||||
type testFeedWithPointer struct {
|
||||
a *uint64
|
||||
b *string
|
||||
}
|
||||
|
||||
type testFeed2 struct {
|
||||
a uint64
|
||||
b string
|
||||
c []byte
|
||||
}
|
||||
|
||||
func (testFeed2) method1() {
|
||||
|
||||
}
|
||||
|
||||
type testFeed3 struct {
|
||||
a uint64
|
||||
b string
|
||||
c, d []byte
|
||||
}
|
||||
|
||||
func (testFeed3) method1() {
|
||||
|
||||
}
|
||||
|
||||
func (testFeed3) method2() {
|
||||
|
||||
}
|
||||
|
||||
func (testFeed3) method3() {
|
||||
|
||||
}
|
||||
|
||||
type testFeedIface interface {
|
||||
method1()
|
||||
method2()
|
||||
}
|
||||
8
async/event/interface.go
Normal file
8
async/event/interface.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package event
|
||||
|
||||
// SubscriberSender is an abstract representation of an *event.Feed
|
||||
// to use in describing types that accept or return an *event.Feed.
|
||||
type SubscriberSender interface {
|
||||
Subscribe(channel interface{}) Subscription
|
||||
Send(value interface{}) (nsent int)
|
||||
}
|
||||
@@ -28,25 +28,6 @@ import (
|
||||
// request backoff time.
|
||||
const waitQuotient = 10
|
||||
|
||||
// Subscription represents a stream of events. The carrier of the events is typically a
|
||||
// channel, but isn't part of the interface.
|
||||
//
|
||||
// Subscriptions can fail while established. Failures are reported through an error
|
||||
// channel. It receives a value if there is an issue with the subscription (e.g. the
|
||||
// network connection delivering the events has been closed). Only one value will ever be
|
||||
// sent.
|
||||
//
|
||||
// The error channel is closed when the subscription ends successfully (i.e. when the
|
||||
// source of events is closed). It is also closed when Unsubscribe is called.
|
||||
//
|
||||
// The Unsubscribe method cancels the sending of events. You must call Unsubscribe in all
|
||||
// cases to ensure that resources related to the subscription are released. It can be
|
||||
// called any number of times.
|
||||
type Subscription interface {
|
||||
Err() <-chan error // returns the error channel
|
||||
Unsubscribe() // cancels sending of events, closing the error channel
|
||||
}
|
||||
|
||||
// NewSubscription runs a producer function as a subscription in a new goroutine. The
|
||||
// channel given to the producer is closed when Unsubscribe is called. If fn returns an
|
||||
// error, it is sent on the subscription's error channel.
|
||||
|
||||
@@ -2,7 +2,6 @@ package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
@@ -28,8 +28,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const blobCommitmentVersionKZG uint8 = 0x01
|
||||
|
||||
var defaultLatestValidHash = bytesutil.PadTo([]byte{0xff}, 32)
|
||||
|
||||
// notifyForkchoiceUpdate signals execution engine the fork choice updates. Execution engine should:
|
||||
@@ -219,17 +217,25 @@ func (s *Service) notifyNewPayload(ctx context.Context, preStateVersion int,
|
||||
}
|
||||
|
||||
var lastValidHash []byte
|
||||
var parentRoot *common.Hash
|
||||
var versionedHashes []common.Hash
|
||||
var requests *enginev1.ExecutionRequests
|
||||
if blk.Version() >= version.Deneb {
|
||||
var versionedHashes []common.Hash
|
||||
versionedHashes, err = kzgCommitmentsToVersionedHashes(blk.Block().Body())
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get versioned hashes to feed the engine")
|
||||
}
|
||||
pr := common.Hash(blk.Block().ParentRoot())
|
||||
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &pr)
|
||||
} else {
|
||||
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, []common.Hash{}, &common.Hash{} /*empty version hashes and root before Deneb*/)
|
||||
prh := common.Hash(blk.Block().ParentRoot())
|
||||
parentRoot = &prh
|
||||
}
|
||||
if blk.Version() >= version.Electra {
|
||||
requests, err = blk.Block().Body().ExecutionRequests()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get execution requests")
|
||||
}
|
||||
}
|
||||
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, parentRoot, requests)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
newPayloadValidNodeCount.Inc()
|
||||
@@ -323,7 +329,26 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
|
||||
var attr payloadattribute.Attributer
|
||||
switch st.Version() {
|
||||
case version.Deneb, version.Electra:
|
||||
case version.Electra:
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
attr, err = payloadattribute.New(&enginev1.PayloadAttributesV4{
|
||||
Timestamp: uint64(t.Unix()),
|
||||
PrevRandao: prevRando,
|
||||
SuggestedFeeRecipient: val.FeeRecipient[:],
|
||||
Withdrawals: withdrawals,
|
||||
ParentBeaconBlockRoot: headRoot,
|
||||
TargetBlobCount: field_params.MaxBlobsPerBlock / 2,
|
||||
MaxBlobCount: field_params.MaxBlobsPerBlock,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
case version.Deneb:
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
@@ -402,13 +427,7 @@ func kzgCommitmentsToVersionedHashes(body interfaces.ReadOnlyBeaconBlockBody) ([
|
||||
|
||||
versionedHashes := make([]common.Hash, len(commitments))
|
||||
for i, commitment := range commitments {
|
||||
versionedHashes[i] = ConvertKzgCommitmentToVersionedHash(commitment)
|
||||
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(commitment)
|
||||
}
|
||||
return versionedHashes, nil
|
||||
}
|
||||
|
||||
func ConvertKzgCommitmentToVersionedHash(commitment []byte) common.Hash {
|
||||
versionedHash := sha256.Sum256(commitment)
|
||||
versionedHash[0] = blobCommitmentVersionKZG
|
||||
return versionedHash
|
||||
}
|
||||
|
||||
@@ -162,6 +162,10 @@ func (s *Service) sendLightClientFinalityUpdate(ctx context.Context, signed inte
|
||||
postState state.BeaconState) (int, error) {
|
||||
// Get attested state
|
||||
attestedRoot := signed.Block().ParentRoot()
|
||||
attestedBlock, err := s.cfg.BeaconDB.Block(ctx, attestedRoot)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "could not get attested block")
|
||||
}
|
||||
attestedState, err := s.cfg.StateGen.StateByRoot(ctx, attestedRoot)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "could not get attested state")
|
||||
@@ -183,6 +187,7 @@ func (s *Service) sendLightClientFinalityUpdate(ctx context.Context, signed inte
|
||||
postState,
|
||||
signed,
|
||||
attestedState,
|
||||
attestedBlock,
|
||||
finalizedBlock,
|
||||
)
|
||||
|
||||
@@ -208,6 +213,10 @@ func (s *Service) sendLightClientOptimisticUpdate(ctx context.Context, signed in
|
||||
postState state.BeaconState) (int, error) {
|
||||
// Get attested state
|
||||
attestedRoot := signed.Block().ParentRoot()
|
||||
attestedBlock, err := s.cfg.BeaconDB.Block(ctx, attestedRoot)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "could not get attested block")
|
||||
}
|
||||
attestedState, err := s.cfg.StateGen.StateByRoot(ctx, attestedRoot)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "could not get attested state")
|
||||
@@ -218,6 +227,7 @@ func (s *Service) sendLightClientOptimisticUpdate(ctx context.Context, signed in
|
||||
postState,
|
||||
signed,
|
||||
attestedState,
|
||||
attestedBlock,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -273,7 +273,6 @@ func (s *Service) reportPostBlockProcessing(
|
||||
func (s *Service) executePostFinalizationTasks(ctx context.Context, finalizedState state.BeaconState) {
|
||||
finalized := s.cfg.ForkChoiceStore.FinalizedCheckpoint()
|
||||
go func() {
|
||||
finalizedState.SaveValidatorIndices() // used to handle Validator index invariant from EIP6110
|
||||
s.sendNewFinalizedEvent(ctx, finalizedState)
|
||||
}()
|
||||
depCtx, cancel := context.WithTimeout(context.Background(), depositDeadline)
|
||||
|
||||
@@ -329,8 +329,6 @@ func (s *Service) StartFromSavedState(saved state.BeaconState) error {
|
||||
return errors.Wrap(err, "failed to initialize blockchain service")
|
||||
}
|
||||
|
||||
saved.SaveValidatorIndices() // used to handle Validator index invariant from EIP6110
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ type mockBeaconNode struct {
|
||||
}
|
||||
|
||||
// StateFeed mocks the same method in the beacon node.
|
||||
func (mbn *mockBeaconNode) StateFeed() *event.Feed {
|
||||
func (mbn *mockBeaconNode) StateFeed() event.SubscriberSender {
|
||||
mbn.mu.Lock()
|
||||
defer mbn.mu.Unlock()
|
||||
if mbn.stateFeed == nil {
|
||||
|
||||
@@ -98,6 +98,44 @@ func (s *ChainService) BlockNotifier() blockfeed.Notifier {
|
||||
return s.blockNotifier
|
||||
}
|
||||
|
||||
type EventFeedWrapper struct {
|
||||
feed *event.Feed
|
||||
subscribed chan struct{} // this channel is closed once a subscription is made
|
||||
}
|
||||
|
||||
func (w *EventFeedWrapper) Subscribe(channel interface{}) event.Subscription {
|
||||
select {
|
||||
case <-w.subscribed:
|
||||
break // already closed
|
||||
default:
|
||||
close(w.subscribed)
|
||||
}
|
||||
return w.feed.Subscribe(channel)
|
||||
}
|
||||
|
||||
func (w *EventFeedWrapper) Send(value interface{}) int {
|
||||
return w.feed.Send(value)
|
||||
}
|
||||
|
||||
// WaitForSubscription allows test to wait for the feed to have a subscription before beginning to send events.
|
||||
func (w *EventFeedWrapper) WaitForSubscription(ctx context.Context) error {
|
||||
select {
|
||||
case <-w.subscribed:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
var _ event.SubscriberSender = &EventFeedWrapper{}
|
||||
|
||||
func NewEventFeedWrapper() *EventFeedWrapper {
|
||||
return &EventFeedWrapper{
|
||||
feed: new(event.Feed),
|
||||
subscribed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// MockBlockNotifier mocks the block notifier.
|
||||
type MockBlockNotifier struct {
|
||||
feed *event.Feed
|
||||
@@ -131,7 +169,7 @@ func (msn *MockStateNotifier) ReceivedEvents() []*feed.Event {
|
||||
}
|
||||
|
||||
// StateFeed returns a state feed.
|
||||
func (msn *MockStateNotifier) StateFeed() *event.Feed {
|
||||
func (msn *MockStateNotifier) StateFeed() event.SubscriberSender {
|
||||
msn.feedLock.Lock()
|
||||
defer msn.feedLock.Unlock()
|
||||
|
||||
@@ -159,6 +197,23 @@ func (msn *MockStateNotifier) StateFeed() *event.Feed {
|
||||
return msn.feed
|
||||
}
|
||||
|
||||
// NewSimpleStateNotifier makes a state feed without the custom mock feed machinery.
|
||||
func NewSimpleStateNotifier() *MockStateNotifier {
|
||||
return &MockStateNotifier{feed: new(event.Feed)}
|
||||
}
|
||||
|
||||
type SimpleNotifier struct {
|
||||
Feed event.SubscriberSender
|
||||
}
|
||||
|
||||
func (n *SimpleNotifier) StateFeed() event.SubscriberSender {
|
||||
return n.Feed
|
||||
}
|
||||
|
||||
func (n *SimpleNotifier) OperationFeed() event.SubscriberSender {
|
||||
return n.Feed
|
||||
}
|
||||
|
||||
// OperationNotifier mocks the same method in the chain service.
|
||||
func (s *ChainService) OperationNotifier() opfeed.Notifier {
|
||||
if s.opNotifier == nil {
|
||||
@@ -173,7 +228,7 @@ type MockOperationNotifier struct {
|
||||
}
|
||||
|
||||
// OperationFeed returns an operation feed.
|
||||
func (mon *MockOperationNotifier) OperationFeed() *event.Feed {
|
||||
func (mon *MockOperationNotifier) OperationFeed() event.SubscriberSender {
|
||||
if mon.feed == nil {
|
||||
mon.feed = new(event.Feed)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func ProcessDeposits(
|
||||
beaconState state.BeaconState,
|
||||
deposits []*ethpb.Deposit,
|
||||
) (state.BeaconState, error) {
|
||||
batchVerified, err := blocks.BatchVerifyDepositsSignatures(ctx, deposits)
|
||||
allSignaturesVerified, err := blocks.BatchVerifyDepositsSignatures(ctx, deposits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func ProcessDeposits(
|
||||
if deposit == nil || deposit.Data == nil {
|
||||
return nil, errors.New("got a nil deposit in block")
|
||||
}
|
||||
beaconState, err = ProcessDeposit(beaconState, deposit, batchVerified)
|
||||
beaconState, err = ProcessDeposit(beaconState, deposit, allSignaturesVerified)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not process deposit from %#x", bytesutil.Trunc(deposit.Data.PublicKey))
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func ProcessDeposits(
|
||||
// amount=deposit.data.amount,
|
||||
// signature=deposit.data.signature,
|
||||
// )
|
||||
func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verifySignature bool) (state.BeaconState, error) {
|
||||
func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, allSignaturesVerified bool) (state.BeaconState, error) {
|
||||
if err := blocks.VerifyDeposit(beaconState, deposit); err != nil {
|
||||
if deposit == nil || deposit.Data == nil {
|
||||
return nil, err
|
||||
@@ -92,7 +92,7 @@ func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verif
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ApplyDeposit(beaconState, deposit.Data, verifySignature)
|
||||
return ApplyDeposit(beaconState, deposit.Data, allSignaturesVerified)
|
||||
}
|
||||
|
||||
// ApplyDeposit
|
||||
@@ -115,13 +115,13 @@ func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verif
|
||||
// # Increase balance by deposit amount
|
||||
// index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
||||
// increase_balance(state, index, amount)
|
||||
func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, verifySignature bool) (state.BeaconState, error) {
|
||||
func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, allSignaturesVerified bool) (state.BeaconState, error) {
|
||||
pubKey := data.PublicKey
|
||||
amount := data.Amount
|
||||
withdrawalCredentials := data.WithdrawalCredentials
|
||||
index, ok := beaconState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
if !ok {
|
||||
if verifySignature {
|
||||
if !allSignaturesVerified {
|
||||
valid, err := blocks.IsValidDepositSignature(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -199,7 +199,7 @@ func TestProcessDeposit_SkipsInvalidDeposit(t *testing.T) {
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
newState, err := altair.ProcessDeposit(beaconState, dep[0], true)
|
||||
newState, err := altair.ProcessDeposit(beaconState, dep[0], false)
|
||||
require.NoError(t, err, "Expected invalid block deposit to be ignored without error")
|
||||
|
||||
if newState.Eth1DepositIndex() != 1 {
|
||||
|
||||
@@ -55,12 +55,26 @@ func BatchVerifyDepositsSignatures(ctx context.Context, deposits []*ethpb.Deposi
|
||||
return false, err
|
||||
}
|
||||
|
||||
verified := false
|
||||
if err := verifyDepositDataWithDomain(ctx, deposits, domain); err != nil {
|
||||
log.WithError(err).Debug("Failed to batch verify deposits signatures, will try individual verify")
|
||||
verified = true
|
||||
return false, nil
|
||||
}
|
||||
return verified, nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// BatchVerifyPendingDepositsSignatures batch verifies pending deposit signatures.
|
||||
func BatchVerifyPendingDepositsSignatures(ctx context.Context, deposits []*ethpb.PendingDeposit) (bool, error) {
|
||||
var err error
|
||||
domain, err := signing.ComputeDomain(params.BeaconConfig().DomainDeposit, nil, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := verifyPendingDepositDataWithDomain(ctx, deposits, domain); err != nil {
|
||||
log.WithError(err).Debug("Failed to batch verify deposits signatures, will try individual verify")
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsValidDepositSignature returns whether deposit_data is valid
|
||||
@@ -159,3 +173,44 @@ func verifyDepositDataWithDomain(ctx context.Context, deps []*ethpb.Deposit, dom
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyPendingDepositDataWithDomain(ctx context.Context, deps []*ethpb.PendingDeposit, domain []byte) error {
|
||||
if len(deps) == 0 {
|
||||
return nil
|
||||
}
|
||||
pks := make([]bls.PublicKey, len(deps))
|
||||
sigs := make([][]byte, len(deps))
|
||||
msgs := make([][32]byte, len(deps))
|
||||
for i, dep := range deps {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
if dep == nil {
|
||||
return errors.New("nil deposit")
|
||||
}
|
||||
dpk, err := bls.PublicKeyFromBytes(dep.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pks[i] = dpk
|
||||
sigs[i] = dep.Signature
|
||||
depositMessage := ðpb.DepositMessage{
|
||||
PublicKey: dep.PublicKey,
|
||||
WithdrawalCredentials: dep.WithdrawalCredentials,
|
||||
Amount: dep.Amount,
|
||||
}
|
||||
sr, err := signing.ComputeSigningRoot(depositMessage, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgs[i] = sr
|
||||
}
|
||||
verify, err := bls.VerifyMultipleSignatures(sigs, msgs, pks)
|
||||
if err != nil {
|
||||
return errors.Errorf("could not verify multiple signatures: %v", err)
|
||||
}
|
||||
if !verify {
|
||||
return errors.New("one or more deposit signatures did not verify")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,6 +17,41 @@ import (
|
||||
)
|
||||
|
||||
func TestBatchVerifyDepositsSignatures_Ok(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
domain, err := signing.ComputeDomain(params.BeaconConfig().DomainDeposit, nil, nil)
|
||||
require.NoError(t, err)
|
||||
deposit := ðpb.Deposit{
|
||||
Data: ðpb.Deposit_Data{
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
Amount: 3000,
|
||||
},
|
||||
}
|
||||
sr, err := signing.ComputeSigningRoot(ðpb.DepositMessage{
|
||||
PublicKey: deposit.Data.PublicKey,
|
||||
WithdrawalCredentials: deposit.Data.WithdrawalCredentials,
|
||||
Amount: 3000,
|
||||
}, domain)
|
||||
require.NoError(t, err)
|
||||
sig := sk.Sign(sr[:])
|
||||
deposit.Data.Signature = sig.Marshal()
|
||||
leaf, err := deposit.Data.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
// We then create a merkle branch for the test.
|
||||
depositTrie, err := trie.GenerateTrieFromItems([][]byte{leaf[:]}, params.BeaconConfig().DepositContractTreeDepth)
|
||||
require.NoError(t, err, "Could not generate trie")
|
||||
proof, err := depositTrie.MerkleProof(0)
|
||||
require.NoError(t, err, "Could not generate proof")
|
||||
|
||||
deposit.Proof = proof
|
||||
require.NoError(t, err)
|
||||
verified, err := blocks.BatchVerifyDepositsSignatures(context.Background(), []*ethpb.Deposit{deposit})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, verified)
|
||||
}
|
||||
|
||||
func TestBatchVerifyDepositsSignatures_InvalidSignature(t *testing.T) {
|
||||
deposit := ðpb.Deposit{
|
||||
Data: ðpb.Deposit_Data{
|
||||
PublicKey: bytesutil.PadTo([]byte{1, 2, 3}, 48),
|
||||
@@ -34,9 +69,9 @@ func TestBatchVerifyDepositsSignatures_Ok(t *testing.T) {
|
||||
|
||||
deposit.Proof = proof
|
||||
require.NoError(t, err)
|
||||
ok, err := blocks.BatchVerifyDepositsSignatures(context.Background(), []*ethpb.Deposit{deposit})
|
||||
verified, err := blocks.BatchVerifyDepositsSignatures(context.Background(), []*ethpb.Deposit{deposit})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, false, verified)
|
||||
}
|
||||
|
||||
func TestVerifyDeposit_MerkleBranchFailsVerification(t *testing.T) {
|
||||
@@ -93,3 +128,54 @@ func TestIsValidDepositSignature_Ok(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, valid)
|
||||
}
|
||||
|
||||
func TestBatchVerifyPendingDepositsSignatures_Ok(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
domain, err := signing.ComputeDomain(params.BeaconConfig().DomainDeposit, nil, nil)
|
||||
require.NoError(t, err)
|
||||
pendingDeposit := ðpb.PendingDeposit{
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
Amount: 3000,
|
||||
}
|
||||
sr, err := signing.ComputeSigningRoot(ðpb.DepositMessage{
|
||||
PublicKey: pendingDeposit.PublicKey,
|
||||
WithdrawalCredentials: pendingDeposit.WithdrawalCredentials,
|
||||
Amount: 3000,
|
||||
}, domain)
|
||||
require.NoError(t, err)
|
||||
sig := sk.Sign(sr[:])
|
||||
pendingDeposit.Signature = sig.Marshal()
|
||||
|
||||
sk2, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
pendingDeposit2 := ðpb.PendingDeposit{
|
||||
PublicKey: sk2.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
Amount: 4000,
|
||||
}
|
||||
sr2, err := signing.ComputeSigningRoot(ðpb.DepositMessage{
|
||||
PublicKey: pendingDeposit2.PublicKey,
|
||||
WithdrawalCredentials: pendingDeposit2.WithdrawalCredentials,
|
||||
Amount: 4000,
|
||||
}, domain)
|
||||
require.NoError(t, err)
|
||||
sig2 := sk2.Sign(sr2[:])
|
||||
pendingDeposit2.Signature = sig2.Marshal()
|
||||
|
||||
verified, err := blocks.BatchVerifyPendingDepositsSignatures(context.Background(), []*ethpb.PendingDeposit{pendingDeposit, pendingDeposit2})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, verified)
|
||||
}
|
||||
|
||||
func TestBatchVerifyPendingDepositsSignatures_InvalidSignature(t *testing.T) {
|
||||
pendingDeposit := ðpb.PendingDeposit{
|
||||
PublicKey: bytesutil.PadTo([]byte{1, 2, 3}, 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
verified, err := blocks.BatchVerifyPendingDepositsSignatures(context.Background(), []*ethpb.PendingDeposit{pendingDeposit})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, verified)
|
||||
}
|
||||
|
||||
@@ -213,6 +213,11 @@ func NewGenesisBlockForState(ctx context.Context, st state.BeaconState) (interfa
|
||||
},
|
||||
BlsToExecutionChanges: make([]*ethpb.SignedBLSToExecutionChange, 0),
|
||||
BlobKzgCommitments: make([][]byte, 0),
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Withdrawals: make([]*enginev1.WithdrawalRequest, 0),
|
||||
Deposits: make([]*enginev1.DepositRequest, 0),
|
||||
Consolidations: make([]*enginev1.ConsolidationRequest, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
|
||||
@@ -32,11 +32,13 @@ go_library(
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//contracts/deposit:go_default_library",
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
@@ -52,24 +54,27 @@ go_test(
|
||||
"deposit_fuzz_test.go",
|
||||
"deposits_test.go",
|
||||
"effective_balance_updates_test.go",
|
||||
"export_test.go",
|
||||
"registry_updates_test.go",
|
||||
"transition_test.go",
|
||||
"upgrade_test.go",
|
||||
"validator_test.go",
|
||||
"withdrawals_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
":go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/testing:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ProcessPendingConsolidations implements the spec definition below. This method makes mutating
|
||||
@@ -22,25 +23,28 @@ import (
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def process_pending_consolidations(state: BeaconState) -> None:
|
||||
// next_pending_consolidation = 0
|
||||
// for pending_consolidation in state.pending_consolidations:
|
||||
// source_validator = state.validators[pending_consolidation.source_index]
|
||||
// if source_validator.slashed:
|
||||
// next_pending_consolidation += 1
|
||||
// continue
|
||||
// if source_validator.withdrawable_epoch > get_current_epoch(state):
|
||||
// break
|
||||
// def process_pending_consolidations(state: BeaconState) -> None:
|
||||
//
|
||||
// # Churn any target excess active balance of target and raise its max
|
||||
// switch_to_compounding_validator(state, pending_consolidation.target_index)
|
||||
// # Move active balance to target. Excess balance is withdrawable.
|
||||
// active_balance = get_active_balance(state, pending_consolidation.source_index)
|
||||
// decrease_balance(state, pending_consolidation.source_index, active_balance)
|
||||
// increase_balance(state, pending_consolidation.target_index, active_balance)
|
||||
// next_epoch = Epoch(get_current_epoch(state) + 1)
|
||||
// next_pending_consolidation = 0
|
||||
// for pending_consolidation in state.pending_consolidations:
|
||||
// source_validator = state.validators[pending_consolidation.source_index]
|
||||
// if source_validator.slashed:
|
||||
// next_pending_consolidation += 1
|
||||
// continue
|
||||
// if source_validator.withdrawable_epoch > next_epoch:
|
||||
// break
|
||||
//
|
||||
// state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:]
|
||||
// # Calculate the consolidated balance
|
||||
// max_effective_balance = get_max_effective_balance(source_validator)
|
||||
// source_effective_balance = min(state.balances[pending_consolidation.source_index], max_effective_balance)
|
||||
//
|
||||
// # Move active balance to target. Excess balance is withdrawable.
|
||||
// decrease_balance(state, pending_consolidation.source_index, source_effective_balance)
|
||||
// increase_balance(state, pending_consolidation.target_index, source_effective_balance)
|
||||
// next_pending_consolidation += 1
|
||||
//
|
||||
// state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:]
|
||||
func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) error {
|
||||
_, span := trace.StartSpan(ctx, "electra.ProcessPendingConsolidations")
|
||||
defer span.End()
|
||||
@@ -51,12 +55,11 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) err
|
||||
|
||||
nextEpoch := slots.ToEpoch(st.Slot()) + 1
|
||||
|
||||
var nextPendingConsolidation uint64
|
||||
pendingConsolidations, err := st.PendingConsolidations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var nextPendingConsolidation uint64
|
||||
for _, pc := range pendingConsolidations {
|
||||
sourceValidator, err := st.ValidatorAtIndex(pc.SourceIndex)
|
||||
if err != nil {
|
||||
@@ -70,18 +73,16 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) err
|
||||
break
|
||||
}
|
||||
|
||||
if err := SwitchToCompoundingValidator(st, pc.TargetIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
activeBalance, err := st.ActiveBalanceAtIndex(pc.SourceIndex)
|
||||
validatorBalance, err := st.BalanceAtIndex(pc.SourceIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := helpers.DecreaseBalance(st, pc.SourceIndex, activeBalance); err != nil {
|
||||
b := min(validatorBalance, helpers.ValidatorMaxEffectiveBalance(sourceValidator))
|
||||
|
||||
if err := helpers.DecreaseBalance(st, pc.SourceIndex, b); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := helpers.IncreaseBalance(st, pc.TargetIndex, activeBalance); err != nil {
|
||||
if err := helpers.IncreaseBalance(st, pc.TargetIndex, b); err != nil {
|
||||
return err
|
||||
}
|
||||
nextPendingConsolidation++
|
||||
@@ -101,6 +102,16 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) err
|
||||
// state: BeaconState,
|
||||
// consolidation_request: ConsolidationRequest
|
||||
// ) -> None:
|
||||
// if is_valid_switch_to_compounding_request(state, consolidation_request):
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
// request_source_pubkey = consolidation_request.source_pubkey
|
||||
// source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey))
|
||||
// switch_to_compounding_validator(state, source_index)
|
||||
// return
|
||||
//
|
||||
// # Verify that source != target, so a consolidation cannot be used as an exit.
|
||||
// if consolidation_request.source_pubkey == consolidation_request.target_pubkey:
|
||||
// return
|
||||
// # If the pending consolidations queue is full, consolidation requests are ignored
|
||||
// if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT:
|
||||
// return
|
||||
@@ -121,10 +132,6 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) err
|
||||
// source_validator = state.validators[source_index]
|
||||
// target_validator = state.validators[target_index]
|
||||
//
|
||||
// # Verify that source != target, so a consolidation cannot be used as an exit.
|
||||
// if source_index == target_index:
|
||||
// return
|
||||
//
|
||||
// # Verify source withdrawal credentials
|
||||
// has_correct_credential = has_execution_withdrawal_credential(source_validator)
|
||||
// is_correct_source_address = (
|
||||
@@ -160,19 +167,14 @@ func ProcessPendingConsolidations(ctx context.Context, st state.BeaconState) err
|
||||
// source_index=source_index,
|
||||
// target_index=target_index
|
||||
// ))
|
||||
//
|
||||
// # Churn any target excess active balance of target and raise its max
|
||||
// if has_eth1_withdrawal_credential(target_validator):
|
||||
// switch_to_compounding_validator(state, target_index)
|
||||
func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, reqs []*enginev1.ConsolidationRequest) error {
|
||||
if len(reqs) == 0 || st == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
activeBal, err := helpers.TotalActiveBalance(st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
churnLimit := helpers.ConsolidationChurnLimit(primitives.Gwei(activeBal))
|
||||
if churnLimit <= primitives.Gwei(params.BeaconConfig().MinActivationBalance) {
|
||||
return nil
|
||||
}
|
||||
curEpoch := slots.ToEpoch(st.Slot())
|
||||
ffe := params.BeaconConfig().FarFutureEpoch
|
||||
minValWithdrawDelay := params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
||||
@@ -182,22 +184,44 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("cannot process consolidation requests: %w", ctx.Err())
|
||||
}
|
||||
if IsValidSwitchToCompoundingRequest(st, cr) {
|
||||
srcIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(cr.SourcePubkey))
|
||||
if !ok {
|
||||
log.Error("failed to find source validator index")
|
||||
continue
|
||||
}
|
||||
if err := SwitchToCompoundingValidator(st, srcIdx); err != nil {
|
||||
log.WithError(err).Error("failed to switch to compounding validator")
|
||||
}
|
||||
continue
|
||||
}
|
||||
sourcePubkey := bytesutil.ToBytes48(cr.SourcePubkey)
|
||||
targetPubkey := bytesutil.ToBytes48(cr.TargetPubkey)
|
||||
if sourcePubkey == targetPubkey {
|
||||
continue
|
||||
}
|
||||
|
||||
if npc, err := st.NumPendingConsolidations(); err != nil {
|
||||
return fmt.Errorf("failed to fetch number of pending consolidations: %w", err) // This should never happen.
|
||||
} else if npc >= pcLimit {
|
||||
return nil
|
||||
}
|
||||
|
||||
srcIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(cr.SourcePubkey))
|
||||
if !ok {
|
||||
continue
|
||||
activeBal, err := helpers.TotalActiveBalance(st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tgtIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(cr.TargetPubkey))
|
||||
if !ok {
|
||||
continue
|
||||
churnLimit := helpers.ConsolidationChurnLimit(primitives.Gwei(activeBal))
|
||||
if churnLimit <= primitives.Gwei(params.BeaconConfig().MinActivationBalance) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if srcIdx == tgtIdx {
|
||||
srcIdx, ok := st.ValidatorIndexByPubkey(sourcePubkey)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tgtIdx, ok := st.ValidatorIndexByPubkey(targetPubkey)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -237,7 +261,8 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
|
||||
// Initiate the exit of the source validator.
|
||||
exitEpoch, err := ComputeConsolidationEpochAndUpdateChurn(ctx, st, primitives.Gwei(srcV.EffectiveBalance))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute consolidaiton epoch: %w", err)
|
||||
log.WithError(err).Error("failed to compute consolidation epoch")
|
||||
continue
|
||||
}
|
||||
srcV.ExitEpoch = exitEpoch
|
||||
srcV.WithdrawableEpoch = exitEpoch + minValWithdrawDelay
|
||||
@@ -248,7 +273,95 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
|
||||
if err := st.AppendPendingConsolidation(ð.PendingConsolidation{SourceIndex: srcIdx, TargetIndex: tgtIdx}); err != nil {
|
||||
return fmt.Errorf("failed to append pending consolidation: %w", err) // This should never happen.
|
||||
}
|
||||
|
||||
if helpers.HasETH1WithdrawalCredential(tgtV) {
|
||||
if err := SwitchToCompoundingValidator(st, tgtIdx); err != nil {
|
||||
log.WithError(err).Error("failed to switch to compounding validator")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValidSwitchToCompoundingRequest returns true if the given consolidation request is valid for switching to compounding.
|
||||
//
|
||||
// Spec code:
|
||||
//
|
||||
// def is_valid_switch_to_compounding_request(
|
||||
//
|
||||
// state: BeaconState,
|
||||
// consolidation_request: ConsolidationRequest
|
||||
//
|
||||
// ) -> bool:
|
||||
//
|
||||
// # Switch to compounding requires source and target be equal
|
||||
// if consolidation_request.source_pubkey != consolidation_request.target_pubkey:
|
||||
// return False
|
||||
//
|
||||
// # Verify pubkey exists
|
||||
// source_pubkey = consolidation_request.source_pubkey
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
// if source_pubkey not in validator_pubkeys:
|
||||
// return False
|
||||
//
|
||||
// source_validator = state.validators[ValidatorIndex(validator_pubkeys.index(source_pubkey))]
|
||||
//
|
||||
// # Verify request has been authorized
|
||||
// if source_validator.withdrawal_credentials[12:] != consolidation_request.source_address:
|
||||
// return False
|
||||
//
|
||||
// # Verify source withdrawal credentials
|
||||
// if not has_eth1_withdrawal_credential(source_validator):
|
||||
// return False
|
||||
//
|
||||
// # Verify the source is active
|
||||
// current_epoch = get_current_epoch(state)
|
||||
// if not is_active_validator(source_validator, current_epoch):
|
||||
// return False
|
||||
//
|
||||
// # Verify exit for source has not been initiated
|
||||
// if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
|
||||
// return False
|
||||
//
|
||||
// return True
|
||||
func IsValidSwitchToCompoundingRequest(st state.BeaconState, req *enginev1.ConsolidationRequest) bool {
|
||||
if req.SourcePubkey == nil || req.TargetPubkey == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !bytes.Equal(req.SourcePubkey, req.TargetPubkey) {
|
||||
return false
|
||||
}
|
||||
|
||||
srcIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(req.SourcePubkey))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// As per the consensus specification, this error is not considered an assertion.
|
||||
// Therefore, if the source_pubkey is not found in validator_pubkeys, we simply return false.
|
||||
srcV, err := st.ValidatorAtIndexReadOnly(srcIdx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
sourceAddress := req.SourceAddress
|
||||
withdrawalCreds := srcV.GetWithdrawalCredentials()
|
||||
if len(withdrawalCreds) != 32 || len(sourceAddress) != 20 || !bytes.HasSuffix(withdrawalCreds, sourceAddress) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !helpers.HasETH1WithdrawalCredential(srcV) {
|
||||
return false
|
||||
}
|
||||
|
||||
curEpoch := slots.ToEpoch(st.Slot())
|
||||
if !helpers.IsActiveValidatorUsingTrie(srcV, curEpoch) {
|
||||
return false
|
||||
}
|
||||
|
||||
if srcV.ExitEpoch() != params.BeaconConfig().FarFutureEpoch {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
)
|
||||
|
||||
func TestProcessPendingConsolidations(t *testing.T) {
|
||||
@@ -80,10 +81,10 @@ func TestProcessPendingConsolidations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), num)
|
||||
|
||||
// v1 is switched to compounding validator.
|
||||
// v1 withdrawal credentials should not be updated.
|
||||
v1, err := st.ValidatorAtIndex(1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().CompoundingWithdrawalPrefixByte, v1.WithdrawalCredentials[0])
|
||||
require.Equal(t, params.BeaconConfig().ETH1AddressWithdrawalPrefixByte, v1.WithdrawalCredentials[0])
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -201,38 +202,6 @@ func TestProcessPendingConsolidations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func stateWithActiveBalanceETH(t *testing.T, balETH uint64) state.BeaconState {
|
||||
gwei := balETH * 1_000_000_000
|
||||
balPerVal := params.BeaconConfig().MinActivationBalance
|
||||
numVals := gwei / balPerVal
|
||||
|
||||
vals := make([]*eth.Validator, numVals)
|
||||
bals := make([]uint64, numVals)
|
||||
for i := uint64(0); i < numVals; i++ {
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(i)
|
||||
vals[i] = ð.Validator{
|
||||
ActivationEpoch: 0,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: balPerVal,
|
||||
WithdrawalCredentials: wc,
|
||||
}
|
||||
bals[i] = balPerVal
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoUnsafeElectra(ð.BeaconStateElectra{
|
||||
Slot: 10 * params.BeaconConfig().SlotsPerEpoch,
|
||||
Validators: vals,
|
||||
Balances: bals,
|
||||
Fork: ð.Fork{
|
||||
CurrentVersion: params.BeaconConfig().ElectraForkVersion,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
func TestProcessConsolidationRequests(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -428,3 +397,87 @@ func TestProcessConsolidationRequests(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidSwitchToCompoundingRequest(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 1)
|
||||
t.Run("nil source pubkey", func(t *testing.T) {
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
SourcePubkey: nil,
|
||||
TargetPubkey: []byte{'a'},
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("nil target pubkey", func(t *testing.T) {
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
TargetPubkey: nil,
|
||||
SourcePubkey: []byte{'a'},
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("different source and target pubkey", func(t *testing.T) {
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
TargetPubkey: []byte{'a'},
|
||||
SourcePubkey: []byte{'b'},
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("source validator not found in state", func(t *testing.T) {
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: make([]byte, 20),
|
||||
TargetPubkey: []byte{'a'},
|
||||
SourcePubkey: []byte{'a'},
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("incorrect source address", func(t *testing.T) {
|
||||
v, err := st.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
pubkey := v.PublicKey
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: make([]byte, 20),
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("incorrect eth1 withdrawal credential", func(t *testing.T) {
|
||||
v, err := st.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
pubkey := v.PublicKey
|
||||
wc := v.WithdrawalCredentials
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: wc[12:],
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("is valid compounding request", func(t *testing.T) {
|
||||
v, err := st.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
pubkey := v.PublicKey
|
||||
wc := v.WithdrawalCredentials
|
||||
v.WithdrawalCredentials[0] = 1
|
||||
require.NoError(t, st.UpdateValidatorAtIndex(0, v))
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: wc[12:],
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.Equal(t, true, ok)
|
||||
})
|
||||
t.Run("already has an exit epoch", func(t *testing.T) {
|
||||
v, err := st.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
pubkey := v.PublicKey
|
||||
wc := v.WithdrawalCredentials
|
||||
v.ExitEpoch = 100
|
||||
require.NoError(t, st.UpdateValidatorAtIndex(0, v))
|
||||
ok := electra.IsValidSwitchToCompoundingRequest(st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: wc[12:],
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package electra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
@@ -17,6 +16,7 @@ import (
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ func ProcessDeposits(
|
||||
defer span.End()
|
||||
// Attempt to verify all deposit signatures at once, if this fails then fall back to processing
|
||||
// individual deposits with signature verification enabled.
|
||||
batchVerified, err := blocks.BatchVerifyDepositsSignatures(ctx, deposits)
|
||||
allSignaturesVerified, err := blocks.BatchVerifyDepositsSignatures(ctx, deposits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify deposit signatures in batch")
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func ProcessDeposits(
|
||||
if d == nil || d.Data == nil {
|
||||
return nil, errors.New("got a nil deposit in block")
|
||||
}
|
||||
beaconState, err = ProcessDeposit(beaconState, d, batchVerified)
|
||||
beaconState, err = ProcessDeposit(beaconState, d, allSignaturesVerified)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not process deposit from %#x", bytesutil.Trunc(d.Data.PublicKey))
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func ProcessDeposits(
|
||||
// amount=deposit.data.amount,
|
||||
// signature=deposit.data.signature,
|
||||
// )
|
||||
func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verifySignature bool) (state.BeaconState, error) {
|
||||
func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, allSignaturesVerified bool) (state.BeaconState, error) {
|
||||
if err := blocks.VerifyDeposit(beaconState, deposit); err != nil {
|
||||
if deposit == nil || deposit.Data == nil {
|
||||
return nil, err
|
||||
@@ -92,37 +92,49 @@ func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verif
|
||||
if err := beaconState.SetEth1DepositIndex(beaconState.Eth1DepositIndex() + 1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ApplyDeposit(beaconState, deposit.Data, verifySignature)
|
||||
return ApplyDeposit(beaconState, deposit.Data, allSignaturesVerified)
|
||||
}
|
||||
|
||||
// ApplyDeposit
|
||||
// def apply_deposit(state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64, signature: BLSSignature) -> None:
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
// if pubkey not in validator_pubkeys:
|
||||
// ApplyDeposit adds the incoming deposit as a pending deposit on the state
|
||||
//
|
||||
// # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
// if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
|
||||
// add_validator_to_registry(state, pubkey, withdrawal_credentials, amount)
|
||||
// Spec pseudocode definition:
|
||||
// def apply_deposit(state: BeaconState,
|
||||
//
|
||||
// else:
|
||||
//
|
||||
// # Increase balance by deposit amount
|
||||
// index = ValidatorIndex(validator_pubkeys.index(pubkey))
|
||||
// state.pending_balance_deposits.append(PendingBalanceDeposit(index=index, amount=amount)) # [Modified in Electra:EIP-7251]
|
||||
// # Check if valid deposit switch to compounding credentials
|
||||
//
|
||||
// if ( is_compounding_withdrawal_credential(withdrawal_credentials) and has_eth1_withdrawal_credential(state.validators[index])
|
||||
//
|
||||
// and is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature)
|
||||
// ):
|
||||
// switch_to_compounding_validator(state, index)
|
||||
func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, verifySignature bool) (state.BeaconState, error) {
|
||||
// pubkey: BLSPubkey,
|
||||
// withdrawal_credentials: Bytes32,
|
||||
// amount: uint64,
|
||||
// signature: BLSSignature) -> None:
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
// if pubkey not in validator_pubkeys:
|
||||
// # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
// if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
|
||||
// add_validator_to_registry(state, pubkey, withdrawal_credentials, Gwei(0)) # [Modified in Electra:EIP7251]
|
||||
// # [New in Electra:EIP7251]
|
||||
// state.pending_deposits.append(PendingDeposit(
|
||||
// pubkey=pubkey,
|
||||
// withdrawal_credentials=withdrawal_credentials,
|
||||
// amount=amount,
|
||||
// signature=signature,
|
||||
// slot=GENESIS_SLOT, # Use GENESIS_SLOT to distinguish from a pending deposit request
|
||||
// ))
|
||||
// else:
|
||||
// # Increase balance by deposit amount
|
||||
// # [Modified in Electra:EIP7251]
|
||||
// state.pending_deposits.append(PendingDeposit(
|
||||
// pubkey=pubkey,
|
||||
// withdrawal_credentials=withdrawal_credentials,
|
||||
// amount=amount,
|
||||
// signature=signature,
|
||||
// slot=GENESIS_SLOT # Use GENESIS_SLOT to distinguish from a pending deposit request
|
||||
// ))
|
||||
func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, allSignaturesVerified bool) (state.BeaconState, error) {
|
||||
pubKey := data.PublicKey
|
||||
amount := data.Amount
|
||||
withdrawalCredentials := data.WithdrawalCredentials
|
||||
index, ok := beaconState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
signature := data.Signature
|
||||
_, ok := beaconState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
if !ok {
|
||||
if verifySignature {
|
||||
if !allSignaturesVerified {
|
||||
valid, err := IsValidDepositSignature(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify deposit signature")
|
||||
@@ -131,32 +143,20 @@ func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, verif
|
||||
return beaconState, nil
|
||||
}
|
||||
}
|
||||
if err := AddValidatorToRegistry(beaconState, pubKey, withdrawalCredentials, amount); err != nil {
|
||||
|
||||
if err := AddValidatorToRegistry(beaconState, pubKey, withdrawalCredentials, 0); err != nil { // # [Modified in Electra:EIP7251]
|
||||
return nil, errors.Wrap(err, "could not add validator to registry")
|
||||
}
|
||||
} else {
|
||||
// no validation on top-ups (phase0 feature). no validation before state change
|
||||
if err := beaconState.AppendPendingBalanceDeposit(index, amount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val, err := beaconState.ValidatorAtIndex(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if helpers.IsCompoundingWithdrawalCredential(withdrawalCredentials) && helpers.HasETH1WithdrawalCredential(val) {
|
||||
if verifySignature {
|
||||
valid, err := IsValidDepositSignature(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify deposit signature")
|
||||
}
|
||||
if !valid {
|
||||
return beaconState, nil
|
||||
}
|
||||
}
|
||||
if err := SwitchToCompoundingValidator(beaconState, index); err != nil {
|
||||
return nil, errors.Wrap(err, "could not switch to compound validator")
|
||||
}
|
||||
}
|
||||
}
|
||||
// no validation on top-ups (phase0 feature). no validation before state change
|
||||
if err := beaconState.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: amount,
|
||||
Signature: signature,
|
||||
Slot: params.BeaconConfig().GenesisSlot,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return beaconState, nil
|
||||
}
|
||||
@@ -185,152 +185,364 @@ func verifyDepositDataSigningRoot(obj *ethpb.Deposit_Data, domain []byte) error
|
||||
return deposit.VerifyDepositSignature(obj, domain)
|
||||
}
|
||||
|
||||
// ProcessPendingBalanceDeposits implements the spec definition below. This method mutates the state.
|
||||
// ProcessPendingDeposits implements the spec definition below. This method mutates the state.
|
||||
// Iterating over `pending_deposits` queue this function runs the following checks before applying pending deposit:
|
||||
// 1. All Eth1 bridge deposits are processed before the first deposit request gets processed.
|
||||
// 2. Deposit position in the queue is finalized.
|
||||
// 3. Deposit does not exceed the `MAX_PENDING_DEPOSITS_PER_EPOCH` limit.
|
||||
// 4. Deposit does not exceed the activation churn limit.
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def process_pending_balance_deposits(state: BeaconState) -> None:
|
||||
// available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state)
|
||||
// processed_amount = 0
|
||||
// next_deposit_index = 0
|
||||
// deposits_to_postpone = []
|
||||
// def process_pending_deposits(state: BeaconState) -> None:
|
||||
//
|
||||
// for deposit in state.pending_balance_deposits:
|
||||
// validator = state.validators[deposit.index]
|
||||
// next_epoch = Epoch(get_current_epoch(state) + 1)
|
||||
// available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state)
|
||||
// processed_amount = 0
|
||||
// next_deposit_index = 0
|
||||
// deposits_to_postpone = []
|
||||
// is_churn_limit_reached = False
|
||||
// finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
|
||||
//
|
||||
// for deposit in state.pending_deposits:
|
||||
// # Do not process deposit requests if Eth1 bridge deposits are not yet applied.
|
||||
// if (
|
||||
// # Is deposit request
|
||||
// deposit.slot > GENESIS_SLOT and
|
||||
// # There are pending Eth1 bridge deposits
|
||||
// state.eth1_deposit_index < state.deposit_requests_start_index
|
||||
// ):
|
||||
// break
|
||||
//
|
||||
// # Check if deposit has been finalized, otherwise, stop processing.
|
||||
// if deposit.slot > finalized_slot:
|
||||
// break
|
||||
//
|
||||
// # Check if number of processed deposits has not reached the limit, otherwise, stop processing.
|
||||
// if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH:
|
||||
// break
|
||||
//
|
||||
// # Read validator state
|
||||
// is_validator_exited = False
|
||||
// is_validator_withdrawn = False
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
// if deposit.pubkey in validator_pubkeys:
|
||||
// validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))]
|
||||
// is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH
|
||||
// is_validator_withdrawn = validator.withdrawable_epoch < next_epoch
|
||||
//
|
||||
// if is_validator_withdrawn:
|
||||
// # Deposited balance will never become active. Increase balance but do not consume churn
|
||||
// apply_pending_deposit(state, deposit)
|
||||
// elif is_validator_exited:
|
||||
// # Validator is exiting, postpone the deposit until after withdrawable epoch
|
||||
// if validator.exit_epoch < FAR_FUTURE_EPOCH:
|
||||
// if get_current_epoch(state) <= validator.withdrawable_epoch:
|
||||
// deposits_to_postpone.append(deposit)
|
||||
// # Deposited balance will never become active. Increase balance but do not consume churn
|
||||
// else:
|
||||
// increase_balance(state, deposit.index, deposit.amount)
|
||||
// # Validator is not exiting, attempt to process deposit
|
||||
// else:
|
||||
// # Deposit does not fit in the churn, no more deposit processing in this epoch.
|
||||
// if processed_amount + deposit.amount > available_for_processing:
|
||||
// break
|
||||
// # Deposit fits in the churn, process it. Increase balance and consume churn.
|
||||
// else:
|
||||
// increase_balance(state, deposit.index, deposit.amount)
|
||||
// processed_amount += deposit.amount
|
||||
// # Regardless of how the deposit was handled, we move on in the queue.
|
||||
// next_deposit_index += 1
|
||||
//
|
||||
// state.pending_balance_deposits = state.pending_balance_deposits[next_deposit_index:]
|
||||
//
|
||||
// if len(state.pending_balance_deposits) == 0:
|
||||
// state.deposit_balance_to_consume = Gwei(0)
|
||||
// deposits_to_postpone.append(deposit)
|
||||
// else:
|
||||
// state.deposit_balance_to_consume = available_for_processing - processed_amount
|
||||
// # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
|
||||
// is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing
|
||||
// if is_churn_limit_reached:
|
||||
// break
|
||||
//
|
||||
// state.pending_balance_deposits += deposits_to_postpone
|
||||
func ProcessPendingBalanceDeposits(ctx context.Context, st state.BeaconState, activeBalance primitives.Gwei) error {
|
||||
_, span := trace.StartSpan(ctx, "electra.ProcessPendingBalanceDeposits")
|
||||
// # Consume churn and apply deposit.
|
||||
// processed_amount += deposit.amount
|
||||
// apply_pending_deposit(state, deposit)
|
||||
//
|
||||
// # Regardless of how the deposit was handled, we move on in the queue.
|
||||
// next_deposit_index += 1
|
||||
//
|
||||
// state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone
|
||||
//
|
||||
// # Accumulate churn only if the churn limit has been hit.
|
||||
// if is_churn_limit_reached:
|
||||
// state.deposit_balance_to_consume = available_for_processing - processed_amount
|
||||
// else:
|
||||
// state.deposit_balance_to_consume = Gwei(0)
|
||||
func ProcessPendingDeposits(ctx context.Context, st state.BeaconState, activeBalance primitives.Gwei) error {
|
||||
_, span := trace.StartSpan(ctx, "electra.ProcessPendingDeposits")
|
||||
defer span.End()
|
||||
|
||||
if st == nil || st.IsNil() {
|
||||
return errors.New("nil state")
|
||||
}
|
||||
|
||||
// constants & initializations
|
||||
nextEpoch := slots.ToEpoch(st.Slot()) + 1
|
||||
processedAmount := uint64(0)
|
||||
nextDepositIndex := uint64(0)
|
||||
isChurnLimitReached := false
|
||||
|
||||
var pendingDepositsToBatchVerify []*ethpb.PendingDeposit
|
||||
var pendingDepositsToPostpone []*eth.PendingDeposit
|
||||
|
||||
depBalToConsume, err := st.DepositBalanceToConsume()
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "could not get deposit balance to consume")
|
||||
}
|
||||
availableForProcessing := depBalToConsume + helpers.ActivationExitChurnLimit(activeBalance)
|
||||
processedAmount := uint64(0)
|
||||
nextDepositIndex := 0
|
||||
var depositsToPostpone []*eth.PendingBalanceDeposit
|
||||
|
||||
deposits, err := st.PendingBalanceDeposits()
|
||||
finalizedSlot, err := slots.EpochStart(st.FinalizedCheckpoint().Epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized slot")
|
||||
}
|
||||
|
||||
startIndex, err := st.DepositRequestsStartIndex()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get starting pendingDeposit index")
|
||||
}
|
||||
|
||||
pendingDeposits, err := st.PendingDeposits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// constants
|
||||
ffe := params.BeaconConfig().FarFutureEpoch
|
||||
nextEpoch := slots.ToEpoch(st.Slot()) + 1
|
||||
|
||||
for _, balanceDeposit := range deposits {
|
||||
v, err := st.ValidatorAtIndexReadOnly(balanceDeposit.Index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch validator at index: %w", err)
|
||||
for _, pendingDeposit := range pendingDeposits {
|
||||
// Do not process pendingDeposit requests if Eth1 bridge deposits are not yet applied.
|
||||
if pendingDeposit.Slot > params.BeaconConfig().GenesisSlot && st.Eth1DepositIndex() < startIndex {
|
||||
break
|
||||
}
|
||||
|
||||
// If the validator is currently exiting, postpone the deposit until after the withdrawable
|
||||
// epoch.
|
||||
if v.ExitEpoch() < ffe {
|
||||
if nextEpoch <= v.WithdrawableEpoch() {
|
||||
depositsToPostpone = append(depositsToPostpone, balanceDeposit)
|
||||
} else {
|
||||
// The deposited balance will never become active. Therefore, we increase the balance but do
|
||||
// not consume the churn.
|
||||
if err := helpers.IncreaseBalance(st, balanceDeposit.Index, balanceDeposit.Amount); err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if pendingDeposit has been finalized, otherwise, stop processing.
|
||||
if pendingDeposit.Slot > finalizedSlot {
|
||||
break
|
||||
}
|
||||
|
||||
// Check if number of processed deposits has not reached the limit, otherwise, stop processing.
|
||||
if nextDepositIndex >= params.BeaconConfig().MaxPendingDepositsPerEpoch {
|
||||
break
|
||||
}
|
||||
|
||||
var isValidatorExited bool
|
||||
var isValidatorWithdrawn bool
|
||||
index, found := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(pendingDeposit.PublicKey))
|
||||
if found {
|
||||
val, err := st.ValidatorAtIndexReadOnly(index)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get validator")
|
||||
}
|
||||
isValidatorExited = val.ExitEpoch() < params.BeaconConfig().FarFutureEpoch
|
||||
isValidatorWithdrawn = val.WithdrawableEpoch() < nextEpoch
|
||||
}
|
||||
|
||||
if isValidatorWithdrawn {
|
||||
// note: the validator will never be active, just increase the balance
|
||||
if err := helpers.IncreaseBalance(st, index, pendingDeposit.Amount); err != nil {
|
||||
return errors.Wrap(err, "could not increase balance")
|
||||
}
|
||||
} else if isValidatorExited {
|
||||
pendingDepositsToPostpone = append(pendingDepositsToPostpone, pendingDeposit)
|
||||
} else {
|
||||
// Validator is not exiting, attempt to process deposit.
|
||||
if primitives.Gwei(processedAmount+balanceDeposit.Amount) > availableForProcessing {
|
||||
isChurnLimitReached = primitives.Gwei(processedAmount+pendingDeposit.Amount) > availableForProcessing
|
||||
if isChurnLimitReached {
|
||||
break
|
||||
}
|
||||
// Deposit fits in churn, process it. Increase balance and consume churn.
|
||||
if err := helpers.IncreaseBalance(st, balanceDeposit.Index, balanceDeposit.Amount); err != nil {
|
||||
return err
|
||||
processedAmount += pendingDeposit.Amount
|
||||
|
||||
// note: the following code deviates from the spec in order to perform batch signature verification
|
||||
if found {
|
||||
if err := helpers.IncreaseBalance(st, index, pendingDeposit.Amount); err != nil {
|
||||
return errors.Wrap(err, "could not increase balance")
|
||||
}
|
||||
} else {
|
||||
// Collect deposit for batch signature verification
|
||||
pendingDepositsToBatchVerify = append(pendingDepositsToBatchVerify, pendingDeposit)
|
||||
}
|
||||
processedAmount += balanceDeposit.Amount
|
||||
}
|
||||
|
||||
// Regardless of how the deposit was handled, we move on in the queue.
|
||||
// Regardless of how the pendingDeposit was handled, we move on in the queue.
|
||||
nextDepositIndex++
|
||||
}
|
||||
|
||||
// Perform batch signature verification on pending deposits that require validator registration
|
||||
if err = batchProcessNewPendingDeposits(ctx, st, pendingDepositsToBatchVerify); err != nil {
|
||||
return errors.Wrap(err, "could not process pending deposits with new public keys")
|
||||
}
|
||||
|
||||
// Combined operation:
|
||||
// - state.pending_balance_deposits = state.pending_balance_deposits[next_deposit_index:]
|
||||
// - state.pending_balance_deposits += deposits_to_postpone
|
||||
// However, the number of remaining deposits must be maintained to properly update the deposit
|
||||
// - state.pending_deposits = state.pending_deposits[next_deposit_index:]
|
||||
// - state.pending_deposits += deposits_to_postpone
|
||||
// However, the number of remaining deposits must be maintained to properly update the pendingDeposit
|
||||
// balance to consume.
|
||||
numRemainingDeposits := len(deposits[nextDepositIndex:])
|
||||
deposits = append(deposits[nextDepositIndex:], depositsToPostpone...)
|
||||
if err := st.SetPendingBalanceDeposits(deposits); err != nil {
|
||||
pendingDeposits = append(pendingDeposits[nextDepositIndex:], pendingDepositsToPostpone...)
|
||||
if err := st.SetPendingDeposits(pendingDeposits); err != nil {
|
||||
return errors.Wrap(err, "could not set pending deposits")
|
||||
}
|
||||
// Accumulate churn only if the churn limit has been hit.
|
||||
if isChurnLimitReached {
|
||||
return st.SetDepositBalanceToConsume(availableForProcessing - primitives.Gwei(processedAmount))
|
||||
}
|
||||
return st.SetDepositBalanceToConsume(0)
|
||||
}
|
||||
|
||||
// batchProcessNewPendingDeposits should only be used to process new deposits that require validator registration
|
||||
func batchProcessNewPendingDeposits(ctx context.Context, state state.BeaconState, pendingDeposits []*ethpb.PendingDeposit) error {
|
||||
// Return early if there are no deposits to process
|
||||
if len(pendingDeposits) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try batch verification of all deposit signatures
|
||||
allSignaturesVerified, err := blocks.BatchVerifyPendingDepositsSignatures(ctx, pendingDeposits)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "batch signature verification failed")
|
||||
}
|
||||
|
||||
// Process each deposit individually
|
||||
for _, pendingDeposit := range pendingDeposits {
|
||||
validSignature := allSignaturesVerified
|
||||
|
||||
// If batch verification failed, check the individual deposit signature
|
||||
if !allSignaturesVerified {
|
||||
validSignature, err = blocks.IsValidDepositSignature(ðpb.Deposit_Data{
|
||||
PublicKey: bytesutil.SafeCopyBytes(pendingDeposit.PublicKey),
|
||||
WithdrawalCredentials: bytesutil.SafeCopyBytes(pendingDeposit.WithdrawalCredentials),
|
||||
Amount: pendingDeposit.Amount,
|
||||
Signature: bytesutil.SafeCopyBytes(pendingDeposit.Signature),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "individual deposit signature verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Add validator to the registry if the signature is valid
|
||||
if validSignature {
|
||||
err = AddValidatorToRegistry(state, pendingDeposit.PublicKey, pendingDeposit.WithdrawalCredentials, pendingDeposit.Amount)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to add validator to registry")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyPendingDeposit implements the spec definition below.
|
||||
// Note : This function is NOT used by ProcessPendingDeposits due to simplified logic for more readable batch processing
|
||||
//
|
||||
// Spec Definition:
|
||||
//
|
||||
// def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> None:
|
||||
//
|
||||
// """
|
||||
// Applies ``deposit`` to the ``state``.
|
||||
// """
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
// if deposit.pubkey not in validator_pubkeys:
|
||||
// # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
// if is_valid_deposit_signature(
|
||||
// deposit.pubkey,
|
||||
// deposit.withdrawal_credentials,
|
||||
// deposit.amount,
|
||||
// deposit.signature
|
||||
// ):
|
||||
// add_validator_to_registry(state, deposit.pubkey, deposit.withdrawal_credentials, deposit.amount)
|
||||
// else:
|
||||
// validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey))
|
||||
// # Increase balance
|
||||
// increase_balance(state, validator_index, deposit.amount)
|
||||
func ApplyPendingDeposit(ctx context.Context, st state.BeaconState, deposit *ethpb.PendingDeposit) error {
|
||||
_, span := trace.StartSpan(ctx, "electra.ApplyPendingDeposit")
|
||||
defer span.End()
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(deposit.PublicKey))
|
||||
if !ok {
|
||||
verified, err := blocks.IsValidDepositSignature(ðpb.Deposit_Data{
|
||||
PublicKey: bytesutil.SafeCopyBytes(deposit.PublicKey),
|
||||
WithdrawalCredentials: bytesutil.SafeCopyBytes(deposit.WithdrawalCredentials),
|
||||
Amount: deposit.Amount,
|
||||
Signature: bytesutil.SafeCopyBytes(deposit.Signature),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not verify deposit signature")
|
||||
}
|
||||
|
||||
if verified {
|
||||
if err := AddValidatorToRegistry(st, deposit.PublicKey, deposit.WithdrawalCredentials, deposit.Amount); err != nil {
|
||||
return errors.Wrap(err, "could not add validator to registry")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return helpers.IncreaseBalance(st, index, deposit.Amount)
|
||||
}
|
||||
|
||||
// AddValidatorToRegistry updates the beacon state with validator information
|
||||
// def add_validator_to_registry(state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64) -> None:
|
||||
//
|
||||
// index = get_index_for_new_validator(state)
|
||||
// validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) # [Modified in Electra:EIP7251]
|
||||
// set_or_append_list(state.validators, index, validator)
|
||||
// set_or_append_list(state.balances, index, amount)
|
||||
// set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
// set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
// set_or_append_list(state.inactivity_scores, index, uint64(0))
|
||||
func AddValidatorToRegistry(beaconState state.BeaconState, pubKey []byte, withdrawalCredentials []byte, amount uint64) error {
|
||||
val := GetValidatorFromDeposit(pubKey, withdrawalCredentials, amount)
|
||||
if err := beaconState.AppendValidator(val); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.AppendBalance(amount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if numRemainingDeposits == 0 {
|
||||
return st.SetDepositBalanceToConsume(0)
|
||||
} else {
|
||||
return st.SetDepositBalanceToConsume(availableForProcessing - primitives.Gwei(processedAmount))
|
||||
// only active in altair and only when it's a new validator (after append balance)
|
||||
if beaconState.Version() >= version.Altair {
|
||||
if err := beaconState.AppendInactivityScore(0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.AppendPreviousParticipationBits(0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.AppendCurrentParticipationBits(0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidatorFromDeposit gets a new validator object with provided parameters
|
||||
//
|
||||
// def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64) -> Validator:
|
||||
//
|
||||
// validator = Validator(
|
||||
// pubkey=pubkey,
|
||||
// withdrawal_credentials=withdrawal_credentials,
|
||||
// activation_eligibility_epoch=FAR_FUTURE_EPOCH,
|
||||
// activation_epoch=FAR_FUTURE_EPOCH,
|
||||
// exit_epoch=FAR_FUTURE_EPOCH,
|
||||
// withdrawable_epoch=FAR_FUTURE_EPOCH,
|
||||
// effective_balance=Gwei(0),
|
||||
// )
|
||||
//
|
||||
// # [Modified in Electra:EIP7251]
|
||||
// max_effective_balance = get_max_effective_balance(validator)
|
||||
// validator.effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance)
|
||||
//
|
||||
// return validator
|
||||
func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount uint64) *ethpb.Validator {
|
||||
validator := ðpb.Validator{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: 0,
|
||||
}
|
||||
maxEffectiveBalance := helpers.ValidatorMaxEffectiveBalance(validator)
|
||||
validator.EffectiveBalance = min(amount-(amount%params.BeaconConfig().EffectiveBalanceIncrement), maxEffectiveBalance)
|
||||
return validator
|
||||
}
|
||||
|
||||
// ProcessDepositRequests is a function as part of electra to process execution layer deposits
|
||||
func ProcessDepositRequests(ctx context.Context, beaconState state.BeaconState, requests []*enginev1.DepositRequest) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "electra.ProcessDepositRequests")
|
||||
_, span := trace.StartSpan(ctx, "electra.ProcessDepositRequests")
|
||||
defer span.End()
|
||||
|
||||
if len(requests) == 0 {
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
deposits := make([]*ethpb.Deposit, 0)
|
||||
for _, req := range requests {
|
||||
if req == nil {
|
||||
return nil, errors.New("got a nil DepositRequest")
|
||||
}
|
||||
deposits = append(deposits, ðpb.Deposit{
|
||||
Data: ðpb.Deposit_Data{
|
||||
PublicKey: req.Pubkey,
|
||||
WithdrawalCredentials: req.WithdrawalCredentials,
|
||||
Amount: req.Amount,
|
||||
Signature: req.Signature,
|
||||
},
|
||||
})
|
||||
}
|
||||
batchVerified, err := blocks.BatchVerifyDepositsSignatures(ctx, deposits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify deposit signatures in batch")
|
||||
}
|
||||
var err error
|
||||
for _, receipt := range requests {
|
||||
beaconState, err = processDepositRequest(beaconState, receipt, batchVerified)
|
||||
beaconState, err = processDepositRequest(beaconState, receipt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not apply deposit request")
|
||||
}
|
||||
@@ -342,30 +554,38 @@ func ProcessDepositRequests(ctx context.Context, beaconState state.BeaconState,
|
||||
// def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
|
||||
//
|
||||
// # Set deposit request start index
|
||||
// if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUEST_START_INDEX:
|
||||
// if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX:
|
||||
// state.deposit_requests_start_index = deposit_request.index
|
||||
//
|
||||
// apply_deposit(
|
||||
// state=state,
|
||||
// # Create pending deposit
|
||||
// state.pending_deposits.append(PendingDeposit(
|
||||
// pubkey=deposit_request.pubkey,
|
||||
// withdrawal_credentials=deposit_request.withdrawal_credentials,
|
||||
// amount=deposit_request.amount,
|
||||
// signature=deposit_request.signature,
|
||||
// )
|
||||
func processDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest, verifySignature bool) (state.BeaconState, error) {
|
||||
// slot=state.slot,
|
||||
// ))
|
||||
func processDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) (state.BeaconState, error) {
|
||||
requestsStartIndex, err := beaconState.DepositRequestsStartIndex()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get deposit requests start index")
|
||||
}
|
||||
if requestsStartIndex == params.BeaconConfig().UnsetDepositRequestsStartIndex {
|
||||
if request == nil {
|
||||
return nil, errors.New("nil deposit request")
|
||||
}
|
||||
if err := beaconState.SetDepositRequestsStartIndex(request.Index); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set deposit requests start index")
|
||||
}
|
||||
}
|
||||
return ApplyDeposit(beaconState, ðpb.Deposit_Data{
|
||||
if err := beaconState.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: bytesutil.SafeCopyBytes(request.Pubkey),
|
||||
Amount: request.Amount,
|
||||
WithdrawalCredentials: bytesutil.SafeCopyBytes(request.WithdrawalCredentials),
|
||||
Signature: bytesutil.SafeCopyBytes(request.Signature),
|
||||
}, verifySignature)
|
||||
Slot: beaconState.Slot(),
|
||||
}); err != nil {
|
||||
return nil, errors.Wrap(err, "could not append deposit request")
|
||||
}
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
stateTesting "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/testing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls/common"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -20,7 +22,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
)
|
||||
|
||||
func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
func TestProcessPendingDeposits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
state state.BeaconState
|
||||
@@ -48,17 +50,10 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
{
|
||||
name: "more deposits than balance to consume processes partial deposits",
|
||||
state: func() state.BeaconState {
|
||||
st := stateWithActiveBalanceETH(t, 1_000)
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(100))
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
deps := make([]*eth.PendingBalanceDeposit, 20)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
deps[i] = ð.PendingBalanceDeposit{
|
||||
Amount: uint64(amountAvailForProcessing) / 10,
|
||||
Index: primitives.ValidatorIndex(i),
|
||||
}
|
||||
}
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(deps))
|
||||
depositAmount := uint64(amountAvailForProcessing) / 10
|
||||
st := stateWithPendingDeposits(t, 1_000, 20, depositAmount)
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(100))
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
@@ -74,25 +69,45 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
}
|
||||
|
||||
// Half of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingBalanceDeposits()
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(remaining))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "withdrawn validators should not consume churn",
|
||||
state: func() state.BeaconState {
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
depositAmount := uint64(amountAvailForProcessing)
|
||||
// set the pending deposits to the maximum churn limit
|
||||
st := stateWithPendingDeposits(t, 1_000, 2, depositAmount)
|
||||
vals := st.Validators()
|
||||
vals[1].WithdrawableEpoch = 0
|
||||
require.NoError(t, st.SetValidators(vals))
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
// Validators 0..9 should have their balance increased
|
||||
for i := primitives.ValidatorIndex(0); i < 2; i++ {
|
||||
b, err := st.BalanceAtIndex(i)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance+uint64(amountAvailForProcessing), b)
|
||||
}
|
||||
|
||||
// All pending deposits should have been processed
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(remaining))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "less deposits than balance to consume processes all deposits",
|
||||
state: func() state.BeaconState {
|
||||
st := stateWithActiveBalanceETH(t, 1_000)
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(0))
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
deps := make([]*eth.PendingBalanceDeposit, 5)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
deps[i] = ð.PendingBalanceDeposit{
|
||||
Amount: uint64(amountAvailForProcessing) / 5,
|
||||
Index: primitives.ValidatorIndex(i),
|
||||
}
|
||||
}
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(deps))
|
||||
depositAmount := uint64(amountAvailForProcessing) / 5
|
||||
st := stateWithPendingDeposits(t, 1_000, 5, depositAmount)
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(0))
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
@@ -108,7 +123,73 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
}
|
||||
|
||||
// All of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingBalanceDeposits()
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(remaining))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "process pending deposit for unknown key, activates new key",
|
||||
state: func() state.BeaconState {
|
||||
st := stateWithActiveBalanceETH(t, 0)
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(0)
|
||||
dep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
|
||||
require.NoError(t, st.SetPendingDeposits([]*eth.PendingDeposit{dep}))
|
||||
require.Equal(t, 0, len(st.Validators()))
|
||||
require.Equal(t, 0, len(st.Balances()))
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
res, err := st.DepositBalanceToConsume()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Gwei(0), res)
|
||||
b, err := st.BalanceAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, b)
|
||||
|
||||
// All of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(remaining))
|
||||
|
||||
// validator becomes active
|
||||
require.Equal(t, 1, len(st.Validators()))
|
||||
require.Equal(t, 1, len(st.Balances()))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "process excess balance that uses a point to infinity signature, processed as a topup",
|
||||
state: func() state.BeaconState {
|
||||
excessBalance := uint64(100)
|
||||
st := stateWithActiveBalanceETH(t, 32)
|
||||
validators := st.Validators()
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(0)
|
||||
validators[0].PublicKey = sk.PublicKey().Marshal()
|
||||
validators[0].WithdrawalCredentials = wc
|
||||
dep := stateTesting.GeneratePendingDeposit(t, sk, excessBalance, bytesutil.ToBytes32(wc), 0)
|
||||
dep.Signature = common.InfiniteSignature[:]
|
||||
require.NoError(t, st.SetValidators(validators))
|
||||
require.NoError(t, st.SetPendingDeposits([]*eth.PendingDeposit{dep}))
|
||||
return st
|
||||
}(),
|
||||
check: func(t *testing.T, st state.BeaconState) {
|
||||
res, err := st.DepositBalanceToConsume()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Gwei(0), res)
|
||||
b, err := st.BalanceAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance+uint64(100), b)
|
||||
|
||||
// All of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(remaining))
|
||||
},
|
||||
@@ -116,17 +197,10 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
{
|
||||
name: "exiting validator deposit postponed",
|
||||
state: func() state.BeaconState {
|
||||
st := stateWithActiveBalanceETH(t, 1_000)
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(0))
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
deps := make([]*eth.PendingBalanceDeposit, 5)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
deps[i] = ð.PendingBalanceDeposit{
|
||||
Amount: uint64(amountAvailForProcessing) / 5,
|
||||
Index: primitives.ValidatorIndex(i),
|
||||
}
|
||||
}
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(deps))
|
||||
depositAmount := uint64(amountAvailForProcessing) / 5
|
||||
st := stateWithPendingDeposits(t, 1_000, 5, depositAmount)
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(0))
|
||||
v, err := st.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
v.ExitEpoch = 10
|
||||
@@ -148,7 +222,7 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
|
||||
// All of the balance deposits should have been processed, except validator index 0 was
|
||||
// added back to the pending deposits queue.
|
||||
remaining, err := st.PendingBalanceDeposits()
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(remaining))
|
||||
},
|
||||
@@ -156,15 +230,7 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
{
|
||||
name: "exited validator balance increased",
|
||||
state: func() state.BeaconState {
|
||||
st := stateWithActiveBalanceETH(t, 1_000)
|
||||
deps := make([]*eth.PendingBalanceDeposit, 1)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
deps[i] = ð.PendingBalanceDeposit{
|
||||
Amount: 1_000_000,
|
||||
Index: primitives.ValidatorIndex(i),
|
||||
}
|
||||
}
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(deps))
|
||||
st := stateWithPendingDeposits(t, 1_000, 1, 1_000_000)
|
||||
v, err := st.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
v.ExitEpoch = 2
|
||||
@@ -182,7 +248,7 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
require.Equal(t, uint64(1_100_000), b)
|
||||
|
||||
// All of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingBalanceDeposits()
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(remaining))
|
||||
},
|
||||
@@ -199,7 +265,7 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
tab, err = helpers.TotalActiveBalance(tt.state)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
err = electra.ProcessPendingBalanceDeposits(context.TODO(), tt.state, primitives.Gwei(tab))
|
||||
err = electra.ProcessPendingDeposits(context.TODO(), tt.state, primitives.Gwei(tab))
|
||||
require.Equal(t, tt.wantErr, err != nil, "wantErr=%v, got err=%s", tt.wantErr, err)
|
||||
if tt.check != nil {
|
||||
tt.check(t, tt.state)
|
||||
@@ -208,6 +274,27 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchProcessNewPendingDeposits(t *testing.T) {
|
||||
t.Run("invalid batch initiates correct individual validation", func(t *testing.T) {
|
||||
st := stateWithActiveBalanceETH(t, 0)
|
||||
require.Equal(t, 0, len(st.Validators()))
|
||||
require.Equal(t, 0, len(st.Balances()))
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(0)
|
||||
validDep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
|
||||
invalidDep := ð.PendingDeposit{}
|
||||
// have a combination of valid and invalid deposits
|
||||
deps := []*eth.PendingDeposit{validDep, invalidDep}
|
||||
require.NoError(t, electra.BatchProcessNewPendingDeposits(context.Background(), st, deps))
|
||||
// successfully added to register
|
||||
require.Equal(t, 1, len(st.Validators()))
|
||||
require.Equal(t, 1, len(st.Balances()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessDepositRequests(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 1)
|
||||
sk, err := bls.RandKey()
|
||||
@@ -220,7 +307,7 @@ func TestProcessDepositRequests(t *testing.T) {
|
||||
})
|
||||
t.Run("nil request errors", func(t *testing.T) {
|
||||
_, err = electra.ProcessDepositRequests(context.Background(), st, []*enginev1.DepositRequest{nil})
|
||||
require.ErrorContains(t, "got a nil DepositRequest", err)
|
||||
require.ErrorContains(t, "nil deposit request", err)
|
||||
})
|
||||
|
||||
vals := st.Validators()
|
||||
@@ -230,7 +317,7 @@ func TestProcessDepositRequests(t *testing.T) {
|
||||
bals := st.Balances()
|
||||
bals[0] = params.BeaconConfig().MinActivationBalance + 2000
|
||||
require.NoError(t, st.SetBalances(bals))
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(make([]*eth.PendingBalanceDeposit, 0))) // reset pbd as the determinitstic state populates this already
|
||||
require.NoError(t, st.SetPendingDeposits(make([]*eth.PendingDeposit, 0))) // reset pbd as the determinitstic state populates this already
|
||||
withdrawalCred := make([]byte, 32)
|
||||
withdrawalCred[0] = params.BeaconConfig().CompoundingWithdrawalPrefixByte
|
||||
depositMessage := ð.DepositMessage{
|
||||
@@ -255,11 +342,10 @@ func TestProcessDepositRequests(t *testing.T) {
|
||||
st, err = electra.ProcessDepositRequests(context.Background(), st, requests)
|
||||
require.NoError(t, err)
|
||||
|
||||
pbd, err := st.PendingBalanceDeposits()
|
||||
pbd, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(pbd))
|
||||
require.Equal(t, 1, len(pbd))
|
||||
require.Equal(t, uint64(1000), pbd[0].Amount)
|
||||
require.Equal(t, uint64(2000), pbd[1].Amount)
|
||||
}
|
||||
|
||||
func TestProcessDeposit_Electra_Simple(t *testing.T) {
|
||||
@@ -286,7 +372,7 @@ func TestProcessDeposit_Electra_Simple(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
pdSt, err := electra.ProcessDeposits(context.Background(), st, deps)
|
||||
require.NoError(t, err)
|
||||
pbd, err := pdSt.PendingBalanceDeposits()
|
||||
pbd, err := pdSt.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, pbd[2].Amount)
|
||||
require.Equal(t, 3, len(pbd))
|
||||
@@ -322,7 +408,7 @@ func TestProcessDeposit_SkipsInvalidDeposit(t *testing.T) {
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
newState, err := electra.ProcessDeposit(beaconState, dep[0], true)
|
||||
newState, err := electra.ProcessDeposit(beaconState, dep[0], false)
|
||||
require.NoError(t, err, "Expected invalid block deposit to be ignored without error")
|
||||
|
||||
if newState.Eth1DepositIndex() != 1 {
|
||||
@@ -359,42 +445,128 @@ func TestApplyDeposit_TopUps_WithBadSignature(t *testing.T) {
|
||||
vals[0].PublicKey = sk.PublicKey().Marshal()
|
||||
vals[0].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
require.NoError(t, st.SetValidators(vals))
|
||||
adSt, err := electra.ApplyDeposit(st, depositData, true)
|
||||
adSt, err := electra.ApplyDeposit(st, depositData, false)
|
||||
require.NoError(t, err)
|
||||
pbd, err := adSt.PendingBalanceDeposits()
|
||||
pbd, err := adSt.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pbd))
|
||||
require.Equal(t, topUpAmount, pbd[0].Amount)
|
||||
}
|
||||
|
||||
func TestApplyDeposit_Electra_SwitchToCompoundingValidator(t *testing.T) {
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 3)
|
||||
// stateWithActiveBalanceETH generates a mock beacon state given a balance in eth
|
||||
func stateWithActiveBalanceETH(t *testing.T, balETH uint64) state.BeaconState {
|
||||
gwei := balETH * 1_000_000_000
|
||||
balPerVal := params.BeaconConfig().MinActivationBalance
|
||||
numVals := gwei / balPerVal
|
||||
|
||||
vals := make([]*eth.Validator, numVals)
|
||||
bals := make([]uint64, numVals)
|
||||
for i := uint64(0); i < numVals; i++ {
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(i)
|
||||
vals[i] = ð.Validator{
|
||||
ActivationEpoch: 0,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: balPerVal,
|
||||
WithdrawalCredentials: wc,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
bals[i] = balPerVal
|
||||
}
|
||||
st, err := state_native.InitializeFromProtoUnsafeElectra(ð.BeaconStateElectra{
|
||||
Slot: 10 * params.BeaconConfig().SlotsPerEpoch,
|
||||
Validators: vals,
|
||||
Balances: bals,
|
||||
Fork: ð.Fork{
|
||||
CurrentVersion: params.BeaconConfig().ElectraForkVersion,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// set some fake finalized checkpoint
|
||||
require.NoError(t, st.SetFinalizedCheckpoint(ð.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: make([]byte, 32),
|
||||
}))
|
||||
return st
|
||||
}
|
||||
|
||||
// stateWithPendingDeposits with pending deposits and existing ethbalance
|
||||
func stateWithPendingDeposits(t *testing.T, balETH uint64, numDeposits, amount uint64) state.BeaconState {
|
||||
st := stateWithActiveBalanceETH(t, balETH)
|
||||
deps := make([]*eth.PendingDeposit, numDeposits)
|
||||
validators := st.Validators()
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(i)
|
||||
validators[i].PublicKey = sk.PublicKey().Marshal()
|
||||
validators[i].WithdrawalCredentials = wc
|
||||
deps[i] = stateTesting.GeneratePendingDeposit(t, sk, amount, bytesutil.ToBytes32(wc), 0)
|
||||
}
|
||||
require.NoError(t, st.SetValidators(validators))
|
||||
require.NoError(t, st.SetPendingDeposits(deps))
|
||||
return st
|
||||
}
|
||||
|
||||
func TestApplyPendingDeposit_TopUp(t *testing.T) {
|
||||
excessBalance := uint64(100)
|
||||
st := stateWithActiveBalanceETH(t, 32)
|
||||
validators := st.Validators()
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
withdrawalCred := make([]byte, 32)
|
||||
withdrawalCred[0] = params.BeaconConfig().CompoundingWithdrawalPrefixByte
|
||||
depositData := ð.Deposit_Data{
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
Amount: 1000,
|
||||
WithdrawalCredentials: withdrawalCred,
|
||||
Signature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
}
|
||||
vals := st.Validators()
|
||||
vals[0].PublicKey = sk.PublicKey().Marshal()
|
||||
vals[0].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
require.NoError(t, st.SetValidators(vals))
|
||||
bals := st.Balances()
|
||||
bals[0] = params.BeaconConfig().MinActivationBalance + 2000
|
||||
require.NoError(t, st.SetBalances(bals))
|
||||
sr, err := signing.ComputeSigningRoot(depositData, bytesutil.ToBytes(3, 32))
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(0)
|
||||
validators[0].PublicKey = sk.PublicKey().Marshal()
|
||||
validators[0].WithdrawalCredentials = wc
|
||||
dep := stateTesting.GeneratePendingDeposit(t, sk, excessBalance, bytesutil.ToBytes32(wc), 0)
|
||||
dep.Signature = common.InfiniteSignature[:]
|
||||
require.NoError(t, st.SetValidators(validators))
|
||||
|
||||
require.NoError(t, electra.ApplyPendingDeposit(context.Background(), st, dep))
|
||||
|
||||
b, err := st.BalanceAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
sig := sk.Sign(sr[:])
|
||||
depositData.Signature = sig.Marshal()
|
||||
adSt, err := electra.ApplyDeposit(st, depositData, false)
|
||||
require.NoError(t, err)
|
||||
pbd, err := adSt.PendingBalanceDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(pbd))
|
||||
require.Equal(t, uint64(1000), pbd[0].Amount)
|
||||
require.Equal(t, uint64(2000), pbd[1].Amount)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance+uint64(excessBalance), b)
|
||||
}
|
||||
|
||||
func TestApplyPendingDeposit_UnknownKey(t *testing.T) {
|
||||
st := stateWithActiveBalanceETH(t, 0)
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(0)
|
||||
dep := stateTesting.GeneratePendingDeposit(t, sk, params.BeaconConfig().MinActivationBalance, bytesutil.ToBytes32(wc), 0)
|
||||
require.Equal(t, 0, len(st.Validators()))
|
||||
require.NoError(t, electra.ApplyPendingDeposit(context.Background(), st, dep))
|
||||
// activates new validator
|
||||
require.Equal(t, 1, len(st.Validators()))
|
||||
b, err := st.BalanceAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, b)
|
||||
}
|
||||
|
||||
func TestApplyPendingDeposit_InvalidSignature(t *testing.T) {
|
||||
st := stateWithActiveBalanceETH(t, 0)
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(0)
|
||||
dep := ð.PendingDeposit{
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: wc,
|
||||
Amount: 100,
|
||||
}
|
||||
require.Equal(t, 0, len(st.Validators()))
|
||||
require.NoError(t, electra.ApplyPendingDeposit(context.Background(), st, dep))
|
||||
// no validator added
|
||||
require.Equal(t, 0, len(st.Validators()))
|
||||
// no topup either
|
||||
require.Equal(t, 0, len(st.Balances()))
|
||||
}
|
||||
|
||||
3
beacon-chain/core/electra/export_test.go
Normal file
3
beacon-chain/core/electra/export_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package electra
|
||||
|
||||
var BatchProcessNewPendingDeposits = batchProcessNewPendingDeposits
|
||||
@@ -44,7 +44,7 @@ var (
|
||||
// process_registry_updates(state)
|
||||
// process_slashings(state)
|
||||
// process_eth1_data_reset(state)
|
||||
// process_pending_balance_deposits(state) # New in EIP7251
|
||||
// process_pending_deposits(state) # New in EIP7251
|
||||
// process_pending_consolidations(state) # New in EIP7251
|
||||
// process_effective_balance_updates(state)
|
||||
// process_slashings_reset(state)
|
||||
@@ -94,7 +94,7 @@ func ProcessEpoch(ctx context.Context, state state.BeaconState) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ProcessPendingBalanceDeposits(ctx, state, primitives.Gwei(bp.ActiveCurrentEpoch)); err != nil {
|
||||
if err = ProcessPendingDeposits(ctx, state, primitives.Gwei(bp.ActiveCurrentEpoch)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ProcessPendingConsolidations(ctx, state); err != nil {
|
||||
|
||||
@@ -78,23 +78,19 @@ func ProcessOperations(
|
||||
return nil, errors.Wrap(err, "could not process bls-to-execution changes")
|
||||
}
|
||||
// new in electra
|
||||
e, err := bb.Execution()
|
||||
requests, err := bb.ExecutionRequests()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution data from block")
|
||||
return nil, errors.Wrap(err, "could not get execution requests")
|
||||
}
|
||||
exe, ok := e.(interfaces.ExecutionDataElectra)
|
||||
if !ok {
|
||||
return nil, errors.New("could not cast execution data to electra execution data")
|
||||
}
|
||||
st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests())
|
||||
st, err = ProcessDepositRequests(ctx, st, requests.Deposits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process deposit receipts")
|
||||
}
|
||||
st, err = ProcessWithdrawalRequests(ctx, st, exe.WithdrawalRequests())
|
||||
st, err = ProcessWithdrawalRequests(ctx, st, requests.Withdrawals)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution layer withdrawal requests")
|
||||
}
|
||||
if err := ProcessConsolidationRequests(ctx, st, exe.ConsolidationRequests()); err != nil {
|
||||
if err := ProcessConsolidationRequests(ctx, st, requests.Consolidations); err != nil {
|
||||
return nil, fmt.Errorf("could not process consolidation requests: %w", err)
|
||||
}
|
||||
return st, nil
|
||||
|
||||
@@ -57,14 +57,17 @@ func TestProcessEpoch_CanProcessElectra(t *testing.T) {
|
||||
require.NoError(t, st.SetSlot(10*params.BeaconConfig().SlotsPerEpoch))
|
||||
require.NoError(t, st.SetDepositBalanceToConsume(100))
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
deps := make([]*ethpb.PendingBalanceDeposit, 20)
|
||||
validators := st.Validators()
|
||||
deps := make([]*ethpb.PendingDeposit, 20)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
deps[i] = ðpb.PendingBalanceDeposit{
|
||||
Amount: uint64(amountAvailForProcessing) / 10,
|
||||
Index: primitives.ValidatorIndex(i),
|
||||
deps[i] = ðpb.PendingDeposit{
|
||||
PublicKey: validators[i].PublicKey,
|
||||
WithdrawalCredentials: validators[i].WithdrawalCredentials,
|
||||
Amount: uint64(amountAvailForProcessing) / 10,
|
||||
Slot: 0,
|
||||
}
|
||||
}
|
||||
require.NoError(t, st.SetPendingBalanceDeposits(deps))
|
||||
require.NoError(t, st.SetPendingDeposits(deps))
|
||||
require.NoError(t, st.SetPendingConsolidations([]*ethpb.PendingConsolidation{
|
||||
{
|
||||
SourceIndex: 2,
|
||||
@@ -108,7 +111,7 @@ func TestProcessEpoch_CanProcessElectra(t *testing.T) {
|
||||
require.Equal(t, primitives.Gwei(100), res)
|
||||
|
||||
// Half of the balance deposits should have been processed.
|
||||
remaining, err := st.PendingBalanceDeposits()
|
||||
remaining, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 10, len(remaining))
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
@@ -102,7 +101,7 @@ import (
|
||||
// earliest_exit_epoch=earliest_exit_epoch,
|
||||
// consolidation_balance_to_consume=0,
|
||||
// earliest_consolidation_epoch=compute_activation_exit_epoch(get_current_epoch(pre)),
|
||||
// pending_balance_deposits=[],
|
||||
// pending_deposits=[],
|
||||
// pending_partial_withdrawals=[],
|
||||
// pending_consolidations=[],
|
||||
// )
|
||||
@@ -245,26 +244,23 @@ func UpgradeToElectra(beaconState state.BeaconState) (state.BeaconState, error)
|
||||
CurrentSyncCommittee: currentSyncCommittee,
|
||||
NextSyncCommittee: nextSyncCommittee,
|
||||
LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: payloadHeader.ParentHash(),
|
||||
FeeRecipient: payloadHeader.FeeRecipient(),
|
||||
StateRoot: payloadHeader.StateRoot(),
|
||||
ReceiptsRoot: payloadHeader.ReceiptsRoot(),
|
||||
LogsBloom: payloadHeader.LogsBloom(),
|
||||
PrevRandao: payloadHeader.PrevRandao(),
|
||||
BlockNumber: payloadHeader.BlockNumber(),
|
||||
GasLimit: payloadHeader.GasLimit(),
|
||||
GasUsed: payloadHeader.GasUsed(),
|
||||
Timestamp: payloadHeader.Timestamp(),
|
||||
ExtraData: payloadHeader.ExtraData(),
|
||||
BaseFeePerGas: payloadHeader.BaseFeePerGas(),
|
||||
BlockHash: payloadHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
DepositRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP6110]
|
||||
WithdrawalRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP7002]
|
||||
ConsolidationRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP7251]
|
||||
ParentHash: payloadHeader.ParentHash(),
|
||||
FeeRecipient: payloadHeader.FeeRecipient(),
|
||||
StateRoot: payloadHeader.StateRoot(),
|
||||
ReceiptsRoot: payloadHeader.ReceiptsRoot(),
|
||||
LogsBloom: payloadHeader.LogsBloom(),
|
||||
PrevRandao: payloadHeader.PrevRandao(),
|
||||
BlockNumber: payloadHeader.BlockNumber(),
|
||||
GasLimit: payloadHeader.GasLimit(),
|
||||
GasUsed: payloadHeader.GasUsed(),
|
||||
Timestamp: payloadHeader.Timestamp(),
|
||||
ExtraData: payloadHeader.ExtraData(),
|
||||
BaseFeePerGas: payloadHeader.BaseFeePerGas(),
|
||||
BlockHash: payloadHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
},
|
||||
NextWithdrawalIndex: wi,
|
||||
NextWithdrawalValidatorIndex: vi,
|
||||
@@ -276,7 +272,7 @@ func UpgradeToElectra(beaconState state.BeaconState) (state.BeaconState, error)
|
||||
EarliestExitEpoch: earliestExitEpoch,
|
||||
ConsolidationBalanceToConsume: helpers.ConsolidationChurnLimit(primitives.Gwei(tab)),
|
||||
EarliestConsolidationEpoch: helpers.ActivationExitEpoch(slots.ToEpoch(beaconState.Slot())),
|
||||
PendingBalanceDeposits: make([]*ethpb.PendingBalanceDeposit, 0),
|
||||
PendingDeposits: make([]*ethpb.PendingDeposit, 0),
|
||||
PendingPartialWithdrawals: make([]*ethpb.PendingPartialWithdrawal, 0),
|
||||
PendingConsolidations: make([]*ethpb.PendingConsolidation, 0),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
@@ -113,24 +112,21 @@ func TestUpgradeToElectra(t *testing.T) {
|
||||
wdRoot, err := prevHeader.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
wanted := &enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: prevHeader.ParentHash(),
|
||||
FeeRecipient: prevHeader.FeeRecipient(),
|
||||
StateRoot: prevHeader.StateRoot(),
|
||||
ReceiptsRoot: prevHeader.ReceiptsRoot(),
|
||||
LogsBloom: prevHeader.LogsBloom(),
|
||||
PrevRandao: prevHeader.PrevRandao(),
|
||||
BlockNumber: prevHeader.BlockNumber(),
|
||||
GasLimit: prevHeader.GasLimit(),
|
||||
GasUsed: prevHeader.GasUsed(),
|
||||
Timestamp: prevHeader.Timestamp(),
|
||||
ExtraData: prevHeader.ExtraData(),
|
||||
BaseFeePerGas: prevHeader.BaseFeePerGas(),
|
||||
BlockHash: prevHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
DepositRequestsRoot: bytesutil.Bytes32(0),
|
||||
WithdrawalRequestsRoot: bytesutil.Bytes32(0),
|
||||
ConsolidationRequestsRoot: bytesutil.Bytes32(0),
|
||||
ParentHash: prevHeader.ParentHash(),
|
||||
FeeRecipient: prevHeader.FeeRecipient(),
|
||||
StateRoot: prevHeader.StateRoot(),
|
||||
ReceiptsRoot: prevHeader.ReceiptsRoot(),
|
||||
LogsBloom: prevHeader.LogsBloom(),
|
||||
PrevRandao: prevHeader.PrevRandao(),
|
||||
BlockNumber: prevHeader.BlockNumber(),
|
||||
GasLimit: prevHeader.GasLimit(),
|
||||
GasUsed: prevHeader.GasUsed(),
|
||||
Timestamp: prevHeader.Timestamp(),
|
||||
ExtraData: prevHeader.ExtraData(),
|
||||
BaseFeePerGas: prevHeader.BaseFeePerGas(),
|
||||
BlockHash: prevHeader.BlockHash(),
|
||||
TransactionsRoot: txRoot,
|
||||
WithdrawalsRoot: wdRoot,
|
||||
}
|
||||
require.DeepEqual(t, wanted, protoHeader)
|
||||
|
||||
@@ -173,10 +169,10 @@ func TestUpgradeToElectra(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, helpers.ActivationExitEpoch(slots.ToEpoch(preForkState.Slot())), earliestConsolidationEpoch)
|
||||
|
||||
pendingBalanceDeposits, err := mSt.PendingBalanceDeposits()
|
||||
pendingDeposits, err := mSt.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(pendingBalanceDeposits))
|
||||
require.Equal(t, uint64(1000), pendingBalanceDeposits[1].Amount)
|
||||
require.Equal(t, 2, len(pendingDeposits))
|
||||
require.Equal(t, uint64(1000), pendingDeposits[1].Amount)
|
||||
|
||||
numPendingPartialWithdrawals, err := mSt.NumPendingPartialWithdrawals()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -3,87 +3,22 @@ package electra
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls/common"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// AddValidatorToRegistry updates the beacon state with validator information
|
||||
// def add_validator_to_registry(state: BeaconState,
|
||||
//
|
||||
// pubkey: BLSPubkey,
|
||||
// withdrawal_credentials: Bytes32,
|
||||
// amount: uint64) -> None:
|
||||
// index = get_index_for_new_validator(state)
|
||||
// validator = get_validator_from_deposit(pubkey, withdrawal_credentials)
|
||||
// set_or_append_list(state.validators, index, validator)
|
||||
// set_or_append_list(state.balances, index, 0) # [Modified in Electra:EIP7251]
|
||||
// set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
// set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000))
|
||||
// set_or_append_list(state.inactivity_scores, index, uint64(0))
|
||||
// state.pending_balance_deposits.append(PendingBalanceDeposit(index=index, amount=amount)) # [New in Electra:EIP7251]
|
||||
func AddValidatorToRegistry(beaconState state.BeaconState, pubKey []byte, withdrawalCredentials []byte, amount uint64) error {
|
||||
val := ValidatorFromDeposit(pubKey, withdrawalCredentials)
|
||||
if err := beaconState.AppendValidator(val); err != nil {
|
||||
return err
|
||||
}
|
||||
index, ok := beaconState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey))
|
||||
if !ok {
|
||||
return errors.New("could not find validator in registry")
|
||||
}
|
||||
if err := beaconState.AppendBalance(0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.AppendPendingBalanceDeposit(index, amount); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.AppendInactivityScore(0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := beaconState.AppendPreviousParticipationBits(0); err != nil {
|
||||
return err
|
||||
}
|
||||
return beaconState.AppendCurrentParticipationBits(0)
|
||||
}
|
||||
|
||||
// ValidatorFromDeposit gets a new validator object with provided parameters
|
||||
//
|
||||
// def get_validator_from_deposit(pubkey: BLSPubkey, withdrawal_credentials: Bytes32) -> Validator:
|
||||
//
|
||||
// return Validator(
|
||||
// pubkey=pubkey,
|
||||
// withdrawal_credentials=withdrawal_credentials,
|
||||
// activation_eligibility_epoch=FAR_FUTURE_EPOCH,
|
||||
// activation_epoch=FAR_FUTURE_EPOCH,
|
||||
// exit_epoch=FAR_FUTURE_EPOCH,
|
||||
// withdrawable_epoch=FAR_FUTURE_EPOCH,
|
||||
// effective_balance=0, # [Modified in Electra:EIP7251]
|
||||
//
|
||||
// )
|
||||
func ValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte) *ethpb.Validator {
|
||||
return ðpb.Validator{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: 0, // [Modified in Electra:EIP7251]
|
||||
}
|
||||
}
|
||||
|
||||
// SwitchToCompoundingValidator
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
// validator = state.validators[index]
|
||||
// if has_eth1_withdrawal_credential(validator):
|
||||
// validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
|
||||
// queue_excess_active_balance(state, index)
|
||||
// def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
//
|
||||
// validator = state.validators[index]
|
||||
// validator.withdrawal_credentials = COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:]
|
||||
// queue_excess_active_balance(state, index)
|
||||
func SwitchToCompoundingValidator(s state.BeaconState, idx primitives.ValidatorIndex) error {
|
||||
v, err := s.ValidatorAtIndex(idx)
|
||||
if err != nil {
|
||||
@@ -92,28 +27,32 @@ func SwitchToCompoundingValidator(s state.BeaconState, idx primitives.ValidatorI
|
||||
if len(v.WithdrawalCredentials) == 0 {
|
||||
return errors.New("validator has no withdrawal credentials")
|
||||
}
|
||||
if helpers.HasETH1WithdrawalCredential(v) {
|
||||
v.WithdrawalCredentials[0] = params.BeaconConfig().CompoundingWithdrawalPrefixByte
|
||||
if err := s.UpdateValidatorAtIndex(idx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return QueueExcessActiveBalance(s, idx)
|
||||
|
||||
v.WithdrawalCredentials[0] = params.BeaconConfig().CompoundingWithdrawalPrefixByte
|
||||
if err := s.UpdateValidatorAtIndex(idx, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return QueueExcessActiveBalance(s, idx)
|
||||
}
|
||||
|
||||
// QueueExcessActiveBalance queues validators with balances above the min activation balance and adds to pending balance deposit.
|
||||
// QueueExcessActiveBalance queues validators with balances above the min activation balance and adds to pending deposit.
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
// balance = state.balances[index]
|
||||
// if balance > MIN_ACTIVATION_BALANCE:
|
||||
// excess_balance = balance - MIN_ACTIVATION_BALANCE
|
||||
// state.balances[index] = MIN_ACTIVATION_BALANCE
|
||||
// state.pending_balance_deposits.append(
|
||||
// PendingBalanceDeposit(index=index, amount=excess_balance)
|
||||
// )
|
||||
// def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
//
|
||||
// balance = state.balances[index]
|
||||
// if balance > MIN_ACTIVATION_BALANCE:
|
||||
// excess_balance = balance - MIN_ACTIVATION_BALANCE
|
||||
// state.balances[index] = MIN_ACTIVATION_BALANCE
|
||||
// validator = state.validators[index]
|
||||
// state.pending_deposits.append(PendingDeposit(
|
||||
// pubkey=validator.pubkey,
|
||||
// withdrawal_credentials=validator.withdrawal_credentials,
|
||||
// amount=excess_balance,
|
||||
// signature=bls.G2_POINT_AT_INFINITY,
|
||||
// slot=GENESIS_SLOT,
|
||||
// ))
|
||||
func QueueExcessActiveBalance(s state.BeaconState, idx primitives.ValidatorIndex) error {
|
||||
bal, err := s.BalanceAtIndex(idx)
|
||||
if err != nil {
|
||||
@@ -121,11 +60,21 @@ func QueueExcessActiveBalance(s state.BeaconState, idx primitives.ValidatorIndex
|
||||
}
|
||||
|
||||
if bal > params.BeaconConfig().MinActivationBalance {
|
||||
excessBalance := bal - params.BeaconConfig().MinActivationBalance
|
||||
if err := s.UpdateBalancesAtIndex(idx, params.BeaconConfig().MinActivationBalance); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.AppendPendingBalanceDeposit(idx, excessBalance)
|
||||
excessBalance := bal - params.BeaconConfig().MinActivationBalance
|
||||
val, err := s.ValidatorAtIndex(idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: val.PublicKey,
|
||||
WithdrawalCredentials: val.WithdrawalCredentials,
|
||||
Amount: excessBalance,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
Slot: params.BeaconConfig().GenesisSlot,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -134,15 +83,21 @@ func QueueExcessActiveBalance(s state.BeaconState, idx primitives.ValidatorIndex
|
||||
//
|
||||
// Spec definition:
|
||||
//
|
||||
// def queue_entire_balance_and_reset_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
// balance = state.balances[index]
|
||||
// state.balances[index] = 0
|
||||
// validator = state.validators[index]
|
||||
// validator.effective_balance = 0
|
||||
// validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH
|
||||
// state.pending_balance_deposits.append(
|
||||
// PendingBalanceDeposit(index=index, amount=balance)
|
||||
// )
|
||||
// def queue_entire_balance_and_reset_validator(state: BeaconState, index: ValidatorIndex) -> None:
|
||||
//
|
||||
// balance = state.balances[index]
|
||||
// state.balances[index] = 0
|
||||
// validator = state.validators[index]
|
||||
// validator.effective_balance = 0
|
||||
// validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH
|
||||
// state.pending_deposits.append(PendingDeposit(
|
||||
// pubkey=validator.pubkey,
|
||||
// withdrawal_credentials=validator.withdrawal_credentials,
|
||||
// amount=balance,
|
||||
// signature=bls.G2_POINT_AT_INFINITY,
|
||||
// slot=GENESIS_SLOT,
|
||||
//
|
||||
// ))
|
||||
//
|
||||
//nolint:dupword
|
||||
func QueueEntireBalanceAndResetValidator(s state.BeaconState, idx primitives.ValidatorIndex) error {
|
||||
@@ -166,5 +121,11 @@ func QueueEntireBalanceAndResetValidator(s state.BeaconState, idx primitives.Val
|
||||
return err
|
||||
}
|
||||
|
||||
return s.AppendPendingBalanceDeposit(idx, bal)
|
||||
return s.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: v.PublicKey,
|
||||
WithdrawalCredentials: v.WithdrawalCredentials,
|
||||
Amount: bal,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
Slot: params.BeaconConfig().GenesisSlot,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -14,20 +13,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
)
|
||||
|
||||
func TestAddValidatorToRegistry(t *testing.T) {
|
||||
st, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, electra.AddValidatorToRegistry(st, make([]byte, fieldparams.BLSPubkeyLength), make([]byte, fieldparams.RootLength), 100))
|
||||
balances := st.Balances()
|
||||
require.Equal(t, 1, len(balances))
|
||||
require.Equal(t, uint64(0), balances[0])
|
||||
pbds, err := st.PendingBalanceDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pbds))
|
||||
require.Equal(t, uint64(100), pbds[0].Amount)
|
||||
require.Equal(t, primitives.ValidatorIndex(0), pbds[0].Index)
|
||||
}
|
||||
|
||||
func TestSwitchToCompoundingValidator(t *testing.T) {
|
||||
s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{
|
||||
Validators: []*eth.Validator{
|
||||
@@ -60,7 +45,7 @@ func TestSwitchToCompoundingValidator(t *testing.T) {
|
||||
b, err := s.BalanceAtIndex(1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, b, "balance was changed")
|
||||
pbd, err := s.PendingBalanceDeposits()
|
||||
pbd, err := s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pbd), "pending balance deposits should be empty")
|
||||
|
||||
@@ -69,11 +54,10 @@ func TestSwitchToCompoundingValidator(t *testing.T) {
|
||||
b, err = s.BalanceAtIndex(2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, b, "balance was not changed")
|
||||
pbd, err = s.PendingBalanceDeposits()
|
||||
pbd, err = s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pbd), "pending balance deposits should have one element")
|
||||
require.Equal(t, uint64(100_000), pbd[0].Amount, "pending balance deposit amount is incorrect")
|
||||
require.Equal(t, primitives.ValidatorIndex(2), pbd[0].Index, "pending balance deposit index is incorrect")
|
||||
}
|
||||
|
||||
func TestQueueEntireBalanceAndResetValidator(t *testing.T) {
|
||||
@@ -97,11 +81,10 @@ func TestQueueEntireBalanceAndResetValidator(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(0), v.EffectiveBalance, "effective balance was not reset")
|
||||
require.Equal(t, params.BeaconConfig().FarFutureEpoch, v.ActivationEligibilityEpoch, "activation eligibility epoch was not reset")
|
||||
pbd, err := s.PendingBalanceDeposits()
|
||||
pbd, err := s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pbd), "pending balance deposits should have one element")
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance+100_000, pbd[0].Amount, "pending balance deposit amount is incorrect")
|
||||
require.Equal(t, primitives.ValidatorIndex(0), pbd[0].Index, "pending balance deposit index is incorrect")
|
||||
}
|
||||
|
||||
func TestSwitchToCompoundingValidator_Ok(t *testing.T) {
|
||||
@@ -114,7 +97,7 @@ func TestSwitchToCompoundingValidator_Ok(t *testing.T) {
|
||||
require.NoError(t, st.SetBalances(bals))
|
||||
require.NoError(t, electra.SwitchToCompoundingValidator(st, 0))
|
||||
|
||||
pbd, err := st.PendingBalanceDeposits()
|
||||
pbd, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1010), pbd[0].Amount) // appends it at the end
|
||||
val, err := st.ValidatorAtIndex(0)
|
||||
@@ -132,7 +115,7 @@ func TestQueueExcessActiveBalance_Ok(t *testing.T) {
|
||||
err := electra.QueueExcessActiveBalance(st, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
pbd, err := st.PendingBalanceDeposits()
|
||||
pbd, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1000), pbd[0].Amount) // appends it at the end
|
||||
|
||||
@@ -149,7 +132,7 @@ func TestQueueEntireBalanceAndResetValidator_Ok(t *testing.T) {
|
||||
err := electra.QueueEntireBalanceAndResetValidator(st, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
pbd, err := st.PendingBalanceDeposits()
|
||||
pbd, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pbd))
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance-1000, pbd[0].Amount)
|
||||
|
||||
@@ -147,11 +147,17 @@ func ProcessRegistryUpdates(ctx context.Context, st state.BeaconState) (state.Be
|
||||
// epoch = get_current_epoch(state)
|
||||
// total_balance = get_total_active_balance(state)
|
||||
// adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance)
|
||||
// if state.version == electra:
|
||||
// increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from total balance to avoid uint64 overflow
|
||||
// penalty_per_effective_balance_increment = adjusted_total_slashing_balance // (total_balance // increment)
|
||||
// for index, validator in enumerate(state.validators):
|
||||
// if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
|
||||
// increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
|
||||
// penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
|
||||
// penalty = penalty_numerator // total_balance * increment
|
||||
// if state.version == electra:
|
||||
// effective_balance_increments = validator.effective_balance // increment
|
||||
// penalty = penalty_per_effective_balance_increment * effective_balance_increments
|
||||
// decrease_balance(state, ValidatorIndex(index), penalty)
|
||||
func ProcessSlashings(st state.BeaconState, slashingMultiplier uint64) (state.BeaconState, error) {
|
||||
currentEpoch := time.CurrentEpoch(st)
|
||||
@@ -177,13 +183,26 @@ func ProcessSlashings(st state.BeaconState, slashingMultiplier uint64) (state.Be
|
||||
// below equally.
|
||||
increment := params.BeaconConfig().EffectiveBalanceIncrement
|
||||
minSlashing := math.Min(totalSlashing*slashingMultiplier, totalBalance)
|
||||
|
||||
// Modified in Electra:EIP7251
|
||||
var penaltyPerEffectiveBalanceIncrement uint64
|
||||
if st.Version() >= version.Electra {
|
||||
penaltyPerEffectiveBalanceIncrement = minSlashing / (totalBalance / increment)
|
||||
}
|
||||
|
||||
bals := st.Balances()
|
||||
changed := false
|
||||
err = st.ReadFromEveryValidator(func(idx int, val state.ReadOnlyValidator) error {
|
||||
correctEpoch := (currentEpoch + exitLength/2) == val.WithdrawableEpoch()
|
||||
if val.Slashed() && correctEpoch {
|
||||
penaltyNumerator := val.EffectiveBalance() / increment * minSlashing
|
||||
penalty := penaltyNumerator / totalBalance * increment
|
||||
var penalty uint64
|
||||
if st.Version() >= version.Electra {
|
||||
effectiveBalanceIncrements := val.EffectiveBalance() / increment
|
||||
penalty = penaltyPerEffectiveBalanceIncrement * effectiveBalanceIncrements
|
||||
} else {
|
||||
penaltyNumerator := val.EffectiveBalance() / increment * minSlashing
|
||||
penalty = penaltyNumerator / totalBalance * increment
|
||||
}
|
||||
bals[idx] = helpers.DecreaseBalanceWithVal(bals[idx], penalty)
|
||||
changed = true
|
||||
}
|
||||
|
||||
@@ -448,3 +448,75 @@ func TestProcessHistoricalDataUpdate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessSlashings_SlashedElectra(t *testing.T) {
|
||||
tests := []struct {
|
||||
state *ethpb.BeaconStateElectra
|
||||
want uint64
|
||||
}{
|
||||
{
|
||||
state: ðpb.BeaconStateElectra{
|
||||
Validators: []*ethpb.Validator{
|
||||
{Slashed: true,
|
||||
WithdrawableEpoch: params.BeaconConfig().EpochsPerSlashingsVector / 2,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance},
|
||||
{ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance}},
|
||||
Balances: []uint64{params.BeaconConfig().MaxEffectiveBalance, params.BeaconConfig().MaxEffectiveBalance},
|
||||
Slashings: []uint64{0, 1e9},
|
||||
},
|
||||
want: uint64(29000000000),
|
||||
},
|
||||
{
|
||||
state: ðpb.BeaconStateElectra{
|
||||
Validators: []*ethpb.Validator{
|
||||
{Slashed: true,
|
||||
WithdrawableEpoch: params.BeaconConfig().EpochsPerSlashingsVector / 2,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance},
|
||||
{ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance},
|
||||
{ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance},
|
||||
},
|
||||
Balances: []uint64{params.BeaconConfig().MaxEffectiveBalance, params.BeaconConfig().MaxEffectiveBalance},
|
||||
Slashings: []uint64{0, 1e9},
|
||||
},
|
||||
want: uint64(30500000000),
|
||||
},
|
||||
{
|
||||
state: ðpb.BeaconStateElectra{
|
||||
Validators: []*ethpb.Validator{
|
||||
{Slashed: true,
|
||||
WithdrawableEpoch: params.BeaconConfig().EpochsPerSlashingsVector / 2,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra},
|
||||
{ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra},
|
||||
{ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra},
|
||||
},
|
||||
Balances: []uint64{params.BeaconConfig().MaxEffectiveBalance * 10, params.BeaconConfig().MaxEffectiveBalance * 20},
|
||||
Slashings: []uint64{0, 2 * 1e9},
|
||||
},
|
||||
want: uint64(317000001536),
|
||||
},
|
||||
{
|
||||
state: ðpb.BeaconStateElectra{
|
||||
Validators: []*ethpb.Validator{
|
||||
{Slashed: true,
|
||||
WithdrawableEpoch: params.BeaconConfig().EpochsPerSlashingsVector / 2,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra - params.BeaconConfig().EffectiveBalanceIncrement},
|
||||
{ExitEpoch: params.BeaconConfig().FarFutureEpoch, EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra - params.BeaconConfig().EffectiveBalanceIncrement}},
|
||||
Balances: []uint64{params.BeaconConfig().MaxEffectiveBalanceElectra - params.BeaconConfig().EffectiveBalanceIncrement, params.BeaconConfig().MaxEffectiveBalanceElectra - params.BeaconConfig().EffectiveBalanceIncrement},
|
||||
Slashings: []uint64{0, 1e9},
|
||||
},
|
||||
want: uint64(2044000000727),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprint(i), func(t *testing.T) {
|
||||
original := proto.Clone(tt.state)
|
||||
s, err := state_native.InitializeFromProtoElectra(tt.state)
|
||||
require.NoError(t, err)
|
||||
helpers.ClearCache()
|
||||
newState, err := epoch.ProcessSlashings(s, params.BeaconConfig().ProportionalSlashingMultiplierBellatrix)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.want, newState.Balances()[0], "ProcessSlashings({%v}) = newState; newState.Balances[0] = %d", original, newState.Balances()[0])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ import "github.com/prysmaticlabs/prysm/v5/async/event"
|
||||
|
||||
// Notifier interface defines the methods of the service that provides beacon block operation updates to consumers.
|
||||
type Notifier interface {
|
||||
OperationFeed() *event.Feed
|
||||
OperationFeed() event.SubscriberSender
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ import "github.com/prysmaticlabs/prysm/v5/async/event"
|
||||
|
||||
// Notifier interface defines the methods of the service that provides state updates to consumers.
|
||||
type Notifier interface {
|
||||
StateFeed() *event.Feed
|
||||
StateFeed() event.SubscriberSender
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ go_test(
|
||||
"validators_test.go",
|
||||
"weak_subjectivity_test.go",
|
||||
],
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":go_default_library"],
|
||||
shard_count = 2,
|
||||
tags = ["CI_race_detection"],
|
||||
|
||||
@@ -12,12 +12,10 @@ go_library(
|
||||
"//consensus-types:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//encoding/ssz:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/migration:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
@@ -29,11 +27,13 @@ go_test(
|
||||
srcs = ["lightclient_test.go"],
|
||||
deps = [
|
||||
":go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/ssz:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -13,15 +13,11 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
v11 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpbv1 "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
|
||||
ethpbv2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v5/proto/migration"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -29,16 +25,6 @@ const (
|
||||
executionBranchNumOfLeaves = 4
|
||||
)
|
||||
|
||||
// createLightClientFinalityUpdate - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_finality_update
|
||||
// def create_light_client_finality_update(update: LightClientUpdate) -> LightClientFinalityUpdate:
|
||||
//
|
||||
// return LightClientFinalityUpdate(
|
||||
// attested_header=update.attested_header,
|
||||
// finalized_header=update.finalized_header,
|
||||
// finality_branch=update.finality_branch,
|
||||
// sync_aggregate=update.sync_aggregate,
|
||||
// signature_slot=update.signature_slot,
|
||||
// )
|
||||
func createLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientFinalityUpdate {
|
||||
finalityUpdate := ðpbv2.LightClientFinalityUpdate{
|
||||
AttestedHeader: update.AttestedHeader,
|
||||
@@ -51,14 +37,6 @@ func createLightClientFinalityUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2
|
||||
return finalityUpdate
|
||||
}
|
||||
|
||||
// createLightClientOptimisticUpdate - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_optimistic_update
|
||||
// def create_light_client_optimistic_update(update: LightClientUpdate) -> LightClientOptimisticUpdate:
|
||||
//
|
||||
// return LightClientOptimisticUpdate(
|
||||
// attested_header=update.attested_header,
|
||||
// sync_aggregate=update.sync_aggregate,
|
||||
// signature_slot=update.signature_slot,
|
||||
// )
|
||||
func createLightClientOptimisticUpdate(update *ethpbv2.LightClientUpdate) *ethpbv2.LightClientOptimisticUpdate {
|
||||
optimisticUpdate := ðpbv2.LightClientOptimisticUpdate{
|
||||
AttestedHeader: update.AttestedHeader,
|
||||
@@ -74,9 +52,10 @@ func NewLightClientFinalityUpdateFromBeaconState(
|
||||
state state.BeaconState,
|
||||
block interfaces.ReadOnlySignedBeaconBlock,
|
||||
attestedState state.BeaconState,
|
||||
attestedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
finalizedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
) (*ethpbv2.LightClientFinalityUpdate, error) {
|
||||
update, err := NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, finalizedBlock)
|
||||
update, err := NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,8 +68,9 @@ func NewLightClientOptimisticUpdateFromBeaconState(
|
||||
state state.BeaconState,
|
||||
block interfaces.ReadOnlySignedBeaconBlock,
|
||||
attestedState state.BeaconState,
|
||||
attestedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
) (*ethpbv2.LightClientOptimisticUpdate, error) {
|
||||
update, err := NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, nil)
|
||||
update, err := NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,68 +78,12 @@ func NewLightClientOptimisticUpdateFromBeaconState(
|
||||
return createLightClientOptimisticUpdate(update), nil
|
||||
}
|
||||
|
||||
// NewLightClientUpdateFromBeaconState implements https://github.com/ethereum/consensus-specs/blob/d70dcd9926a4bbe987f1b4e65c3e05bd029fcfb8/specs/altair/light-client/full-node.md#create_light_client_update
|
||||
// def create_light_client_update(state: BeaconState,
|
||||
//
|
||||
// block: SignedBeaconBlock,
|
||||
// attested_state: BeaconState,
|
||||
// finalized_block: Optional[SignedBeaconBlock]) -> LightClientUpdate:
|
||||
// assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH
|
||||
// assert sum(block.message.body.sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
|
||||
//
|
||||
// assert state.slot == state.latest_block_header.slot
|
||||
// header = state.latest_block_header.copy()
|
||||
// header.state_root = hash_tree_root(state)
|
||||
// assert hash_tree_root(header) == hash_tree_root(block.message)
|
||||
// update_signature_period = compute_sync_committee_period(compute_epoch_at_slot(block.message.slot))
|
||||
//
|
||||
// assert attested_state.slot == attested_state.latest_block_header.slot
|
||||
// attested_header = attested_state.latest_block_header.copy()
|
||||
// attested_header.state_root = hash_tree_root(attested_state)
|
||||
// assert hash_tree_root(attested_header) == block.message.parent_root
|
||||
// update_attested_period = compute_sync_committee_period(compute_epoch_at_slot(attested_header.slot))
|
||||
//
|
||||
// # `next_sync_committee` is only useful if the message is signed by the current sync committee
|
||||
// if update_attested_period == update_signature_period:
|
||||
// next_sync_committee = attested_state.next_sync_committee
|
||||
// next_sync_committee_branch = compute_merkle_proof_for_state(attested_state, NEXT_SYNC_COMMITTEE_INDEX)
|
||||
// else:
|
||||
// next_sync_committee = SyncCommittee()
|
||||
// next_sync_committee_branch = [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
|
||||
//
|
||||
// # Indicate finality whenever possible
|
||||
// if finalized_block is not None:
|
||||
// if finalized_block.message.slot != GENESIS_SLOT:
|
||||
// finalized_header = BeaconBlockHeader(
|
||||
// slot=finalized_block.message.slot,
|
||||
// proposer_index=finalized_block.message.proposer_index,
|
||||
// parent_root=finalized_block.message.parent_root,
|
||||
// state_root=finalized_block.message.state_root,
|
||||
// body_root=hash_tree_root(finalized_block.message.body),
|
||||
// )
|
||||
// assert hash_tree_root(finalized_header) == attested_state.finalized_checkpoint.root
|
||||
// else:
|
||||
// assert attested_state.finalized_checkpoint.root == Bytes32()
|
||||
// finalized_header = BeaconBlockHeader()
|
||||
// finality_branch = compute_merkle_proof_for_state(attested_state, FINALIZED_ROOT_INDEX)
|
||||
// else:
|
||||
// finalized_header = BeaconBlockHeader()
|
||||
// finality_branch = [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
|
||||
//
|
||||
// return LightClientUpdate(
|
||||
// attested_header=attested_header,
|
||||
// next_sync_committee=next_sync_committee,
|
||||
// next_sync_committee_branch=next_sync_committee_branch,
|
||||
// finalized_header=finalized_header,
|
||||
// finality_branch=finality_branch,
|
||||
// sync_aggregate=block.message.body.sync_aggregate,
|
||||
// signature_slot=block.message.slot,
|
||||
// )
|
||||
func NewLightClientUpdateFromBeaconState(
|
||||
ctx context.Context,
|
||||
state state.BeaconState,
|
||||
block interfaces.ReadOnlySignedBeaconBlock,
|
||||
attestedState state.BeaconState,
|
||||
attestedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
finalizedBlock interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientUpdate, error) {
|
||||
// assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH
|
||||
attestedEpoch := slots.ToEpoch(attestedState.Slot())
|
||||
@@ -223,73 +147,30 @@ func NewLightClientUpdateFromBeaconState(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get attested header root")
|
||||
}
|
||||
if attestedHeaderRoot != block.Block().ParentRoot() {
|
||||
return nil, fmt.Errorf("attested header root %#x not equal to block parent root %#x", attestedHeaderRoot, block.Block().ParentRoot())
|
||||
attestedBlockRoot, err := attestedBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get attested block root")
|
||||
}
|
||||
// assert hash_tree_root(attested_header) == hash_tree_root(attested_block.message) == block.message.parent_root
|
||||
if attestedHeaderRoot != block.Block().ParentRoot() || attestedHeaderRoot != attestedBlockRoot {
|
||||
return nil, fmt.Errorf("attested header root %#x not equal to block parent root %#x or attested block root %#x", attestedHeaderRoot, block.Block().ParentRoot(), attestedBlockRoot)
|
||||
}
|
||||
|
||||
// update_attested_period = compute_sync_committee_period(compute_epoch_at_slot(attested_header.slot))
|
||||
updateAttestedPeriod := slots.SyncCommitteePeriod(slots.ToEpoch(attestedHeader.Slot))
|
||||
// update_attested_period = compute_sync_committee_period_at_slot(attested_block.message.slot)
|
||||
updateAttestedPeriod := slots.SyncCommitteePeriod(slots.ToEpoch(attestedBlock.Block().Slot()))
|
||||
|
||||
// update = LightClientUpdate()
|
||||
result, err := createDefaultLightClientUpdate(block.Block().Version())
|
||||
result, err := createDefaultLightClientUpdate()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create default light client update")
|
||||
}
|
||||
|
||||
// update.attested_header = block_to_light_client_header(attested_block)
|
||||
blockHeader := ðpbv1.BeaconBlockHeader{
|
||||
Slot: attestedHeader.Slot,
|
||||
ProposerIndex: attestedHeader.ProposerIndex,
|
||||
ParentRoot: attestedHeader.ParentRoot,
|
||||
StateRoot: attestedHeader.StateRoot,
|
||||
BodyRoot: attestedHeader.BodyRoot,
|
||||
}
|
||||
switch block.Block().Version() {
|
||||
case version.Altair, version.Bellatrix:
|
||||
result.AttestedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{
|
||||
HeaderAltair: ðpbv2.LightClientHeader{Beacon: blockHeader},
|
||||
},
|
||||
}
|
||||
case version.Capella:
|
||||
executionPayloadHeader, err := getExecutionPayloadHeaderCapella(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload header")
|
||||
}
|
||||
executionPayloadProof, err := blocks.PayloadProof(ctx, block.Block())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload proof")
|
||||
}
|
||||
result.AttestedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{
|
||||
HeaderCapella: ðpbv2.LightClientHeaderCapella{
|
||||
Beacon: blockHeader,
|
||||
Execution: executionPayloadHeader,
|
||||
ExecutionBranch: executionPayloadProof,
|
||||
},
|
||||
},
|
||||
}
|
||||
case version.Deneb:
|
||||
executionPayloadHeader, err := getExecutionPayloadHeaderDeneb(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload header")
|
||||
}
|
||||
executionPayloadProof, err := blocks.PayloadProof(ctx, block.Block())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload proof")
|
||||
}
|
||||
result.AttestedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{
|
||||
HeaderDeneb: ðpbv2.LightClientHeaderDeneb{
|
||||
Beacon: blockHeader,
|
||||
Execution: executionPayloadHeader,
|
||||
ExecutionBranch: executionPayloadProof,
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported block version %s", version.String(block.Block().Version()))
|
||||
attestedLightClientHeader, err := BlockToLightClientHeader(attestedBlock)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get attested light client header")
|
||||
}
|
||||
result.AttestedHeader = attestedLightClientHeader
|
||||
|
||||
// if update_attested_period == update_signature_period
|
||||
if updateAttestedPeriod == updateSignaturePeriod {
|
||||
@@ -319,70 +200,11 @@ func NewLightClientUpdateFromBeaconState(
|
||||
// if finalized_block.message.slot != GENESIS_SLOT
|
||||
if finalizedBlock.Block().Slot() != 0 {
|
||||
// update.finalized_header = block_to_light_client_header(finalized_block)
|
||||
v1alpha1FinalizedHeader, err := finalizedBlock.Header()
|
||||
finalizedLightClientHeader, err := BlockToLightClientHeader(finalizedBlock)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get finalized header")
|
||||
}
|
||||
finalizedHeader := migration.V1Alpha1SignedHeaderToV1(v1alpha1FinalizedHeader).GetMessage()
|
||||
finalizedHeaderRoot, err := finalizedHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get finalized header root")
|
||||
}
|
||||
switch block.Block().Version() {
|
||||
case version.Altair, version.Bellatrix:
|
||||
result.FinalizedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{
|
||||
HeaderAltair: ðpbv2.LightClientHeader{Beacon: finalizedHeader},
|
||||
},
|
||||
}
|
||||
case version.Capella:
|
||||
executionPayloadHeader, err := getExecutionPayloadHeaderCapella(finalizedBlock)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload header")
|
||||
}
|
||||
executionPayloadProof, err := blocks.PayloadProof(ctx, finalizedBlock.Block())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload proof")
|
||||
}
|
||||
result.FinalizedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{
|
||||
HeaderCapella: ðpbv2.LightClientHeaderCapella{
|
||||
Beacon: finalizedHeader,
|
||||
Execution: executionPayloadHeader,
|
||||
ExecutionBranch: executionPayloadProof,
|
||||
},
|
||||
},
|
||||
}
|
||||
case version.Deneb:
|
||||
executionPayloadHeader, err := getExecutionPayloadHeaderDeneb(finalizedBlock)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload header")
|
||||
}
|
||||
executionPayloadProof, err := blocks.PayloadProof(ctx, finalizedBlock.Block())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload proof")
|
||||
}
|
||||
result.FinalizedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{
|
||||
HeaderDeneb: ðpbv2.LightClientHeaderDeneb{
|
||||
Beacon: finalizedHeader,
|
||||
Execution: executionPayloadHeader,
|
||||
ExecutionBranch: executionPayloadProof,
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported block version %s", version.String(block.Block().Version()))
|
||||
}
|
||||
|
||||
// assert hash_tree_root(update.finalized_header.beacon) == attested_state.finalized_checkpoint.root
|
||||
if finalizedHeaderRoot != bytesutil.ToBytes32(attestedState.FinalizedCheckpoint().Root) {
|
||||
return nil, fmt.Errorf(
|
||||
"finalized header root %#x not equal to attested finalized checkpoint root %#x",
|
||||
finalizedHeaderRoot,
|
||||
bytesutil.ToBytes32(attestedState.FinalizedCheckpoint().Root),
|
||||
)
|
||||
return nil, errors.Wrap(err, "could not get finalized light client header")
|
||||
}
|
||||
result.FinalizedHeader = finalizedLightClientHeader
|
||||
} else {
|
||||
// assert attested_state.finalized_checkpoint.root == Bytes32()
|
||||
if !bytes.Equal(attestedState.FinalizedCheckpoint().Root, make([]byte, 32)) {
|
||||
@@ -411,7 +233,7 @@ func NewLightClientUpdateFromBeaconState(
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createDefaultLightClientUpdate(v int) (*ethpbv2.LightClientUpdate, error) {
|
||||
func createDefaultLightClientUpdate() (*ethpbv2.LightClientUpdate, error) {
|
||||
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
|
||||
pubKeys := make([][]byte, syncCommitteeSize)
|
||||
for i := uint64(0); i < syncCommitteeSize; i++ {
|
||||
@@ -421,218 +243,26 @@ func createDefaultLightClientUpdate(v int) (*ethpbv2.LightClientUpdate, error) {
|
||||
Pubkeys: pubKeys,
|
||||
AggregatePubkey: make([]byte, fieldparams.BLSPubkeyLength),
|
||||
}
|
||||
nextSyncCommitteeBranch := make([][]byte, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
for i := 0; i < fieldparams.NextSyncCommitteeBranchDepth; i++ {
|
||||
nextSyncCommitteeBranch := make([][]byte, fieldparams.SyncCommitteeBranchDepth)
|
||||
for i := 0; i < fieldparams.SyncCommitteeBranchDepth; i++ {
|
||||
nextSyncCommitteeBranch[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
executionBranch := make([][]byte, executionBranchNumOfLeaves)
|
||||
for i := 0; i < executionBranchNumOfLeaves; i++ {
|
||||
executionBranch[i] = make([]byte, 32)
|
||||
}
|
||||
finalizedBlockHeader := ðpbv1.BeaconBlockHeader{
|
||||
Slot: 0,
|
||||
ProposerIndex: 0,
|
||||
ParentRoot: make([]byte, 32),
|
||||
StateRoot: make([]byte, 32),
|
||||
BodyRoot: make([]byte, 32),
|
||||
}
|
||||
finalityBranch := make([][]byte, FinalityBranchNumOfLeaves)
|
||||
for i := 0; i < FinalityBranchNumOfLeaves; i++ {
|
||||
finalityBranch[i] = make([]byte, 32)
|
||||
}
|
||||
|
||||
var finalizedHeader *ethpbv2.LightClientHeaderContainer
|
||||
switch v {
|
||||
case version.Altair, version.Bellatrix:
|
||||
finalizedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{
|
||||
HeaderAltair: ðpbv2.LightClientHeader{
|
||||
Beacon: finalizedBlockHeader,
|
||||
},
|
||||
},
|
||||
}
|
||||
case version.Capella:
|
||||
finalizedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{
|
||||
HeaderCapella: ðpbv2.LightClientHeaderCapella{
|
||||
Beacon: finalizedBlockHeader,
|
||||
Execution: createEmptyExecutionPayloadHeaderCapella(),
|
||||
ExecutionBranch: executionBranch,
|
||||
},
|
||||
},
|
||||
}
|
||||
case version.Deneb:
|
||||
finalizedHeader = ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{
|
||||
HeaderDeneb: ðpbv2.LightClientHeaderDeneb{
|
||||
Beacon: finalizedBlockHeader,
|
||||
Execution: createEmptyExecutionPayloadHeaderDeneb(),
|
||||
ExecutionBranch: executionBranch,
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported block version %s", version.String(v))
|
||||
}
|
||||
|
||||
return ðpbv2.LightClientUpdate{
|
||||
NextSyncCommittee: nextSyncCommittee,
|
||||
NextSyncCommitteeBranch: nextSyncCommitteeBranch,
|
||||
FinalizedHeader: finalizedHeader,
|
||||
FinalityBranch: finalityBranch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createEmptyExecutionPayloadHeaderCapella() *enginev1.ExecutionPayloadHeaderCapella {
|
||||
return &enginev1.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
BlockNumber: 0,
|
||||
GasLimit: 0,
|
||||
GasUsed: 0,
|
||||
Timestamp: 0,
|
||||
ExtraData: make([]byte, 32),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
TransactionsRoot: make([]byte, 32),
|
||||
WithdrawalsRoot: make([]byte, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func createEmptyExecutionPayloadHeaderDeneb() *enginev1.ExecutionPayloadHeaderDeneb {
|
||||
return &enginev1.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
BlockNumber: 0,
|
||||
GasLimit: 0,
|
||||
GasUsed: 0,
|
||||
Timestamp: 0,
|
||||
ExtraData: make([]byte, 32),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
TransactionsRoot: make([]byte, 32),
|
||||
WithdrawalsRoot: make([]byte, 32),
|
||||
}
|
||||
}
|
||||
|
||||
func getExecutionPayloadHeaderCapella(block interfaces.ReadOnlySignedBeaconBlock) (*enginev1.ExecutionPayloadHeaderCapella, error) {
|
||||
payloadInterface, err := block.Block().Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution data")
|
||||
}
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions")
|
||||
}
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals")
|
||||
}
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
|
||||
execution := &enginev1.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: payloadInterface.ParentHash(),
|
||||
FeeRecipient: payloadInterface.FeeRecipient(),
|
||||
StateRoot: payloadInterface.StateRoot(),
|
||||
ReceiptsRoot: payloadInterface.ReceiptsRoot(),
|
||||
LogsBloom: payloadInterface.LogsBloom(),
|
||||
PrevRandao: payloadInterface.PrevRandao(),
|
||||
BlockNumber: payloadInterface.BlockNumber(),
|
||||
GasLimit: payloadInterface.GasLimit(),
|
||||
GasUsed: payloadInterface.GasUsed(),
|
||||
Timestamp: payloadInterface.Timestamp(),
|
||||
ExtraData: payloadInterface.ExtraData(),
|
||||
BaseFeePerGas: payloadInterface.BaseFeePerGas(),
|
||||
BlockHash: payloadInterface.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
}
|
||||
|
||||
return execution, nil
|
||||
}
|
||||
|
||||
func getExecutionPayloadHeaderDeneb(block interfaces.ReadOnlySignedBeaconBlock) (*enginev1.ExecutionPayloadHeaderDeneb, error) {
|
||||
payloadInterface, err := block.Block().Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution data")
|
||||
}
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions")
|
||||
}
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals")
|
||||
}
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
|
||||
execution := &enginev1.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: payloadInterface.ParentHash(),
|
||||
FeeRecipient: payloadInterface.FeeRecipient(),
|
||||
StateRoot: payloadInterface.StateRoot(),
|
||||
ReceiptsRoot: payloadInterface.ReceiptsRoot(),
|
||||
LogsBloom: payloadInterface.LogsBloom(),
|
||||
PrevRandao: payloadInterface.PrevRandao(),
|
||||
BlockNumber: payloadInterface.BlockNumber(),
|
||||
GasLimit: payloadInterface.GasLimit(),
|
||||
GasUsed: payloadInterface.GasUsed(),
|
||||
Timestamp: payloadInterface.Timestamp(),
|
||||
ExtraData: payloadInterface.ExtraData(),
|
||||
BaseFeePerGas: payloadInterface.BaseFeePerGas(),
|
||||
BlockHash: payloadInterface.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
}
|
||||
|
||||
return execution, nil
|
||||
}
|
||||
|
||||
func ComputeTransactionsRoot(payload interfaces.ExecutionData) ([]byte, error) {
|
||||
transactionsRoot, err := payload.TransactionsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
@@ -669,8 +299,45 @@ func ComputeWithdrawalsRoot(payload interfaces.ExecutionData) ([]byte, error) {
|
||||
return withdrawalsRoot, nil
|
||||
}
|
||||
|
||||
func BlockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeader, error) {
|
||||
if block.Version() != version.Altair {
|
||||
func BlockToLightClientHeader(block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderContainer, error) {
|
||||
switch block.Version() {
|
||||
case version.Altair, version.Bellatrix:
|
||||
altairHeader, err := blockToLightClientHeaderAltair(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get header")
|
||||
}
|
||||
return ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderAltair{
|
||||
HeaderAltair: altairHeader,
|
||||
},
|
||||
}, nil
|
||||
case version.Capella:
|
||||
capellaHeader, err := blockToLightClientHeaderCapella(context.Background(), block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get capella header")
|
||||
}
|
||||
return ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderCapella{
|
||||
HeaderCapella: capellaHeader,
|
||||
},
|
||||
}, nil
|
||||
case version.Deneb, version.Electra:
|
||||
denebHeader, err := blockToLightClientHeaderDeneb(context.Background(), block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get header")
|
||||
}
|
||||
return ðpbv2.LightClientHeaderContainer{
|
||||
Header: ðpbv2.LightClientHeaderContainer_HeaderDeneb{
|
||||
HeaderDeneb: denebHeader,
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported block version %s", version.String(block.Version()))
|
||||
}
|
||||
}
|
||||
|
||||
func blockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeader, error) {
|
||||
if block.Version() < version.Altair {
|
||||
return nil, fmt.Errorf("block version is %s instead of Altair", version.String(block.Version()))
|
||||
}
|
||||
|
||||
@@ -692,8 +359,8 @@ func BlockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlockToLightClientHeaderCapella(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderCapella, error) {
|
||||
if block.Version() != version.Capella {
|
||||
func blockToLightClientHeaderCapella(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderCapella, error) {
|
||||
if block.Version() < version.Capella {
|
||||
return nil, fmt.Errorf("block version is %s instead of Capella", version.String(block.Version()))
|
||||
}
|
||||
|
||||
@@ -754,9 +421,9 @@ func BlockToLightClientHeaderCapella(ctx context.Context, block interfaces.ReadO
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlockToLightClientHeaderDeneb(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderDeneb, error) {
|
||||
if block.Version() != version.Deneb {
|
||||
return nil, fmt.Errorf("block version is %s instead of Deneb", version.String(block.Version()))
|
||||
func blockToLightClientHeaderDeneb(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderDeneb, error) {
|
||||
if block.Version() < version.Deneb {
|
||||
return nil, fmt.Errorf("block version is %s instead of Deneb/Electra", version.String(block.Version()))
|
||||
}
|
||||
|
||||
payload, err := block.Block().Body().Execution()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -138,20 +138,17 @@ func TestState_CanSaveRetrieve(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetSlot(100))
|
||||
p, err := blocks.WrappedExecutionPayloadHeaderElectra(&enginev1.ExecutionPayloadHeaderElectra{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
ExtraData: []byte("foo"),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
TransactionsRoot: make([]byte, 32),
|
||||
WithdrawalsRoot: make([]byte, 32),
|
||||
DepositRequestsRoot: make([]byte, 32),
|
||||
WithdrawalRequestsRoot: make([]byte, 32),
|
||||
ConsolidationRequestsRoot: make([]byte, 32),
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
ExtraData: []byte("foo"),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
TransactionsRoot: make([]byte, 32),
|
||||
WithdrawalsRoot: make([]byte, 32),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, st.SetLatestExecutionPayloadHeader(p))
|
||||
|
||||
@@ -44,8 +44,6 @@ var (
|
||||
GetPayloadMethodV4,
|
||||
GetPayloadBodiesByHashV1,
|
||||
GetPayloadBodiesByRangeV1,
|
||||
GetPayloadBodiesByHashV2,
|
||||
GetPayloadBodiesByRangeV2,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -63,6 +61,8 @@ const (
|
||||
ForkchoiceUpdatedMethodV2 = "engine_forkchoiceUpdatedV2"
|
||||
// ForkchoiceUpdatedMethodV3 v3 request string for JSON-RPC.
|
||||
ForkchoiceUpdatedMethodV3 = "engine_forkchoiceUpdatedV3"
|
||||
// ForkchoiceUpdatedMethodV4 v4 request string for JSON-RPC.
|
||||
ForkchoiceUpdatedMethodV4 = "engine_forkchoiceUpdatedV3"
|
||||
// GetPayloadMethod v1 request string for JSON-RPC.
|
||||
GetPayloadMethod = "engine_getPayloadV1"
|
||||
// GetPayloadMethodV2 v2 request string for JSON-RPC.
|
||||
@@ -77,12 +77,8 @@ const (
|
||||
BlockByNumberMethod = "eth_getBlockByNumber"
|
||||
// GetPayloadBodiesByHashV1 is the engine_getPayloadBodiesByHashX JSON-RPC method for pre-Electra payloads.
|
||||
GetPayloadBodiesByHashV1 = "engine_getPayloadBodiesByHashV1"
|
||||
// GetPayloadBodiesByHashV2 is the engine_getPayloadBodiesByHashX JSON-RPC method introduced by Electra.
|
||||
GetPayloadBodiesByHashV2 = "engine_getPayloadBodiesByHashV2"
|
||||
// GetPayloadBodiesByRangeV1 is the engine_getPayloadBodiesByRangeX JSON-RPC method for pre-Electra payloads.
|
||||
GetPayloadBodiesByRangeV1 = "engine_getPayloadBodiesByRangeV1"
|
||||
// GetPayloadBodiesByRangeV2 is the engine_getPayloadBodiesByRangeX JSON-RPC method introduced by Electra.
|
||||
GetPayloadBodiesByRangeV2 = "engine_getPayloadBodiesByRangeV2"
|
||||
// ExchangeCapabilities request string for JSON-RPC.
|
||||
ExchangeCapabilities = "engine_exchangeCapabilities"
|
||||
// Defines the seconds before timing out engine endpoints with non-block execution semantics.
|
||||
@@ -114,7 +110,7 @@ type PayloadReconstructor interface {
|
||||
// EngineCaller defines a client that can interact with an Ethereum
|
||||
// execution node's engine service via JSON-RPC.
|
||||
type EngineCaller interface {
|
||||
NewPayload(ctx context.Context, payload interfaces.ExecutionData, versionedHashes []common.Hash, parentBlockRoot *common.Hash) ([]byte, error)
|
||||
NewPayload(ctx context.Context, payload interfaces.ExecutionData, versionedHashes []common.Hash, parentBlockRoot *common.Hash, executionRequests *pb.ExecutionRequests) ([]byte, error)
|
||||
ForkchoiceUpdated(
|
||||
ctx context.Context, state *pb.ForkchoiceState, attrs payloadattribute.Attributer,
|
||||
) (*pb.PayloadIDBytes, []byte, error)
|
||||
@@ -125,8 +121,8 @@ type EngineCaller interface {
|
||||
|
||||
var ErrEmptyBlockHash = errors.New("Block hash is empty 0x0000...")
|
||||
|
||||
// NewPayload calls the engine_newPayloadVX method via JSON-RPC.
|
||||
func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionData, versionedHashes []common.Hash, parentBlockRoot *common.Hash) ([]byte, error) {
|
||||
// NewPayload request calls the engine_newPayloadVX method via JSON-RPC.
|
||||
func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionData, versionedHashes []common.Hash, parentBlockRoot *common.Hash, executionRequests *pb.ExecutionRequests) ([]byte, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.NewPayload")
|
||||
defer span.End()
|
||||
start := time.Now()
|
||||
@@ -163,18 +159,20 @@ func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionDa
|
||||
if !ok {
|
||||
return nil, errors.New("execution data must be a Deneb execution payload")
|
||||
}
|
||||
err := s.rpcClient.CallContext(ctx, result, NewPayloadMethodV3, payloadPb, versionedHashes, parentBlockRoot)
|
||||
if err != nil {
|
||||
return nil, handleRPCError(err)
|
||||
}
|
||||
case *pb.ExecutionPayloadElectra:
|
||||
payloadPb, ok := payload.Proto().(*pb.ExecutionPayloadElectra)
|
||||
if !ok {
|
||||
return nil, errors.New("execution data must be a Electra execution payload")
|
||||
}
|
||||
err := s.rpcClient.CallContext(ctx, result, NewPayloadMethodV4, payloadPb, versionedHashes, parentBlockRoot)
|
||||
if err != nil {
|
||||
return nil, handleRPCError(err)
|
||||
if executionRequests == nil {
|
||||
err := s.rpcClient.CallContext(ctx, result, NewPayloadMethodV3, payloadPb, versionedHashes, parentBlockRoot)
|
||||
if err != nil {
|
||||
return nil, handleRPCError(err)
|
||||
}
|
||||
} else {
|
||||
flattenedRequests, err := pb.EncodeExecutionRequests(executionRequests)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to encode execution requests")
|
||||
}
|
||||
err = s.rpcClient.CallContext(ctx, result, NewPayloadMethodV4, payloadPb, versionedHashes, parentBlockRoot, flattenedRequests, fieldparams.MaxBlobsPerBlock/2)
|
||||
if err != nil {
|
||||
return nil, handleRPCError(err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown execution data type")
|
||||
@@ -234,7 +232,7 @@ func (s *Service) ForkchoiceUpdated(
|
||||
if err != nil {
|
||||
return nil, nil, handleRPCError(err)
|
||||
}
|
||||
case version.Deneb, version.Electra:
|
||||
case version.Deneb:
|
||||
a, err := attrs.PbV3()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -243,6 +241,15 @@ func (s *Service) ForkchoiceUpdated(
|
||||
if err != nil {
|
||||
return nil, nil, handleRPCError(err)
|
||||
}
|
||||
case version.Electra:
|
||||
a, err := attrs.PbV4()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = s.rpcClient.CallContext(ctx, result, ForkchoiceUpdatedMethodV4, state, a)
|
||||
if err != nil {
|
||||
return nil, nil, handleRPCError(err)
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown payload attribute version: %v", attrs.Version())
|
||||
}
|
||||
@@ -269,7 +276,7 @@ func (s *Service) ForkchoiceUpdated(
|
||||
func getPayloadMethodAndMessage(slot primitives.Slot) (string, proto.Message) {
|
||||
pe := slots.ToEpoch(slot)
|
||||
if pe >= params.BeaconConfig().ElectraForkEpoch {
|
||||
return GetPayloadMethodV4, &pb.ExecutionPayloadElectraWithValueAndBlobsBundle{}
|
||||
return GetPayloadMethodV4, &pb.ExecutionBundleElectra{}
|
||||
}
|
||||
if pe >= params.BeaconConfig().DenebForkEpoch {
|
||||
return GetPayloadMethodV3, &pb.ExecutionPayloadDenebWithValueAndBlobsBundle{}
|
||||
@@ -309,13 +316,16 @@ func (s *Service) ExchangeCapabilities(ctx context.Context) ([]string, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.ExchangeCapabilities")
|
||||
defer span.End()
|
||||
|
||||
result := &pb.ExchangeCapabilities{}
|
||||
var result []string
|
||||
err := s.rpcClient.CallContext(ctx, &result, ExchangeCapabilities, supportedEngineEndpoints)
|
||||
if err != nil {
|
||||
return nil, handleRPCError(err)
|
||||
}
|
||||
|
||||
var unsupported []string
|
||||
for _, s1 := range supportedEngineEndpoints {
|
||||
supported := false
|
||||
for _, s2 := range result.SupportedMethods {
|
||||
for _, s2 := range result {
|
||||
if s1 == s2 {
|
||||
supported = true
|
||||
break
|
||||
@@ -328,7 +338,7 @@ func (s *Service) ExchangeCapabilities(ctx context.Context) ([]string, error) {
|
||||
if len(unsupported) != 0 {
|
||||
log.Warnf("Please update client, detected the following unsupported engine methods: %s", unsupported)
|
||||
}
|
||||
return result.SupportedMethods, handleRPCError(err)
|
||||
return result, handleRPCError(err)
|
||||
}
|
||||
|
||||
// GetTerminalBlockHash returns the valid terminal block hash based on total difficulty.
|
||||
@@ -566,7 +576,7 @@ func fullPayloadFromPayloadBody(
|
||||
Transactions: pb.RecastHexutilByteSlice(body.Transactions),
|
||||
Withdrawals: body.Withdrawals,
|
||||
}) // We can't get the block value and don't care about the block value for this instance
|
||||
case version.Deneb:
|
||||
case version.Deneb, version.Electra:
|
||||
ebg, err := header.ExcessBlobGas()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to extract ExcessBlobGas attribute from execution payload header")
|
||||
@@ -595,50 +605,6 @@ func fullPayloadFromPayloadBody(
|
||||
ExcessBlobGas: ebg,
|
||||
BlobGasUsed: bgu,
|
||||
}) // We can't get the block value and don't care about the block value for this instance
|
||||
case version.Electra:
|
||||
ebg, err := header.ExcessBlobGas()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to extract ExcessBlobGas attribute from execution payload header")
|
||||
}
|
||||
bgu, err := header.BlobGasUsed()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to extract BlobGasUsed attribute from execution payload header")
|
||||
}
|
||||
wr, err := pb.JsonWithdrawalRequestsToProto(body.WithdrawalRequests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dr, err := pb.JsonDepositRequestsToProto(body.DepositRequests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cr, err := pb.JsonConsolidationRequestsToProto(body.ConsolidationRequests)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blocks.WrappedExecutionPayloadElectra(
|
||||
&pb.ExecutionPayloadElectra{
|
||||
ParentHash: header.ParentHash(),
|
||||
FeeRecipient: header.FeeRecipient(),
|
||||
StateRoot: header.StateRoot(),
|
||||
ReceiptsRoot: header.ReceiptsRoot(),
|
||||
LogsBloom: header.LogsBloom(),
|
||||
PrevRandao: header.PrevRandao(),
|
||||
BlockNumber: header.BlockNumber(),
|
||||
GasLimit: header.GasLimit(),
|
||||
GasUsed: header.GasUsed(),
|
||||
Timestamp: header.Timestamp(),
|
||||
ExtraData: header.ExtraData(),
|
||||
BaseFeePerGas: header.BaseFeePerGas(),
|
||||
BlockHash: header.BlockHash(),
|
||||
Transactions: pb.RecastHexutilByteSlice(body.Transactions),
|
||||
Withdrawals: body.Withdrawals,
|
||||
ExcessBlobGas: ebg,
|
||||
BlobGasUsed: bgu,
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ConsolidationRequests: cr,
|
||||
}) // We can't get the block value and don't care about the block value for this instance
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown execution block version for payload %d", bVersion)
|
||||
}
|
||||
@@ -761,7 +727,7 @@ func buildEmptyExecutionPayload(v int) (proto.Message, error) {
|
||||
Transactions: make([][]byte, 0),
|
||||
Withdrawals: make([]*pb.Withdrawal, 0),
|
||||
}, nil
|
||||
case version.Deneb:
|
||||
case version.Deneb, version.Electra:
|
||||
return &pb.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
@@ -775,22 +741,6 @@ func buildEmptyExecutionPayload(v int) (proto.Message, error) {
|
||||
Transactions: make([][]byte, 0),
|
||||
Withdrawals: make([]*pb.Withdrawal, 0),
|
||||
}, nil
|
||||
case version.Electra:
|
||||
return &pb.ExecutionPayloadElectra{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
ExtraData: make([]byte, 0),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
Transactions: make([][]byte, 0),
|
||||
Withdrawals: make([]*pb.Withdrawal, 0),
|
||||
WithdrawalRequests: make([]*pb.WithdrawalRequest, 0),
|
||||
DepositRequests: make([]*pb.DepositRequest, 0),
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.Wrapf(ErrUnsupportedVersion, "version=%s", version.String(v))
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestClient_IPC(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayload(req)
|
||||
require.NoError(t, err)
|
||||
latestValidHash, err := srv.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
latestValidHash, err := srv.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, bytesutil.ToBytes32(want.LatestValidHash), bytesutil.ToBytes32(latestValidHash))
|
||||
})
|
||||
@@ -134,7 +134,7 @@ func TestClient_IPC(t *testing.T) {
|
||||
require.Equal(t, true, ok)
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadCapella(req)
|
||||
require.NoError(t, err)
|
||||
latestValidHash, err := srv.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
latestValidHash, err := srv.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, bytesutil.ToBytes32(want.LatestValidHash), bytesutil.ToBytes32(latestValidHash))
|
||||
})
|
||||
@@ -163,7 +163,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.CapellaForkEpoch = 1
|
||||
cfg.DenebForkEpoch = 2
|
||||
cfg.ElectraForkEpoch = 2
|
||||
cfg.ElectraForkEpoch = 3
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
t.Run(GetPayloadMethod, func(t *testing.T) {
|
||||
@@ -322,7 +322,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
})
|
||||
t.Run(GetPayloadMethodV4, func(t *testing.T) {
|
||||
payloadId := [8]byte{1}
|
||||
want, ok := fix["ExecutionPayloadElectraWithValue"].(*pb.GetPayloadV4ResponseJson)
|
||||
want, ok := fix["ExecutionBundleElectra"].(*pb.GetPayloadV4ResponseJson)
|
||||
require.Equal(t, true, ok)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -358,7 +358,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
client.rpcClient = rpcClient
|
||||
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
resp, err := client.GetPayload(ctx, payloadId, 2*params.BeaconConfig().SlotsPerEpoch)
|
||||
resp, err := client.GetPayload(ctx, payloadId, 3*params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, resp.OverrideBuilder)
|
||||
g, err := resp.ExecutionData.ExcessBlobGas()
|
||||
@@ -374,18 +374,35 @@ func TestClient_HTTP(t *testing.T) {
|
||||
require.DeepEqual(t, proofs, resp.BlobsBundle.Proofs)
|
||||
blobs := [][]byte{bytesutil.PadTo([]byte("a"), fieldparams.BlobLength), bytesutil.PadTo([]byte("b"), fieldparams.BlobLength)}
|
||||
require.DeepEqual(t, blobs, resp.BlobsBundle.Blobs)
|
||||
ede, ok := resp.ExecutionData.(interfaces.ExecutionDataElectra)
|
||||
require.Equal(t, true, ok)
|
||||
require.NotNil(t, ede.WithdrawalRequests())
|
||||
wrequestsNotOverMax := len(ede.WithdrawalRequests()) <= int(params.BeaconConfig().MaxWithdrawalRequestsPerPayload)
|
||||
require.Equal(t, true, wrequestsNotOverMax)
|
||||
require.NotNil(t, ede.DepositRequests())
|
||||
drequestsNotOverMax := len(ede.DepositRequests()) <= int(params.BeaconConfig().MaxDepositRequestsPerPayload)
|
||||
require.Equal(t, true, drequestsNotOverMax)
|
||||
require.NotNil(t, ede.ConsolidationRequests())
|
||||
consolidationsNotOverMax := len(ede.ConsolidationRequests()) <= int(params.BeaconConfig().MaxConsolidationsRequestsPerPayload)
|
||||
require.Equal(t, true, consolidationsNotOverMax)
|
||||
requests := &pb.ExecutionRequests{
|
||||
Deposits: []*pb.DepositRequest{
|
||||
{
|
||||
Pubkey: bytesutil.PadTo([]byte{byte('a')}, fieldparams.BLSPubkeyLength),
|
||||
WithdrawalCredentials: bytesutil.PadTo([]byte{byte('b')}, fieldparams.RootLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
Signature: bytesutil.PadTo([]byte{byte('c')}, fieldparams.BLSSignatureLength),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
Withdrawals: []*pb.WithdrawalRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('d')}, common.AddressLength),
|
||||
ValidatorPubkey: bytesutil.PadTo([]byte{byte('e')}, fieldparams.BLSPubkeyLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
},
|
||||
},
|
||||
Consolidations: []*pb.ConsolidationRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('f')}, common.AddressLength),
|
||||
SourcePubkey: bytesutil.PadTo([]byte{byte('g')}, fieldparams.BLSPubkeyLength),
|
||||
TargetPubkey: bytesutil.PadTo([]byte{byte('h')}, fieldparams.BLSPubkeyLength),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.DeepEqual(t, requests, resp.ExecutionRequests)
|
||||
})
|
||||
|
||||
t.Run(ForkchoiceUpdatedMethod+" VALID status", func(t *testing.T) {
|
||||
forkChoiceState := &pb.ForkchoiceState{
|
||||
HeadBlockHash: []byte("head"),
|
||||
@@ -536,7 +553,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayload(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
@@ -550,7 +567,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadCapella(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
@@ -564,21 +581,46 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, nil)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
t.Run(NewPayloadMethodV4+" VALID status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
|
||||
execPayload, ok := fix["ExecutionPayloadDeneb"].(*pb.ExecutionPayloadDeneb)
|
||||
require.Equal(t, true, ok)
|
||||
want, ok := fix["ValidPayloadStatus"].(*pb.PayloadStatus)
|
||||
require.Equal(t, true, ok)
|
||||
client := newPayloadV4Setup(t, want, execPayload)
|
||||
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
requests := &pb.ExecutionRequests{
|
||||
Deposits: []*pb.DepositRequest{
|
||||
{
|
||||
Pubkey: bytesutil.PadTo([]byte{byte('a')}, fieldparams.BLSPubkeyLength),
|
||||
WithdrawalCredentials: bytesutil.PadTo([]byte{byte('b')}, fieldparams.RootLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
Signature: bytesutil.PadTo([]byte{byte('c')}, fieldparams.BLSSignatureLength),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
Withdrawals: []*pb.WithdrawalRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('d')}, common.AddressLength),
|
||||
ValidatorPubkey: bytesutil.PadTo([]byte{byte('e')}, fieldparams.BLSPubkeyLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
},
|
||||
},
|
||||
Consolidations: []*pb.ConsolidationRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('f')}, common.AddressLength),
|
||||
SourcePubkey: bytesutil.PadTo([]byte{byte('g')}, fieldparams.BLSPubkeyLength),
|
||||
TargetPubkey: bytesutil.PadTo([]byte{byte('h')}, fieldparams.BLSPubkeyLength),
|
||||
},
|
||||
},
|
||||
}
|
||||
client := newPayloadV4Setup(t, want, execPayload, requests)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, requests)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
@@ -592,7 +634,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayload(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -606,7 +648,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadCapella(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -620,21 +662,46 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, nil)
|
||||
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
t.Run(NewPayloadMethodV4+" SYNCING status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
|
||||
execPayload, ok := fix["ExecutionPayloadDeneb"].(*pb.ExecutionPayloadDeneb)
|
||||
require.Equal(t, true, ok)
|
||||
want, ok := fix["SyncingStatus"].(*pb.PayloadStatus)
|
||||
require.Equal(t, true, ok)
|
||||
client := newPayloadV4Setup(t, want, execPayload)
|
||||
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
requests := &pb.ExecutionRequests{
|
||||
Deposits: []*pb.DepositRequest{
|
||||
{
|
||||
Pubkey: bytesutil.PadTo([]byte{byte('a')}, fieldparams.BLSPubkeyLength),
|
||||
WithdrawalCredentials: bytesutil.PadTo([]byte{byte('b')}, fieldparams.RootLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
Signature: bytesutil.PadTo([]byte{byte('c')}, fieldparams.BLSSignatureLength),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
Withdrawals: []*pb.WithdrawalRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('d')}, common.AddressLength),
|
||||
ValidatorPubkey: bytesutil.PadTo([]byte{byte('e')}, fieldparams.BLSPubkeyLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
},
|
||||
},
|
||||
Consolidations: []*pb.ConsolidationRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('f')}, common.AddressLength),
|
||||
SourcePubkey: bytesutil.PadTo([]byte{byte('g')}, fieldparams.BLSPubkeyLength),
|
||||
TargetPubkey: bytesutil.PadTo([]byte{byte('h')}, fieldparams.BLSPubkeyLength),
|
||||
},
|
||||
},
|
||||
}
|
||||
client := newPayloadV4Setup(t, want, execPayload, requests)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, requests)
|
||||
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -648,7 +715,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayload(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -662,7 +729,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadCapella(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -676,21 +743,45 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, nil)
|
||||
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
t.Run(NewPayloadMethodV4+" INVALID_BLOCK_HASH status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
|
||||
execPayload, ok := fix["ExecutionPayloadDeneb"].(*pb.ExecutionPayloadDeneb)
|
||||
require.Equal(t, true, ok)
|
||||
want, ok := fix["InvalidBlockHashStatus"].(*pb.PayloadStatus)
|
||||
require.Equal(t, true, ok)
|
||||
client := newPayloadV4Setup(t, want, execPayload)
|
||||
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
requests := &pb.ExecutionRequests{
|
||||
Deposits: []*pb.DepositRequest{
|
||||
{
|
||||
Pubkey: bytesutil.PadTo([]byte{byte('a')}, fieldparams.BLSPubkeyLength),
|
||||
WithdrawalCredentials: bytesutil.PadTo([]byte{byte('b')}, fieldparams.RootLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
Signature: bytesutil.PadTo([]byte{byte('c')}, fieldparams.BLSSignatureLength),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
Withdrawals: []*pb.WithdrawalRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('d')}, common.AddressLength),
|
||||
ValidatorPubkey: bytesutil.PadTo([]byte{byte('e')}, fieldparams.BLSPubkeyLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
},
|
||||
},
|
||||
Consolidations: []*pb.ConsolidationRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('f')}, common.AddressLength),
|
||||
SourcePubkey: bytesutil.PadTo([]byte{byte('g')}, fieldparams.BLSPubkeyLength),
|
||||
TargetPubkey: bytesutil.PadTo([]byte{byte('h')}, fieldparams.BLSPubkeyLength),
|
||||
},
|
||||
},
|
||||
}
|
||||
client := newPayloadV4Setup(t, want, execPayload, requests)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, requests)
|
||||
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -704,7 +795,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayload(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
@@ -718,7 +809,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadCapella(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
@@ -732,21 +823,46 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, nil)
|
||||
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
t.Run(NewPayloadMethodV4+" INVALID status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
|
||||
execPayload, ok := fix["ExecutionPayloadDeneb"].(*pb.ExecutionPayloadDeneb)
|
||||
require.Equal(t, true, ok)
|
||||
want, ok := fix["InvalidStatus"].(*pb.PayloadStatus)
|
||||
require.Equal(t, true, ok)
|
||||
client := newPayloadV4Setup(t, want, execPayload)
|
||||
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayloadDeneb(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
requests := &pb.ExecutionRequests{
|
||||
Deposits: []*pb.DepositRequest{
|
||||
{
|
||||
Pubkey: bytesutil.PadTo([]byte{byte('a')}, fieldparams.BLSPubkeyLength),
|
||||
WithdrawalCredentials: bytesutil.PadTo([]byte{byte('b')}, fieldparams.RootLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
Signature: bytesutil.PadTo([]byte{byte('c')}, fieldparams.BLSSignatureLength),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
Withdrawals: []*pb.WithdrawalRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('d')}, common.AddressLength),
|
||||
ValidatorPubkey: bytesutil.PadTo([]byte{byte('e')}, fieldparams.BLSPubkeyLength),
|
||||
Amount: params.BeaconConfig().MinActivationBalance,
|
||||
},
|
||||
},
|
||||
Consolidations: []*pb.ConsolidationRequest{
|
||||
{
|
||||
SourceAddress: bytesutil.PadTo([]byte{byte('f')}, common.AddressLength),
|
||||
SourcePubkey: bytesutil.PadTo([]byte{byte('g')}, fieldparams.BLSPubkeyLength),
|
||||
TargetPubkey: bytesutil.PadTo([]byte{byte('h')}, fieldparams.BLSPubkeyLength),
|
||||
},
|
||||
},
|
||||
}
|
||||
client := newPayloadV4Setup(t, want, execPayload, requests)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'}, requests)
|
||||
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
@@ -760,7 +876,7 @@ func TestClient_HTTP(t *testing.T) {
|
||||
// We call the RPC method via HTTP and expect a proper result.
|
||||
wrappedPayload, err := blocks.WrappedExecutionPayload(execPayload)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{})
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{}, nil)
|
||||
require.ErrorIs(t, ErrUnknownPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
@@ -1417,10 +1533,9 @@ func fixtures() map[string]interface{} {
|
||||
"ExecutionPayload": s.ExecutionPayload,
|
||||
"ExecutionPayloadCapella": s.ExecutionPayloadCapella,
|
||||
"ExecutionPayloadDeneb": s.ExecutionPayloadDeneb,
|
||||
"ExecutionPayloadElectra": s.ExecutionPayloadElectra,
|
||||
"ExecutionPayloadCapellaWithValue": s.ExecutionPayloadWithValueCapella,
|
||||
"ExecutionPayloadDenebWithValue": s.ExecutionPayloadWithValueDeneb,
|
||||
"ExecutionPayloadElectraWithValue": s.ExecutionPayloadWithValueElectra,
|
||||
"ExecutionBundleElectra": s.ExecutionBundleElectra,
|
||||
"ValidPayloadStatus": s.ValidPayloadStatus,
|
||||
"InvalidBlockHashStatus": s.InvalidBlockHashStatus,
|
||||
"AcceptedStatus": s.AcceptedStatus,
|
||||
@@ -1558,40 +1673,6 @@ func fixturesStruct() *payloadFixtures {
|
||||
TargetPubkey: &tPubkey,
|
||||
}
|
||||
}
|
||||
dr, err := pb.JsonDepositRequestsToProto(depositRequests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wr, err := pb.JsonWithdrawalRequestsToProto(withdrawalRequests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cr, err := pb.JsonConsolidationRequestsToProto(consolidationRequests)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
executionPayloadFixtureElectra := &pb.ExecutionPayloadElectra{
|
||||
ParentHash: foo[:],
|
||||
FeeRecipient: bar,
|
||||
StateRoot: foo[:],
|
||||
ReceiptsRoot: foo[:],
|
||||
LogsBloom: baz,
|
||||
PrevRandao: foo[:],
|
||||
BlockNumber: 1,
|
||||
GasLimit: 1,
|
||||
GasUsed: 1,
|
||||
Timestamp: 1,
|
||||
ExtraData: foo[:],
|
||||
BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength),
|
||||
BlockHash: foo[:],
|
||||
Transactions: [][]byte{foo[:]},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlobGasUsed: 2,
|
||||
ExcessBlobGas: 3,
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ConsolidationRequests: cr,
|
||||
}
|
||||
hexUint := hexutil.Uint64(1)
|
||||
executionPayloadWithValueFixtureCapella := &pb.GetPayloadV2ResponseJson{
|
||||
ExecutionPayload: &pb.ExecutionPayloadCapellaJSON{
|
||||
@@ -1641,28 +1722,44 @@ func fixturesStruct() *payloadFixtures {
|
||||
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
|
||||
},
|
||||
}
|
||||
executionPayloadWithValueFixtureElectra := &pb.GetPayloadV4ResponseJson{
|
||||
|
||||
depositRequestBytes, err := hexutil.Decode("0x610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"620000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"4059730700000063000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"00000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
if err != nil {
|
||||
panic("failed to decode deposit request")
|
||||
}
|
||||
withdrawalRequestBytes, err := hexutil.Decode("0x6400000000000000000000000000000000000000" +
|
||||
"6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040597307000000")
|
||||
if err != nil {
|
||||
panic("failed to decode withdrawal request")
|
||||
}
|
||||
consolidationRequestBytes, err := hexutil.Decode("0x6600000000000000000000000000000000000000" +
|
||||
"670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
|
||||
"680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
if err != nil {
|
||||
panic("failed to decode consolidation request")
|
||||
}
|
||||
executionBundleFixtureElectra := &pb.GetPayloadV4ResponseJson{
|
||||
ShouldOverrideBuilder: true,
|
||||
ExecutionPayload: &pb.ExecutionPayloadElectraJSON{
|
||||
ParentHash: &common.Hash{'a'},
|
||||
FeeRecipient: &common.Address{'b'},
|
||||
StateRoot: &common.Hash{'c'},
|
||||
ReceiptsRoot: &common.Hash{'d'},
|
||||
LogsBloom: &hexutil.Bytes{'e'},
|
||||
PrevRandao: &common.Hash{'f'},
|
||||
BaseFeePerGas: "0x123",
|
||||
BlockHash: &common.Hash{'g'},
|
||||
Transactions: []hexutil.Bytes{{'h'}},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlockNumber: &hexUint,
|
||||
GasLimit: &hexUint,
|
||||
GasUsed: &hexUint,
|
||||
Timestamp: &hexUint,
|
||||
BlobGasUsed: &bgu,
|
||||
ExcessBlobGas: &ebg,
|
||||
DepositRequests: depositRequests,
|
||||
WithdrawalRequests: withdrawalRequests,
|
||||
ConsolidationRequests: consolidationRequests,
|
||||
ExecutionPayload: &pb.ExecutionPayloadDenebJSON{
|
||||
ParentHash: &common.Hash{'a'},
|
||||
FeeRecipient: &common.Address{'b'},
|
||||
StateRoot: &common.Hash{'c'},
|
||||
ReceiptsRoot: &common.Hash{'d'},
|
||||
LogsBloom: &hexutil.Bytes{'e'},
|
||||
PrevRandao: &common.Hash{'f'},
|
||||
BaseFeePerGas: "0x123",
|
||||
BlockHash: &common.Hash{'g'},
|
||||
Transactions: []hexutil.Bytes{{'h'}},
|
||||
Withdrawals: []*pb.Withdrawal{},
|
||||
BlockNumber: &hexUint,
|
||||
GasLimit: &hexUint,
|
||||
GasUsed: &hexUint,
|
||||
Timestamp: &hexUint,
|
||||
BlobGasUsed: &bgu,
|
||||
ExcessBlobGas: &ebg,
|
||||
},
|
||||
BlockValue: "0x11fffffffff",
|
||||
BlobsBundle: &pb.BlobBundleJSON{
|
||||
@@ -1670,6 +1767,7 @@ func fixturesStruct() *payloadFixtures {
|
||||
Proofs: []hexutil.Bytes{[]byte("proof1"), []byte("proof2")},
|
||||
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
|
||||
},
|
||||
ExecutionRequests: []hexutil.Bytes{depositRequestBytes, withdrawalRequestBytes, consolidationRequestBytes},
|
||||
}
|
||||
parent := bytesutil.PadTo([]byte("parentHash"), fieldparams.RootLength)
|
||||
sha3Uncles := bytesutil.PadTo([]byte("sha3Uncles"), fieldparams.RootLength)
|
||||
@@ -1762,10 +1860,9 @@ func fixturesStruct() *payloadFixtures {
|
||||
ExecutionPayloadCapella: executionPayloadFixtureCapella,
|
||||
ExecutionPayloadDeneb: executionPayloadFixtureDeneb,
|
||||
EmptyExecutionPayloadDeneb: emptyExecutionPayloadDeneb,
|
||||
ExecutionPayloadElectra: executionPayloadFixtureElectra,
|
||||
ExecutionPayloadWithValueCapella: executionPayloadWithValueFixtureCapella,
|
||||
ExecutionPayloadWithValueDeneb: executionPayloadWithValueFixtureDeneb,
|
||||
ExecutionPayloadWithValueElectra: executionPayloadWithValueFixtureElectra,
|
||||
ExecutionBundleElectra: executionBundleFixtureElectra,
|
||||
ValidPayloadStatus: validStatus,
|
||||
InvalidBlockHashStatus: inValidBlockHashStatus,
|
||||
AcceptedStatus: acceptedStatus,
|
||||
@@ -1787,10 +1884,9 @@ type payloadFixtures struct {
|
||||
ExecutionPayloadCapella *pb.ExecutionPayloadCapella
|
||||
EmptyExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
|
||||
ExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
|
||||
ExecutionPayloadElectra *pb.ExecutionPayloadElectra
|
||||
ExecutionPayloadWithValueCapella *pb.GetPayloadV2ResponseJson
|
||||
ExecutionPayloadWithValueDeneb *pb.GetPayloadV3ResponseJson
|
||||
ExecutionPayloadWithValueElectra *pb.GetPayloadV4ResponseJson
|
||||
ExecutionBundleElectra *pb.GetPayloadV4ResponseJson
|
||||
ValidPayloadStatus *pb.PayloadStatus
|
||||
InvalidBlockHashStatus *pb.PayloadStatus
|
||||
AcceptedStatus *pb.PayloadStatus
|
||||
@@ -2149,7 +2245,7 @@ func newPayloadV3Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.Execu
|
||||
return service
|
||||
}
|
||||
|
||||
func newPayloadV4Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.ExecutionPayloadElectra) *Service {
|
||||
func newPayloadV4Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.ExecutionPayloadDeneb, requests *pb.ExecutionRequests) *Service {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
defer func() {
|
||||
@@ -2158,14 +2254,28 @@ func newPayloadV4Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.Execu
|
||||
enc, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
jsonRequestString := string(enc)
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, string("engine_newPayloadV4"),
|
||||
))
|
||||
|
||||
reqArg, err := json.Marshal(payload)
|
||||
reqPayload, err := json.Marshal(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We expect the JSON string RPC request contains the right arguments.
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, string(reqArg),
|
||||
jsonRequestString, string(reqPayload),
|
||||
))
|
||||
|
||||
reqRequests, err := pb.EncodeExecutionRequests(requests)
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonRequests, err := json.Marshal(reqRequests)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, string(jsonRequests),
|
||||
))
|
||||
|
||||
resp := map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
@@ -2222,11 +2332,10 @@ func Test_ExchangeCapabilities(t *testing.T) {
|
||||
defer func() {
|
||||
require.NoError(t, r.Body.Close())
|
||||
}()
|
||||
exchangeCapabilities := &pb.ExchangeCapabilities{}
|
||||
resp := map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": exchangeCapabilities,
|
||||
"result": []string{},
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(resp)
|
||||
require.NoError(t, err)
|
||||
@@ -2255,14 +2364,11 @@ func Test_ExchangeCapabilities(t *testing.T) {
|
||||
defer func() {
|
||||
require.NoError(t, r.Body.Close())
|
||||
}()
|
||||
exchangeCapabilities := &pb.ExchangeCapabilities{
|
||||
SupportedMethods: []string{"A", "B", "C"},
|
||||
}
|
||||
|
||||
resp := map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": exchangeCapabilities,
|
||||
"result": []string{"A", "B", "C"},
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -3,7 +3,6 @@ package execution
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -131,21 +130,10 @@ func TestParseRequest(t *testing.T) {
|
||||
strToHexBytes(t, "0x66756c6c00000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
method: GetPayloadBodiesByHashV2,
|
||||
byteArgs: []hexutil.Bytes{
|
||||
strToHexBytes(t, "0x656d707479000000000000000000000000000000000000000000000000000000"),
|
||||
strToHexBytes(t, "0x66756c6c00000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
},
|
||||
{
|
||||
method: GetPayloadBodiesByRangeV1,
|
||||
hexArgs: []string{hexutil.EncodeUint64(0), hexutil.EncodeUint64(1)},
|
||||
},
|
||||
{
|
||||
method: GetPayloadBodiesByRangeV2,
|
||||
hexArgs: []string{hexutil.EncodeUint64(math.MaxUint64), hexutil.EncodeUint64(1)},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.method, func(t *testing.T) {
|
||||
@@ -191,9 +179,7 @@ func TestParseRequest(t *testing.T) {
|
||||
func TestCallCount(t *testing.T) {
|
||||
methods := []string{
|
||||
GetPayloadBodiesByHashV1,
|
||||
GetPayloadBodiesByHashV2,
|
||||
GetPayloadBodiesByRangeV1,
|
||||
GetPayloadBodiesByRangeV2,
|
||||
}
|
||||
cases := []struct {
|
||||
method string
|
||||
@@ -201,10 +187,8 @@ func TestCallCount(t *testing.T) {
|
||||
}{
|
||||
{method: GetPayloadBodiesByHashV1, count: 1},
|
||||
{method: GetPayloadBodiesByHashV1, count: 2},
|
||||
{method: GetPayloadBodiesByHashV2, count: 1},
|
||||
{method: GetPayloadBodiesByRangeV1, count: 1},
|
||||
{method: GetPayloadBodiesByRangeV1, count: 2},
|
||||
{method: GetPayloadBodiesByRangeV2, count: 1},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.method, func(t *testing.T) {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
pb "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -87,10 +86,7 @@ func (r *blindedBlockReconstructor) addToBatch(b interfaces.ReadOnlySignedBeacon
|
||||
return nil
|
||||
}
|
||||
|
||||
func payloadBodyMethodForBlock(b interface{ Version() int }) string {
|
||||
if b.Version() > version.Deneb {
|
||||
return GetPayloadBodiesByHashV2
|
||||
}
|
||||
func payloadBodyMethodForBlock(_ interface{ Version() int }) string {
|
||||
return GetPayloadBodiesByHashV1
|
||||
}
|
||||
|
||||
@@ -243,9 +239,6 @@ func (r *blindedBlockReconstructor) unblinded() ([]interfaces.SignedBeaconBlock,
|
||||
return unblinded, nil
|
||||
}
|
||||
|
||||
func rangeMethodForHashMethod(method string) string {
|
||||
if method == GetPayloadBodiesByHashV2 {
|
||||
return GetPayloadBodiesByRangeV2
|
||||
}
|
||||
func rangeMethodForHashMethod(_ string) string {
|
||||
return GetPayloadBodiesByRangeV1
|
||||
}
|
||||
|
||||
@@ -25,33 +25,6 @@ func (v versioner) Version() int {
|
||||
return v.version
|
||||
}
|
||||
|
||||
func TestPayloadBodyMethodForBlock(t *testing.T) {
|
||||
cases := []struct {
|
||||
versions []int
|
||||
want string
|
||||
}{
|
||||
{
|
||||
versions: []int{version.Phase0, version.Altair, version.Bellatrix, version.Capella, version.Deneb},
|
||||
want: GetPayloadBodiesByHashV1,
|
||||
},
|
||||
{
|
||||
versions: []int{version.Electra},
|
||||
want: GetPayloadBodiesByHashV2,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
for _, v := range c.versions {
|
||||
t.Run(version.String(v), func(t *testing.T) {
|
||||
v := versioner{version: v}
|
||||
require.Equal(t, c.want, payloadBodyMethodForBlock(v))
|
||||
})
|
||||
}
|
||||
}
|
||||
t.Run("post-electra", func(t *testing.T) {
|
||||
require.Equal(t, GetPayloadBodiesByHashV2, payloadBodyMethodForBlock(versioner{version: version.Electra + 1}))
|
||||
})
|
||||
}
|
||||
|
||||
func payloadToBody(t *testing.T, ed interfaces.ExecutionData) *pb.ExecutionPayloadBody {
|
||||
body := &pb.ExecutionPayloadBody{}
|
||||
txs, err := ed.Transactions()
|
||||
@@ -64,12 +37,6 @@ func payloadToBody(t *testing.T, ed interfaces.ExecutionData) *pb.ExecutionPaylo
|
||||
for i := range txs {
|
||||
body.Transactions = append(body.Transactions, txs[i])
|
||||
}
|
||||
eed, isElectra := ed.(interfaces.ExecutionDataElectra)
|
||||
if isElectra {
|
||||
body.DepositRequests = pb.ProtoDepositRequestsToJson(eed.DepositRequests())
|
||||
body.WithdrawalRequests = pb.ProtoWithdrawalRequestsToJson(eed.WithdrawalRequests())
|
||||
body.ConsolidationRequests = pb.ProtoConsolidationRequestsToJson(eed.ConsolidationRequests())
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
@@ -132,7 +99,7 @@ func testBlindedBlockFixtures(t *testing.T) *blindedBlockFixtures {
|
||||
afterSkipBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t)+3, 0, util.WithPayloadSetter(afterSkip))
|
||||
fx.afterSkipDeneb = blindedBlockWithHeader(t, afterSkipBlock)
|
||||
|
||||
electra := fixturesStruct().ExecutionPayloadElectra
|
||||
electra := fixturesStruct().ExecutionPayloadDeneb
|
||||
electra.BlockHash = bytesutil.PadTo([]byte("electra"), 32)
|
||||
electra.BlockNumber = 5
|
||||
electraBlock, _ := util.GenerateTestElectraBlockWithSidecar(t, [32]byte{}, electraSlot(t), 0, util.WithElectraPayload(electra))
|
||||
@@ -164,6 +131,7 @@ func TestPayloadBodiesViaUnblinder(t *testing.T) {
|
||||
|
||||
payload, err := bbr.payloadForHeader(fx.denebBlock.blinded.header, fx.denebBlock.blinded.block.Version())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, version.Deneb, fx.denebBlock.blinded.block.Version())
|
||||
unblindFull, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(fx.denebBlock.blinded.block, payload)
|
||||
require.NoError(t, err)
|
||||
testAssertReconstructedEquivalent(t, fx.denebBlock.full, unblindFull)
|
||||
@@ -352,22 +320,6 @@ func TestReconstructBlindedBlockBatchFallbackToRange(t *testing.T) {
|
||||
}
|
||||
mockWriteResult(t, w, msg, executionPayloadBodies)
|
||||
})
|
||||
// separate methods for the electra block
|
||||
srv.register(GetPayloadBodiesByHashV2, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
|
||||
executionPayloadBodies := []*pb.ExecutionPayloadBody{nil}
|
||||
mockWriteResult(t, w, msg, executionPayloadBodies)
|
||||
})
|
||||
srv.register(GetPayloadBodiesByRangeV2, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
|
||||
p := mockParseUintList(t, msg.Params)
|
||||
require.Equal(t, 2, len(p))
|
||||
start, count := p[0], p[1]
|
||||
require.Equal(t, fx.electra.blinded.header.BlockNumber(), start)
|
||||
require.Equal(t, uint64(1), count)
|
||||
executionPayloadBodies := []*pb.ExecutionPayloadBody{
|
||||
payloadToBody(t, fx.electra.blinded.header),
|
||||
}
|
||||
mockWriteResult(t, w, msg, executionPayloadBodies)
|
||||
})
|
||||
blind := []interfaces.ReadOnlySignedBeaconBlock{
|
||||
fx.denebBlock.blinded.block,
|
||||
fx.emptyDenebBlock.blinded.block,
|
||||
@@ -386,13 +338,8 @@ func TestReconstructBlindedBlockBatchDenebAndElectra(t *testing.T) {
|
||||
t.Run("deneb and electra", func(t *testing.T) {
|
||||
cli, srv := newMockEngine(t)
|
||||
fx := testBlindedBlockFixtures(t)
|
||||
// The reconstructed should make separate calls for the deneb (v1) and electra (v2) blocks.
|
||||
srv.register(GetPayloadBodiesByHashV1, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
|
||||
executionPayloadBodies := []*pb.ExecutionPayloadBody{payloadToBody(t, fx.denebBlock.blinded.header)}
|
||||
mockWriteResult(t, w, msg, executionPayloadBodies)
|
||||
})
|
||||
srv.register(GetPayloadBodiesByHashV2, func(msg *jsonrpcMessage, w http.ResponseWriter, r *http.Request) {
|
||||
executionPayloadBodies := []*pb.ExecutionPayloadBody{payloadToBody(t, fx.electra.blinded.header)}
|
||||
executionPayloadBodies := []*pb.ExecutionPayloadBody{payloadToBody(t, fx.denebBlock.blinded.header), payloadToBody(t, fx.electra.blinded.header)}
|
||||
mockWriteResult(t, w, msg, executionPayloadBodies)
|
||||
})
|
||||
blinded := []interfaces.ReadOnlySignedBeaconBlock{
|
||||
|
||||
@@ -73,7 +73,7 @@ type goodNotifier struct {
|
||||
MockStateFeed *event.Feed
|
||||
}
|
||||
|
||||
func (g *goodNotifier) StateFeed() *event.Feed {
|
||||
func (g *goodNotifier) StateFeed() event.SubscriberSender {
|
||||
if g.MockStateFeed == nil {
|
||||
g.MockStateFeed = new(event.Feed)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ type EngineClient struct {
|
||||
}
|
||||
|
||||
// NewPayload --
|
||||
func (e *EngineClient) NewPayload(_ context.Context, _ interfaces.ExecutionData, _ []common.Hash, _ *common.Hash) ([]byte, error) {
|
||||
func (e *EngineClient) NewPayload(_ context.Context, _ interfaces.ExecutionData, _ []common.Hash, _ *common.Hash, _ *pb.ExecutionRequests) ([]byte, error) {
|
||||
return e.NewPayloadResp, e.ErrNewPayload
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (e *EngineClient) ForkchoiceUpdated(
|
||||
}
|
||||
|
||||
// GetPayload --
|
||||
func (e *EngineClient) GetPayload(_ context.Context, _ [8]byte, s primitives.Slot) (*blocks.GetPayloadResponse, error) {
|
||||
func (e *EngineClient) GetPayload(_ context.Context, _ [8]byte, _ primitives.Slot) (*blocks.GetPayloadResponse, error) {
|
||||
return e.GetPayloadResponse, e.ErrGetPayload
|
||||
}
|
||||
|
||||
|
||||
@@ -398,7 +398,7 @@ func initSyncWaiter(ctx context.Context, complete chan struct{}) func() error {
|
||||
}
|
||||
|
||||
// StateFeed implements statefeed.Notifier.
|
||||
func (b *BeaconNode) StateFeed() *event.Feed {
|
||||
func (b *BeaconNode) StateFeed() event.SubscriberSender {
|
||||
return b.stateFeed
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ func (b *BeaconNode) BlockFeed() *event.Feed {
|
||||
}
|
||||
|
||||
// OperationFeed implements opfeed.Notifier.
|
||||
func (b *BeaconNode) OperationFeed() *event.Feed {
|
||||
func (b *BeaconNode) OperationFeed() event.SubscriberSender {
|
||||
return b.opFeed
|
||||
}
|
||||
|
||||
|
||||
@@ -179,8 +179,6 @@ go_test(
|
||||
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/protocol:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/host/blank:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/net/swarm/testing:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/security/noise:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_pubsub//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_pubsub//pb:go_default_library",
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
@@ -236,7 +235,7 @@ func TestService_BroadcastAttestationWithDiscoveryAttempts(t *testing.T) {
|
||||
bootNode := bootListener.Self()
|
||||
subnet := uint64(5)
|
||||
|
||||
var listeners []*discover.UDPv5
|
||||
var listeners []*listenerWrapper
|
||||
var hosts []host.Host
|
||||
// setup other nodes.
|
||||
cfg = &Config{
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestPeer_AtMaxLimit(t *testing.T) {
|
||||
}()
|
||||
|
||||
for i := 0; i < highWatermarkBuffer; i++ {
|
||||
addPeer(t, s.peers, peers.PeerConnected)
|
||||
addPeer(t, s.peers, peers.PeerConnected, false)
|
||||
}
|
||||
|
||||
// create alternate host
|
||||
@@ -159,7 +159,7 @@ func TestService_RejectInboundPeersBeyondLimit(t *testing.T) {
|
||||
inboundLimit += 1
|
||||
// Add in up to inbound peer limit.
|
||||
for i := 0; i < int(inboundLimit); i++ {
|
||||
addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
|
||||
addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false)
|
||||
}
|
||||
valid = s.InterceptAccept(&maEndpoints{raddr: multiAddress})
|
||||
if valid {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
bh "github.com/libp2p/go-libp2p/p2p/host/blank"
|
||||
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
@@ -29,8 +29,10 @@ func TestDialRelayNode_InvalidPeerString(t *testing.T) {
|
||||
|
||||
func TestDialRelayNode_OK(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
relay := bh.NewBlankHost(swarmt.GenSwarm(t))
|
||||
host := bh.NewBlankHost(swarmt.GenSwarm(t))
|
||||
relay, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
|
||||
require.NoError(t, err)
|
||||
host, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
|
||||
require.NoError(t, err)
|
||||
relayAddr := fmt.Sprintf("%s/p2p/%s", relay.Addrs()[0], relay.ID().String())
|
||||
|
||||
assert.NoError(t, dialRelayNode(ctx, host, relayAddr), "Unexpected error when dialing relay node")
|
||||
|
||||
@@ -24,6 +24,11 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
type ListenerRebooter interface {
|
||||
Listener
|
||||
RebootListener() error
|
||||
}
|
||||
|
||||
// Listener defines the discovery V5 network interface that is used
|
||||
// to communicate with other peers.
|
||||
type Listener interface {
|
||||
@@ -47,6 +52,87 @@ type quicProtocol uint16
|
||||
// quicProtocol is the "quic" key, which holds the QUIC port of the node.
|
||||
func (quicProtocol) ENRKey() string { return "quic" }
|
||||
|
||||
type listenerWrapper struct {
|
||||
mu sync.RWMutex
|
||||
listener *discover.UDPv5
|
||||
listenerCreator func() (*discover.UDPv5, error)
|
||||
}
|
||||
|
||||
func newListener(listenerCreator func() (*discover.UDPv5, error)) (*listenerWrapper, error) {
|
||||
rawListener, err := listenerCreator()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create new listener")
|
||||
}
|
||||
return &listenerWrapper{
|
||||
listener: rawListener,
|
||||
listenerCreator: listenerCreator,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) Self() *enode.Node {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.Self()
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) Close() {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
l.listener.Close()
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) Lookup(id enode.ID) []*enode.Node {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.Lookup(id)
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) Resolve(node *enode.Node) *enode.Node {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.Resolve(node)
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) RandomNodes() enode.Iterator {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.RandomNodes()
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) Ping(node *enode.Node) error {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.Ping(node)
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) RequestENR(node *enode.Node) (*enode.Node, error) {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.RequestENR(node)
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) LocalNode() *enode.LocalNode {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.listener.LocalNode()
|
||||
}
|
||||
|
||||
func (l *listenerWrapper) RebootListener() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
// Close current listener
|
||||
l.listener.Close()
|
||||
|
||||
newListener, err := l.listenerCreator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.listener = newListener
|
||||
return nil
|
||||
}
|
||||
|
||||
// RefreshENR uses an epoch to refresh the enr entry for our node
|
||||
// with the tracked committee ids for the epoch, allowing our node
|
||||
// to be dynamically discoverable by others given our tracked committee ids.
|
||||
@@ -110,55 +196,78 @@ func (s *Service) RefreshENR() {
|
||||
func (s *Service) listenForNewNodes() {
|
||||
iterator := filterNodes(s.ctx, s.dv5Listener.RandomNodes(), s.filterPeer)
|
||||
defer iterator.Close()
|
||||
connectivityTicker := time.NewTicker(1 * time.Minute)
|
||||
thresholdCount := 0
|
||||
|
||||
for {
|
||||
// Exit if service's context is canceled.
|
||||
if s.ctx.Err() != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if s.isPeerAtLimit(false /* inbound */) {
|
||||
// Pause the main loop for a period to stop looking
|
||||
// for new peers.
|
||||
log.Trace("Not looking for peers, at peer limit")
|
||||
time.Sleep(pollingPeriod)
|
||||
continue
|
||||
}
|
||||
wantedCount := s.wantedPeerDials()
|
||||
if wantedCount == 0 {
|
||||
log.Trace("Not looking for peers, at peer limit")
|
||||
time.Sleep(pollingPeriod)
|
||||
continue
|
||||
}
|
||||
// Restrict dials if limit is applied.
|
||||
if flags.MaxDialIsActive() {
|
||||
wantedCount = min(wantedCount, flags.Get().MaxConcurrentDials)
|
||||
}
|
||||
wantedNodes := enode.ReadNodes(iterator, wantedCount)
|
||||
wg := new(sync.WaitGroup)
|
||||
for i := 0; i < len(wantedNodes); i++ {
|
||||
node := wantedNodes[i]
|
||||
peerInfo, _, err := convertToAddrInfo(node)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not convert to peer info")
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
case <-connectivityTicker.C:
|
||||
// Skip the connectivity check if not enabled.
|
||||
if !features.Get().EnableDiscoveryReboot {
|
||||
continue
|
||||
}
|
||||
|
||||
if peerInfo == nil {
|
||||
if !s.isBelowOutboundPeerThreshold() {
|
||||
// Reset counter if we are beyond the threshold
|
||||
thresholdCount = 0
|
||||
continue
|
||||
}
|
||||
|
||||
// Make sure that peer is not dialed too often, for each connection attempt there's a backoff period.
|
||||
s.Peers().RandomizeBackOff(peerInfo.ID)
|
||||
wg.Add(1)
|
||||
go func(info *peer.AddrInfo) {
|
||||
if err := s.connectWithPeer(s.ctx, *info); err != nil {
|
||||
log.WithError(err).Tracef("Could not connect with peer %s", info.String())
|
||||
thresholdCount++
|
||||
// Reboot listener if connectivity drops
|
||||
if thresholdCount > 5 {
|
||||
log.WithField("outboundConnectionCount", len(s.peers.OutboundConnected())).Warn("Rebooting discovery listener, reached threshold.")
|
||||
if err := s.dv5Listener.RebootListener(); err != nil {
|
||||
log.WithError(err).Error("Could not reboot listener")
|
||||
continue
|
||||
}
|
||||
wg.Done()
|
||||
}(peerInfo)
|
||||
iterator = filterNodes(s.ctx, s.dv5Listener.RandomNodes(), s.filterPeer)
|
||||
thresholdCount = 0
|
||||
}
|
||||
default:
|
||||
if s.isPeerAtLimit(false /* inbound */) {
|
||||
// Pause the main loop for a period to stop looking
|
||||
// for new peers.
|
||||
log.Trace("Not looking for peers, at peer limit")
|
||||
time.Sleep(pollingPeriod)
|
||||
continue
|
||||
}
|
||||
wantedCount := s.wantedPeerDials()
|
||||
if wantedCount == 0 {
|
||||
log.Trace("Not looking for peers, at peer limit")
|
||||
time.Sleep(pollingPeriod)
|
||||
continue
|
||||
}
|
||||
// Restrict dials if limit is applied.
|
||||
if flags.MaxDialIsActive() {
|
||||
wantedCount = min(wantedCount, flags.Get().MaxConcurrentDials)
|
||||
}
|
||||
wantedNodes := enode.ReadNodes(iterator, wantedCount)
|
||||
wg := new(sync.WaitGroup)
|
||||
for i := 0; i < len(wantedNodes); i++ {
|
||||
node := wantedNodes[i]
|
||||
peerInfo, _, err := convertToAddrInfo(node)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not convert to peer info")
|
||||
continue
|
||||
}
|
||||
|
||||
if peerInfo == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Make sure that peer is not dialed too often, for each connection attempt there's a backoff period.
|
||||
s.Peers().RandomizeBackOff(peerInfo.ID)
|
||||
wg.Add(1)
|
||||
go func(info *peer.AddrInfo) {
|
||||
if err := s.connectWithPeer(s.ctx, *info); err != nil {
|
||||
log.WithError(err).Tracef("Could not connect with peer %s", info.String())
|
||||
}
|
||||
wg.Done()
|
||||
}(peerInfo)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,14 +408,17 @@ func (s *Service) createLocalNode(
|
||||
func (s *Service) startDiscoveryV5(
|
||||
addr net.IP,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
) (*discover.UDPv5, error) {
|
||||
listener, err := s.createListener(addr, privKey)
|
||||
) (*listenerWrapper, error) {
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(addr, privKey)
|
||||
}
|
||||
wrappedListener, err := newListener(createListener)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create listener")
|
||||
}
|
||||
record := listener.Self()
|
||||
record := wrappedListener.Self()
|
||||
log.WithField("ENR", record.String()).Info("Started discovery v5")
|
||||
return listener, nil
|
||||
return wrappedListener, nil
|
||||
}
|
||||
|
||||
// filterPeer validates each node that we retrieve from our dht. We
|
||||
@@ -398,6 +510,22 @@ func (s *Service) isPeerAtLimit(inbound bool) bool {
|
||||
return activePeers >= maxPeers || numOfConns >= maxPeers
|
||||
}
|
||||
|
||||
// isBelowOutboundPeerThreshold checks if the number of outbound peers that
|
||||
// we are connected to satisfies the minimum expected outbound peer count
|
||||
// according to our peer limit.
|
||||
func (s *Service) isBelowOutboundPeerThreshold() bool {
|
||||
maxPeers := int(s.cfg.MaxPeers)
|
||||
inBoundLimit := s.Peers().InboundLimit()
|
||||
// Impossible Condition
|
||||
if maxPeers < inBoundLimit {
|
||||
return false
|
||||
}
|
||||
outboundFloor := maxPeers - inBoundLimit
|
||||
outBoundThreshold := outboundFloor / 2
|
||||
outBoundCount := len(s.Peers().OutboundConnected())
|
||||
return outBoundCount < outBoundThreshold
|
||||
}
|
||||
|
||||
func (s *Service) wantedPeerDials() int {
|
||||
maxPeers := int(s.cfg.MaxPeers)
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestStartDiscV5_DiscoverAllPeers(t *testing.T) {
|
||||
|
||||
bootNode := bootListener.Self()
|
||||
|
||||
var listeners []*discover.UDPv5
|
||||
var listeners []*listenerWrapper
|
||||
for i := 1; i <= 5; i++ {
|
||||
port = 3000 + i
|
||||
cfg := &Config{
|
||||
@@ -231,6 +231,37 @@ func TestCreateLocalNode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebootDiscoveryListener(t *testing.T) {
|
||||
port := 1024
|
||||
ipAddr, pkey := createAddrAndPrivKey(t)
|
||||
s := &Service{
|
||||
genesisTime: time.Now(),
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
}
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
require.NoError(t, err)
|
||||
currentPubkey := listener.Self().Pubkey()
|
||||
currentID := listener.Self().ID()
|
||||
currentPort := listener.Self().UDP()
|
||||
currentAddr := listener.Self().IP()
|
||||
|
||||
assert.NoError(t, listener.RebootListener())
|
||||
|
||||
newPubkey := listener.Self().Pubkey()
|
||||
newID := listener.Self().ID()
|
||||
newPort := listener.Self().UDP()
|
||||
newAddr := listener.Self().IP()
|
||||
|
||||
assert.Equal(t, true, currentPubkey.Equal(newPubkey))
|
||||
assert.Equal(t, currentID, newID)
|
||||
assert.Equal(t, currentPort, newPort)
|
||||
assert.Equal(t, currentAddr.String(), newAddr.String())
|
||||
}
|
||||
|
||||
func TestMultiAddrsConversion_InvalidIPAddr(t *testing.T) {
|
||||
addr := net.ParseIP("invalidIP")
|
||||
_, pkey := createAddrAndPrivKey(t)
|
||||
@@ -347,19 +378,44 @@ func TestInboundPeerLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
for i := 0; i < 30; i++ {
|
||||
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
|
||||
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false)
|
||||
}
|
||||
|
||||
require.Equal(t, true, s.isPeerAtLimit(false), "not at limit for outbound peers")
|
||||
require.Equal(t, false, s.isPeerAtLimit(true), "at limit for inbound peers")
|
||||
|
||||
for i := 0; i < highWatermarkBuffer; i++ {
|
||||
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED))
|
||||
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), false)
|
||||
}
|
||||
|
||||
require.Equal(t, true, s.isPeerAtLimit(true), "not at limit for inbound peers")
|
||||
}
|
||||
|
||||
func TestOutboundPeerThreshold(t *testing.T) {
|
||||
fakePeer := testp2p.NewTestP2P(t)
|
||||
s := &Service{
|
||||
cfg: &Config{MaxPeers: 30},
|
||||
ipLimiter: leakybucket.NewCollector(ipLimit, ipBurst, 1*time.Second, false),
|
||||
peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
|
||||
PeerLimit: 30,
|
||||
ScorerParams: &scorers.Config{},
|
||||
}),
|
||||
host: fakePeer.BHost,
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), true)
|
||||
}
|
||||
|
||||
require.Equal(t, true, s.isBelowOutboundPeerThreshold(), "not at outbound peer threshold")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
_ = addPeer(t, s.peers, peerdata.PeerConnectionState(ethpb.ConnectionState_CONNECTED), true)
|
||||
}
|
||||
|
||||
require.Equal(t, false, s.isBelowOutboundPeerThreshold(), "still at outbound peer threshold")
|
||||
}
|
||||
|
||||
func TestUDPMultiAddress(t *testing.T) {
|
||||
port := 6500
|
||||
ipAddr, pkey := createAddrAndPrivKey(t)
|
||||
@@ -370,7 +426,11 @@ func TestUDPMultiAddress(t *testing.T) {
|
||||
genesisTime: genesisTime,
|
||||
genesisValidatorsRoot: genesisValidatorsRoot,
|
||||
}
|
||||
listener, err := s.createListener(ipAddr, pkey)
|
||||
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
require.NoError(t, err)
|
||||
defer listener.Close()
|
||||
s.dv5Listener = listener
|
||||
@@ -417,7 +477,7 @@ func TestCorrectUDPVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
// addPeer is a helper to add a peer with a given connection state)
|
||||
func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState) peer.ID {
|
||||
func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState, outbound bool) peer.ID {
|
||||
// Set up some peers with different states
|
||||
mhBytes := []byte{0x11, 0x04}
|
||||
idBytes := make([]byte, 4)
|
||||
@@ -426,7 +486,11 @@ func addPeer(t *testing.T, p *peers.Status, state peerdata.PeerConnectionState)
|
||||
mhBytes = append(mhBytes, idBytes...)
|
||||
id, err := peer.IDFromBytes(mhBytes)
|
||||
require.NoError(t, err)
|
||||
p.Add(new(enr.Record), id, nil, network.DirInbound)
|
||||
dir := network.DirInbound
|
||||
if outbound {
|
||||
dir = network.DirOutbound
|
||||
}
|
||||
p.Add(new(enr.Record), id, nil, dir)
|
||||
p.SetConnectionState(id, state)
|
||||
p.SetMetadata(id, wrapper.WrappedMetadataV0(ðpb.MetaDataV0{
|
||||
SeqNumber: 0,
|
||||
@@ -455,7 +519,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
}
|
||||
listener, err := s.createListener(ipAddr, pkey)
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
assert.NoError(t, err)
|
||||
s.dv5Listener = listener
|
||||
s.metaData = wrapper.WrappedMetadataV0(new(ethpb.MetaDataV0))
|
||||
@@ -484,7 +551,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
}
|
||||
listener, err := s.createListener(ipAddr, pkey)
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
assert.NoError(t, err)
|
||||
s.dv5Listener = listener
|
||||
s.metaData = wrapper.WrappedMetadataV0(new(ethpb.MetaDataV0))
|
||||
@@ -506,7 +576,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
}
|
||||
listener, err := s.createListener(ipAddr, pkey)
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Update params
|
||||
@@ -537,7 +610,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
}
|
||||
listener, err := s.createListener(ipAddr, pkey)
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Update params
|
||||
@@ -575,7 +651,10 @@ func TestRefreshENR_ForkBoundaries(t *testing.T) {
|
||||
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
|
||||
cfg: &Config{UDPPort: uint(port)},
|
||||
}
|
||||
listener, err := s.createListener(ipAddr, pkey)
|
||||
createListener := func() (*discover.UDPv5, error) {
|
||||
return s.createListener(ipAddr, pkey)
|
||||
}
|
||||
listener, err := newListener(createListener)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Update params
|
||||
|
||||
@@ -110,7 +110,7 @@ type BeaconStateElectraCreator struct{}
|
||||
type PowBlockCreator struct{}
|
||||
type HistoricalSummaryCreator struct{}
|
||||
type BlobIdentifierCreator struct{}
|
||||
type PendingBalanceDepositCreator struct{}
|
||||
type PendingDepositCreator struct{}
|
||||
type PendingPartialWithdrawalCreator struct{}
|
||||
type PendingConsolidationCreator struct{}
|
||||
type StatusCreator struct{}
|
||||
@@ -279,8 +279,8 @@ func (BeaconStateElectraCreator) Create() MarshalerProtoMessage { return ðpb.
|
||||
func (PowBlockCreator) Create() MarshalerProtoMessage { return ðpb.PowBlock{} }
|
||||
func (HistoricalSummaryCreator) Create() MarshalerProtoMessage { return ðpb.HistoricalSummary{} }
|
||||
func (BlobIdentifierCreator) Create() MarshalerProtoMessage { return ðpb.BlobIdentifier{} }
|
||||
func (PendingBalanceDepositCreator) Create() MarshalerProtoMessage {
|
||||
return ðpb.PendingBalanceDeposit{}
|
||||
func (PendingDepositCreator) Create() MarshalerProtoMessage {
|
||||
return ðpb.PendingDeposit{}
|
||||
}
|
||||
func (PendingPartialWithdrawalCreator) Create() MarshalerProtoMessage {
|
||||
return ðpb.PendingPartialWithdrawal{}
|
||||
@@ -397,7 +397,7 @@ var creators = []MarshalerProtoCreator{
|
||||
PowBlockCreator{},
|
||||
HistoricalSummaryCreator{},
|
||||
BlobIdentifierCreator{},
|
||||
PendingBalanceDepositCreator{},
|
||||
PendingDepositCreator{},
|
||||
PendingPartialWithdrawalCreator{},
|
||||
PendingConsolidationCreator{},
|
||||
StatusCreator{},
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
ma "github.com/multiformats/go-multiaddr"
|
||||
@@ -52,7 +51,7 @@ func TestStartDiscv5_DifferentForkDigests(t *testing.T) {
|
||||
StateNotifier: &mock.MockStateNotifier{},
|
||||
}
|
||||
|
||||
var listeners []*discover.UDPv5
|
||||
var listeners []*listenerWrapper
|
||||
for i := 1; i <= 5; i++ {
|
||||
port := 3000 + i
|
||||
cfg.UDPPort = uint(port)
|
||||
@@ -139,7 +138,7 @@ func TestStartDiscv5_SameForkDigests_DifferentNextForkData(t *testing.T) {
|
||||
UDPPort: uint(port),
|
||||
}
|
||||
|
||||
var listeners []*discover.UDPv5
|
||||
var listeners []*listenerWrapper
|
||||
for i := 1; i <= 5; i++ {
|
||||
port := 3000 + i
|
||||
cfg.UDPPort = uint(port)
|
||||
|
||||
@@ -182,6 +182,7 @@ func pubsubGossipParam() pubsub.GossipSubParams {
|
||||
gParams := pubsub.DefaultGossipSubParams()
|
||||
gParams.Dlo = gossipSubDlo
|
||||
gParams.D = gossipSubD
|
||||
gParams.Dhi = gossipSubDhi
|
||||
gParams.HeartbeatInterval = gossipSubHeartbeatInterval
|
||||
gParams.HistoryLength = gossipSubMcacheLen
|
||||
gParams.HistoryGossip = gossipSubMcacheGossip
|
||||
|
||||
@@ -71,7 +71,7 @@ type Service struct {
|
||||
subnetsLock map[uint64]*sync.RWMutex
|
||||
subnetsLockLock sync.Mutex // Lock access to subnetsLock
|
||||
initializationLock sync.Mutex
|
||||
dv5Listener Listener
|
||||
dv5Listener ListenerRebooter
|
||||
startupErr error
|
||||
ctx context.Context
|
||||
host host.Host
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/libp2p/go-libp2p"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
@@ -69,6 +68,8 @@ func (mockListener) RandomNodes() enode.Iterator {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockListener) RebootListener() error { panic("implement me") }
|
||||
|
||||
func createHost(t *testing.T, port int) (host.Host, *ecdsa.PrivateKey, net.IP) {
|
||||
_, pkey := createAddrAndPrivKey(t)
|
||||
ipAddr := net.ParseIP("127.0.0.1")
|
||||
@@ -210,7 +211,7 @@ func TestListenForNewNodes(t *testing.T) {
|
||||
|
||||
bootNode := bootListener.Self()
|
||||
|
||||
var listeners []*discover.UDPv5
|
||||
var listeners []*listenerWrapper
|
||||
var hosts []host.Host
|
||||
// setup other nodes.
|
||||
cs := startup.NewClockSynchronizer()
|
||||
|
||||
@@ -22,9 +22,11 @@ go_library(
|
||||
"//beacon-chain/p2p/peers/scorers:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/metadata:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//p2p/enr:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/connmgr:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/control:go_default_library",
|
||||
@@ -34,8 +36,7 @@ go_library(
|
||||
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/peerstore:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//core/protocol:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/host/blank:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/net/swarm/testing:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p//p2p/transport/tcp:go_default_library",
|
||||
"@com_github_libp2p_go_libp2p_pubsub//:go_default_library",
|
||||
"@com_github_multiformats_go_multiaddr//:go_default_library",
|
||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/libp2p/go-libp2p"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
core "github.com/libp2p/go-libp2p/core"
|
||||
"github.com/libp2p/go-libp2p/core/control"
|
||||
@@ -18,8 +19,7 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/libp2p/go-libp2p/core/protocol"
|
||||
bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
|
||||
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
ssz "github.com/prysmaticlabs/fastssz"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/encoder"
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/peers/scorers"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/metadata"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -52,7 +53,8 @@ type TestP2P struct {
|
||||
// NewTestP2P initializes a new p2p test service.
|
||||
func NewTestP2P(t *testing.T) *TestP2P {
|
||||
ctx := context.Background()
|
||||
h := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableQUIC))
|
||||
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}), libp2p.Transport(tcp.NewTCPTransport), libp2p.DefaultListenAddrs)
|
||||
require.NoError(t, err)
|
||||
ps, err := pubsub.NewFloodSub(ctx, h,
|
||||
pubsub.WithMessageSigning(false),
|
||||
pubsub.WithStrictSignatureVerification(false),
|
||||
@@ -86,13 +88,17 @@ func (p *TestP2P) Connect(b *TestP2P) {
|
||||
}
|
||||
|
||||
func connect(a, b host.Host) error {
|
||||
pinfo := b.Peerstore().PeerInfo(b.ID())
|
||||
pinfo := peer.AddrInfo{
|
||||
ID: b.ID(),
|
||||
Addrs: b.Addrs(),
|
||||
}
|
||||
return a.Connect(context.Background(), pinfo)
|
||||
}
|
||||
|
||||
// ReceiveRPC simulates an incoming RPC.
|
||||
func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
|
||||
h := bhost.NewBlankHost(swarmt.GenSwarm(p.t))
|
||||
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
|
||||
require.NoError(p.t, err)
|
||||
if err := connect(h, p.BHost); err != nil {
|
||||
p.t.Fatalf("Failed to connect two peers for RPC: %v", err)
|
||||
}
|
||||
@@ -122,7 +128,8 @@ func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
|
||||
|
||||
// ReceivePubSub simulates an incoming message over pubsub on a given topic.
|
||||
func (p *TestP2P) ReceivePubSub(topic string, msg proto.Message) {
|
||||
h := bhost.NewBlankHost(swarmt.GenSwarm(p.t))
|
||||
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
|
||||
require.NoError(p.t, err)
|
||||
ps, err := pubsub.NewFloodSub(context.Background(), h,
|
||||
pubsub.WithMessageSigning(false),
|
||||
pubsub.WithStrictSignatureVerification(false),
|
||||
|
||||
@@ -89,5 +89,6 @@ go_test(
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_x_exp//maps:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -585,6 +585,15 @@ func (s *Service) beaconEndpoints(
|
||||
handler: server.GetBlockAttestations,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v2/beacon/blocks/{block_id}/attestations",
|
||||
name: namespace + ".GetBlockAttestationsV2",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetBlockAttestations,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/beacon/blinded_blocks/{block_id}",
|
||||
name: namespace + ".GetBlindedBlock",
|
||||
@@ -679,14 +688,33 @@ func (s *Service) beaconEndpoints(
|
||||
handler: server.GetAttesterSlashings,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v2/beacon/pool/attester_slashings",
|
||||
name: namespace + ".GetAttesterSlashingsV2",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetAttesterSlashingsV2,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/beacon/pool/attester_slashings",
|
||||
name: namespace + ".SubmitAttesterSlashing",
|
||||
name: namespace + ".SubmitAttesterSlashings",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.SubmitAttesterSlashing,
|
||||
handler: server.SubmitAttesterSlashings,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/eth/v2/beacon/pool/attester_slashings",
|
||||
name: namespace + ".SubmitAttesterSlashingsV2",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.SubmitAttesterSlashingsV2,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
{
|
||||
@@ -773,6 +801,15 @@ func (s *Service) beaconEndpoints(
|
||||
handler: server.GetValidatorBalances,
|
||||
methods: []string{http.MethodGet, http.MethodPost},
|
||||
},
|
||||
{
|
||||
template: "/eth/v1/beacon/deposit_snapshot",
|
||||
name: namespace + ".GetDepositSnapshot",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.GetDepositSnapshot,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,6 +979,8 @@ func (s *Service) prysmBeaconEndpoints(
|
||||
ChainInfoFetcher: s.cfg.ChainInfoFetcher,
|
||||
FinalizationFetcher: s.cfg.FinalizationFetcher,
|
||||
CoreService: coreService,
|
||||
Broadcaster: s.cfg.Broadcaster,
|
||||
BlobReceiver: s.cfg.BlobReceiver,
|
||||
}
|
||||
|
||||
const namespace = "prysm.beacon"
|
||||
@@ -992,6 +1031,16 @@ func (s *Service) prysmBeaconEndpoints(
|
||||
handler: server.GetChainHead,
|
||||
methods: []string{http.MethodGet},
|
||||
},
|
||||
{
|
||||
template: "/prysm/v1/beacon/blobs",
|
||||
name: namespace + ".PublishBlobs",
|
||||
middleware: []middleware.Middleware{
|
||||
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
|
||||
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
|
||||
},
|
||||
handler: server.PublishBlobs,
|
||||
methods: []string{http.MethodPost},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ package rpc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func Test_endpoints(t *testing.T) {
|
||||
@@ -31,15 +33,16 @@ func Test_endpoints(t *testing.T) {
|
||||
"/eth/v2/beacon/blinded_blocks": {http.MethodPost},
|
||||
"/eth/v1/beacon/blocks": {http.MethodPost},
|
||||
"/eth/v2/beacon/blocks": {http.MethodPost},
|
||||
"/eth/v1/beacon/blocks/{block_id}": {http.MethodGet},
|
||||
"/eth/v2/beacon/blocks/{block_id}": {http.MethodGet},
|
||||
"/eth/v1/beacon/blocks/{block_id}/root": {http.MethodGet},
|
||||
"/eth/v1/beacon/blocks/{block_id}/attestations": {http.MethodGet},
|
||||
"/eth/v2/beacon/blocks/{block_id}/attestations": {http.MethodGet},
|
||||
"/eth/v1/beacon/blob_sidecars/{block_id}": {http.MethodGet},
|
||||
"/eth/v1/beacon/deposit_snapshot": {http.MethodGet},
|
||||
"/eth/v1/beacon/blinded_blocks/{block_id}": {http.MethodGet},
|
||||
"/eth/v1/beacon/pool/attestations": {http.MethodGet, http.MethodPost},
|
||||
"/eth/v1/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost},
|
||||
"/eth/v2/beacon/pool/attester_slashings": {http.MethodGet, http.MethodPost},
|
||||
"/eth/v1/beacon/pool/proposer_slashings": {http.MethodGet, http.MethodPost},
|
||||
"/eth/v1/beacon/pool/sync_committees": {http.MethodPost},
|
||||
"/eth/v1/beacon/pool/voluntary_exits": {http.MethodGet, http.MethodPost},
|
||||
@@ -69,7 +72,6 @@ func Test_endpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
debugRoutes := map[string][]string{
|
||||
"/eth/v1/debug/beacon/states/{state_id}": {http.MethodGet},
|
||||
"/eth/v2/debug/beacon/states/{state_id}": {http.MethodGet},
|
||||
"/eth/v2/debug/beacon/heads": {http.MethodGet},
|
||||
"/eth/v1/debug/fork_choice": {http.MethodGet},
|
||||
@@ -115,6 +117,7 @@ func Test_endpoints(t *testing.T) {
|
||||
"/eth/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
|
||||
"/prysm/v1/beacon/states/{state_id}/validator_count": {http.MethodGet},
|
||||
"/prysm/v1/beacon/chain_head": {http.MethodGet},
|
||||
"/prysm/v1/beacon/blobs": {http.MethodPost},
|
||||
}
|
||||
|
||||
prysmNodeRoutes := map[string][]string{
|
||||
@@ -133,22 +136,18 @@ func Test_endpoints(t *testing.T) {
|
||||
|
||||
s := &Service{cfg: &Config{}}
|
||||
|
||||
routesMap := combineMaps(beaconRoutes, builderRoutes, configRoutes, debugRoutes, eventsRoutes, nodeRoutes, validatorRoutes, rewardsRoutes, lightClientRoutes, blobRoutes, prysmValidatorRoutes, prysmNodeRoutes, prysmBeaconRoutes)
|
||||
actual := s.endpoints(true, nil, nil, nil, nil, nil, nil)
|
||||
for _, e := range actual {
|
||||
methods, ok := routesMap[e.template]
|
||||
assert.Equal(t, true, ok, "endpoint "+e.template+" not found")
|
||||
if ok {
|
||||
for _, em := range e.methods {
|
||||
methodFound := false
|
||||
for _, m := range methods {
|
||||
if m == em {
|
||||
methodFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, true, methodFound, "method "+em+" for endpoint "+e.template+" not found")
|
||||
}
|
||||
endpoints := s.endpoints(true, nil, nil, nil, nil, nil, nil)
|
||||
actualRoutes := make(map[string][]string, len(endpoints))
|
||||
for _, e := range endpoints {
|
||||
if _, ok := actualRoutes[e.template]; ok {
|
||||
actualRoutes[e.template] = append(actualRoutes[e.template], e.methods...)
|
||||
} else {
|
||||
actualRoutes[e.template] = e.methods
|
||||
}
|
||||
}
|
||||
expectedRoutes := combineMaps(beaconRoutes, builderRoutes, configRoutes, debugRoutes, eventsRoutes, nodeRoutes, validatorRoutes, rewardsRoutes, lightClientRoutes, blobRoutes, prysmValidatorRoutes, prysmNodeRoutes, prysmBeaconRoutes)
|
||||
|
||||
assert.Equal(t, true, maps.EqualFunc(expectedRoutes, actualRoutes, func(actualMethods []string, expectedMethods []string) bool {
|
||||
return slices.Equal(expectedMethods, actualMethods)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -200,16 +200,10 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestations")
|
||||
defer span.End()
|
||||
|
||||
blockId := r.PathValue("block_id")
|
||||
if blockId == "" {
|
||||
httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest)
|
||||
blk, isOptimistic, root := s.blockData(ctx, w, r)
|
||||
if blk == nil {
|
||||
return
|
||||
}
|
||||
blk, err := s.Blocker.Block(ctx, []byte(blockId))
|
||||
if !shared.WriteBlockFetchError(w, blk, err) {
|
||||
return
|
||||
}
|
||||
|
||||
consensusAtts := blk.Block().Body().Attestations()
|
||||
atts := make([]*structs.Attestation, len(consensusAtts))
|
||||
for i, att := range consensusAtts {
|
||||
@@ -221,17 +215,6 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
root, err := blk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := &structs.GetBlockAttestationsResponse{
|
||||
Data: atts,
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
@@ -240,6 +223,79 @@ func (s *Server) GetBlockAttestations(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// GetBlockAttestationsV2 retrieves attestation included in requested block.
|
||||
func (s *Server) GetBlockAttestationsV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlockAttestationsV2")
|
||||
defer span.End()
|
||||
|
||||
blk, isOptimistic, root := s.blockData(ctx, w, r)
|
||||
if blk == nil {
|
||||
return
|
||||
}
|
||||
consensusAtts := blk.Block().Body().Attestations()
|
||||
|
||||
v := blk.Block().Version()
|
||||
var attStructs []interface{}
|
||||
if v >= version.Electra {
|
||||
for _, att := range consensusAtts {
|
||||
a, ok := att.(*eth.AttestationElectra)
|
||||
if !ok {
|
||||
httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestations electra of type %T", att), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
attStruct := structs.AttElectraFromConsensus(a)
|
||||
attStructs = append(attStructs, attStruct)
|
||||
}
|
||||
} else {
|
||||
for _, att := range consensusAtts {
|
||||
a, ok := att.(*eth.Attestation)
|
||||
if !ok {
|
||||
httputil.HandleError(w, fmt.Sprintf("unable to convert consensus attestation of type %T", att), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
attStruct := structs.AttFromConsensus(a)
|
||||
attStructs = append(attStructs, attStruct)
|
||||
}
|
||||
}
|
||||
|
||||
attBytes, err := json.Marshal(attStructs)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, fmt.Sprintf("failed to marshal attestations: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
resp := &structs.GetBlockAttestationsV2Response{
|
||||
Version: version.String(v),
|
||||
ExecutionOptimistic: isOptimistic,
|
||||
Finalized: s.FinalizationFetcher.IsFinalized(ctx, root),
|
||||
Data: attBytes,
|
||||
}
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
func (s *Server) blockData(ctx context.Context, w http.ResponseWriter, r *http.Request) (interfaces.ReadOnlySignedBeaconBlock, bool, [32]byte) {
|
||||
blockId := r.PathValue("block_id")
|
||||
if blockId == "" {
|
||||
httputil.HandleError(w, "block_id is required in URL params", http.StatusBadRequest)
|
||||
return nil, false, [32]byte{}
|
||||
}
|
||||
blk, err := s.Blocker.Block(ctx, []byte(blockId))
|
||||
if !shared.WriteBlockFetchError(w, blk, err) {
|
||||
return nil, false, [32]byte{}
|
||||
}
|
||||
|
||||
root, err := blk.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get block root: "+err.Error(), http.StatusInternalServerError)
|
||||
return nil, false, [32]byte{}
|
||||
}
|
||||
isOptimistic, err := s.OptimisticModeFetcher.IsOptimisticForRoot(ctx, root)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not check if block is optimistic: "+err.Error(), http.StatusInternalServerError)
|
||||
return nil, false, [32]byte{}
|
||||
}
|
||||
return blk, isOptimistic, root
|
||||
}
|
||||
|
||||
// PublishBlindedBlock instructs the beacon node to use the components of the `SignedBlindedBeaconBlock` to construct
|
||||
// and publish a SignedBeaconBlock by swapping out the transactions_root for the corresponding full list of `transactions`.
|
||||
// The beacon node should broadcast a newly constructed SignedBeaconBlock to the beacon network, to be included in the
|
||||
@@ -1512,10 +1568,6 @@ func (s *Server) GetDepositSnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetDepositSnapshot")
|
||||
defer span.End()
|
||||
|
||||
if s.BeaconDB == nil {
|
||||
httputil.HandleError(w, "Could not retrieve beaconDB", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
eth1data, err := s.BeaconDB.ExecutionChainData(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not retrieve execution chain data: "+err.Error(), http.StatusInternalServerError)
|
||||
@@ -1527,7 +1579,7 @@ func (s *Server) GetDepositSnapshot(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
snapshot := eth1data.DepositSnapshot
|
||||
if snapshot == nil || len(snapshot.Finalized) == 0 {
|
||||
httputil.HandleError(w, "No Finalized Snapshot Available", http.StatusNotFound)
|
||||
httputil.HandleError(w, "No finalized snapshot available", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if len(snapshot.Finalized) > depositsnapshot.DepositContractDepth {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
@@ -467,25 +468,71 @@ func (s *Server) GetAttesterSlashings(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
sourceSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, headState, true /* return unlimited slashings */)
|
||||
ss := make([]*eth.AttesterSlashing, 0, len(sourceSlashings))
|
||||
for _, slashing := range sourceSlashings {
|
||||
s, ok := slashing.(*eth.AttesterSlashing)
|
||||
if ok {
|
||||
ss = append(ss, s)
|
||||
} else {
|
||||
httputil.HandleError(w, fmt.Sprintf("unable to convert slashing of type %T", slashing), http.StatusInternalServerError)
|
||||
slashings := make([]*structs.AttesterSlashing, len(sourceSlashings))
|
||||
for i, slashing := range sourceSlashings {
|
||||
as, ok := slashing.(*eth.AttesterSlashing)
|
||||
if !ok {
|
||||
httputil.HandleError(w, fmt.Sprintf("Unable to convert slashing of type %T", slashing), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
slashings[i] = structs.AttesterSlashingFromConsensus(as)
|
||||
}
|
||||
slashings := structs.AttesterSlashingsFromConsensus(ss)
|
||||
|
||||
httputil.WriteJson(w, &structs.GetAttesterSlashingsResponse{Data: slashings})
|
||||
attBytes, err := json.Marshal(slashings)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, fmt.Sprintf("Failed to marshal slashings: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &structs.GetAttesterSlashingsResponse{Data: attBytes})
|
||||
}
|
||||
|
||||
// SubmitAttesterSlashing submits an attester slashing object to node's pool and
|
||||
// GetAttesterSlashingsV2 retrieves attester slashings known by the node but
|
||||
// not necessarily incorporated into any block, supporting both AttesterSlashing and AttesterSlashingElectra.
|
||||
func (s *Server) GetAttesterSlashingsV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.GetAttesterSlashingsV2")
|
||||
defer span.End()
|
||||
|
||||
headState, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var attStructs []interface{}
|
||||
sourceSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, headState, true /* return unlimited slashings */)
|
||||
for _, slashing := range sourceSlashings {
|
||||
if slashing.Version() >= version.Electra {
|
||||
a, ok := slashing.(*eth.AttesterSlashingElectra)
|
||||
if !ok {
|
||||
httputil.HandleError(w, fmt.Sprintf("Unable to convert electra slashing of type %T to an Electra slashing", slashing), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
attStruct := structs.AttesterSlashingElectraFromConsensus(a)
|
||||
attStructs = append(attStructs, attStruct)
|
||||
} else {
|
||||
a, ok := slashing.(*eth.AttesterSlashing)
|
||||
if !ok {
|
||||
httputil.HandleError(w, fmt.Sprintf("Unable to convert slashing of type %T to a Phase0 slashing", slashing), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
attStruct := structs.AttesterSlashingFromConsensus(a)
|
||||
attStructs = append(attStructs, attStruct)
|
||||
}
|
||||
}
|
||||
attBytes, err := json.Marshal(attStructs)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, fmt.Sprintf("Failed to marshal slashing: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
resp := &structs.GetAttesterSlashingsResponse{
|
||||
Version: version.String(sourceSlashings[0].Version()),
|
||||
Data: attBytes,
|
||||
}
|
||||
httputil.WriteJson(w, resp)
|
||||
}
|
||||
|
||||
// SubmitAttesterSlashings submits an attester slashing object to node's pool and
|
||||
// if passes validation node MUST broadcast it to network.
|
||||
func (s *Server) SubmitAttesterSlashing(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttesterSlashing")
|
||||
func (s *Server) SubmitAttesterSlashings(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttesterSlashings")
|
||||
defer span.End()
|
||||
|
||||
var req structs.AttesterSlashing
|
||||
@@ -504,16 +551,80 @@ func (s *Server) SubmitAttesterSlashing(w http.ResponseWriter, r *http.Request)
|
||||
httputil.HandleError(w, "Could not convert request slashing to consensus slashing: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.submitAttesterSlashing(w, ctx, slashing)
|
||||
}
|
||||
|
||||
// SubmitAttesterSlashingsV2 submits an attester slashing object to node's pool and
|
||||
// if passes validation node MUST broadcast it to network.
|
||||
func (s *Server) SubmitAttesterSlashingsV2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.SubmitAttesterSlashingsV2")
|
||||
defer span.End()
|
||||
|
||||
versionHeader := r.Header.Get(api.VersionHeader)
|
||||
if versionHeader == "" {
|
||||
httputil.HandleError(w, api.VersionHeader+" header is required", http.StatusBadRequest)
|
||||
}
|
||||
v, err := version.FromString(versionHeader)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Invalid version: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if v >= version.Electra {
|
||||
var req structs.AttesterSlashingElectra
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
slashing, err := req.ToConsensus()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not convert request slashing to consensus slashing: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.submitAttesterSlashing(w, ctx, slashing)
|
||||
} else {
|
||||
var req structs.AttesterSlashing
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
httputil.HandleError(w, "No data submitted", http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
httputil.HandleError(w, "Could not decode request body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
slashing, err := req.ToConsensus()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not convert request slashing to consensus slashing: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
s.submitAttesterSlashing(w, ctx, slashing)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) submitAttesterSlashing(
|
||||
w http.ResponseWriter,
|
||||
ctx context.Context,
|
||||
slashing eth.AttSlashing,
|
||||
) {
|
||||
headState, err := s.ChainInfoFetcher.HeadState(ctx)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get head state: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
headState, err = transition.ProcessSlotsIfPossible(ctx, headState, slashing.Attestation_1.Data.Slot)
|
||||
headState, err = transition.ProcessSlotsIfPossible(ctx, headState, slashing.FirstAttestation().GetData().Slot)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not process slots: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = blocks.VerifyAttesterSlashing(ctx, headState, slashing)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Invalid attester slashing: "+err.Error(), http.StatusBadRequest)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/go-bitfield"
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
blockchainmock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
ethpbv1alpha1 "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
@@ -983,9 +985,7 @@ func TestSubmitSignedBLSToExecutionChanges_Failures(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetAttesterSlashings(t *testing.T) {
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
slashing1 := ðpbv1alpha1.AttesterSlashing{
|
||||
slashing1PreElectra := ðpbv1alpha1.AttesterSlashing{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{1, 10},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
@@ -1021,7 +1021,7 @@ func TestGetAttesterSlashings(t *testing.T) {
|
||||
Signature: bytesutil.PadTo([]byte("signature2"), 96),
|
||||
},
|
||||
}
|
||||
slashing2 := ðpbv1alpha1.AttesterSlashing{
|
||||
slashing2PreElectra := ðpbv1alpha1.AttesterSlashing{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{3, 30},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
@@ -1057,23 +1057,168 @@ func TestGetAttesterSlashings(t *testing.T) {
|
||||
Signature: bytesutil.PadTo([]byte("signature4"), 96),
|
||||
},
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1, slashing2}},
|
||||
slashing1PostElectra := ðpbv1alpha1.AttesterSlashingElectra{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{1, 10},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: 1,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot1"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature1"), 96),
|
||||
},
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{2, 20},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: 2,
|
||||
CommitteeIndex: 2,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 2,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 20,
|
||||
Root: bytesutil.PadTo([]byte("targetroot2"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature2"), 96),
|
||||
},
|
||||
}
|
||||
slashing2PostElectra := ðpbv1alpha1.AttesterSlashingElectra{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{3, 30},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 3,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot3"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 3,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot3"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 30,
|
||||
Root: bytesutil.PadTo([]byte("targetroot3"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature3"), 96),
|
||||
},
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{4, 40},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: 4,
|
||||
CommitteeIndex: 4,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot4"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 4,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot4"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 40,
|
||||
Root: bytesutil.PadTo([]byte("targetroot4"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("signature4"), 96),
|
||||
},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/beacon/pool/attester_slashings", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
t.Run("V1", func(t *testing.T) {
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
|
||||
s.GetAttesterSlashings(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetAttesterSlashingsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Equal(t, 2, len(resp.Data))
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PreElectra, slashing2PreElectra}},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/pool/attester_slashings", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetAttesterSlashings(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetAttesterSlashingsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
|
||||
var slashings []*structs.AttesterSlashing
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &slashings))
|
||||
|
||||
ss, err := structs.AttesterSlashingsToConsensus(slashings)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.DeepEqual(t, slashing1PreElectra, ss[0])
|
||||
require.DeepEqual(t, slashing2PreElectra, ss[1])
|
||||
})
|
||||
t.Run("V2-post-electra", func(t *testing.T) {
|
||||
bs, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PostElectra, slashing2PostElectra}},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/beacon/pool/attester_slashings", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetAttesterSlashingsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetAttesterSlashingsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
assert.Equal(t, "electra", resp.Version)
|
||||
|
||||
// Unmarshal resp.Data into a slice of slashings
|
||||
var slashings []*structs.AttesterSlashingElectra
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &slashings))
|
||||
|
||||
ss, err := structs.AttesterSlashingsElectraToConsensus(slashings)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.DeepEqual(t, slashing1PostElectra, ss[0])
|
||||
require.DeepEqual(t, slashing2PostElectra, ss[1])
|
||||
})
|
||||
t.Run("V2-pre-electra", func(t *testing.T) {
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{PendingAttSlashings: []ethpbv1alpha1.AttSlashing{slashing1PreElectra, slashing2PreElectra}},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/beacon/pool/attester_slashings", nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetAttesterSlashingsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetAttesterSlashingsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.NotNil(t, resp)
|
||||
require.NotNil(t, resp.Data)
|
||||
|
||||
var slashings []*structs.AttesterSlashing
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &slashings))
|
||||
|
||||
ss, err := structs.AttesterSlashingsToConsensus(slashings)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.DeepEqual(t, slashing1PreElectra, ss[0])
|
||||
require.DeepEqual(t, slashing2PreElectra, ss[1])
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetProposerSlashings(t *testing.T) {
|
||||
@@ -1142,383 +1287,350 @@ func TestGetProposerSlashings(t *testing.T) {
|
||||
assert.Equal(t, 2, len(resp.Data))
|
||||
}
|
||||
|
||||
func TestSubmitAttesterSlashing_Ok(t *testing.T) {
|
||||
func TestSubmitAttesterSlashings(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
_, keys, err := util.DeterministicDepositsAndKeys(1)
|
||||
require.NoError(t, err)
|
||||
validator := ðpbv1alpha1.Validator{
|
||||
PublicKey: keys[0].PublicKey().Marshal(),
|
||||
attestationData1 := ðpbv1alpha1.AttestationData{
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot1"), 32),
|
||||
},
|
||||
}
|
||||
bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error {
|
||||
state.Validators = []*ethpbv1alpha1.Validator{validator}
|
||||
return nil
|
||||
attestationData2 := ðpbv1alpha1.AttestationData{
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot2"), 32),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("V1", func(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
attestationData1.Slot = 1
|
||||
attestationData2.Slot = 1
|
||||
slashing := ðpbv1alpha1.AttesterSlashing{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData1,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData2,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
|
||||
_, keys, err := util.DeterministicDepositsAndKeys(1)
|
||||
require.NoError(t, err)
|
||||
validator := ðpbv1alpha1.Validator{
|
||||
PublicKey: keys[0].PublicKey().Marshal(),
|
||||
}
|
||||
|
||||
bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error {
|
||||
state.Validators = []*ethpbv1alpha1.Validator{validator}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, att := range []*ethpbv1alpha1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(bs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
att.Signature = sig.Marshal()
|
||||
}
|
||||
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitAttesterSlashings(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing)
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
t.Run("accross-fork", func(t *testing.T) {
|
||||
attestationData1.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
attestationData2.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
slashing := ðpbv1alpha1.AttesterSlashing{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData1,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData2,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
config := params.BeaconConfig()
|
||||
config.AltairForkEpoch = 1
|
||||
params.OverrideBeaconConfig(config)
|
||||
|
||||
bs, keys := util.DeterministicGenesisState(t, 1)
|
||||
newBs := bs.Copy()
|
||||
newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, att := range []*ethpbv1alpha1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(newBs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
att.Signature = sig.Marshal()
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitAttesterSlashings(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing)
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
t.Run("invalid-slashing", func(t *testing.T) {
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
_, err = body.WriteString(invalidAttesterSlashing)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitAttesterSlashings(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.StringContains(t, "Invalid attester slashing", e.Message)
|
||||
})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
slashing := ðpbv1alpha1.AttesterSlashing{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: 1,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
|
||||
t.Run("V2", func(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
attestationData1.Slot = 1
|
||||
attestationData2.Slot = 1
|
||||
electraSlashing := ðpbv1alpha1.AttesterSlashingElectra{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData1,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot1"), 32),
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData2,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: 1,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
|
||||
}
|
||||
|
||||
_, keys, err := util.DeterministicDepositsAndKeys(1)
|
||||
require.NoError(t, err)
|
||||
validator := ðpbv1alpha1.Validator{
|
||||
PublicKey: keys[0].PublicKey().Marshal(),
|
||||
}
|
||||
|
||||
ebs, err := util.NewBeaconStateElectra(func(state *ethpbv1alpha1.BeaconStateElectra) error {
|
||||
state.Validators = []*ethpbv1alpha1.Validator{validator}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, att := range []*ethpbv1alpha1.IndexedAttestationElectra{electraSlashing.Attestation_1, electraSlashing.Attestation_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(ebs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
att.Signature = sig.Marshal()
|
||||
}
|
||||
|
||||
chainmock := &blockchainmock.ChainService{State: ebs}
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
toSubmit := structs.AttesterSlashingsElectraFromConsensus([]*ethpbv1alpha1.AttesterSlashingElectra{electraSlashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_electras", &body)
|
||||
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitAttesterSlashingsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, ebs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
assert.DeepEqual(t, electraSlashing, pendingSlashings[0])
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashingElectra)
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
t.Run("accross-fork", func(t *testing.T) {
|
||||
attestationData1.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
attestationData2.Slot = params.BeaconConfig().SlotsPerEpoch
|
||||
slashing := ðpbv1alpha1.AttesterSlashingElectra{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData1,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot2"), 32),
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestationElectra{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: attestationData2,
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for _, att := range []*ethpbv1alpha1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(bs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
att.Signature = sig.Marshal()
|
||||
}
|
||||
params.SetupTestConfigCleanup(t)
|
||||
config := params.BeaconConfig()
|
||||
config.AltairForkEpoch = 1
|
||||
params.OverrideBeaconConfig(config)
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
bs, keys := util.DeterministicGenesisState(t, 1)
|
||||
newBs := bs.Copy()
|
||||
newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
|
||||
toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
for _, att := range []*ethpbv1alpha1.IndexedAttestationElectra{slashing.Attestation_1, slashing.Attestation_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(newBs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
att.Signature = sig.Marshal()
|
||||
}
|
||||
|
||||
s.SubmitAttesterSlashing(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing)
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
func TestSubmitAttesterSlashing_AcrossFork(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
toSubmit := structs.AttesterSlashingsElectraFromConsensus([]*ethpbv1alpha1.AttesterSlashingElectra{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
config := params.BeaconConfig()
|
||||
config.AltairForkEpoch = 1
|
||||
params.OverrideBeaconConfig(config)
|
||||
|
||||
bs, keys := util.DeterministicGenesisState(t, 1)
|
||||
|
||||
slashing := ðpbv1alpha1.AttesterSlashing{
|
||||
Attestation_1: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: params.BeaconConfig().SlotsPerEpoch,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot1"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot1"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot1"), 32),
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Attestation_2: ðpbv1alpha1.IndexedAttestation{
|
||||
AttestingIndices: []uint64{0},
|
||||
Data: ðpbv1alpha1.AttestationData{
|
||||
Slot: params.BeaconConfig().SlotsPerEpoch,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("blockroot2"), 32),
|
||||
Source: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 1,
|
||||
Root: bytesutil.PadTo([]byte("sourceroot2"), 32),
|
||||
},
|
||||
Target: ðpbv1alpha1.Checkpoint{
|
||||
Epoch: 10,
|
||||
Root: bytesutil.PadTo([]byte("targetroot2"), 32),
|
||||
},
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
|
||||
newBs := bs.Copy()
|
||||
newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, att := range []*ethpbv1alpha1.IndexedAttestation{slashing.Attestation_1, slashing.Attestation_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(newBs, att.Data.Target.Epoch, att.Data, params.BeaconConfig().DomainBeaconAttester, keys[0])
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
att.Signature = sig.Marshal()
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
toSubmit := structs.AttesterSlashingsFromConsensus([]*ethpbv1alpha1.AttesterSlashing{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitAttesterSlashing(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashing)
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
|
||||
func TestSubmitAttesterSlashing_InvalidSlashing(t *testing.T) {
|
||||
bs, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
_, err = body.WriteString(invalidAttesterSlashing)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitAttesterSlashing(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.StringContains(t, "Invalid attester slashing", e.Message)
|
||||
}
|
||||
|
||||
func TestSubmitProposerSlashing_Ok(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
_, keys, err := util.DeterministicDepositsAndKeys(1)
|
||||
require.NoError(t, err)
|
||||
validator := ðpbv1alpha1.Validator{
|
||||
PublicKey: keys[0].PublicKey().Marshal(),
|
||||
WithdrawableEpoch: primitives.Epoch(1),
|
||||
}
|
||||
bs, err := util.NewBeaconState(func(state *ethpbv1alpha1.BeaconState) error {
|
||||
state.Validators = []*ethpbv1alpha1.Validator{validator}
|
||||
return nil
|
||||
s.SubmitAttesterSlashingsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.AttesterSlashingElectra)
|
||||
assert.Equal(t, true, ok)
|
||||
})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
slashing := ðpbv1alpha1.ProposerSlashing{
|
||||
Header_1: ðpbv1alpha1.SignedBeaconBlockHeader{
|
||||
Header: ðpbv1alpha1.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
ProposerIndex: 0,
|
||||
ParentRoot: bytesutil.PadTo([]byte("parentroot1"), 32),
|
||||
StateRoot: bytesutil.PadTo([]byte("stateroot1"), 32),
|
||||
BodyRoot: bytesutil.PadTo([]byte("bodyroot1"), 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Header_2: ðpbv1alpha1.SignedBeaconBlockHeader{
|
||||
Header: ðpbv1alpha1.BeaconBlockHeader{
|
||||
Slot: 1,
|
||||
ProposerIndex: 0,
|
||||
ParentRoot: bytesutil.PadTo([]byte("parentroot2"), 32),
|
||||
StateRoot: bytesutil.PadTo([]byte("stateroot2"), 32),
|
||||
BodyRoot: bytesutil.PadTo([]byte("bodyroot2"), 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
|
||||
for _, h := range []*ethpbv1alpha1.SignedBeaconBlockHeader{slashing.Header_1, slashing.Header_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(
|
||||
bs,
|
||||
slots.ToEpoch(h.Header.Slot),
|
||||
h.Header,
|
||||
params.BeaconConfig().DomainBeaconProposer,
|
||||
keys[0],
|
||||
)
|
||||
t.Run("invalid-slashing", func(t *testing.T) {
|
||||
bs, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: &blockchainmock.ChainService{State: bs},
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
_, err = body.WriteString(invalidAttesterSlashing)
|
||||
require.NoError(t, err)
|
||||
h.Signature = sig.Marshal()
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/attester_slashings", &body)
|
||||
request.Header.Set(api.VersionHeader, version.String(version.Electra))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
toSubmit := structs.ProposerSlashingsFromConsensus([]*ethpbv1alpha1.ProposerSlashing{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/proposer_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitProposerSlashing(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.ProposerSlashing)
|
||||
assert.Equal(t, true, ok)
|
||||
}
|
||||
|
||||
func TestSubmitProposerSlashing_AcrossFork(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
transition.SkipSlotCache.Disable()
|
||||
defer transition.SkipSlotCache.Enable()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
config := params.BeaconConfig()
|
||||
config.AltairForkEpoch = 1
|
||||
params.OverrideBeaconConfig(config)
|
||||
|
||||
bs, keys := util.DeterministicGenesisState(t, 1)
|
||||
|
||||
slashing := ðpbv1alpha1.ProposerSlashing{
|
||||
Header_1: ðpbv1alpha1.SignedBeaconBlockHeader{
|
||||
Header: ðpbv1alpha1.BeaconBlockHeader{
|
||||
Slot: params.BeaconConfig().SlotsPerEpoch,
|
||||
ProposerIndex: 0,
|
||||
ParentRoot: bytesutil.PadTo([]byte("parentroot1"), 32),
|
||||
StateRoot: bytesutil.PadTo([]byte("stateroot1"), 32),
|
||||
BodyRoot: bytesutil.PadTo([]byte("bodyroot1"), 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
Header_2: ðpbv1alpha1.SignedBeaconBlockHeader{
|
||||
Header: ðpbv1alpha1.BeaconBlockHeader{
|
||||
Slot: params.BeaconConfig().SlotsPerEpoch,
|
||||
ProposerIndex: 0,
|
||||
ParentRoot: bytesutil.PadTo([]byte("parentroot2"), 32),
|
||||
StateRoot: bytesutil.PadTo([]byte("stateroot2"), 32),
|
||||
BodyRoot: bytesutil.PadTo([]byte("bodyroot2"), 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
}
|
||||
|
||||
newBs := bs.Copy()
|
||||
newBs, err := transition.ProcessSlots(ctx, newBs, params.BeaconConfig().SlotsPerEpoch)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, h := range []*ethpbv1alpha1.SignedBeaconBlockHeader{slashing.Header_1, slashing.Header_2} {
|
||||
sb, err := signing.ComputeDomainAndSign(
|
||||
newBs,
|
||||
slots.ToEpoch(h.Header.Slot),
|
||||
h.Header,
|
||||
params.BeaconConfig().DomainBeaconProposer,
|
||||
keys[0],
|
||||
)
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
h.Signature = sig.Marshal()
|
||||
}
|
||||
|
||||
broadcaster := &p2pMock.MockBroadcaster{}
|
||||
chainmock := &blockchainmock.ChainService{State: bs}
|
||||
s := &Server{
|
||||
ChainInfoFetcher: chainmock,
|
||||
SlashingsPool: &slashingsmock.PoolMock{},
|
||||
Broadcaster: broadcaster,
|
||||
OperationNotifier: chainmock.OperationNotifier(),
|
||||
}
|
||||
|
||||
toSubmit := structs.ProposerSlashingsFromConsensus([]*ethpbv1alpha1.ProposerSlashing{slashing})
|
||||
b, err := json.Marshal(toSubmit[0])
|
||||
require.NoError(t, err)
|
||||
var body bytes.Buffer
|
||||
_, err = body.Write(b)
|
||||
require.NoError(t, err)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://example.com/beacon/pool/proposer_slashings", &body)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.SubmitProposerSlashing(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
pendingSlashings := s.SlashingsPool.PendingProposerSlashings(ctx, bs, true)
|
||||
require.Equal(t, 1, len(pendingSlashings))
|
||||
assert.DeepEqual(t, slashing, pendingSlashings[0])
|
||||
assert.Equal(t, true, broadcaster.BroadcastCalled.Load())
|
||||
require.Equal(t, 1, broadcaster.NumMessages())
|
||||
_, ok := broadcaster.BroadcastMessages[0].(*ethpbv1alpha1.ProposerSlashing)
|
||||
assert.Equal(t, true, ok)
|
||||
s.SubmitAttesterSlashingsV2(writer, request)
|
||||
require.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusBadRequest, e.Code)
|
||||
assert.StringContains(t, "Invalid attester slashing", e.Message)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSubmitProposerSlashing_InvalidSlashing(t *testing.T) {
|
||||
|
||||
@@ -268,6 +268,38 @@ func TestGetBlockV2(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, blk, b)
|
||||
})
|
||||
t.Run("electra", func(t *testing.T) {
|
||||
b := util.NewBeaconBlockElectra()
|
||||
b.Block.Slot = 123
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
||||
mockChainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, version.String(version.Electra), resp.Version)
|
||||
sbb := &structs.SignedBeaconBlockElectra{Message: &structs.BeaconBlockElectra{}}
|
||||
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
||||
sbb.Signature = resp.Data.Signature
|
||||
blk, err := sbb.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, blk, b)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
@@ -461,141 +493,167 @@ func TestGetBlockSSZV2(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
||||
})
|
||||
t.Run("electra", func(t *testing.T) {
|
||||
b := util.NewBeaconBlockElectra()
|
||||
b.Block.Slot = 123
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Server{
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
assert.Equal(t, version.String(version.Electra), writer.Header().Get(api.VersionHeader))
|
||||
sszExpected, err := b.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, sszExpected, writer.Body.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetBlockAttestations(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Body.Attestations = []*eth.Attestation{
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0x00},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 123,
|
||||
CommitteeIndex: 123,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root1"), 32),
|
||||
},
|
||||
Target: ð.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root1"), 32),
|
||||
},
|
||||
preElectraAtts := []*eth.Attestation{
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0x00},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 123,
|
||||
CommitteeIndex: 123,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root1"), 32),
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
||||
},
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0x01},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 456,
|
||||
CommitteeIndex: 456,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root2"), 32),
|
||||
},
|
||||
Target: ð.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root2"), 32),
|
||||
},
|
||||
Target: ð.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root1"), 32),
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
||||
},
|
||||
}
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
||||
mockChainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
||||
},
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0x01},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 456,
|
||||
CommitteeIndex: 456,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root2"), 32),
|
||||
},
|
||||
Target: ð.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root2"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
||||
},
|
||||
}
|
||||
electraAtts := []*eth.AttestationElectra{
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0x00},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 123,
|
||||
CommitteeIndex: 123,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root1"), 32),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root1"), 32),
|
||||
},
|
||||
Target: ð.Checkpoint{
|
||||
Epoch: 123,
|
||||
Root: bytesutil.PadTo([]byte("root1"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig1"), 96),
|
||||
CommitteeBits: primitives.NewAttestationCommitteeBits(),
|
||||
},
|
||||
{
|
||||
AggregationBits: bitfield.Bitlist{0x01},
|
||||
Data: ð.AttestationData{
|
||||
Slot: 456,
|
||||
CommitteeIndex: 456,
|
||||
BeaconBlockRoot: bytesutil.PadTo([]byte("root2"), 32),
|
||||
Source: ð.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root2"), 32),
|
||||
},
|
||||
Target: ð.Checkpoint{
|
||||
Epoch: 456,
|
||||
Root: bytesutil.PadTo([]byte("root2"), 32),
|
||||
},
|
||||
},
|
||||
Signature: bytesutil.PadTo([]byte("sig2"), 96),
|
||||
CommitteeBits: primitives.NewAttestationCommitteeBits(),
|
||||
},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
b := util.NewBeaconBlock()
|
||||
b.Block.Body.Attestations = preElectraAtts
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.GetBlockAttestations(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data))
|
||||
atts := make([]*eth.Attestation, len(b.Block.Body.Attestations))
|
||||
for i, a := range resp.Data {
|
||||
atts[i], err = a.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.DeepEqual(t, b.Block.Body.Attestations, atts)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
b := util.NewBeaconBlockBellatrix()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
r, err := sb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
||||
mockChainService := &chainMock.ChainService{
|
||||
OptimisticRoots: map[[32]byte]bool{r: true},
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
}
|
||||
bb := util.NewBeaconBlockBellatrix()
|
||||
bb.Block.Body.Attestations = preElectraAtts
|
||||
bsb, err := blocks.NewSignedBeaconBlock(bb)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
eb := util.NewBeaconBlockElectra()
|
||||
eb.Block.Body.Attestations = electraAtts
|
||||
esb, err := blocks.NewSignedBeaconBlock(eb)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.GetBlockAttestations(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
b := util.NewBeaconBlock()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
r, err := sb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: sb}
|
||||
t.Run("v1", func(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
|
||||
t.Run("true", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestations(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetBlockAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
require.Equal(t, len(b.Block.Body.Attestations), len(resp.Data))
|
||||
|
||||
atts := make([]*eth.Attestation, len(b.Block.Body.Attestations))
|
||||
for i, a := range resp.Data {
|
||||
atts[i], err = a.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.DeepEqual(t, b.Block.Body.Attestations, atts)
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
|
||||
t.Run("execution-optimistic", func(t *testing.T) {
|
||||
r, err := bsb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
mockChainService := &chainMock.ChainService{
|
||||
OptimisticRoots: map[[32]byte]bool{r: true},
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: mockBlockFetcher,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: bsb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
@@ -604,7 +662,194 @@ func TestGetBlockAttestations(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, false, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
})
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
r, err := sb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("true", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestations(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestations(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsResponse{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, false, resp.ExecutionOptimistic)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("V2", func(t *testing.T) {
|
||||
t.Run("ok-pre-electra", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestationsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetBlockAttestationsV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
|
||||
var attStructs []structs.Attestation
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &attStructs))
|
||||
|
||||
atts := make([]*eth.Attestation, len(attStructs))
|
||||
for i, attStruct := range attStructs {
|
||||
atts[i], err = attStruct.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, b.Block.Body.Attestations, atts)
|
||||
assert.Equal(t, "phase0", resp.Version)
|
||||
})
|
||||
t.Run("ok-post-electra", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: esb},
|
||||
}
|
||||
|
||||
mockBlockFetcher := &testutil.MockBlocker{BlockToReturn: esb}
|
||||
s.Blocker = mockBlockFetcher
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestationsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
resp := &structs.GetBlockAttestationsV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
|
||||
var attStructs []structs.AttestationElectra
|
||||
require.NoError(t, json.Unmarshal(resp.Data, &attStructs))
|
||||
|
||||
atts := make([]*eth.AttestationElectra, len(attStructs))
|
||||
for i, attStruct := range attStructs {
|
||||
atts[i], err = attStruct.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, eb.Block.Body.Attestations, atts)
|
||||
assert.Equal(t, "electra", resp.Version)
|
||||
})
|
||||
t.Run("execution-optimistic", func(t *testing.T) {
|
||||
r, err := bsb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
mockChainService := &chainMock.ChainService{
|
||||
OptimisticRoots: map[[32]byte]bool{r: true},
|
||||
FinalizedRoots: map[[32]byte]bool{},
|
||||
}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: bsb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestationsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, "bellatrix", resp.Version)
|
||||
})
|
||||
t.Run("finalized", func(t *testing.T) {
|
||||
r, err := sb.Block().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("true", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: true}}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestationsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, true, resp.Finalized)
|
||||
assert.Equal(t, "phase0", resp.Version)
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
mockChainService := &chainMock.ChainService{FinalizedRoots: map[[32]byte]bool{r: false}}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v2/beacon/blocks/{block_id}/attestations", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlockAttestationsV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockAttestationsV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, false, resp.ExecutionOptimistic)
|
||||
assert.Equal(t, "phase0", resp.Version)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -759,6 +1004,35 @@ func TestGetBlindedBlock(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, blk, b)
|
||||
})
|
||||
t.Run("electra", func(t *testing.T) {
|
||||
b := util.NewBlindedBeaconBlockElectra()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
mockChainService := &chainMock.ChainService{}
|
||||
s := &Server{
|
||||
OptimisticModeFetcher: mockChainService,
|
||||
FinalizationFetcher: mockChainService,
|
||||
Blocker: &testutil.MockBlocker{BlockToReturn: sb},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v1/beacon/blinded_blocks/{block_id}", nil)
|
||||
request.SetPathValue("block_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBlindedBlock(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBlockV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, version.String(version.Electra), resp.Version)
|
||||
sbb := &structs.SignedBlindedBeaconBlockElectra{Message: &structs.BlindedBeaconBlockElectra{}}
|
||||
require.NoError(t, json.Unmarshal(resp.Data.Message, sbb.Message))
|
||||
sbb.Signature = resp.Data.Signature
|
||||
blk, err := sbb.ToConsensus()
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, blk, b)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
b := util.NewBlindedBeaconBlockBellatrix()
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestGetSpec(t *testing.T) {
|
||||
config.WhistleBlowerRewardQuotientElectra = 79
|
||||
config.PendingPartialWithdrawalsLimit = 80
|
||||
config.MinActivationBalance = 81
|
||||
config.PendingBalanceDepositLimit = 82
|
||||
config.PendingDepositLimit = 82
|
||||
config.MaxPendingPartialsPerWithdrawalsSweep = 83
|
||||
config.PendingConsolidationsLimit = 84
|
||||
config.MaxPartialWithdrawalsPerPayload = 85
|
||||
@@ -150,6 +150,7 @@ func TestGetSpec(t *testing.T) {
|
||||
config.MaxCellsInExtendedMatrix = 91
|
||||
config.UnsetDepositRequestsStartIndex = 92
|
||||
config.MaxDepositRequestsPerPayload = 93
|
||||
config.MaxPendingDepositsPerEpoch = 94
|
||||
|
||||
var dbp [4]byte
|
||||
copy(dbp[:], []byte{'0', '0', '0', '1'})
|
||||
@@ -188,7 +189,7 @@ func TestGetSpec(t *testing.T) {
|
||||
data, ok := resp.Data.(map[string]interface{})
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
assert.Equal(t, 154, len(data))
|
||||
assert.Equal(t, 155, len(data))
|
||||
for k, v := range data {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
switch k {
|
||||
@@ -499,7 +500,7 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "80", v)
|
||||
case "MIN_ACTIVATION_BALANCE":
|
||||
assert.Equal(t, "81", v)
|
||||
case "PENDING_BALANCE_DEPOSITS_LIMIT":
|
||||
case "PENDING_DEPOSITS_LIMIT":
|
||||
assert.Equal(t, "82", v)
|
||||
case "MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP":
|
||||
assert.Equal(t, "83", v)
|
||||
@@ -523,6 +524,8 @@ func TestGetSpec(t *testing.T) {
|
||||
assert.Equal(t, "92", v)
|
||||
case "MAX_DEPOSIT_REQUESTS_PER_PAYLOAD":
|
||||
assert.Equal(t, "93", v)
|
||||
case "MAX_PENDING_DEPOSITS_PER_EPOCH":
|
||||
assert.Equal(t, "94", v)
|
||||
default:
|
||||
t.Errorf("Incorrect key: %s", k)
|
||||
}
|
||||
|
||||
@@ -88,6 +88,12 @@ func (s *Server) getBeaconStateV2(ctx context.Context, w http.ResponseWriter, id
|
||||
httputil.HandleError(w, errMsgStateFromConsensus+": "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
case version.Electra:
|
||||
respSt, err = structs.BeaconStateElectraFromConsensus(st)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, errMsgStateFromConsensus+": "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
default:
|
||||
httputil.HandleError(w, "Unsupported state version", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -167,6 +167,34 @@ func TestGetBeaconStateV2(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(resp.Data, st))
|
||||
assert.Equal(t, "123", st.Slot)
|
||||
})
|
||||
t.Run("Electra", func(t *testing.T) {
|
||||
fakeState, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fakeState.SetSlot(123))
|
||||
chainService := &blockchainmock.ChainService{}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{
|
||||
BeaconState: fakeState,
|
||||
},
|
||||
HeadFetcher: chainService,
|
||||
OptimisticModeFetcher: chainService,
|
||||
FinalizationFetcher: chainService,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v2/debug/beacon/states/{state_id}", nil)
|
||||
request.SetPathValue("state_id", "head")
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetBeaconStateV2(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
resp := &structs.GetBeaconStateV2Response{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
|
||||
assert.Equal(t, version.String(version.Electra), resp.Version)
|
||||
st := &structs.BeaconStateElectra{}
|
||||
require.NoError(t, json.Unmarshal(resp.Data, st))
|
||||
assert.Equal(t, "123", st.Slot)
|
||||
})
|
||||
t.Run("execution optimistic", func(t *testing.T) {
|
||||
parentRoot := [32]byte{'a'}
|
||||
blk := util.NewBeaconBlock()
|
||||
|
||||
@@ -20,6 +20,7 @@ go_library(
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/core/transition:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
@@ -29,12 +30,16 @@ go_library(
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["events_test.go"],
|
||||
srcs = [
|
||||
"events_test.go",
|
||||
"http_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/blockchain/testing:go_default_library",
|
||||
@@ -49,9 +54,9 @@ go_test(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_r3labs_sse_v2//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
time2 "time"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/api"
|
||||
"github.com/prysmaticlabs/prysm/v5/api/server/structs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
||||
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
chaintime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
|
||||
@@ -26,9 +28,13 @@ import (
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const DefaultEventFeedDepth = 1000
|
||||
|
||||
const (
|
||||
InvalidTopic = "__invalid__"
|
||||
// HeadTopic represents a new chain head event topic.
|
||||
HeadTopic = "head"
|
||||
// BlockTopic represents a new produced block event topic.
|
||||
@@ -59,25 +65,84 @@ const (
|
||||
LightClientOptimisticUpdateTopic = "light_client_optimistic_update"
|
||||
)
|
||||
|
||||
const topicDataMismatch = "Event data type %T does not correspond to event topic %s"
|
||||
var (
|
||||
errInvalidTopicName = errors.New("invalid topic name")
|
||||
errNoValidTopics = errors.New("no valid topics specified")
|
||||
errSlowReader = errors.New("client failed to read fast enough to keep outgoing buffer below threshold")
|
||||
errNotRequested = errors.New("event not requested by client")
|
||||
errUnhandledEventData = errors.New("unable to represent event data in the event stream")
|
||||
errWriterUnusable = errors.New("http response writer is unusable")
|
||||
)
|
||||
|
||||
const chanBuffer = 1000
|
||||
// StreamingResponseWriter defines a type that can be used by the eventStreamer.
|
||||
// This must be an http.ResponseWriter that supports flushing and hijacking.
|
||||
type StreamingResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
}
|
||||
|
||||
var casesHandled = map[string]bool{
|
||||
HeadTopic: true,
|
||||
BlockTopic: true,
|
||||
AttestationTopic: true,
|
||||
VoluntaryExitTopic: true,
|
||||
FinalizedCheckpointTopic: true,
|
||||
ChainReorgTopic: true,
|
||||
SyncCommitteeContributionTopic: true,
|
||||
BLSToExecutionChangeTopic: true,
|
||||
PayloadAttributesTopic: true,
|
||||
BlobSidecarTopic: true,
|
||||
ProposerSlashingTopic: true,
|
||||
AttesterSlashingTopic: true,
|
||||
LightClientFinalityUpdateTopic: true,
|
||||
LightClientOptimisticUpdateTopic: true,
|
||||
// The eventStreamer uses lazyReaders to defer serialization until the moment the value is ready to be written to the client.
|
||||
type lazyReader func() io.Reader
|
||||
|
||||
var opsFeedEventTopics = map[feed.EventType]string{
|
||||
operation.AggregatedAttReceived: AttestationTopic,
|
||||
operation.UnaggregatedAttReceived: AttestationTopic,
|
||||
operation.ExitReceived: VoluntaryExitTopic,
|
||||
operation.SyncCommitteeContributionReceived: SyncCommitteeContributionTopic,
|
||||
operation.BLSToExecutionChangeReceived: BLSToExecutionChangeTopic,
|
||||
operation.BlobSidecarReceived: BlobSidecarTopic,
|
||||
operation.AttesterSlashingReceived: AttesterSlashingTopic,
|
||||
operation.ProposerSlashingReceived: ProposerSlashingTopic,
|
||||
}
|
||||
|
||||
var stateFeedEventTopics = map[feed.EventType]string{
|
||||
statefeed.NewHead: HeadTopic,
|
||||
statefeed.MissedSlot: PayloadAttributesTopic,
|
||||
statefeed.FinalizedCheckpoint: FinalizedCheckpointTopic,
|
||||
statefeed.LightClientFinalityUpdate: LightClientFinalityUpdateTopic,
|
||||
statefeed.LightClientOptimisticUpdate: LightClientOptimisticUpdateTopic,
|
||||
statefeed.Reorg: ChainReorgTopic,
|
||||
statefeed.BlockProcessed: BlockTopic,
|
||||
}
|
||||
|
||||
var topicsForStateFeed = topicsForFeed(stateFeedEventTopics)
|
||||
var topicsForOpsFeed = topicsForFeed(opsFeedEventTopics)
|
||||
|
||||
func topicsForFeed(em map[feed.EventType]string) map[string]bool {
|
||||
topics := make(map[string]bool, len(em))
|
||||
for _, topic := range em {
|
||||
topics[topic] = true
|
||||
}
|
||||
return topics
|
||||
}
|
||||
|
||||
type topicRequest struct {
|
||||
topics map[string]bool
|
||||
needStateFeed bool
|
||||
needOpsFeed bool
|
||||
}
|
||||
|
||||
func (req *topicRequest) requested(topic string) bool {
|
||||
return req.topics[topic]
|
||||
}
|
||||
|
||||
func newTopicRequest(topics []string) (*topicRequest, error) {
|
||||
req := &topicRequest{topics: make(map[string]bool)}
|
||||
for _, name := range topics {
|
||||
if topicsForStateFeed[name] {
|
||||
req.needStateFeed = true
|
||||
} else if topicsForOpsFeed[name] {
|
||||
req.needOpsFeed = true
|
||||
} else {
|
||||
return nil, errors.Wrapf(errInvalidTopicName, name)
|
||||
}
|
||||
req.topics[name] = true
|
||||
}
|
||||
if len(req.topics) == 0 || (!req.needStateFeed && !req.needOpsFeed) {
|
||||
return nil, errNoValidTopics
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// StreamEvents provides an endpoint to subscribe to the beacon node Server-Sent-Events stream.
|
||||
@@ -88,326 +153,423 @@ func (s *Server) StreamEvents(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "events.StreamEvents")
|
||||
defer span.End()
|
||||
|
||||
flusher, ok := w.(http.Flusher)
|
||||
topics, err := newTopicRequest(r.URL.Query()["topics"])
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sw, ok := w.(StreamingResponseWriter)
|
||||
if !ok {
|
||||
httputil.HandleError(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
msg := "beacon node misconfiguration: http stack may not support required response handling features, like flushing"
|
||||
httputil.HandleError(w, msg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
topics := r.URL.Query()["topics"]
|
||||
if len(topics) == 0 {
|
||||
httputil.HandleError(w, "No topics specified to subscribe to", http.StatusBadRequest)
|
||||
return
|
||||
depth := s.EventFeedDepth
|
||||
if depth == 0 {
|
||||
depth = DefaultEventFeedDepth
|
||||
}
|
||||
topicsMap := make(map[string]bool)
|
||||
for _, topic := range topics {
|
||||
if _, ok := casesHandled[topic]; !ok {
|
||||
httputil.HandleError(w, fmt.Sprintf("Invalid topic: %s", topic), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
topicsMap[topic] = true
|
||||
}
|
||||
|
||||
// Subscribe to event feeds from information received in the beacon node runtime.
|
||||
opsChan := make(chan *feed.Event, chanBuffer)
|
||||
opsSub := s.OperationNotifier.OperationFeed().Subscribe(opsChan)
|
||||
stateChan := make(chan *feed.Event, chanBuffer)
|
||||
stateSub := s.StateNotifier.StateFeed().Subscribe(stateChan)
|
||||
defer opsSub.Unsubscribe()
|
||||
defer stateSub.Unsubscribe()
|
||||
|
||||
// Set up SSE response headers
|
||||
w.Header().Set("Content-Type", api.EventStreamMediaType)
|
||||
w.Header().Set("Connection", api.KeepAlive)
|
||||
|
||||
// Handle each event received and context cancellation.
|
||||
// We send a keepalive dummy message immediately to prevent clients
|
||||
// stalling while waiting for the first response chunk.
|
||||
// After that we send a keepalive dummy message every SECONDS_PER_SLOT
|
||||
// to prevent anyone (e.g. proxy servers) from closing connections.
|
||||
if err := sendKeepalive(w, flusher); err != nil {
|
||||
es, err := newEventStreamer(depth, s.KeepAliveInterval)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
keepaliveTicker := time2.NewTicker(time2.Duration(params.BeaconConfig().SecondsPerSlot) * time2.Second)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
api.SetSSEHeaders(sw)
|
||||
go es.outboxWriteLoop(ctx, cancel, sw)
|
||||
if err := es.recvEventLoop(ctx, cancel, topics, s); err != nil {
|
||||
log.WithError(err).Debug("Shutting down StreamEvents handler.")
|
||||
}
|
||||
}
|
||||
|
||||
func newEventStreamer(buffSize int, ka time.Duration) (*eventStreamer, error) {
|
||||
if ka == 0 {
|
||||
ka = time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second
|
||||
}
|
||||
return &eventStreamer{
|
||||
outbox: make(chan lazyReader, buffSize),
|
||||
keepAlive: ka,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type eventStreamer struct {
|
||||
outbox chan lazyReader
|
||||
keepAlive time.Duration
|
||||
}
|
||||
|
||||
func (es *eventStreamer) recvEventLoop(ctx context.Context, cancel context.CancelFunc, req *topicRequest, s *Server) error {
|
||||
eventsChan := make(chan *feed.Event, len(es.outbox))
|
||||
if req.needOpsFeed {
|
||||
opsSub := s.OperationNotifier.OperationFeed().Subscribe(eventsChan)
|
||||
defer opsSub.Unsubscribe()
|
||||
}
|
||||
if req.needStateFeed {
|
||||
stateSub := s.StateNotifier.StateFeed().Subscribe(eventsChan)
|
||||
defer stateSub.Unsubscribe()
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case event := <-opsChan:
|
||||
if err := handleBlockOperationEvents(w, flusher, topicsMap, event); err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
case event := <-stateChan:
|
||||
if err := s.handleStateEvents(ctx, w, flusher, topicsMap, event); err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
case <-keepaliveTicker.C:
|
||||
if err := sendKeepalive(w, flusher); err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case event := <-eventsChan:
|
||||
lr, err := s.lazyReaderForEvent(ctx, event, req)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errNotRequested) {
|
||||
log.WithField("event_type", fmt.Sprintf("%v", event.Data)).WithError(err).Error("StreamEvents API endpoint received an event it was unable to handle.")
|
||||
}
|
||||
continue
|
||||
}
|
||||
// If the client can't keep up, the outbox will eventually completely fill, at which
|
||||
// safeWrite will error, and we'll hit the below return statement, at which point the deferred
|
||||
// Unsuscribe calls will be made and the event feed will stop writing to this channel.
|
||||
// Since the outbox and event stream channels are separately buffered, the event subscription
|
||||
// channel should stay relatively empty, which gives this loop time to unsubscribe
|
||||
// and cleanup before the event stream channel fills and disrupts other readers.
|
||||
if err := es.safeWrite(ctx, lr); err != nil {
|
||||
cancel()
|
||||
// note: we could hijack the connection and close it here. Does that cause issues? What are the benefits?
|
||||
// A benefit of hijack and close is that it may force an error on the remote end, however just closing the context of the
|
||||
// http handler may be sufficient to cause the remote http response reader to close.
|
||||
if errors.Is(err, errSlowReader) {
|
||||
log.WithError(err).Warn("Client is unable to keep up with event stream, shutting down.")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (es *eventStreamer) safeWrite(ctx context.Context, rf func() io.Reader) error {
|
||||
if rf == nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case es.outbox <- rf:
|
||||
return nil
|
||||
default:
|
||||
// If this is the case, the select case to write to the outbox could not proceed, meaning the outbox is full.
|
||||
// If a reader can't keep up with the stream, we shut them down.
|
||||
return errSlowReader
|
||||
}
|
||||
}
|
||||
|
||||
// newlineReader is used to write keep-alives to the client.
|
||||
// keep-alives in the sse protocol are a single ':' colon followed by 2 newlines.
|
||||
func newlineReader() io.Reader {
|
||||
return bytes.NewBufferString(":\n\n")
|
||||
}
|
||||
|
||||
// outboxWriteLoop runs in a separate goroutine. Its job is to write the values in the outbox to
|
||||
// the client as fast as the client can read them.
|
||||
func (es *eventStreamer) outboxWriteLoop(ctx context.Context, cancel context.CancelFunc, w StreamingResponseWriter) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Event streamer shutting down due to error.")
|
||||
}
|
||||
}()
|
||||
defer cancel()
|
||||
// Write a keepalive at the start to test the connection and simplify test setup.
|
||||
if err = es.writeOutbox(ctx, w, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
kaT := time.NewTimer(es.keepAlive)
|
||||
// Ensure the keepalive timer is stopped and drained if it has fired.
|
||||
defer func() {
|
||||
if !kaT.Stop() {
|
||||
<-kaT.C
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
return
|
||||
case <-kaT.C:
|
||||
if err = es.writeOutbox(ctx, w, nil); err != nil {
|
||||
return
|
||||
}
|
||||
// In this case the timer doesn't need to be Stopped before the Reset call after the select statement,
|
||||
// because the timer has already fired.
|
||||
case lr := <-es.outbox:
|
||||
if err = es.writeOutbox(ctx, w, lr); err != nil {
|
||||
return
|
||||
}
|
||||
// We don't know if the timer fired concurrently to this case being ready, so we need to check the return
|
||||
// of Stop and drain the timer channel if it fired. We won't need to do this in go 1.23.
|
||||
if !kaT.Stop() {
|
||||
<-kaT.C
|
||||
}
|
||||
}
|
||||
kaT.Reset(es.keepAlive)
|
||||
}
|
||||
}
|
||||
|
||||
func writeLazyReaderWithRecover(w StreamingResponseWriter, lr lazyReader) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.WithField("panic", r).Error("Recovered from panic while writing event to client.")
|
||||
err = errWriterUnusable
|
||||
}
|
||||
}()
|
||||
_, err = io.Copy(w, lr())
|
||||
return err
|
||||
}
|
||||
|
||||
func (es *eventStreamer) writeOutbox(ctx context.Context, w StreamingResponseWriter, first lazyReader) error {
|
||||
needKeepAlive := true
|
||||
if first != nil {
|
||||
if err := writeLazyReaderWithRecover(w, first); err != nil {
|
||||
return err
|
||||
}
|
||||
needKeepAlive = false
|
||||
}
|
||||
// While the first event was being read by the client, further events may be queued in the outbox.
|
||||
// We can drain them right away rather than go back out to the outer select statement, where the keepAlive timer
|
||||
// may have fired, triggering an unnecessary extra keep-alive write and flush.
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case rf := <-es.outbox:
|
||||
if err := writeLazyReaderWithRecover(w, rf); err != nil {
|
||||
return err
|
||||
}
|
||||
needKeepAlive = false
|
||||
default:
|
||||
if needKeepAlive {
|
||||
if err := writeLazyReaderWithRecover(w, newlineReader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleBlockOperationEvents(w http.ResponseWriter, flusher http.Flusher, requestedTopics map[string]bool, event *feed.Event) error {
|
||||
switch event.Type {
|
||||
case operation.AggregatedAttReceived:
|
||||
if _, ok := requestedTopics[AttestationTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
attData, ok := event.Data.(*operation.AggregatedAttReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, AttestationTopic)
|
||||
}
|
||||
att := structs.AttFromConsensus(attData.Attestation.Aggregate)
|
||||
return send(w, flusher, AttestationTopic, att)
|
||||
case operation.UnaggregatedAttReceived:
|
||||
if _, ok := requestedTopics[AttestationTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
attData, ok := event.Data.(*operation.UnAggregatedAttReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, AttestationTopic)
|
||||
}
|
||||
a, ok := attData.Attestation.(*eth.Attestation)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, AttestationTopic)
|
||||
}
|
||||
att := structs.AttFromConsensus(a)
|
||||
return send(w, flusher, AttestationTopic, att)
|
||||
case operation.ExitReceived:
|
||||
if _, ok := requestedTopics[VoluntaryExitTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
exitData, ok := event.Data.(*operation.ExitReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, VoluntaryExitTopic)
|
||||
}
|
||||
exit := structs.SignedExitFromConsensus(exitData.Exit)
|
||||
return send(w, flusher, VoluntaryExitTopic, exit)
|
||||
case operation.SyncCommitteeContributionReceived:
|
||||
if _, ok := requestedTopics[SyncCommitteeContributionTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
contributionData, ok := event.Data.(*operation.SyncCommitteeContributionReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, SyncCommitteeContributionTopic)
|
||||
}
|
||||
contribution := structs.SignedContributionAndProofFromConsensus(contributionData.Contribution)
|
||||
return send(w, flusher, SyncCommitteeContributionTopic, contribution)
|
||||
case operation.BLSToExecutionChangeReceived:
|
||||
if _, ok := requestedTopics[BLSToExecutionChangeTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
changeData, ok := event.Data.(*operation.BLSToExecutionChangeReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, BLSToExecutionChangeTopic)
|
||||
}
|
||||
return send(w, flusher, BLSToExecutionChangeTopic, structs.SignedBLSChangeFromConsensus(changeData.Change))
|
||||
case operation.BlobSidecarReceived:
|
||||
if _, ok := requestedTopics[BlobSidecarTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
blobData, ok := event.Data.(*operation.BlobSidecarReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, BlobSidecarTopic)
|
||||
}
|
||||
versionedHash := blockchain.ConvertKzgCommitmentToVersionedHash(blobData.Blob.KzgCommitment)
|
||||
blobEvent := &structs.BlobSidecarEvent{
|
||||
BlockRoot: hexutil.Encode(blobData.Blob.BlockRootSlice()),
|
||||
Index: fmt.Sprintf("%d", blobData.Blob.Index),
|
||||
Slot: fmt.Sprintf("%d", blobData.Blob.Slot()),
|
||||
VersionedHash: versionedHash.String(),
|
||||
KzgCommitment: hexutil.Encode(blobData.Blob.KzgCommitment),
|
||||
}
|
||||
return send(w, flusher, BlobSidecarTopic, blobEvent)
|
||||
case operation.AttesterSlashingReceived:
|
||||
if _, ok := requestedTopics[AttesterSlashingTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
attesterSlashingData, ok := event.Data.(*operation.AttesterSlashingReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, AttesterSlashingTopic)
|
||||
}
|
||||
slashing, ok := attesterSlashingData.AttesterSlashing.(*eth.AttesterSlashing)
|
||||
if ok {
|
||||
return send(w, flusher, AttesterSlashingTopic, structs.AttesterSlashingFromConsensus(slashing))
|
||||
}
|
||||
// TODO: extend to Electra
|
||||
case operation.ProposerSlashingReceived:
|
||||
if _, ok := requestedTopics[ProposerSlashingTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
proposerSlashingData, ok := event.Data.(*operation.ProposerSlashingReceivedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, ProposerSlashingTopic)
|
||||
}
|
||||
return send(w, flusher, ProposerSlashingTopic, structs.ProposerSlashingFromConsensus(proposerSlashingData.ProposerSlashing))
|
||||
func jsonMarshalReader(name string, v any) io.Reader {
|
||||
d, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("type_name", fmt.Sprintf("%T", v)).Error("Could not marshal event data.")
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return bytes.NewBufferString("event: " + name + "\ndata: " + string(d) + "\n\n")
|
||||
}
|
||||
|
||||
func (s *Server) handleStateEvents(ctx context.Context, w http.ResponseWriter, flusher http.Flusher, requestedTopics map[string]bool, event *feed.Event) error {
|
||||
switch event.Type {
|
||||
case statefeed.NewHead:
|
||||
if _, ok := requestedTopics[HeadTopic]; ok {
|
||||
headData, ok := event.Data.(*ethpb.EventHead)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, HeadTopic)
|
||||
}
|
||||
head := &structs.HeadEvent{
|
||||
Slot: fmt.Sprintf("%d", headData.Slot),
|
||||
Block: hexutil.Encode(headData.Block),
|
||||
State: hexutil.Encode(headData.State),
|
||||
EpochTransition: headData.EpochTransition,
|
||||
ExecutionOptimistic: headData.ExecutionOptimistic,
|
||||
PreviousDutyDependentRoot: hexutil.Encode(headData.PreviousDutyDependentRoot),
|
||||
CurrentDutyDependentRoot: hexutil.Encode(headData.CurrentDutyDependentRoot),
|
||||
}
|
||||
return send(w, flusher, HeadTopic, head)
|
||||
func topicForEvent(event *feed.Event) string {
|
||||
switch event.Data.(type) {
|
||||
case *operation.AggregatedAttReceivedData:
|
||||
return AttestationTopic
|
||||
case *operation.UnAggregatedAttReceivedData:
|
||||
return AttestationTopic
|
||||
case *operation.ExitReceivedData:
|
||||
return VoluntaryExitTopic
|
||||
case *operation.SyncCommitteeContributionReceivedData:
|
||||
return SyncCommitteeContributionTopic
|
||||
case *operation.BLSToExecutionChangeReceivedData:
|
||||
return BLSToExecutionChangeTopic
|
||||
case *operation.BlobSidecarReceivedData:
|
||||
return BlobSidecarTopic
|
||||
case *operation.AttesterSlashingReceivedData:
|
||||
return AttesterSlashingTopic
|
||||
case *operation.ProposerSlashingReceivedData:
|
||||
return ProposerSlashingTopic
|
||||
case *ethpb.EventHead:
|
||||
return HeadTopic
|
||||
case *ethpb.EventFinalizedCheckpoint:
|
||||
return FinalizedCheckpointTopic
|
||||
case *ethpbv2.LightClientFinalityUpdateWithVersion:
|
||||
return LightClientFinalityUpdateTopic
|
||||
case *ethpbv2.LightClientOptimisticUpdateWithVersion:
|
||||
return LightClientOptimisticUpdateTopic
|
||||
case *ethpb.EventChainReorg:
|
||||
return ChainReorgTopic
|
||||
case *statefeed.BlockProcessedData:
|
||||
return BlockTopic
|
||||
default:
|
||||
if event.Type == statefeed.MissedSlot {
|
||||
return PayloadAttributesTopic
|
||||
}
|
||||
if _, ok := requestedTopics[PayloadAttributesTopic]; ok {
|
||||
return s.sendPayloadAttributes(ctx, w, flusher)
|
||||
}
|
||||
case statefeed.MissedSlot:
|
||||
if _, ok := requestedTopics[PayloadAttributesTopic]; ok {
|
||||
return s.sendPayloadAttributes(ctx, w, flusher)
|
||||
}
|
||||
case statefeed.FinalizedCheckpoint:
|
||||
if _, ok := requestedTopics[FinalizedCheckpointTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
checkpointData, ok := event.Data.(*ethpb.EventFinalizedCheckpoint)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, FinalizedCheckpointTopic)
|
||||
}
|
||||
checkpoint := &structs.FinalizedCheckpointEvent{
|
||||
Block: hexutil.Encode(checkpointData.Block),
|
||||
State: hexutil.Encode(checkpointData.State),
|
||||
Epoch: fmt.Sprintf("%d", checkpointData.Epoch),
|
||||
ExecutionOptimistic: checkpointData.ExecutionOptimistic,
|
||||
}
|
||||
return send(w, flusher, FinalizedCheckpointTopic, checkpoint)
|
||||
case statefeed.LightClientFinalityUpdate:
|
||||
if _, ok := requestedTopics[LightClientFinalityUpdateTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
updateData, ok := event.Data.(*ethpbv2.LightClientFinalityUpdateWithVersion)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, LightClientFinalityUpdateTopic)
|
||||
}
|
||||
update, err := structs.LightClientFinalityUpdateFromConsensus(updateData.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateEvent := &structs.LightClientFinalityUpdateEvent{
|
||||
Version: version.String(int(updateData.Version)),
|
||||
Data: update,
|
||||
}
|
||||
return send(w, flusher, LightClientFinalityUpdateTopic, updateEvent)
|
||||
case statefeed.LightClientOptimisticUpdate:
|
||||
if _, ok := requestedTopics[LightClientOptimisticUpdateTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
updateData, ok := event.Data.(*ethpbv2.LightClientOptimisticUpdateWithVersion)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, LightClientOptimisticUpdateTopic)
|
||||
}
|
||||
update, err := structs.LightClientOptimisticUpdateFromConsensus(updateData.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateEvent := &structs.LightClientOptimisticUpdateEvent{
|
||||
Version: version.String(int(updateData.Version)),
|
||||
Data: update,
|
||||
}
|
||||
return send(w, flusher, LightClientOptimisticUpdateTopic, updateEvent)
|
||||
case statefeed.Reorg:
|
||||
if _, ok := requestedTopics[ChainReorgTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
reorgData, ok := event.Data.(*ethpb.EventChainReorg)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, ChainReorgTopic)
|
||||
}
|
||||
reorg := &structs.ChainReorgEvent{
|
||||
Slot: fmt.Sprintf("%d", reorgData.Slot),
|
||||
Depth: fmt.Sprintf("%d", reorgData.Depth),
|
||||
OldHeadBlock: hexutil.Encode(reorgData.OldHeadBlock),
|
||||
NewHeadBlock: hexutil.Encode(reorgData.NewHeadBlock),
|
||||
OldHeadState: hexutil.Encode(reorgData.OldHeadState),
|
||||
NewHeadState: hexutil.Encode(reorgData.NewHeadState),
|
||||
Epoch: fmt.Sprintf("%d", reorgData.Epoch),
|
||||
ExecutionOptimistic: reorgData.ExecutionOptimistic,
|
||||
}
|
||||
return send(w, flusher, ChainReorgTopic, reorg)
|
||||
case statefeed.BlockProcessed:
|
||||
if _, ok := requestedTopics[BlockTopic]; !ok {
|
||||
return nil
|
||||
}
|
||||
blkData, ok := event.Data.(*statefeed.BlockProcessedData)
|
||||
if !ok {
|
||||
return write(w, flusher, topicDataMismatch, event.Data, BlockTopic)
|
||||
}
|
||||
blockRoot, err := blkData.SignedBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get block root: "+err.Error())
|
||||
}
|
||||
blk := &structs.BlockEvent{
|
||||
Slot: fmt.Sprintf("%d", blkData.Slot),
|
||||
Block: hexutil.Encode(blockRoot[:]),
|
||||
ExecutionOptimistic: blkData.Optimistic,
|
||||
}
|
||||
return send(w, flusher, BlockTopic, blk)
|
||||
return InvalidTopic
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) lazyReaderForEvent(ctx context.Context, event *feed.Event, topics *topicRequest) (lazyReader, error) {
|
||||
eventName := topicForEvent(event)
|
||||
if !topics.requested(eventName) {
|
||||
return nil, errNotRequested
|
||||
}
|
||||
if eventName == PayloadAttributesTopic {
|
||||
return s.currentPayloadAttributes(ctx)
|
||||
}
|
||||
if event == nil || event.Data == nil {
|
||||
return nil, errors.New("event or event data is nil")
|
||||
}
|
||||
switch v := event.Data.(type) {
|
||||
case *ethpb.EventHead:
|
||||
// The head event is a special case because, if the client requested the payload attributes topic,
|
||||
// we send two event messages in reaction; the head event and the payload attributes.
|
||||
headReader := func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.HeadEventFromV1(v))
|
||||
}
|
||||
// Don't do the expensive attr lookup unless the client requested it.
|
||||
if !topics.requested(PayloadAttributesTopic) {
|
||||
return headReader, nil
|
||||
}
|
||||
// Since payload attributes could change before the outbox is written, we need to do a blocking operation to
|
||||
// get the current payload attributes right here.
|
||||
attrReader, err := s.currentPayloadAttributes(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get payload attributes for head event")
|
||||
}
|
||||
return func() io.Reader {
|
||||
return io.MultiReader(headReader(), attrReader())
|
||||
}, nil
|
||||
case *operation.AggregatedAttReceivedData:
|
||||
return func() io.Reader {
|
||||
att := structs.AttFromConsensus(v.Attestation.Aggregate)
|
||||
return jsonMarshalReader(eventName, att)
|
||||
}, nil
|
||||
case *operation.UnAggregatedAttReceivedData:
|
||||
att, ok := v.Attestation.(*eth.Attestation)
|
||||
if !ok {
|
||||
return nil, errors.Wrapf(errUnhandledEventData, "Unexpected type %T for the .Attestation field of UnAggregatedAttReceivedData", v.Attestation)
|
||||
}
|
||||
return func() io.Reader {
|
||||
att := structs.AttFromConsensus(att)
|
||||
return jsonMarshalReader(eventName, att)
|
||||
}, nil
|
||||
case *operation.ExitReceivedData:
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.SignedExitFromConsensus(v.Exit))
|
||||
}, nil
|
||||
case *operation.SyncCommitteeContributionReceivedData:
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.SignedContributionAndProofFromConsensus(v.Contribution))
|
||||
}, nil
|
||||
case *operation.BLSToExecutionChangeReceivedData:
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.SignedBLSChangeFromConsensus(v.Change))
|
||||
}, nil
|
||||
case *operation.BlobSidecarReceivedData:
|
||||
return func() io.Reader {
|
||||
versionedHash := primitives.ConvertKzgCommitmentToVersionedHash(v.Blob.KzgCommitment)
|
||||
return jsonMarshalReader(eventName, &structs.BlobSidecarEvent{
|
||||
BlockRoot: hexutil.Encode(v.Blob.BlockRootSlice()),
|
||||
Index: fmt.Sprintf("%d", v.Blob.Index),
|
||||
Slot: fmt.Sprintf("%d", v.Blob.Slot()),
|
||||
VersionedHash: versionedHash.String(),
|
||||
KzgCommitment: hexutil.Encode(v.Blob.KzgCommitment),
|
||||
})
|
||||
}, nil
|
||||
case *operation.AttesterSlashingReceivedData:
|
||||
slashing, ok := v.AttesterSlashing.(*eth.AttesterSlashing)
|
||||
if !ok {
|
||||
return nil, errors.Wrapf(errUnhandledEventData, "Unexpected type %T for the .AttesterSlashing field of AttesterSlashingReceivedData", v.AttesterSlashing)
|
||||
}
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.AttesterSlashingFromConsensus(slashing))
|
||||
}, nil
|
||||
case *operation.ProposerSlashingReceivedData:
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.ProposerSlashingFromConsensus(v.ProposerSlashing))
|
||||
}, nil
|
||||
case *ethpb.EventFinalizedCheckpoint:
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.FinalizedCheckpointEventFromV1(v))
|
||||
}, nil
|
||||
case *ethpbv2.LightClientFinalityUpdateWithVersion:
|
||||
cv, err := structs.LightClientFinalityUpdateFromConsensus(v.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "LightClientFinalityUpdateWithVersion event conversion failure")
|
||||
}
|
||||
ev := &structs.LightClientFinalityUpdateEvent{
|
||||
Version: version.String(int(v.Version)),
|
||||
Data: cv,
|
||||
}
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, ev)
|
||||
}, nil
|
||||
case *ethpbv2.LightClientOptimisticUpdateWithVersion:
|
||||
cv, err := structs.LightClientOptimisticUpdateFromConsensus(v.Data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "LightClientOptimisticUpdateWithVersion event conversion failure")
|
||||
}
|
||||
ev := &structs.LightClientOptimisticUpdateEvent{
|
||||
Version: version.String(int(v.Version)),
|
||||
Data: cv,
|
||||
}
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, ev)
|
||||
}, nil
|
||||
case *ethpb.EventChainReorg:
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(eventName, structs.EventChainReorgFromV1(v))
|
||||
}, nil
|
||||
case *statefeed.BlockProcessedData:
|
||||
blockRoot, err := v.SignedBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute block root for BlockProcessedData state feed event")
|
||||
}
|
||||
return func() io.Reader {
|
||||
blk := &structs.BlockEvent{
|
||||
Slot: fmt.Sprintf("%d", v.Slot),
|
||||
Block: hexutil.Encode(blockRoot[:]),
|
||||
ExecutionOptimistic: v.Optimistic,
|
||||
}
|
||||
return jsonMarshalReader(eventName, blk)
|
||||
}, nil
|
||||
default:
|
||||
return nil, errors.Wrapf(errUnhandledEventData, "event data type %T unsupported", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This event stream is intended to be used by builders and relays.
|
||||
// Parent fields are based on state at N_{current_slot}, while the rest of fields are based on state of N_{current_slot + 1}
|
||||
func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWriter, flusher http.Flusher) error {
|
||||
func (s *Server) currentPayloadAttributes(ctx context.Context) (lazyReader, error) {
|
||||
headRoot, err := s.HeadFetcher.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head root: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head root")
|
||||
}
|
||||
st, err := s.HeadFetcher.HeadState(ctx)
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head state")
|
||||
}
|
||||
// advance the head state
|
||||
headState, err := transition.ProcessSlotsIfPossible(ctx, st, s.ChainInfoFetcher.CurrentSlot()+1)
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not advance head state: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not advance head state")
|
||||
}
|
||||
|
||||
headBlock, err := s.HeadFetcher.HeadBlock(ctx)
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head block: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head block")
|
||||
}
|
||||
|
||||
headPayload, err := headBlock.Block().Body().Execution()
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get execution payload: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get execution payload")
|
||||
}
|
||||
|
||||
t, err := slots.ToTime(headState.GenesisTime(), headState.Slot())
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state slot time: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head state slot time")
|
||||
}
|
||||
|
||||
prevRando, err := helpers.RandaoMix(headState, time.CurrentEpoch(headState))
|
||||
prevRando, err := helpers.RandaoMix(headState, chaintime.CurrentEpoch(headState))
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state randao mix: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head state randao mix")
|
||||
}
|
||||
|
||||
proposerIndex, err := helpers.BeaconProposerIndex(ctx, headState)
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state proposer index: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head state proposer index")
|
||||
}
|
||||
feeRecipient := params.BeaconConfig().DefaultFeeRecipient.Bytes()
|
||||
tValidator, exists := s.TrackedValidatorsCache.Validator(proposerIndex)
|
||||
@@ -425,7 +587,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
case version.Capella:
|
||||
withdrawals, _, err := headState.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state expected withdrawals: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head state expected withdrawals")
|
||||
}
|
||||
attributes = &structs.PayloadAttributesV2{
|
||||
Timestamp: fmt.Sprintf("%d", t.Unix()),
|
||||
@@ -433,14 +595,14 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
SuggestedFeeRecipient: hexutil.Encode(feeRecipient),
|
||||
Withdrawals: structs.WithdrawalsFromConsensus(withdrawals),
|
||||
}
|
||||
case version.Deneb, version.Electra:
|
||||
case version.Deneb:
|
||||
withdrawals, _, err := headState.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head state expected withdrawals: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head state expected withdrawals")
|
||||
}
|
||||
parentRoot, err := headBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not get head block root: "+err.Error())
|
||||
return nil, errors.Wrap(err, "could not get head block root")
|
||||
}
|
||||
attributes = &structs.PayloadAttributesV3{
|
||||
Timestamp: fmt.Sprintf("%d", t.Unix()),
|
||||
@@ -449,13 +611,30 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
Withdrawals: structs.WithdrawalsFromConsensus(withdrawals),
|
||||
ParentBeaconBlockRoot: hexutil.Encode(parentRoot[:]),
|
||||
}
|
||||
case version.Electra:
|
||||
withdrawals, _, err := headState.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get head state expected withdrawals")
|
||||
}
|
||||
parentRoot, err := headBlock.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get head block root")
|
||||
}
|
||||
attributes = &structs.PayloadAttributesV4{
|
||||
Timestamp: fmt.Sprintf("%d", t.Unix()),
|
||||
PrevRandao: hexutil.Encode(prevRando),
|
||||
SuggestedFeeRecipient: hexutil.Encode(feeRecipient),
|
||||
Withdrawals: structs.WithdrawalsFromConsensus(withdrawals),
|
||||
ParentBeaconBlockRoot: hexutil.Encode(parentRoot[:]),
|
||||
// TODO: derive target and max from the payload
|
||||
}
|
||||
default:
|
||||
return write(w, flusher, "Payload version %s is not supported", version.String(headState.Version()))
|
||||
return nil, errors.Wrapf(err, "Payload version %s is not supported", version.String(headState.Version()))
|
||||
}
|
||||
|
||||
attributesBytes, err := json.Marshal(attributes)
|
||||
if err != nil {
|
||||
return write(w, flusher, err.Error())
|
||||
return nil, errors.Wrap(err, "errors marshaling payload attributes to json")
|
||||
}
|
||||
eventData := structs.PayloadAttributesEventData{
|
||||
ProposerIndex: fmt.Sprintf("%d", proposerIndex),
|
||||
@@ -467,31 +646,12 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
}
|
||||
eventDataBytes, err := json.Marshal(eventData)
|
||||
if err != nil {
|
||||
return write(w, flusher, err.Error())
|
||||
return nil, errors.Wrap(err, "errors marshaling payload attributes event data to json")
|
||||
}
|
||||
return send(w, flusher, PayloadAttributesTopic, &structs.PayloadAttributesEvent{
|
||||
Version: version.String(headState.Version()),
|
||||
Data: eventDataBytes,
|
||||
})
|
||||
}
|
||||
|
||||
func send(w http.ResponseWriter, flusher http.Flusher, name string, data interface{}) error {
|
||||
j, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return write(w, flusher, "Could not marshal event to JSON: "+err.Error())
|
||||
}
|
||||
return write(w, flusher, "event: %s\ndata: %s\n\n", name, string(j))
|
||||
}
|
||||
|
||||
func sendKeepalive(w http.ResponseWriter, flusher http.Flusher) error {
|
||||
return write(w, flusher, ":\n\n")
|
||||
}
|
||||
|
||||
func write(w http.ResponseWriter, flusher http.Flusher, format string, a ...any) error {
|
||||
_, err := fmt.Fprintf(w, format, a...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not write to response writer")
|
||||
}
|
||||
flusher.Flush()
|
||||
return nil
|
||||
return func() io.Reader {
|
||||
return jsonMarshalReader(PayloadAttributesTopic, &structs.PayloadAttributesEvent{
|
||||
Version: version.String(headState.Version()),
|
||||
Data: eventDataBytes,
|
||||
})
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
@@ -23,56 +24,99 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/eth/v1"
|
||||
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/assert"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
sse "github.com/r3labs/sse/v2"
|
||||
)
|
||||
|
||||
type flushableResponseRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
flushed bool
|
||||
func requireAllEventsReceived(t *testing.T, stn, opn *mockChain.EventFeedWrapper, events []*feed.Event, req *topicRequest, s *Server, w *StreamingResponseWriterRecorder) {
|
||||
// maxBufferSize param copied from sse lib client code
|
||||
sseR := sse.NewEventStreamReader(w.Body(), 1<<24)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
expected := make(map[string]bool)
|
||||
for i := range events {
|
||||
ev := events[i]
|
||||
// serialize the event the same way the server will so that we can compare expectation to results.
|
||||
top := topicForEvent(ev)
|
||||
eb, err := s.lazyReaderForEvent(context.Background(), ev, req)
|
||||
require.NoError(t, err)
|
||||
exb, err := io.ReadAll(eb())
|
||||
require.NoError(t, err)
|
||||
exs := string(exb[0 : len(exb)-2]) // remove trailing double newline
|
||||
|
||||
if topicsForOpsFeed[top] {
|
||||
if err := opn.WaitForSubscription(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Send the event on the feed.
|
||||
s.OperationNotifier.OperationFeed().Send(ev)
|
||||
} else {
|
||||
if err := stn.WaitForSubscription(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Send the event on the feed.
|
||||
s.StateNotifier.StateFeed().Send(ev)
|
||||
}
|
||||
expected[exs] = true
|
||||
}
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
ev, err := sseR.ReadEvent()
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
str := string(ev)
|
||||
delete(expected, str)
|
||||
if len(expected) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
break
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("context canceled / timed out waiting for events, err=%v", ctx.Err())
|
||||
}
|
||||
require.Equal(t, 0, len(expected), "expected events not seen")
|
||||
}
|
||||
|
||||
func (f *flushableResponseRecorder) Flush() {
|
||||
f.flushed = true
|
||||
func (tr *topicRequest) testHttpRequest(_ *testing.T) *http.Request {
|
||||
tq := make([]string, 0, len(tr.topics))
|
||||
for topic := range tr.topics {
|
||||
tq = append(tq, "topics="+topic)
|
||||
}
|
||||
return httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?%s", strings.Join(tq, "&")), nil)
|
||||
}
|
||||
|
||||
func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
t.Run("operations", func(t *testing.T) {
|
||||
s := &Server{
|
||||
StateNotifier: &mockChain.MockStateNotifier{},
|
||||
OperationNotifier: &mockChain.MockOperationNotifier{},
|
||||
}
|
||||
func operationEventsFixtures(t *testing.T) (*topicRequest, []*feed.Event) {
|
||||
topics, err := newTopicRequest([]string{
|
||||
AttestationTopic,
|
||||
VoluntaryExitTopic,
|
||||
SyncCommitteeContributionTopic,
|
||||
BLSToExecutionChangeTopic,
|
||||
BlobSidecarTopic,
|
||||
AttesterSlashingTopic,
|
||||
ProposerSlashingTopic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
ro, err := blocks.NewROBlob(util.HydrateBlobSidecar(ð.BlobSidecar{}))
|
||||
require.NoError(t, err)
|
||||
vblob := blocks.NewVerifiedROBlob(ro)
|
||||
|
||||
topics := []string{
|
||||
AttestationTopic,
|
||||
VoluntaryExitTopic,
|
||||
SyncCommitteeContributionTopic,
|
||||
BLSToExecutionChangeTopic,
|
||||
BlobSidecarTopic,
|
||||
AttesterSlashingTopic,
|
||||
ProposerSlashingTopic,
|
||||
}
|
||||
for i, topic := range topics {
|
||||
topics[i] = "topics=" + topic
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?%s", strings.Join(topics, "&")), nil)
|
||||
w := &flushableResponseRecorder{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.StreamEvents(w, request)
|
||||
}()
|
||||
// wait for initiation of StreamEvents
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
return topics, []*feed.Event{
|
||||
&feed.Event{
|
||||
Type: operation.UnaggregatedAttReceived,
|
||||
Data: &operation.UnAggregatedAttReceivedData{
|
||||
Attestation: util.HydrateAttestation(ð.Attestation{}),
|
||||
},
|
||||
})
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.AggregatedAttReceived,
|
||||
Data: &operation.AggregatedAttReceivedData{
|
||||
Attestation: ð.AggregateAttestationAndProof{
|
||||
@@ -81,8 +125,8 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
SelectionProof: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
})
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.ExitReceived,
|
||||
Data: &operation.ExitReceivedData{
|
||||
Exit: ð.SignedVoluntaryExit{
|
||||
@@ -93,8 +137,8 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
})
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.SyncCommitteeContributionReceived,
|
||||
Data: &operation.SyncCommitteeContributionReceivedData{
|
||||
Contribution: ð.SignedContributionAndProof{
|
||||
@@ -112,8 +156,8 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
})
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.BLSToExecutionChangeReceived,
|
||||
Data: &operation.BLSToExecutionChangeReceivedData{
|
||||
Change: ð.SignedBLSToExecutionChange{
|
||||
@@ -125,18 +169,14 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
})
|
||||
ro, err := blocks.NewROBlob(util.HydrateBlobSidecar(ð.BlobSidecar{}))
|
||||
require.NoError(t, err)
|
||||
vblob := blocks.NewVerifiedROBlob(ro)
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.BlobSidecarReceived,
|
||||
Data: &operation.BlobSidecarReceivedData{
|
||||
Blob: &vblob,
|
||||
},
|
||||
})
|
||||
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.AttesterSlashingReceived,
|
||||
Data: &operation.AttesterSlashingReceivedData{
|
||||
AttesterSlashing: ð.AttesterSlashing{
|
||||
@@ -168,9 +208,8 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
s.OperationNotifier.OperationFeed().Send(&feed.Event{
|
||||
},
|
||||
&feed.Event{
|
||||
Type: operation.ProposerSlashingReceived,
|
||||
Data: &operation.ProposerSlashingReceivedData{
|
||||
ProposerSlashing: ð.ProposerSlashing{
|
||||
@@ -192,100 +231,107 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
request.Context().Done()
|
||||
func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
t.Run("operations", func(t *testing.T) {
|
||||
stn := mockChain.NewEventFeedWrapper()
|
||||
opn := mockChain.NewEventFeedWrapper()
|
||||
s := &Server{
|
||||
StateNotifier: &mockChain.SimpleNotifier{Feed: stn},
|
||||
OperationNotifier: &mockChain.SimpleNotifier{Feed: opn},
|
||||
}
|
||||
|
||||
resp := w.Result()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, body)
|
||||
assert.Equal(t, operationsResult, string(body))
|
||||
topics, events := operationEventsFixtures(t)
|
||||
request := topics.testHttpRequest(t)
|
||||
w := NewStreamingResponseWriterRecorder()
|
||||
|
||||
go func() {
|
||||
s.StreamEvents(w, request)
|
||||
}()
|
||||
|
||||
requireAllEventsReceived(t, stn, opn, events, topics, s, w)
|
||||
})
|
||||
t.Run("state", func(t *testing.T) {
|
||||
stn := mockChain.NewEventFeedWrapper()
|
||||
opn := mockChain.NewEventFeedWrapper()
|
||||
s := &Server{
|
||||
StateNotifier: &mockChain.MockStateNotifier{},
|
||||
OperationNotifier: &mockChain.MockOperationNotifier{},
|
||||
StateNotifier: &mockChain.SimpleNotifier{Feed: stn},
|
||||
OperationNotifier: &mockChain.SimpleNotifier{Feed: opn},
|
||||
}
|
||||
|
||||
topics := []string{HeadTopic, FinalizedCheckpointTopic, ChainReorgTopic, BlockTopic}
|
||||
for i, topic := range topics {
|
||||
topics[i] = "topics=" + topic
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?%s", strings.Join(topics, "&")), nil)
|
||||
w := &flushableResponseRecorder{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
topics, err := newTopicRequest([]string{
|
||||
HeadTopic,
|
||||
FinalizedCheckpointTopic,
|
||||
ChainReorgTopic,
|
||||
BlockTopic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
request := topics.testHttpRequest(t)
|
||||
w := NewStreamingResponseWriterRecorder()
|
||||
|
||||
b, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlock(ð.SignedBeaconBlock{}))
|
||||
require.NoError(t, err)
|
||||
events := []*feed.Event{
|
||||
&feed.Event{
|
||||
Type: statefeed.BlockProcessed,
|
||||
Data: &statefeed.BlockProcessedData{
|
||||
Slot: 0,
|
||||
BlockRoot: [32]byte{},
|
||||
SignedBlock: b,
|
||||
Verified: true,
|
||||
Optimistic: false,
|
||||
},
|
||||
},
|
||||
&feed.Event{
|
||||
Type: statefeed.NewHead,
|
||||
Data: ðpb.EventHead{
|
||||
Slot: 0,
|
||||
Block: make([]byte, 32),
|
||||
State: make([]byte, 32),
|
||||
EpochTransition: true,
|
||||
PreviousDutyDependentRoot: make([]byte, 32),
|
||||
CurrentDutyDependentRoot: make([]byte, 32),
|
||||
ExecutionOptimistic: false,
|
||||
},
|
||||
},
|
||||
&feed.Event{
|
||||
Type: statefeed.Reorg,
|
||||
Data: ðpb.EventChainReorg{
|
||||
Slot: 0,
|
||||
Depth: 0,
|
||||
OldHeadBlock: make([]byte, 32),
|
||||
NewHeadBlock: make([]byte, 32),
|
||||
OldHeadState: make([]byte, 32),
|
||||
NewHeadState: make([]byte, 32),
|
||||
Epoch: 0,
|
||||
ExecutionOptimistic: false,
|
||||
},
|
||||
},
|
||||
&feed.Event{
|
||||
Type: statefeed.FinalizedCheckpoint,
|
||||
Data: ðpb.EventFinalizedCheckpoint{
|
||||
Block: make([]byte, 32),
|
||||
State: make([]byte, 32),
|
||||
Epoch: 0,
|
||||
ExecutionOptimistic: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
go func() {
|
||||
s.StreamEvents(w, request)
|
||||
}()
|
||||
// wait for initiation of StreamEvents
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
s.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.NewHead,
|
||||
Data: ðpb.EventHead{
|
||||
Slot: 0,
|
||||
Block: make([]byte, 32),
|
||||
State: make([]byte, 32),
|
||||
EpochTransition: true,
|
||||
PreviousDutyDependentRoot: make([]byte, 32),
|
||||
CurrentDutyDependentRoot: make([]byte, 32),
|
||||
ExecutionOptimistic: false,
|
||||
},
|
||||
})
|
||||
s.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.FinalizedCheckpoint,
|
||||
Data: ðpb.EventFinalizedCheckpoint{
|
||||
Block: make([]byte, 32),
|
||||
State: make([]byte, 32),
|
||||
Epoch: 0,
|
||||
ExecutionOptimistic: false,
|
||||
},
|
||||
})
|
||||
s.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.Reorg,
|
||||
Data: ðpb.EventChainReorg{
|
||||
Slot: 0,
|
||||
Depth: 0,
|
||||
OldHeadBlock: make([]byte, 32),
|
||||
NewHeadBlock: make([]byte, 32),
|
||||
OldHeadState: make([]byte, 32),
|
||||
NewHeadState: make([]byte, 32),
|
||||
Epoch: 0,
|
||||
ExecutionOptimistic: false,
|
||||
},
|
||||
})
|
||||
b, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlock(ð.SignedBeaconBlock{}))
|
||||
require.NoError(t, err)
|
||||
s.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.BlockProcessed,
|
||||
Data: &statefeed.BlockProcessedData{
|
||||
Slot: 0,
|
||||
BlockRoot: [32]byte{},
|
||||
SignedBlock: b,
|
||||
Verified: true,
|
||||
Optimistic: false,
|
||||
},
|
||||
})
|
||||
|
||||
// wait for feed
|
||||
time.Sleep(1 * time.Second)
|
||||
request.Context().Done()
|
||||
|
||||
resp := w.Result()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, body)
|
||||
assert.Equal(t, stateResult, string(body))
|
||||
requireAllEventsReceived(t, stn, opn, events, topics, s, w)
|
||||
})
|
||||
t.Run("payload attributes", func(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
getState func() state.BeaconState
|
||||
getBlock func() interfaces.SignedBeaconBlock
|
||||
expected string
|
||||
SetTrackedValidatorsCache func(*cache.TrackedValidatorsCache)
|
||||
}
|
||||
testCases := []testCase{
|
||||
@@ -301,7 +347,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
},
|
||||
expected: payloadAttributesBellatrixResult,
|
||||
},
|
||||
{
|
||||
name: "capella",
|
||||
@@ -315,7 +360,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
},
|
||||
expected: payloadAttributesCapellaResult,
|
||||
},
|
||||
{
|
||||
name: "deneb",
|
||||
@@ -329,7 +373,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
},
|
||||
expected: payloadAttributesDenebResult,
|
||||
},
|
||||
{
|
||||
name: "electra",
|
||||
@@ -343,7 +386,6 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
},
|
||||
expected: payloadAttributesElectraResultWithTVC,
|
||||
SetTrackedValidatorsCache: func(c *cache.TrackedValidatorsCache) {
|
||||
c.Set(cache.TrackedValidator{
|
||||
Active: true,
|
||||
@@ -368,9 +410,11 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
Slot: ¤tSlot,
|
||||
}
|
||||
|
||||
stn := mockChain.NewEventFeedWrapper()
|
||||
opn := mockChain.NewEventFeedWrapper()
|
||||
s := &Server{
|
||||
StateNotifier: &mockChain.MockStateNotifier{},
|
||||
OperationNotifier: &mockChain.MockOperationNotifier{},
|
||||
StateNotifier: &mockChain.SimpleNotifier{Feed: stn},
|
||||
OperationNotifier: &mockChain.SimpleNotifier{Feed: opn},
|
||||
HeadFetcher: mockChainService,
|
||||
ChainInfoFetcher: mockChainService,
|
||||
TrackedValidatorsCache: cache.NewTrackedValidatorsCache(),
|
||||
@@ -378,100 +422,76 @@ func TestStreamEvents_OperationsEvents(t *testing.T) {
|
||||
if tc.SetTrackedValidatorsCache != nil {
|
||||
tc.SetTrackedValidatorsCache(s.TrackedValidatorsCache)
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://example.com/eth/v1/events?topics=%s", PayloadAttributesTopic), nil)
|
||||
w := &flushableResponseRecorder{
|
||||
ResponseRecorder: httptest.NewRecorder(),
|
||||
}
|
||||
topics, err := newTopicRequest([]string{PayloadAttributesTopic})
|
||||
require.NoError(t, err)
|
||||
request := topics.testHttpRequest(t)
|
||||
w := NewStreamingResponseWriterRecorder()
|
||||
events := []*feed.Event{&feed.Event{Type: statefeed.MissedSlot}}
|
||||
|
||||
go func() {
|
||||
s.StreamEvents(w, request)
|
||||
}()
|
||||
// wait for initiation of StreamEvents
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
s.StateNotifier.StateFeed().Send(&feed.Event{Type: statefeed.MissedSlot})
|
||||
|
||||
// wait for feed
|
||||
time.Sleep(1 * time.Second)
|
||||
request.Context().Done()
|
||||
|
||||
resp := w.Result()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, body)
|
||||
assert.Equal(t, tc.expected, string(body), "wrong result for "+tc.name)
|
||||
requireAllEventsReceived(t, stn, opn, events, topics, s, w)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const operationsResult = `:
|
||||
func TestStuckReader(t *testing.T) {
|
||||
topics, events := operationEventsFixtures(t)
|
||||
require.Equal(t, 8, len(events))
|
||||
// set eventFeedDepth to a number lower than the events we intend to send to force the server to drop the reader.
|
||||
stn := mockChain.NewEventFeedWrapper()
|
||||
opn := mockChain.NewEventFeedWrapper()
|
||||
s := &Server{
|
||||
StateNotifier: &mockChain.SimpleNotifier{Feed: stn},
|
||||
OperationNotifier: &mockChain.SimpleNotifier{Feed: opn},
|
||||
EventFeedDepth: len(events) - 1,
|
||||
}
|
||||
|
||||
event: attestation
|
||||
data: {"aggregation_bits":"0x00","data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
eventsWritten := make(chan struct{})
|
||||
go func() {
|
||||
for i := range events {
|
||||
ev := events[i]
|
||||
top := topicForEvent(ev)
|
||||
if topicsForOpsFeed[top] {
|
||||
err := opn.WaitForSubscription(ctx)
|
||||
require.NoError(t, err)
|
||||
s.OperationNotifier.OperationFeed().Send(ev)
|
||||
} else {
|
||||
err := stn.WaitForSubscription(ctx)
|
||||
require.NoError(t, err)
|
||||
s.StateNotifier.StateFeed().Send(ev)
|
||||
}
|
||||
}
|
||||
close(eventsWritten)
|
||||
}()
|
||||
|
||||
event: attestation
|
||||
data: {"aggregation_bits":"0x00","data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
|
||||
request := topics.testHttpRequest(t)
|
||||
w := NewStreamingResponseWriterRecorder()
|
||||
|
||||
event: voluntary_exit
|
||||
data: {"message":{"epoch":"0","validator_index":"0"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
|
||||
handlerFinished := make(chan struct{})
|
||||
go func() {
|
||||
s.StreamEvents(w, request)
|
||||
close(handlerFinished)
|
||||
}()
|
||||
|
||||
event: contribution_and_proof
|
||||
data: {"message":{"aggregator_index":"0","contribution":{"slot":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","subcommittee_index":"0","aggregation_bits":"0x00000000000000000000000000000000","signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"selection_proof":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
|
||||
// Make sure that the stream writer shut down when the reader failed to clear the write buffer.
|
||||
select {
|
||||
case <-handlerFinished:
|
||||
// We expect the stream handler to max out the queue buffer and exit gracefully.
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("context canceled / timed out waiting for handler completion, err=%v", ctx.Err())
|
||||
}
|
||||
|
||||
event: bls_to_execution_change
|
||||
data: {"message":{"validator_index":"0","from_bls_pubkey":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","to_execution_address":"0x0000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}
|
||||
|
||||
event: blob_sidecar
|
||||
data: {"block_root":"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c","index":"0","slot":"0","kzg_commitment":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","versioned_hash":"0x01b0761f87b081d5cf10757ccc89f12be355c70e2e29df288b65b30710dcbcd1"}
|
||||
|
||||
event: attester_slashing
|
||||
data: {"attestation_1":{"attesting_indices":["0","1"],"data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"attestation_2":{"attesting_indices":["0","1"],"data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"0","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}
|
||||
|
||||
event: proposer_slashing
|
||||
data: {"signed_header_1":{"message":{"slot":"0","proposer_index":"0","parent_root":"0x0000000000000000000000000000000000000000000000000000000000000000","state_root":"0x0000000000000000000000000000000000000000000000000000000000000000","body_root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"signed_header_2":{"message":{"slot":"0","proposer_index":"0","parent_root":"0x0000000000000000000000000000000000000000000000000000000000000000","state_root":"0x0000000000000000000000000000000000000000000000000000000000000000","body_root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"signature":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}
|
||||
|
||||
`
|
||||
|
||||
const stateResult = `:
|
||||
|
||||
event: head
|
||||
data: {"slot":"0","block":"0x0000000000000000000000000000000000000000000000000000000000000000","state":"0x0000000000000000000000000000000000000000000000000000000000000000","epoch_transition":true,"execution_optimistic":false,"previous_duty_dependent_root":"0x0000000000000000000000000000000000000000000000000000000000000000","current_duty_dependent_root":"0x0000000000000000000000000000000000000000000000000000000000000000"}
|
||||
|
||||
event: finalized_checkpoint
|
||||
data: {"block":"0x0000000000000000000000000000000000000000000000000000000000000000","state":"0x0000000000000000000000000000000000000000000000000000000000000000","epoch":"0","execution_optimistic":false}
|
||||
|
||||
event: chain_reorg
|
||||
data: {"slot":"0","depth":"0","old_head_block":"0x0000000000000000000000000000000000000000000000000000000000000000","old_head_state":"0x0000000000000000000000000000000000000000000000000000000000000000","new_head_block":"0x0000000000000000000000000000000000000000000000000000000000000000","new_head_state":"0x0000000000000000000000000000000000000000000000000000000000000000","epoch":"0","execution_optimistic":false}
|
||||
|
||||
event: block
|
||||
data: {"slot":"0","block":"0xeade62f0457b2fdf48e7d3fc4b60736688286be7c7a3ac4c9a16a5e0600bd9e4","execution_optimistic":false}
|
||||
|
||||
`
|
||||
|
||||
const payloadAttributesBellatrixResult = `:
|
||||
|
||||
event: payload_attributes
|
||||
data: {"version":"bellatrix","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000"}}}
|
||||
|
||||
`
|
||||
|
||||
const payloadAttributesCapellaResult = `:
|
||||
|
||||
event: payload_attributes
|
||||
data: {"version":"capella","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000","withdrawals":[]}}}
|
||||
|
||||
`
|
||||
|
||||
const payloadAttributesDenebResult = `:
|
||||
|
||||
event: payload_attributes
|
||||
data: {"version":"deneb","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0x0000000000000000000000000000000000000000","withdrawals":[],"parent_beacon_block_root":"0xbef96cb938fd48b2403d3e662664325abb0102ed12737cbb80d717520e50cf4a"}}}
|
||||
|
||||
`
|
||||
|
||||
const payloadAttributesElectraResultWithTVC = `:
|
||||
|
||||
event: payload_attributes
|
||||
data: {"version":"electra","data":{"proposer_index":"0","proposal_slot":"1","parent_block_number":"0","parent_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","parent_block_hash":"0x0000000000000000000000000000000000000000000000000000000000000000","payload_attributes":{"timestamp":"12","prev_randao":"0x0000000000000000000000000000000000000000000000000000000000000000","suggested_fee_recipient":"0xd2dbd02e4efe087d7d195de828b9dd25f19a89c9","withdrawals":[],"parent_beacon_block_root":"0x66d641f7eae038f2dd28081b09d2ba279462cc47655c7b7e1fd1159a50c8eb32"}}}
|
||||
|
||||
`
|
||||
// Also make sure all the events were written.
|
||||
select {
|
||||
case <-eventsWritten:
|
||||
// We expect the stream handler to max out the queue buffer and exit gracefully.
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("context canceled / timed out waiting to write all events, err=%v", ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
75
beacon-chain/rpc/eth/events/http_test.go
Normal file
75
beacon-chain/rpc/eth/events/http_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
type StreamingResponseWriterRecorder struct {
|
||||
http.ResponseWriter
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
statusWritten *int
|
||||
status chan int
|
||||
bodyRecording []byte
|
||||
flushed bool
|
||||
}
|
||||
|
||||
func (w *StreamingResponseWriterRecorder) StatusChan() chan int {
|
||||
return w.status
|
||||
}
|
||||
|
||||
func NewStreamingResponseWriterRecorder() *StreamingResponseWriterRecorder {
|
||||
r, w := io.Pipe()
|
||||
return &StreamingResponseWriterRecorder{
|
||||
ResponseWriter: httptest.NewRecorder(),
|
||||
r: r,
|
||||
w: w,
|
||||
status: make(chan int, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter.
|
||||
func (w *StreamingResponseWriterRecorder) Write(data []byte) (int, error) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
n, err := w.w.Write(data)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
// WriteHeader implements http.ResponseWriter.
|
||||
func (w *StreamingResponseWriterRecorder) WriteHeader(statusCode int) {
|
||||
if w.statusWritten != nil {
|
||||
return
|
||||
}
|
||||
w.statusWritten = &statusCode
|
||||
w.status <- statusCode
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *StreamingResponseWriterRecorder) Body() io.Reader {
|
||||
return w.r
|
||||
}
|
||||
|
||||
func (w *StreamingResponseWriterRecorder) RequireStatus(t *testing.T, status int) {
|
||||
if w.statusWritten == nil {
|
||||
t.Fatal("WriteHeader was not called")
|
||||
}
|
||||
require.Equal(t, status, *w.statusWritten)
|
||||
}
|
||||
|
||||
func (w *StreamingResponseWriterRecorder) Flush() {
|
||||
fw, ok := w.ResponseWriter.(http.Flusher)
|
||||
if ok {
|
||||
fw.Flush()
|
||||
}
|
||||
w.flushed = true
|
||||
}
|
||||
|
||||
var _ http.ResponseWriter = &StreamingResponseWriterRecorder{}
|
||||
@@ -4,6 +4,8 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
|
||||
opfeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/operation"
|
||||
@@ -18,4 +20,6 @@ type Server struct {
|
||||
HeadFetcher blockchain.HeadFetcher
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
KeepAliveInterval time.Duration
|
||||
EventFeedDepth int
|
||||
}
|
||||
|
||||
@@ -20,14 +20,12 @@ go_library(
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/ssz:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/migration:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
@@ -55,7 +53,6 @@ go_test(
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
|
||||
@@ -47,7 +47,7 @@ func (s *Server) GetLightClientBootstrap(w http.ResponseWriter, req *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
bootstrap, err := createLightClientBootstrap(ctx, state, blk.Block())
|
||||
bootstrap, err := createLightClientBootstrap(ctx, state, blk)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "could not get light client bootstrap: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -204,6 +204,7 @@ func (s *Server) GetLightClientUpdatesByRange(w http.ResponseWriter, req *http.R
|
||||
state,
|
||||
block,
|
||||
attestedState,
|
||||
attestedBlock,
|
||||
finalizedBlock,
|
||||
)
|
||||
|
||||
@@ -267,7 +268,7 @@ func (s *Server) GetLightClientFinalityUpdate(w http.ResponseWriter, req *http.R
|
||||
return
|
||||
}
|
||||
|
||||
update, err := newLightClientFinalityUpdateFromBeaconState(ctx, st, block, attestedState, finalizedBlock)
|
||||
update, err := newLightClientFinalityUpdateFromBeaconState(ctx, st, block, attestedState, attestedBlock, finalizedBlock)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get light client finality update: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -312,7 +313,7 @@ func (s *Server) GetLightClientOptimisticUpdate(w http.ResponseWriter, req *http
|
||||
return
|
||||
}
|
||||
|
||||
update, err := newLightClientOptimisticUpdateFromBeaconState(ctx, st, block, attestedState)
|
||||
update, err := newLightClientOptimisticUpdateFromBeaconState(ctx, st, block, attestedState, attestedBlock)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not get light client optimistic update: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -19,49 +20,29 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
)
|
||||
|
||||
func TestLightClientHandler_GetLightClientBootstrap_Altair(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
slot := primitives.Slot(params.BeaconConfig().AltairForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
|
||||
b := util.NewBeaconBlockAltair()
|
||||
b.Block.StateRoot = bytesutil.PadTo([]byte("foo"), 32)
|
||||
b.Block.Slot = slot
|
||||
|
||||
signedBlock, err := blocks.NewSignedBeaconBlock(b)
|
||||
|
||||
require.NoError(t, err)
|
||||
header, err := signedBlock.Header()
|
||||
slot := l.State.Slot()
|
||||
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
bs, err := util.NewBeaconStateAltair(func(state *ethpb.BeaconStateAltair) error {
|
||||
state.BlockRoots[0] = r[:]
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, bs.SetSlot(slot))
|
||||
require.NoError(t, bs.SetLatestBlockHeader(header.Header))
|
||||
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: signedBlock}
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
|
||||
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
slot: bs,
|
||||
slot: l.State,
|
||||
}},
|
||||
Blocker: mockBlocker,
|
||||
HeadFetcher: mockChainService,
|
||||
}
|
||||
request := httptest.NewRequest("GET", "http://foo.com/", nil)
|
||||
request.SetPathValue("block_root", hexutil.Encode(r[:]))
|
||||
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
@@ -74,47 +55,74 @@ func TestLightClientHandler_GetLightClientBootstrap_Altair(t *testing.T) {
|
||||
err = json.Unmarshal(resp.Data.Header, &respHeader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "altair", resp.Version)
|
||||
require.Equal(t, hexutil.Encode(header.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.NotNil(t, resp.Data)
|
||||
|
||||
blockHeader, err := l.Block.Header()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
|
||||
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommittee)
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
func TestLightClientHandler_GetLightClientBootstrap_Bellatrix(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestBellatrix()
|
||||
|
||||
slot := l.State.Slot()
|
||||
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
|
||||
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
slot: l.State,
|
||||
}},
|
||||
Blocker: mockBlocker,
|
||||
HeadFetcher: mockChainService,
|
||||
}
|
||||
request := httptest.NewRequest("GET", "http://foo.com/", nil)
|
||||
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetLightClientBootstrap(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
var resp structs.LightClientBootstrapResponse
|
||||
err = json.Unmarshal(writer.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
var respHeader structs.LightClientHeader
|
||||
err = json.Unmarshal(resp.Data.Header, &respHeader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "bellatrix", resp.Version)
|
||||
|
||||
blockHeader, err := l.Block.Header()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
|
||||
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommittee)
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
slot := primitives.Slot(params.BeaconConfig().CapellaForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false) // result is same for true and false
|
||||
|
||||
b := util.NewBeaconBlockCapella()
|
||||
b.Block.StateRoot = bytesutil.PadTo([]byte("foo"), 32)
|
||||
b.Block.Slot = slot
|
||||
|
||||
signedBlock, err := blocks.NewSignedBeaconBlock(b)
|
||||
|
||||
require.NoError(t, err)
|
||||
header, err := signedBlock.Header()
|
||||
slot := l.State.Slot()
|
||||
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
bs, err := util.NewBeaconStateCapella(func(state *ethpb.BeaconStateCapella) error {
|
||||
state.BlockRoots[0] = r[:]
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, bs.SetSlot(slot))
|
||||
require.NoError(t, bs.SetLatestBlockHeader(header.Header))
|
||||
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: signedBlock}
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
|
||||
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
slot: bs,
|
||||
slot: l.State,
|
||||
}},
|
||||
Blocker: mockBlocker,
|
||||
HeadFetcher: mockChainService,
|
||||
}
|
||||
request := httptest.NewRequest("GET", "http://foo.com/", nil)
|
||||
request.SetPathValue("block_root", hexutil.Encode(r[:]))
|
||||
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
@@ -123,51 +131,38 @@ func TestLightClientHandler_GetLightClientBootstrap_Capella(t *testing.T) {
|
||||
var resp structs.LightClientBootstrapResponse
|
||||
err = json.Unmarshal(writer.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
var respHeader structs.LightClientHeaderCapella
|
||||
var respHeader structs.LightClientHeader
|
||||
err = json.Unmarshal(resp.Data.Header, &respHeader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "capella", resp.Version)
|
||||
require.Equal(t, hexutil.Encode(header.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.NotNil(t, resp.Data)
|
||||
|
||||
blockHeader, err := l.Block.Header()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
|
||||
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommittee)
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
func TestLightClientHandler_GetLightClientBootstrap_Deneb(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
slot := primitives.Slot(params.BeaconConfig().DenebForkEpoch * primitives.Epoch(params.BeaconConfig().SlotsPerEpoch)).Add(1)
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false) // result is same for true and false
|
||||
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
b.Block.StateRoot = bytesutil.PadTo([]byte("foo"), 32)
|
||||
b.Block.Slot = slot
|
||||
|
||||
signedBlock, err := blocks.NewSignedBeaconBlock(b)
|
||||
|
||||
require.NoError(t, err)
|
||||
header, err := signedBlock.Header()
|
||||
slot := l.State.Slot()
|
||||
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
bs, err := util.NewBeaconStateDeneb(func(state *ethpb.BeaconStateDeneb) error {
|
||||
state.BlockRoots[0] = r[:]
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, bs.SetSlot(slot))
|
||||
require.NoError(t, bs.SetLatestBlockHeader(header.Header))
|
||||
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: signedBlock}
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
|
||||
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
slot: bs,
|
||||
slot: l.State,
|
||||
}},
|
||||
Blocker: mockBlocker,
|
||||
HeadFetcher: mockChainService,
|
||||
}
|
||||
request := httptest.NewRequest("GET", "http://foo.com/", nil)
|
||||
request.SetPathValue("block_root", hexutil.Encode(r[:]))
|
||||
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
@@ -176,12 +171,58 @@ func TestLightClientHandler_GetLightClientBootstrap_Deneb(t *testing.T) {
|
||||
var resp structs.LightClientBootstrapResponse
|
||||
err = json.Unmarshal(writer.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
var respHeader structs.LightClientHeaderDeneb
|
||||
var respHeader structs.LightClientHeader
|
||||
err = json.Unmarshal(resp.Data.Header, &respHeader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "deneb", resp.Version)
|
||||
require.Equal(t, hexutil.Encode(header.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.NotNil(t, resp.Data)
|
||||
|
||||
blockHeader, err := l.Block.Header()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
|
||||
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommittee)
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
func TestLightClientHandler_GetLightClientBootstrap_Electra(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestElectra(false) // result is same for true and false
|
||||
|
||||
slot := l.State.Slot()
|
||||
stateRoot, err := l.State.HashTreeRoot(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
mockBlocker := &testutil.MockBlocker{BlockToReturn: l.Block}
|
||||
mockChainService := &mock.ChainService{Optimistic: true, Slot: &slot}
|
||||
s := &Server{
|
||||
Stater: &testutil.MockStater{StatesBySlot: map[primitives.Slot]state.BeaconState{
|
||||
slot: l.State,
|
||||
}},
|
||||
Blocker: mockBlocker,
|
||||
HeadFetcher: mockChainService,
|
||||
}
|
||||
request := httptest.NewRequest("GET", "http://foo.com/", nil)
|
||||
request.SetPathValue("block_root", hexutil.Encode(stateRoot[:]))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
|
||||
s.GetLightClientBootstrap(writer, request)
|
||||
require.Equal(t, http.StatusOK, writer.Code)
|
||||
var resp structs.LightClientBootstrapResponse
|
||||
err = json.Unmarshal(writer.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
var respHeader structs.LightClientHeader
|
||||
err = json.Unmarshal(resp.Data.Header, &respHeader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "electra", resp.Version)
|
||||
|
||||
blockHeader, err := l.Block.Header()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hexutil.Encode(blockHeader.Header.BodyRoot), respHeader.Beacon.BodyRoot)
|
||||
require.Equal(t, strconv.FormatUint(uint64(blockHeader.Header.Slot), 10), respHeader.Beacon.Slot)
|
||||
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommittee)
|
||||
require.NotNil(t, resp.Data.CurrentSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
func TestLightClientHandler_GetLightClientUpdatesByRangeAltair(t *testing.T) {
|
||||
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/proto/migration"
|
||||
|
||||
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
|
||||
consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@@ -18,18 +17,17 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
v2 "github.com/prysmaticlabs/prysm/v5/proto/eth/v2"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func createLightClientBootstrap(ctx context.Context, state state.BeaconState, blk interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
func createLightClientBootstrap(ctx context.Context, state state.BeaconState, blk interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
switch blk.Version() {
|
||||
case version.Phase0:
|
||||
return nil, fmt.Errorf("light client bootstrap is not supported for phase0")
|
||||
case version.Altair, version.Bellatrix:
|
||||
return createLightClientBootstrapAltair(ctx, state)
|
||||
return createLightClientBootstrapAltair(ctx, state, blk)
|
||||
case version.Capella:
|
||||
return createLightClientBootstrapCapella(ctx, state, blk)
|
||||
case version.Deneb, version.Electra:
|
||||
@@ -38,24 +36,7 @@ func createLightClientBootstrap(ctx context.Context, state state.BeaconState, bl
|
||||
return nil, fmt.Errorf("unsupported block version %s", version.String(blk.Version()))
|
||||
}
|
||||
|
||||
// createLightClientBootstrapAltair - implements https://github.com/ethereum/consensus-specs/blob/3d235740e5f1e641d3b160c8688f26e7dc5a1894/specs/altair/light-client/full-node.md#create_light_client_bootstrap
|
||||
// def create_light_client_bootstrap(state: BeaconState) -> LightClientBootstrap:
|
||||
//
|
||||
// assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
|
||||
// assert state.slot == state.latest_block_header.slot
|
||||
//
|
||||
// return LightClientBootstrap(
|
||||
// header=BeaconBlockHeader(
|
||||
// slot=state.latest_block_header.slot,
|
||||
// proposer_index=state.latest_block_header.proposer_index,
|
||||
// parent_root=state.latest_block_header.parent_root,
|
||||
// state_root=hash_tree_root(state),
|
||||
// body_root=state.latest_block_header.body_root,
|
||||
// ),
|
||||
// current_sync_committee=state.current_sync_committee,
|
||||
// current_sync_committee_branch=compute_merkle_proof_for_state(state, CURRENT_SYNC_COMMITTEE_INDEX)
|
||||
// )
|
||||
func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconState) (*structs.LightClientBootstrap, error) {
|
||||
func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
// assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
|
||||
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().AltairForkEpoch {
|
||||
return nil, fmt.Errorf("light client bootstrap is not supported before Altair, invalid slot %d", state.Slot())
|
||||
@@ -67,55 +48,63 @@ func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconSta
|
||||
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
// header.state_root = hash_tree_root(state)
|
||||
stateRoot, err := state.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get state root")
|
||||
}
|
||||
latestBlockHeader.StateRoot = stateRoot[:]
|
||||
|
||||
// assert hash_tree_root(header) == hash_tree_root(block.message)
|
||||
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get latest block header root")
|
||||
}
|
||||
beaconBlockRoot, err := block.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
if latestBlockHeaderRoot != beaconBlockRoot {
|
||||
return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot)
|
||||
}
|
||||
|
||||
lightClientHeaderContainer, err := lightclient.BlockToLightClientHeader(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert block to light client header")
|
||||
}
|
||||
lightClientHeader := lightClientHeaderContainer.GetHeaderAltair()
|
||||
|
||||
apiLightClientHeader := &structs.LightClientHeader{
|
||||
Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)),
|
||||
}
|
||||
|
||||
headerJSON, err := json.Marshal(apiLightClientHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert header to raw message")
|
||||
}
|
||||
currentSyncCommittee, err := state.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee")
|
||||
}
|
||||
|
||||
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
|
||||
|
||||
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee proof")
|
||||
}
|
||||
|
||||
branch := make([]string, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
branch := make([]string, fieldparams.SyncCommitteeBranchDepth)
|
||||
for i, proof := range currentSyncCommitteeProof {
|
||||
branch[i] = hexutil.Encode(proof)
|
||||
}
|
||||
|
||||
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
|
||||
if beacon == nil {
|
||||
return nil, fmt.Errorf("could not get beacon block header")
|
||||
}
|
||||
header := &structs.LightClientHeader{
|
||||
Beacon: beacon,
|
||||
}
|
||||
|
||||
// Above shared util function won't calculate state root, so we need to do it manually
|
||||
stateRoot, err := state.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get state root")
|
||||
}
|
||||
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
|
||||
|
||||
headerJson, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert header to raw message")
|
||||
}
|
||||
|
||||
// Return result
|
||||
result := &structs.LightClientBootstrap{
|
||||
Header: headerJson,
|
||||
CurrentSyncCommittee: committee,
|
||||
Header: headerJSON,
|
||||
CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee),
|
||||
CurrentSyncCommitteeBranch: branch,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
// assert compute_epoch_at_slot(state.slot) >= CAPELLA_FORK_EPOCH
|
||||
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().CapellaForkEpoch {
|
||||
return nil, fmt.Errorf("creating Capella light client bootstrap is not supported before Capella, invalid slot %d", state.Slot())
|
||||
@@ -127,111 +116,63 @@ func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconSt
|
||||
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
// header.state_root = hash_tree_root(state)
|
||||
stateRoot, err := state.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get state root")
|
||||
}
|
||||
latestBlockHeader.StateRoot = stateRoot[:]
|
||||
|
||||
// assert hash_tree_root(header) == hash_tree_root(block.message)
|
||||
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get latest block header root")
|
||||
}
|
||||
beaconBlockRoot, err := block.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
if latestBlockHeaderRoot != beaconBlockRoot {
|
||||
return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot)
|
||||
}
|
||||
|
||||
lightClientHeaderContainer, err := lightclient.BlockToLightClientHeader(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert block to light client header")
|
||||
}
|
||||
lightClientHeader := lightClientHeaderContainer.GetHeaderCapella()
|
||||
|
||||
apiLightClientHeader := &structs.LightClientHeader{
|
||||
Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)),
|
||||
}
|
||||
|
||||
headerJSON, err := json.Marshal(apiLightClientHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert header to raw message")
|
||||
}
|
||||
currentSyncCommittee, err := state.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee")
|
||||
}
|
||||
|
||||
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
|
||||
|
||||
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee proof")
|
||||
}
|
||||
|
||||
branch := make([]string, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
branch := make([]string, fieldparams.SyncCommitteeBranchDepth)
|
||||
for i, proof := range currentSyncCommitteeProof {
|
||||
branch[i] = hexutil.Encode(proof)
|
||||
}
|
||||
|
||||
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
|
||||
|
||||
payloadInterface, err := block.Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload")
|
||||
}
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions")
|
||||
}
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals")
|
||||
}
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
}
|
||||
executionPayloadHeader := &structs.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: hexutil.Encode(payloadInterface.ParentHash()),
|
||||
FeeRecipient: hexutil.Encode(payloadInterface.FeeRecipient()),
|
||||
StateRoot: hexutil.Encode(payloadInterface.StateRoot()),
|
||||
ReceiptsRoot: hexutil.Encode(payloadInterface.ReceiptsRoot()),
|
||||
LogsBloom: hexutil.Encode(payloadInterface.LogsBloom()),
|
||||
PrevRandao: hexutil.Encode(payloadInterface.PrevRandao()),
|
||||
BlockNumber: hexutil.EncodeUint64(payloadInterface.BlockNumber()),
|
||||
GasLimit: hexutil.EncodeUint64(payloadInterface.GasLimit()),
|
||||
GasUsed: hexutil.EncodeUint64(payloadInterface.GasUsed()),
|
||||
Timestamp: hexutil.EncodeUint64(payloadInterface.Timestamp()),
|
||||
ExtraData: hexutil.Encode(payloadInterface.ExtraData()),
|
||||
BaseFeePerGas: hexutil.Encode(payloadInterface.BaseFeePerGas()),
|
||||
BlockHash: hexutil.Encode(payloadInterface.BlockHash()),
|
||||
TransactionsRoot: hexutil.Encode(transactionsRoot),
|
||||
WithdrawalsRoot: hexutil.Encode(withdrawalsRoot),
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(ctx, block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload proof")
|
||||
}
|
||||
executionPayloadProofStr := make([]string, len(executionPayloadProof))
|
||||
for i, proof := range executionPayloadProof {
|
||||
executionPayloadProofStr[i] = hexutil.Encode(proof)
|
||||
}
|
||||
header := &structs.LightClientHeaderCapella{
|
||||
Beacon: beacon,
|
||||
Execution: executionPayloadHeader,
|
||||
ExecutionBranch: executionPayloadProofStr,
|
||||
}
|
||||
|
||||
// Above shared util function won't calculate state root, so we need to do it manually
|
||||
stateRoot, err := state.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get state root")
|
||||
}
|
||||
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
|
||||
|
||||
headerJson, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert header to raw message")
|
||||
}
|
||||
|
||||
// Return result
|
||||
result := &structs.LightClientBootstrap{
|
||||
Header: headerJson,
|
||||
CurrentSyncCommittee: committee,
|
||||
Header: headerJSON,
|
||||
CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee),
|
||||
CurrentSyncCommitteeBranch: branch,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlyBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconState, block interfaces.ReadOnlySignedBeaconBlock) (*structs.LightClientBootstrap, error) {
|
||||
// assert compute_epoch_at_slot(state.slot) >= DENEB_FORK_EPOCH
|
||||
if slots.ToEpoch(state.Slot()) < params.BeaconConfig().DenebForkEpoch {
|
||||
return nil, fmt.Errorf("creating Deneb light client bootstrap is not supported before Deneb, invalid slot %d", state.Slot())
|
||||
@@ -243,103 +184,61 @@ func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconStat
|
||||
return nil, fmt.Errorf("state slot %d not equal to latest block header slot %d", state.Slot(), latestBlockHeader.Slot)
|
||||
}
|
||||
|
||||
// Prepare data
|
||||
currentSyncCommittee, err := state.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee")
|
||||
}
|
||||
|
||||
committee := structs.SyncCommitteeFromConsensus(currentSyncCommittee)
|
||||
|
||||
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee proof")
|
||||
}
|
||||
|
||||
branch := make([]string, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
for i, proof := range currentSyncCommitteeProof {
|
||||
branch[i] = hexutil.Encode(proof)
|
||||
}
|
||||
|
||||
beacon := structs.BeaconBlockHeaderFromConsensus(latestBlockHeader)
|
||||
|
||||
payloadInterface, err := block.Body().Execution()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload")
|
||||
}
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions")
|
||||
}
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get transactions root")
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensus_types.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals")
|
||||
}
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
}
|
||||
executionPayloadHeader := &structs.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: hexutil.Encode(payloadInterface.ParentHash()),
|
||||
FeeRecipient: hexutil.Encode(payloadInterface.FeeRecipient()),
|
||||
StateRoot: hexutil.Encode(payloadInterface.StateRoot()),
|
||||
ReceiptsRoot: hexutil.Encode(payloadInterface.ReceiptsRoot()),
|
||||
LogsBloom: hexutil.Encode(payloadInterface.LogsBloom()),
|
||||
PrevRandao: hexutil.Encode(payloadInterface.PrevRandao()),
|
||||
BlockNumber: hexutil.EncodeUint64(payloadInterface.BlockNumber()),
|
||||
GasLimit: hexutil.EncodeUint64(payloadInterface.GasLimit()),
|
||||
GasUsed: hexutil.EncodeUint64(payloadInterface.GasUsed()),
|
||||
Timestamp: hexutil.EncodeUint64(payloadInterface.Timestamp()),
|
||||
ExtraData: hexutil.Encode(payloadInterface.ExtraData()),
|
||||
BaseFeePerGas: hexutil.Encode(payloadInterface.BaseFeePerGas()),
|
||||
BlockHash: hexutil.Encode(payloadInterface.BlockHash()),
|
||||
TransactionsRoot: hexutil.Encode(transactionsRoot),
|
||||
WithdrawalsRoot: hexutil.Encode(withdrawalsRoot),
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(ctx, block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload proof")
|
||||
}
|
||||
executionPayloadProofStr := make([]string, len(executionPayloadProof))
|
||||
for i, proof := range executionPayloadProof {
|
||||
executionPayloadProofStr[i] = hexutil.Encode(proof)
|
||||
}
|
||||
header := &structs.LightClientHeaderDeneb{
|
||||
Beacon: beacon,
|
||||
Execution: executionPayloadHeader,
|
||||
ExecutionBranch: executionPayloadProofStr,
|
||||
}
|
||||
|
||||
// Above shared util function won't calculate state root, so we need to do it manually
|
||||
// header.state_root = hash_tree_root(state)
|
||||
stateRoot, err := state.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get state root")
|
||||
}
|
||||
header.Beacon.StateRoot = hexutil.Encode(stateRoot[:])
|
||||
latestBlockHeader.StateRoot = stateRoot[:]
|
||||
|
||||
headerJson, err := json.Marshal(header)
|
||||
// assert hash_tree_root(header) == hash_tree_root(block.message)
|
||||
latestBlockHeaderRoot, err := latestBlockHeader.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get latest block header root")
|
||||
}
|
||||
beaconBlockRoot, err := block.Block().HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get block root")
|
||||
}
|
||||
if latestBlockHeaderRoot != beaconBlockRoot {
|
||||
return nil, fmt.Errorf("latest block header root %#x not equal to block root %#x", latestBlockHeaderRoot, beaconBlockRoot)
|
||||
}
|
||||
|
||||
lightClientHeaderContainer, err := lightclient.BlockToLightClientHeader(block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert block to light client header")
|
||||
}
|
||||
lightClientHeader := lightClientHeaderContainer.GetHeaderDeneb()
|
||||
|
||||
apiLightClientHeader := &structs.LightClientHeader{
|
||||
Beacon: structs.BeaconBlockHeaderFromConsensus(migration.V1HeaderToV1Alpha1(lightClientHeader.Beacon)),
|
||||
}
|
||||
|
||||
headerJSON, err := json.Marshal(apiLightClientHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not convert header to raw message")
|
||||
}
|
||||
// Return result
|
||||
currentSyncCommittee, err := state.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee")
|
||||
}
|
||||
currentSyncCommitteeProof, err := state.CurrentSyncCommitteeProof(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get current sync committee proof")
|
||||
}
|
||||
var branch []string
|
||||
switch block.Version() {
|
||||
case version.Deneb:
|
||||
branch = make([]string, fieldparams.SyncCommitteeBranchDepth)
|
||||
case version.Electra:
|
||||
branch = make([]string, fieldparams.SyncCommitteeBranchDepthElectra)
|
||||
}
|
||||
for i, proof := range currentSyncCommitteeProof {
|
||||
branch[i] = hexutil.Encode(proof)
|
||||
}
|
||||
result := &structs.LightClientBootstrap{
|
||||
Header: headerJson,
|
||||
CurrentSyncCommittee: committee,
|
||||
Header: headerJSON,
|
||||
CurrentSyncCommittee: structs.SyncCommitteeFromConsensus(currentSyncCommittee),
|
||||
CurrentSyncCommitteeBranch: branch,
|
||||
}
|
||||
|
||||
@@ -351,9 +250,10 @@ func newLightClientUpdateFromBeaconState(
|
||||
state state.BeaconState,
|
||||
block interfaces.ReadOnlySignedBeaconBlock,
|
||||
attestedState state.BeaconState,
|
||||
attestedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
finalizedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
) (*structs.LightClientUpdate, error) {
|
||||
result, err := lightclient.NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, finalizedBlock)
|
||||
result, err := lightclient.NewLightClientUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -366,9 +266,10 @@ func newLightClientFinalityUpdateFromBeaconState(
|
||||
state state.BeaconState,
|
||||
block interfaces.ReadOnlySignedBeaconBlock,
|
||||
attestedState state.BeaconState,
|
||||
attestedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
finalizedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
) (*structs.LightClientFinalityUpdate, error) {
|
||||
result, err := lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, state, block, attestedState, finalizedBlock)
|
||||
result, err := lightclient.NewLightClientFinalityUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock, finalizedBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -381,8 +282,9 @@ func newLightClientOptimisticUpdateFromBeaconState(
|
||||
state state.BeaconState,
|
||||
block interfaces.ReadOnlySignedBeaconBlock,
|
||||
attestedState state.BeaconState,
|
||||
attestedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
) (*structs.LightClientOptimisticUpdate, error) {
|
||||
result, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, state, block, attestedState)
|
||||
result, err := lightclient.NewLightClientOptimisticUpdateFromBeaconState(ctx, state, block, attestedState, attestedBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -391,7 +293,7 @@ func newLightClientOptimisticUpdateFromBeaconState(
|
||||
}
|
||||
|
||||
func IsSyncCommitteeUpdate(update *v2.LightClientUpdate) bool {
|
||||
nextSyncCommitteeBranch := make([][]byte, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
nextSyncCommitteeBranch := make([][]byte, fieldparams.SyncCommitteeBranchDepth)
|
||||
return !reflect.DeepEqual(update.NextSyncCommitteeBranch, nextSyncCommitteeBranch)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
// When the update has relevant sync committee
|
||||
func createNonEmptySyncCommitteeBranch() [][]byte {
|
||||
res := make([][]byte, fieldparams.NextSyncCommitteeBranchDepth)
|
||||
res := make([][]byte, fieldparams.SyncCommitteeBranchDepth)
|
||||
res[0] = []byte("xyz")
|
||||
return res
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func TestIsBetterUpdate(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
},
|
||||
NextSyncCommitteeBranch: make([][]byte, fieldparams.NextSyncCommitteeBranchDepth),
|
||||
NextSyncCommitteeBranch: make([][]byte, fieldparams.SyncCommitteeBranchDepth),
|
||||
SignatureSlot: 9999,
|
||||
},
|
||||
newUpdate: ðpbv2.LightClientUpdate{
|
||||
@@ -147,7 +147,7 @@ func TestIsBetterUpdate(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
},
|
||||
NextSyncCommitteeBranch: make([][]byte, fieldparams.NextSyncCommitteeBranchDepth),
|
||||
NextSyncCommitteeBranch: make([][]byte, fieldparams.SyncCommitteeBranchDepth),
|
||||
SignatureSlot: 9999,
|
||||
},
|
||||
expectedResult: false,
|
||||
|
||||
@@ -1814,10 +1814,7 @@ var BlindedElectraBlock = fmt.Sprintf(`{
|
||||
"blob_gas_used": "1",
|
||||
"excess_blob_gas": "2",
|
||||
"transactions_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"deposit_requests_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"withdrawal_requests_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"consolidation_requests_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
"withdrawals_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
|
||||
},
|
||||
"bls_to_execution_changes": [
|
||||
{
|
||||
@@ -1829,7 +1826,33 @@ var BlindedElectraBlock = fmt.Sprintf(`{
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
],
|
||||
"blob_kzg_commitments":["0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8000"]
|
||||
"blob_kzg_commitments":["0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8000"],
|
||||
"execution_requests": {
|
||||
"deposits": [
|
||||
{
|
||||
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"amount": "123",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"index": "123"
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"validator_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"amount": "123"
|
||||
}
|
||||
],
|
||||
"consolidations": [
|
||||
{
|
||||
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"source_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"target_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
@@ -2225,30 +2248,7 @@ var ElectraBlockContents = fmt.Sprintf(`{
|
||||
"address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"amount": "1"
|
||||
}
|
||||
],
|
||||
"deposit_requests": [
|
||||
{
|
||||
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"amount": "123",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"index": "123"
|
||||
}
|
||||
],
|
||||
"withdrawal_requests": [
|
||||
{
|
||||
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"validator_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"amount": "123"
|
||||
}
|
||||
],
|
||||
"consolidation_requests": [
|
||||
{
|
||||
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"source_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"target_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"bls_to_execution_changes": [
|
||||
{
|
||||
@@ -2260,7 +2260,32 @@ var ElectraBlockContents = fmt.Sprintf(`{
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
}
|
||||
],
|
||||
"blob_kzg_commitments":["0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8000"]
|
||||
"blob_kzg_commitments":["0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8000"],
|
||||
"execution_requests": {
|
||||
"deposits": [
|
||||
{
|
||||
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"withdrawal_credentials": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
|
||||
"amount": "123",
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505",
|
||||
"index": "123"
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"validator_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"amount": "123"
|
||||
}
|
||||
],
|
||||
"consolidations": [
|
||||
{
|
||||
"source_address": "0xabcf8e0d4e9587369b2301d0790347320302cc09",
|
||||
"source_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
|
||||
"target_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
|
||||
|
||||
@@ -287,6 +287,18 @@ func (s *Server) produceBlockV3(ctx context.Context, w http.ResponseWriter, r *h
|
||||
handleProduceDenebV3(w, isSSZ, denebBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
blindedElectraBlockContents, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_BlindedElectra)
|
||||
if ok {
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Electra))
|
||||
handleProduceBlindedElectraV3(w, isSSZ, blindedElectraBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
electraBlockContents, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Electra)
|
||||
if ok {
|
||||
w.Header().Set(api.VersionHeader, version.String(version.Electra))
|
||||
handleProduceElectraV3(w, isSSZ, electraBlockContents, v1alpha1resp.PayloadValue, consensusBlockValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getConsensusBlockValue(ctx context.Context, blockRewardsFetcher rewards.BlockRewardsFetcher, i interface{} /* block as argument */) (string, *httputil.DefaultJsonError) {
|
||||
@@ -587,3 +599,74 @@ func handleProduceDenebV3(
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
|
||||
func handleProduceBlindedElectraV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_BlindedElectra,
|
||||
executionPayloadValue string,
|
||||
consensusPayloadValue string,
|
||||
) {
|
||||
if isSSZ {
|
||||
sszResp, err := blk.BlindedElectra.MarshalSSZ()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteSsz(w, sszResp, "blindedElectraBlockContents.ssz")
|
||||
return
|
||||
}
|
||||
blindedBlock, err := structs.BlindedBeaconBlockElectraFromConsensus(blk.BlindedElectra)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
jsonBytes, err := json.Marshal(blindedBlock)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &structs.ProduceBlockV3Response{
|
||||
Version: version.String(version.Electra),
|
||||
ExecutionPayloadBlinded: true,
|
||||
ExecutionPayloadValue: executionPayloadValue,
|
||||
ConsensusBlockValue: consensusPayloadValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
|
||||
func handleProduceElectraV3(
|
||||
w http.ResponseWriter,
|
||||
isSSZ bool,
|
||||
blk *eth.GenericBeaconBlock_Electra,
|
||||
executionPayloadValue string,
|
||||
consensusBlockValue string,
|
||||
) {
|
||||
if isSSZ {
|
||||
sszResp, err := blk.Electra.MarshalSSZ()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteSsz(w, sszResp, "electraBlockContents.ssz")
|
||||
return
|
||||
}
|
||||
|
||||
blockContents, err := structs.BeaconBlockContentsElectraFromConsensus(blk.Electra)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
jsonBytes, err := json.Marshal(blockContents)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
httputil.WriteJson(w, &structs.ProduceBlockV3Response{
|
||||
Version: version.String(version.Electra),
|
||||
ExecutionPayloadBlinded: false,
|
||||
ExecutionPayloadValue: executionPayloadValue, // mev not available at this point
|
||||
ConsensusBlockValue: consensusBlockValue,
|
||||
Data: jsonBytes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -308,6 +308,75 @@ func TestProduceBlockV2(t *testing.T) {
|
||||
assert.Equal(t, http.StatusInternalServerError, e.Code)
|
||||
assert.StringContains(t, "Prepared block is blinded", e.Message)
|
||||
})
|
||||
t.Run("Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBeaconBlockContentsElectra
|
||||
err = json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &block)
|
||||
require.NoError(t, err)
|
||||
jsonBytes, err := json.Marshal(block.ToUnsigned())
|
||||
require.NoError(t, err)
|
||||
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: true,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
b, err := block.ToUnsigned().ToGeneric()
|
||||
require.NoError(t, err)
|
||||
b.PayloadValue = "2000"
|
||||
return b, nil
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v2/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV2(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"electra","execution_payload_blinded":false,"execution_payload_value":"2000","consensus_block_value":"10000000000","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
})
|
||||
t.Run("Blinded Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBlindedBeaconBlockElectra
|
||||
err = json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block)
|
||||
require.NoError(t, err)
|
||||
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: true,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v2/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV2(writer, request)
|
||||
assert.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusInternalServerError, e.Code)
|
||||
assert.StringContains(t, "Prepared block is blinded", e.Message)
|
||||
})
|
||||
t.Run("invalid query parameter slot empty", func(t *testing.T) {
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
server := &Server{
|
||||
@@ -650,6 +719,76 @@ func TestProduceBlockV2SSZ(t *testing.T) {
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v2/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV2(writer, request)
|
||||
assert.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusInternalServerError, e.Code)
|
||||
assert.StringContains(t, "Prepared block is blinded", e.Message)
|
||||
})
|
||||
t.Run("Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBeaconBlockContentsElectra
|
||||
err = json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &block)
|
||||
require.NoError(t, err)
|
||||
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: true,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
return block.ToUnsigned().ToGeneric()
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v2/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV2(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
g, err := block.ToUnsigned().ToGeneric()
|
||||
require.NoError(t, err)
|
||||
bl, ok := g.Block.(*eth.GenericBeaconBlock_Electra)
|
||||
require.Equal(t, true, ok)
|
||||
ssz, err := bl.Electra.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
})
|
||||
t.Run("Blinded Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBlindedBeaconBlockElectra
|
||||
err = json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block)
|
||||
require.NoError(t, err)
|
||||
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: true,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
return block.Message.ToGeneric()
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v2/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||
writer := httptest.NewRecorder()
|
||||
@@ -944,6 +1083,75 @@ func TestProduceBlindedBlock(t *testing.T) {
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
})
|
||||
t.Run("Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBeaconBlockContentsElectra
|
||||
err = json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &block)
|
||||
require.NoError(t, err)
|
||||
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: false,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
return block.ToUnsigned().ToGeneric()
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v1/validator/blinded_blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlindedBlock(writer, request)
|
||||
assert.Equal(t, http.StatusInternalServerError, writer.Code)
|
||||
e := &httputil.DefaultJsonError{}
|
||||
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), e))
|
||||
assert.Equal(t, http.StatusInternalServerError, e.Code)
|
||||
assert.StringContains(t, "Prepared block is not blinded", e.Message)
|
||||
})
|
||||
t.Run("Blinded Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBlindedBeaconBlockElectra
|
||||
err = json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block)
|
||||
require.NoError(t, err)
|
||||
jsonBytes, err := json.Marshal(block.Message)
|
||||
require.NoError(t, err)
|
||||
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: false,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
b, err := block.Message.ToGeneric()
|
||||
require.NoError(t, err)
|
||||
b.PayloadValue = "2000"
|
||||
return b, nil
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v1/validator/blinded_blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlindedBlock(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"electra","execution_payload_blinded":true,"execution_payload_value":"2000","consensus_block_value":"10000000000","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
})
|
||||
t.Run("invalid query parameter slot empty", func(t *testing.T) {
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
server := &Server{
|
||||
@@ -1309,6 +1517,82 @@ func TestProduceBlockV3(t *testing.T) {
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBeaconBlockContentsElectra
|
||||
err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &block)
|
||||
require.NoError(t, err)
|
||||
jsonBytes, err := json.Marshal(block.ToUnsigned())
|
||||
require.NoError(t, err)
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: false,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
b, err := block.ToUnsigned().ToGeneric()
|
||||
require.NoError(t, err)
|
||||
b.PayloadValue = "2000"
|
||||
return b, nil
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v3/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"electra","execution_payload_blinded":false,"execution_payload_value":"2000","consensus_block_value":"10000000000","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "2000", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Blinded Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBlindedBeaconBlockElectra
|
||||
err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block)
|
||||
require.NoError(t, err)
|
||||
jsonBytes, err := json.Marshal(block.Message)
|
||||
require.NoError(t, err)
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: false,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
b, err := block.Message.ToGeneric()
|
||||
require.NoError(t, err)
|
||||
b.PayloadValue = "2000"
|
||||
return b, nil
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v3/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
want := fmt.Sprintf(`{"version":"electra","execution_payload_blinded":true,"execution_payload_value":"2000","consensus_block_value":"10000000000","data":%s}`, string(jsonBytes))
|
||||
body := strings.ReplaceAll(writer.Body.String(), "\n", "")
|
||||
require.Equal(t, want, body)
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "2000", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("invalid query parameter slot empty", func(t *testing.T) {
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
server := &Server{
|
||||
@@ -1697,4 +1981,86 @@ func TestProduceBlockV3SSZ(t *testing.T) {
|
||||
require.Equal(t, "deneb", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBeaconBlockContentsElectra
|
||||
err := json.Unmarshal([]byte(rpctesting.ElectraBlockContents), &block)
|
||||
require.NoError(t, err)
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: false,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
b, err := block.ToUnsigned().ToGeneric()
|
||||
require.NoError(t, err)
|
||||
b.PayloadValue = "2000"
|
||||
return b, nil
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v3/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
g, err := block.ToUnsigned().ToGeneric()
|
||||
require.NoError(t, err)
|
||||
bl, ok := g.Block.(*eth.GenericBeaconBlock_Electra)
|
||||
require.Equal(t, true, ok)
|
||||
ssz, err := bl.Electra.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "2000", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
t.Run("Blinded Electra", func(t *testing.T) {
|
||||
var block *structs.SignedBlindedBeaconBlockElectra
|
||||
err := json.Unmarshal([]byte(rpctesting.BlindedElectraBlock), &block)
|
||||
require.NoError(t, err)
|
||||
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
|
||||
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), ð.BlockRequest{
|
||||
Slot: 1,
|
||||
RandaoReveal: bRandao,
|
||||
Graffiti: bGraffiti,
|
||||
SkipMevBoost: false,
|
||||
}).Return(
|
||||
func() (*eth.GenericBeaconBlock, error) {
|
||||
b, err := block.Message.ToGeneric()
|
||||
require.NoError(t, err)
|
||||
b.PayloadValue = "2000"
|
||||
return b, nil
|
||||
}())
|
||||
server := &Server{
|
||||
V1Alpha1Server: v1alpha1Server,
|
||||
SyncChecker: syncChecker,
|
||||
OptimisticModeFetcher: chainService,
|
||||
BlockRewardFetcher: rewardFetcher,
|
||||
}
|
||||
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v3/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
|
||||
request.Header.Set("Accept", api.OctetStreamMediaType)
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.ProduceBlockV3(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
g, err := block.Message.ToGeneric()
|
||||
require.NoError(t, err)
|
||||
bl, ok := g.Block.(*eth.GenericBeaconBlock_BlindedElectra)
|
||||
require.Equal(t, true, ok)
|
||||
ssz, err := bl.BlindedElectra.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(ssz), writer.Body.String())
|
||||
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadBlindedHeader))
|
||||
require.Equal(t, "2000", writer.Header().Get(api.ExecutionPayloadValueHeader))
|
||||
require.Equal(t, "electra", writer.Header().Get(api.VersionHeader))
|
||||
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ go_library(
|
||||
"//beacon-chain/blockchain:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db:go_default_library",
|
||||
"//beacon-chain/p2p:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/eth/helpers:go_default_library",
|
||||
"//beacon-chain/rpc/eth/shared:go_default_library",
|
||||
@@ -22,8 +23,10 @@ go_library(
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/sync:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//consensus-types/validator:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//network/httputil:go_default_library",
|
||||
"//proto/eth/v1:go_default_library",
|
||||
@@ -47,13 +50,16 @@ go_test(
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
|
||||
"//beacon-chain/p2p/testing:go_default_library",
|
||||
"//beacon-chain/rpc/core:go_default_library",
|
||||
"//beacon-chain/rpc/lookup:go_default_library",
|
||||
"//beacon-chain/rpc/prysm/testing:go_default_library",
|
||||
"//beacon-chain/rpc/testutil:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/state/stategen/mock:go_default_library",
|
||||
"//beacon-chain/sync/initial-sync/testing:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user