mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
61 Commits
single-att
...
alpha7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8ed51cf9f | ||
|
|
a23b3001f2 | ||
|
|
4bdafef61b | ||
|
|
4821c34042 | ||
|
|
c11e3392d4 | ||
|
|
f498463843 | ||
|
|
cf4ffc97e2 | ||
|
|
3824e8a463 | ||
|
|
21ca4e008f | ||
|
|
6af44a1466 | ||
|
|
2e29164582 | ||
|
|
6d499bc9fc | ||
|
|
7786cb5684 | ||
|
|
c3fb312202 | ||
|
|
446cba21fa | ||
|
|
003b70c34b | ||
|
|
b5d2d90049 | ||
|
|
fe5c942ed2 | ||
|
|
71edf96c7d | ||
|
|
ddafedc268 | ||
|
|
134fe8ce4f | ||
|
|
7e5738bfcd | ||
|
|
377f5a637a | ||
|
|
f95f87c5fe | ||
|
|
315c05b351 | ||
|
|
41f1406262 | ||
|
|
3662cf6009 | ||
|
|
98d8b50b0e | ||
|
|
1a1cc25bd1 | ||
|
|
c333432a88 | ||
|
|
9abebd7fbb | ||
|
|
9110135b86 | ||
|
|
f6e06d7a0c | ||
|
|
8776421a1a | ||
|
|
582444c87e | ||
|
|
26216efc22 | ||
|
|
fe2598db79 | ||
|
|
1a559371ad | ||
|
|
5fb1e10b14 | ||
|
|
944afd18b7 | ||
|
|
f1a2dcaa7d | ||
|
|
29cf448472 | ||
|
|
1e4e826c80 | ||
|
|
5dda6df7bf | ||
|
|
8bb1fcccac | ||
|
|
af3f8d0a33 | ||
|
|
792a4f7d62 | ||
|
|
f697f65ec9 | ||
|
|
0690e445c7 | ||
|
|
41ba44b8c7 | ||
|
|
35665430f6 | ||
|
|
330cfa305e | ||
|
|
3c257978e0 | ||
|
|
5334144273 | ||
|
|
6cf54bc639 | ||
|
|
46e88f051e | ||
|
|
a0bef32e88 | ||
|
|
4a876094ff | ||
|
|
8d0d444347 | ||
|
|
b008a3b055 | ||
|
|
2cc1ff36f9 |
@@ -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:
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -13,10 +13,19 @@ 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 `BlockToLightClientHeader` function.
|
||||
- Electra EIP6110: Queue deposit
|
||||
- Light client support: Implement `BlockToLightClientHeaderXXX` functions upto Deneb
|
||||
- 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
|
||||
- HTTP endpoint for PublishBlobs
|
||||
- GetBlockV2, GetBlindedBlock, ProduceBlockV2, ProduceBlockV3: add Electra case.
|
||||
- SSE implementation that sheds stuck clients. [pr](https://github.com/prysmaticlabs/prysm/pull/14413)
|
||||
|
||||
### 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.
|
||||
@@ -31,12 +40,17 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
- `grpc-gateway-corsdomain` is renamed to http-cors-domain. The old name can still be used as an alias.
|
||||
- `api-timeout` is changed from int flag to duration flag, default value updated.
|
||||
- Light client support: abstracted out the light client headers with different versions.
|
||||
- Electra EIP6110: Queue deposit requests changes from consensus spec pr #3818
|
||||
- `ApplyToEveryValidator` has been changed to prevent misuse bugs, it takes a closure that takes a `ReadOnlyValidator` and returns a raw pointer to a `Validator`.
|
||||
- Removed gorilla mux library and replaced it with net/http updates in go 1.22.
|
||||
- Clean up `ProposeBlock` for validator client to reduce cognitive scoring and enable further changes.
|
||||
- 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.
|
||||
- Switch to compounding when consolidating with source==target.
|
||||
|
||||
### Deprecated
|
||||
- `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal.
|
||||
@@ -58,9 +72,13 @@ 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
|
||||
|
||||
@@ -2673,7 +2691,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 +2742,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.7"
|
||||
|
||||
bls_test_version = "v0.1.1"
|
||||
|
||||
@@ -243,7 +243,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-R9vG5HEL5eGMOAmbkKfJ2jfelNqL5V0xBUPiXOiGM6U=",
|
||||
integrity = "sha256-Vo4+1bsRNYMGXjlpIYKEqt2WPjeGSbjeFQFKVUhf1Yc=",
|
||||
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-TsQtQVPyQA6htrTjL1cd/oWEkM83aK4Y0pT9W6ssKYU=",
|
||||
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-4g7EijlsIJVKGADlVL6ka9ikcwow8WuKHaABONA0Cxg=",
|
||||
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-i5+48P/BqxA+q39OufAiotW2llUq97+4j1++ViI96dM=",
|
||||
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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
)
|
||||
|
||||
// ProcessPreGenesisDeposits processes a deposit for the beacon state before chainstart.
|
||||
@@ -130,7 +128,7 @@ func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, verif
|
||||
return beaconState, nil
|
||||
}
|
||||
}
|
||||
if err := AddValidatorToRegistry(beaconState, pubKey, withdrawalCredentials, amount); err != nil {
|
||||
if err := blocks.AddValidatorToRegistry(beaconState, pubKey, withdrawalCredentials, amount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
@@ -140,72 +138,3 @@ func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, verif
|
||||
}
|
||||
return beaconState, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
// set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) // New in Altair
|
||||
// set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) // New in Altair
|
||||
// set_or_append_list(state.inactivity_scores, index, uint64(0)) // New in Altair
|
||||
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
|
||||
}
|
||||
|
||||
// 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:
|
||||
//
|
||||
// effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
//
|
||||
// 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=effective_balance,
|
||||
// )
|
||||
func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount uint64) *ethpb.Validator {
|
||||
effectiveBalance := amount - (amount % params.BeaconConfig().EffectiveBalanceIncrement)
|
||||
if params.BeaconConfig().MaxEffectiveBalance < effectiveBalance {
|
||||
effectiveBalance = params.BeaconConfig().MaxEffectiveBalance
|
||||
}
|
||||
|
||||
return ðpb.Validator{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: effectiveBalance,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,78 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/math"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
)
|
||||
|
||||
// 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)
|
||||
// set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) // New in Altair
|
||||
// set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) // New in Altair
|
||||
// set_or_append_list(state.inactivity_scores, index, uint64(0)) // New in Altair
|
||||
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
|
||||
}
|
||||
|
||||
// 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:
|
||||
//
|
||||
// effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
|
||||
//
|
||||
// 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=effective_balance,
|
||||
// )
|
||||
func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount uint64) *ethpb.Validator {
|
||||
effectiveBalance := amount - (amount % params.BeaconConfig().EffectiveBalanceIncrement)
|
||||
if params.BeaconConfig().MaxEffectiveBalance < effectiveBalance {
|
||||
effectiveBalance = params.BeaconConfig().MaxEffectiveBalance
|
||||
}
|
||||
|
||||
return ðpb.Validator{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: effectiveBalance,
|
||||
}
|
||||
}
|
||||
|
||||
// ActivateValidatorWithEffectiveBalance updates validator's effective balance, and if it's above MaxEffectiveBalance, validator becomes active in genesis.
|
||||
func ActivateValidatorWithEffectiveBalance(beaconState state.BeaconState, deposits []*ethpb.Deposit) (state.BeaconState, error) {
|
||||
for _, d := range deposits {
|
||||
@@ -63,6 +133,22 @@ func BatchVerifyDepositsSignatures(ctx context.Context, deposits []*ethpb.Deposi
|
||||
return verified, 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
|
||||
}
|
||||
|
||||
verified := false
|
||||
if err := verifyPendingDepositDataWithDomain(ctx, deposits, domain); err != nil {
|
||||
log.WithError(err).Debug("Failed to batch verify deposits signatures, will try individual verify")
|
||||
verified = true
|
||||
}
|
||||
return verified, nil
|
||||
}
|
||||
|
||||
// IsValidDepositSignature returns whether deposit_data is valid
|
||||
// def is_valid_deposit_signature(pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64, signature: BLSSignature) -> bool:
|
||||
//
|
||||
@@ -159,3 +245,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
|
||||
}
|
||||
|
||||
@@ -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,6 +32,7 @@ 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",
|
||||
@@ -65,11 +66,13 @@ go_test(
|
||||
"//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",
|
||||
|
||||
@@ -22,25 +22,29 @@ 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()
|
||||
@@ -70,18 +74,19 @@ 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)
|
||||
sourceEffectiveBalance := helpers.ValidatorMaxEffectiveBalance(sourceValidator)
|
||||
validatorBalance, err := st.BalanceAtIndex(pc.SourceIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := helpers.DecreaseBalance(st, pc.SourceIndex, activeBalance); err != nil {
|
||||
if sourceEffectiveBalance > validatorBalance {
|
||||
sourceEffectiveBalance = validatorBalance
|
||||
}
|
||||
|
||||
if err := helpers.DecreaseBalance(st, pc.SourceIndex, sourceEffectiveBalance); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := helpers.IncreaseBalance(st, pc.TargetIndex, activeBalance); err != nil {
|
||||
if err := helpers.IncreaseBalance(st, pc.TargetIndex, sourceEffectiveBalance); err != nil {
|
||||
return err
|
||||
}
|
||||
nextPendingConsolidation++
|
||||
@@ -101,6 +106,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 +136,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 +171,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 +188,47 @@ func ProcessConsolidationRequests(ctx context.Context, st state.BeaconState, req
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("cannot process consolidation requests: %w", ctx.Err())
|
||||
}
|
||||
canSwitch, err := IsValidSwitchToCompoundingRequest(ctx, st, cr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate consolidation request: %w", err)
|
||||
}
|
||||
if canSwitch {
|
||||
srcIdx, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(cr.SourcePubkey))
|
||||
if !ok {
|
||||
return errors.New("could not find validator in registry")
|
||||
}
|
||||
if err := SwitchToCompoundingValidator(st, srcIdx); err != nil {
|
||||
return fmt.Errorf("failed to switch to compounding validator: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -248,7 +279,94 @@ 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 {
|
||||
return fmt.Errorf("failed to switch to compounding validator: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 have not been initiated
|
||||
// if source_validator.exit_epoch != FAR_FUTURE_EPOCH:
|
||||
// return False
|
||||
//
|
||||
// return True
|
||||
func IsValidSwitchToCompoundingRequest(ctx context.Context, st state.BeaconState, req *enginev1.ConsolidationRequest) (bool, error) {
|
||||
if req.SourcePubkey == nil || req.TargetPubkey == nil {
|
||||
return false, errors.New("nil source or target pubkey")
|
||||
}
|
||||
|
||||
sourcePubKey := bytesutil.ToBytes48(req.SourcePubkey)
|
||||
targetPubKey := bytesutil.ToBytes48(req.TargetPubkey)
|
||||
if sourcePubKey != targetPubKey {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
srcIdx, ok := st.ValidatorIndexByPubkey(sourcePubKey)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
srcV, err := st.ValidatorAtIndex(srcIdx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
sourceAddress := req.SourceAddress
|
||||
withdrawalCreds := srcV.WithdrawalCredentials
|
||||
if len(withdrawalCreds) != 32 || len(sourceAddress) != 20 || !bytes.HasSuffix(withdrawalCreds, sourceAddress) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !helpers.HasETH1WithdrawalCredential(srcV) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
curEpoch := slots.ToEpoch(st.Slot())
|
||||
if !helpers.IsActiveValidator(srcV, curEpoch) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if srcV.ExitEpoch != params.BeaconConfig().FarFutureEpoch {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -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,96 @@ func TestProcessConsolidationRequests(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidSwitchToCompoundingRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, _ := util.DeterministicGenesisStateElectra(t, 1)
|
||||
t.Run("nil source pubkey", func(t *testing.T) {
|
||||
ok, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
SourcePubkey: nil,
|
||||
TargetPubkey: []byte{'a'},
|
||||
})
|
||||
require.ErrorContains(t, "nil source or target pubkey", err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("nil target pubkey", func(t *testing.T) {
|
||||
ok, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
TargetPubkey: nil,
|
||||
SourcePubkey: []byte{'a'},
|
||||
})
|
||||
require.ErrorContains(t, "nil source or target pubkey", err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("different source and target pubkey", func(t *testing.T) {
|
||||
ok, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
TargetPubkey: []byte{'a'},
|
||||
SourcePubkey: []byte{'b'},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
t.Run("source validator not found in state", func(t *testing.T) {
|
||||
ok, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: make([]byte, 20),
|
||||
TargetPubkey: []byte{'a'},
|
||||
SourcePubkey: []byte{'a'},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: make([]byte, 20),
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: wc[12:],
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: wc[12:],
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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, err := electra.IsValidSwitchToCompoundingRequest(ctx, st, &enginev1.ConsolidationRequest{
|
||||
SourceAddress: wc[12:],
|
||||
TargetPubkey: pubkey,
|
||||
SourcePubkey: pubkey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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"
|
||||
@@ -96,31 +95,41 @@ func ProcessDeposit(beaconState state.BeaconState, deposit *ethpb.Deposit, verif
|
||||
}
|
||||
|
||||
// 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:
|
||||
// def apply_deposit(state: BeaconState,
|
||||
//
|
||||
// # 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)
|
||||
//
|
||||
// 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)
|
||||
// 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,
|
||||
// ))
|
||||
// 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
|
||||
// ))
|
||||
func ApplyDeposit(beaconState state.BeaconState, data *ethpb.Deposit_Data, verifySignature 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 {
|
||||
valid, err := IsValidDepositSignature(data)
|
||||
@@ -131,32 +140,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 := blocks.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,47 +182,80 @@ 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:
|
||||
// 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_balance_deposits:
|
||||
// validator = state.validators[deposit.index]
|
||||
// # 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
|
||||
// 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
|
||||
//
|
||||
// state.pending_balance_deposits = state.pending_balance_deposits[next_deposit_index:]
|
||||
// # Check if deposit has been finalized, otherwise, stop processing.
|
||||
// if deposit.slot > finalized_slot:
|
||||
// break
|
||||
//
|
||||
// if len(state.pending_balance_deposits) == 0:
|
||||
// state.deposit_balance_to_consume = Gwei(0)
|
||||
// else:
|
||||
// state.deposit_balance_to_consume = available_for_processing - processed_amount
|
||||
// # Check if number of processed deposits has not reached the limit, otherwise, stop processing.
|
||||
// if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH:
|
||||
// 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")
|
||||
// # 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 < get_current_epoch(state)
|
||||
//
|
||||
// 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
|
||||
// deposits_to_postpone.append(deposit)
|
||||
// else:
|
||||
// # 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
|
||||
//
|
||||
// # 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:]
|
||||
//
|
||||
// # 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)
|
||||
//
|
||||
// state.pending_deposits += deposits_to_postpone
|
||||
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() {
|
||||
@@ -238,99 +268,180 @@ func ProcessPendingBalanceDeposits(ctx context.Context, st state.BeaconState, ac
|
||||
}
|
||||
availableForProcessing := depBalToConsume + helpers.ActivationExitChurnLimit(activeBalance)
|
||||
processedAmount := uint64(0)
|
||||
nextDepositIndex := 0
|
||||
var depositsToPostpone []*eth.PendingBalanceDeposit
|
||||
nextDepositIndex := uint64(0)
|
||||
var depositsToPostpone []*eth.PendingDeposit
|
||||
|
||||
deposits, err := st.PendingBalanceDeposits()
|
||||
deposits, err := st.PendingDeposits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isChurnLimitReached := false
|
||||
finalizedSlot, err := slots.EpochStart(st.FinalizedCheckpoint().Epoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized slot")
|
||||
}
|
||||
// constants
|
||||
ffe := params.BeaconConfig().FarFutureEpoch
|
||||
nextEpoch := slots.ToEpoch(st.Slot()) + 1
|
||||
curEpoch := slots.ToEpoch(st.Slot())
|
||||
|
||||
for _, balanceDeposit := range deposits {
|
||||
v, err := st.ValidatorAtIndexReadOnly(balanceDeposit.Index)
|
||||
// Slice to collect deposits needing signature verification
|
||||
var depositsToVerify []*ethpb.PendingDeposit
|
||||
for _, pendingDeposit := range deposits {
|
||||
startIndex, err := st.DepositRequestsStartIndex()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch validator at index: %w", err)
|
||||
return errors.Wrap(err, "could not get starting pendingDeposit index")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Do not process pendingDeposit requests if Eth1 bridge deposits are not yet applied.
|
||||
if pendingDeposit.Slot > params.BeaconConfig().GenesisSlot && st.Eth1DepositIndex() < startIndex {
|
||||
break
|
||||
}
|
||||
|
||||
// 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() < ffe
|
||||
isValidatorWithdrawn = val.WithdrawableEpoch() < curEpoch
|
||||
}
|
||||
|
||||
if isValidatorWithdrawn {
|
||||
// Deposited balance will never become active. Increase balance but do not consume churn
|
||||
if err := ApplyPendingDeposit(ctx, st, pendingDeposit, true); err != nil {
|
||||
return errors.Wrap(err, "could not apply pending deposit")
|
||||
}
|
||||
} else if isValidatorExited {
|
||||
// Validator is exiting, postpone the pendingDeposit until after withdrawable epoch
|
||||
depositsToPostpone = append(depositsToPostpone, pendingDeposit)
|
||||
} else {
|
||||
// Validator is not exiting, attempt to process deposit.
|
||||
if primitives.Gwei(processedAmount+balanceDeposit.Amount) > availableForProcessing {
|
||||
// Check if pendingDeposit fits in the churn, otherwise, do no more pendingDeposit processing in this epoch.
|
||||
isChurnLimitReached = primitives.Gwei(processedAmount+pendingDeposit.Amount) > availableForProcessing
|
||||
if isChurnLimitReached { // Is churn limit reached?
|
||||
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
|
||||
// Consume churn and apply pendingDeposit.
|
||||
processedAmount += pendingDeposit.Amount
|
||||
if found {
|
||||
if err := ApplyPendingDeposit(ctx, st, pendingDeposit, true); err != nil {
|
||||
return errors.Wrap(err, "could not apply pending deposit")
|
||||
}
|
||||
} else {
|
||||
// Collect deposit for batch signature verification
|
||||
depositsToVerify = append(depositsToVerify, 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++
|
||||
}
|
||||
|
||||
// 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
|
||||
// balance to consume.
|
||||
numRemainingDeposits := len(deposits[nextDepositIndex:])
|
||||
deposits = append(deposits[nextDepositIndex:], depositsToPostpone...)
|
||||
if err := st.SetPendingBalanceDeposits(deposits); err != nil {
|
||||
return err
|
||||
// Perform batch signature verification on deposits on unfound validators
|
||||
if len(depositsToVerify) > 0 {
|
||||
batchVerified, err := blocks.BatchVerifyPendingDepositsSignatures(ctx, depositsToVerify)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not batch verify deposit signatures")
|
||||
}
|
||||
|
||||
// Apply deposits that passed verification
|
||||
for _, dep := range depositsToVerify {
|
||||
if err := ApplyPendingDeposit(ctx, st, dep, batchVerified); err != nil {
|
||||
return errors.Wrap(err, "could not apply pending deposit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if numRemainingDeposits == 0 {
|
||||
return st.SetDepositBalanceToConsume(0)
|
||||
} else {
|
||||
// Combined operation:
|
||||
// - 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.
|
||||
deposits = append(deposits[nextDepositIndex:], depositsToPostpone...)
|
||||
if err := st.SetPendingDeposits(deposits); 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)
|
||||
}
|
||||
|
||||
// ApplyPendingDeposit implements the spec definition below.
|
||||
//
|
||||
// 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, isSigVerified bool) error {
|
||||
_, span := trace.StartSpan(ctx, "electra.ApplyPendingDeposit")
|
||||
defer span.End()
|
||||
index, ok := st.ValidatorIndexByPubkey(bytesutil.ToBytes48(deposit.PublicKey))
|
||||
if !ok {
|
||||
var err error
|
||||
if !isSigVerified {
|
||||
isSigVerified, 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 isSigVerified {
|
||||
if err := blocks.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)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
@@ -341,31 +452,38 @@ func ProcessDepositRequests(ctx context.Context, beaconState state.BeaconState,
|
||||
// processDepositRequest processes the specific deposit receipt
|
||||
// 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:
|
||||
// state.deposit_requests_start_index = deposit_request.index
|
||||
// # Set deposit request start index
|
||||
// if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUEST_START_INDEX:
|
||||
// state.deposit_requests_start_index = deposit_request.index
|
||||
//
|
||||
// apply_deposit(
|
||||
// state=state,
|
||||
// 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) {
|
||||
// state.pending_deposits.append(PendingDeposit(
|
||||
// pubkey=deposit_request.pubkey,
|
||||
// withdrawal_credentials=deposit_request.withdrawal_credentials,
|
||||
// amount=deposit_request.amount,
|
||||
// signature=deposit_request.signature,
|
||||
// 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,7 +69,7 @@ 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))
|
||||
},
|
||||
@@ -82,17 +77,10 @@ func TestProcessPendingBalanceDeposits(t *testing.T) {
|
||||
{
|
||||
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 +96,74 @@ 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))
|
||||
st.SaveValidatorIndices()
|
||||
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 +171,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 +196,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 +204,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 +222,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 +239,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)
|
||||
@@ -220,7 +260,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 +270,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 +295,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 +325,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))
|
||||
@@ -361,40 +400,128 @@ func TestApplyDeposit_TopUps_WithBadSignature(t *testing.T) {
|
||||
require.NoError(t, st.SetValidators(vals))
|
||||
adSt, err := electra.ApplyDeposit(st, depositData, true)
|
||||
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))
|
||||
st.SaveValidatorIndices()
|
||||
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))
|
||||
st.SaveValidatorIndices()
|
||||
|
||||
require.NoError(t, electra.ApplyPendingDeposit(context.Background(), st, dep, false))
|
||||
|
||||
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, false))
|
||||
// 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, false))
|
||||
// no validator added
|
||||
require.Equal(t, 0, len(st.Validators()))
|
||||
// no topup either
|
||||
require.Equal(t, 0, len(st.Balances()))
|
||||
}
|
||||
|
||||
@@ -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,21 @@ 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)
|
||||
// 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 +26,31 @@ 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 +58,21 @@ func QueueExcessActiveBalance(s state.BeaconState, idx primitives.ValidatorIndex
|
||||
}
|
||||
|
||||
if bal > params.BeaconConfig().MinActivationBalance {
|
||||
val, err := s.ValidatorAtIndex(idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
excessBalance := bal - params.BeaconConfig().MinActivationBalance
|
||||
if err := s.UpdateBalancesAtIndex(idx, params.BeaconConfig().MinActivationBalance); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.AppendPendingBalanceDeposit(idx, excessBalance)
|
||||
return s.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: val.PublicKey,
|
||||
WithdrawalCredentials: val.WithdrawalCredentials,
|
||||
Amount: excessBalance,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
Slot: params.BeaconConfig().GenesisSlot,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -134,15 +81,19 @@ 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 +117,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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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++ {
|
||||
@@ -429,210 +251,18 @@ func createDefaultLightClientUpdate(v int) (*ethpbv2.LightClientUpdate, error) {
|
||||
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,7 +299,44 @@ func ComputeWithdrawalsRoot(payload interfaces.ExecutionData) ([]byte, error) {
|
||||
return withdrawalsRoot, nil
|
||||
}
|
||||
|
||||
func BlockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeader, error) {
|
||||
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:
|
||||
denebHeader, err := blockToLightClientHeaderDeneb(context.Background(), block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get deneb 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,7 +359,7 @@ func BlockToLightClientHeaderAltair(block interfaces.ReadOnlySignedBeaconBlock)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlockToLightClientHeaderCapella(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderCapella, error) {
|
||||
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,7 +421,7 @@ func BlockToLightClientHeaderCapella(ctx context.Context, block interfaces.ReadO
|
||||
}, nil
|
||||
}
|
||||
|
||||
func BlockToLightClientHeaderDeneb(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock) (*ethpbv2.LightClientHeaderDeneb, error) {
|
||||
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()))
|
||||
}
|
||||
|
||||
@@ -3,382 +3,600 @@ package light_client_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
consensustypes "github.com/prysmaticlabs/prysm/v5/consensus-types"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
|
||||
v11 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
|
||||
lightClient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
|
||||
light_client "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/util"
|
||||
)
|
||||
|
||||
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconStateAltair(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T) {
|
||||
t.Run("Altair", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
|
||||
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
})
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
t.Run("Capella", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false)
|
||||
|
||||
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
})
|
||||
|
||||
t.Run("Deneb", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false)
|
||||
|
||||
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconStateCapella(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false)
|
||||
func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
|
||||
t.Run("Altair", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
|
||||
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
finalizedBlockHeader, err := l.FinalizedBlock.Header()
|
||||
require.NoError(t, err)
|
||||
|
||||
//zeroHash := params.BeaconConfig().ZeroHash[:]
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
|
||||
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
|
||||
finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
for i, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Capella", func(t *testing.T) {
|
||||
|
||||
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false)
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
finalizedBlockHeader, err := l.FinalizedBlock.Header()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
|
||||
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
for i, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal")
|
||||
}
|
||||
|
||||
// Check Execution BlockHash
|
||||
payloadInterface, err := l.FinalizedBlock.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensustypes.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
require.NoError(t, err)
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
require.NoError(t, err)
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensustypes.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
execution := &v11.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,
|
||||
}
|
||||
require.DeepSSZEqual(t, execution, update.FinalizedHeader.GetHeaderCapella().Execution, "Finalized Block Execution is not equal")
|
||||
})
|
||||
|
||||
t.Run("FinalizedBlock In Previous Fork", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapellaFinalizedBlockAltair(false)
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
finalizedBlockHeader, err := l.FinalizedBlock.Header()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
|
||||
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
for i, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Deneb", func(t *testing.T) {
|
||||
|
||||
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false)
|
||||
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
//zeroHash := params.BeaconConfig().ZeroHash[:]
|
||||
finalizedBlockHeader, err := l.FinalizedBlock.Header()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
|
||||
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
for i, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal")
|
||||
}
|
||||
|
||||
// Check Execution BlockHash
|
||||
payloadInterface, err := l.FinalizedBlock.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensustypes.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
require.NoError(t, err)
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
require.NoError(t, err)
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensustypes.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
execution := &v11.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,
|
||||
}
|
||||
require.DeepSSZEqual(t, execution, update.FinalizedHeader.GetHeaderDeneb().Execution, "Finalized Block Execution is not equal")
|
||||
})
|
||||
|
||||
t.Run("FinalizedBlock In Previous Fork", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDenebFinalizedBlockCapella(false)
|
||||
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
finalizedBlockHeader, err := l.FinalizedBlock.Header()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
|
||||
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
|
||||
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
finalityBranch, err := l.AttestedState.FinalizedRootProof(l.Ctx)
|
||||
require.NoError(t, err)
|
||||
for i, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, finalityBranch[i], leaf, "Leaf is not equal")
|
||||
}
|
||||
|
||||
// Check Execution BlockHash
|
||||
payloadInterface, err := l.FinalizedBlock.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
transactionsRoot, err := payloadInterface.TransactionsRoot()
|
||||
if errors.Is(err, consensustypes.ErrUnsupportedField) {
|
||||
transactions, err := payloadInterface.Transactions()
|
||||
require.NoError(t, err)
|
||||
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
|
||||
require.NoError(t, err)
|
||||
transactionsRoot = transactionsRootArray[:]
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
|
||||
if errors.Is(err, consensustypes.ErrUnsupportedField) {
|
||||
withdrawals, err := payloadInterface.Withdrawals()
|
||||
require.NoError(t, err)
|
||||
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
require.NoError(t, err)
|
||||
withdrawalsRoot = withdrawalsRootArray[:]
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
execution := &v11.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,
|
||||
}
|
||||
require.DeepSSZEqual(t, execution, update.FinalizedHeader.GetHeaderCapella().Execution, "Finalized Block Execution is not equal")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLightClient_NewLightClientOptimisticUpdateFromBeaconStateDeneb(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false)
|
||||
func TestLightClient_BlockToLightClientHeader(t *testing.T) {
|
||||
t.Run("Altair", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
|
||||
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
container, err := lightClient.BlockToLightClientHeader(l.Block)
|
||||
require.NoError(t, err)
|
||||
header := container.GetHeaderAltair()
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
})
|
||||
|
||||
t.Run("Capella", func(t *testing.T) {
|
||||
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false)
|
||||
|
||||
container, err := lightClient.BlockToLightClientHeader(l.Block)
|
||||
require.NoError(t, err)
|
||||
header := container.GetHeaderCapella()
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := lightClient.ComputeTransactionsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := lightClient.ComputeWithdrawalsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
})
|
||||
|
||||
t.Run("Blinded Beacon Block", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(true)
|
||||
|
||||
container, err := lightClient.BlockToLightClientHeader(l.Block)
|
||||
require.NoError(t, err)
|
||||
header := container.GetHeaderCapella()
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := payload.TransactionsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := payload.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Deneb", func(t *testing.T) {
|
||||
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false)
|
||||
|
||||
container, err := lightClient.BlockToLightClientHeader(l.Block)
|
||||
require.NoError(t, err)
|
||||
header := container.GetHeaderDeneb()
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := lightClient.ComputeTransactionsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := lightClient.ComputeWithdrawalsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
blobGasUsed, err := payload.BlobGasUsed()
|
||||
require.NoError(t, err)
|
||||
|
||||
excessBlobGas, err := payload.ExcessBlobGas()
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
})
|
||||
|
||||
t.Run("Blinded Beacon Block", func(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(true)
|
||||
|
||||
container, err := lightClient.BlockToLightClientHeader(l.Block)
|
||||
require.NoError(t, err)
|
||||
header := container.GetHeaderDeneb()
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := payload.TransactionsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := payload.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
blobGasUsed, err := payload.BlobGasUsed()
|
||||
require.NoError(t, err)
|
||||
|
||||
excessBlobGas, err := payload.ExcessBlobGas()
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLightClient_NewLightClientFinalityUpdateFromBeaconStateAltair(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
zeroHash := params.BeaconConfig().ZeroHash[:]
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Slot(0), updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not zero")
|
||||
require.Equal(t, primitives.ValidatorIndex(0), updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not zero")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
for _, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLightClient_NewLightClientFinalityUpdateFromBeaconStateCapella(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false)
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
zeroHash := params.BeaconConfig().ZeroHash[:]
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Slot(0), updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not zero")
|
||||
require.Equal(t, primitives.ValidatorIndex(0), updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not zero")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
for _, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLightClient_NewLightClientFinalityUpdateFromBeaconStateDeneb(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false)
|
||||
|
||||
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, update, "update is nil")
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot, "Signature slot is not equal")
|
||||
|
||||
l.CheckSyncAggregate(update.SyncAggregate)
|
||||
l.CheckAttestedHeader(update.AttestedHeader)
|
||||
|
||||
zeroHash := params.BeaconConfig().ZeroHash[:]
|
||||
require.NotNil(t, update.FinalizedHeader, "Finalized header is nil")
|
||||
updateFinalizedHeaderBeacon, err := update.FinalizedHeader.GetBeacon()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Slot(0), updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not zero")
|
||||
require.Equal(t, primitives.ValidatorIndex(0), updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not zero")
|
||||
require.DeepSSZEqual(t, zeroHash, update.FinalizedHeader.GetHeaderDeneb().Execution.BlockHash, "Execution BlockHash is not zero")
|
||||
require.Equal(t, lightClient.FinalityBranchNumOfLeaves, len(update.FinalityBranch), "Invalid finality branch leaves")
|
||||
for _, leaf := range update.FinalityBranch {
|
||||
require.DeepSSZEqual(t, zeroHash, leaf, "Leaf is not zero")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLightClient_BlockToLightClientHeaderAltair(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestAltair()
|
||||
|
||||
header, err := lightClient.BlockToLightClientHeaderAltair(l.Block)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
}
|
||||
|
||||
func TestLightClient_BlockToLightClientHeaderCapella_NonBlindedBeaconBlock(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(false)
|
||||
|
||||
header, err := lightClient.BlockToLightClientHeaderCapella(l.Ctx, l.Block)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := light_client.ComputeTransactionsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := light_client.ComputeWithdrawalsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
}
|
||||
|
||||
func TestLightClient_BlockToLightClientHeaderCapella_BlindedBeaconBlock(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestCapella(true)
|
||||
|
||||
header, err := lightClient.BlockToLightClientHeaderCapella(l.Ctx, l.Block)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := payload.TransactionsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := payload.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderCapella{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
}
|
||||
|
||||
func TestLightClient_BlockToLightClientHeaderDeneb_NonBlindedBeaconBlock(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(false)
|
||||
|
||||
header, err := lightClient.BlockToLightClientHeaderDeneb(l.Ctx, l.Block)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := light_client.ComputeTransactionsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := light_client.ComputeWithdrawalsRoot(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
blobGasUsed, err := payload.BlobGasUsed()
|
||||
require.NoError(t, err)
|
||||
|
||||
excessBlobGas, err := payload.ExcessBlobGas()
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
}
|
||||
|
||||
func TestLightClient_BlockToLightClientHeaderDeneb_BlindedBeaconBlock(t *testing.T) {
|
||||
l := util.NewTestLightClient(t).SetupTestDeneb(true)
|
||||
|
||||
header, err := lightClient.BlockToLightClientHeaderDeneb(l.Ctx, l.Block)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, header, "header is nil")
|
||||
|
||||
parentRoot := l.Block.Block().ParentRoot()
|
||||
stateRoot := l.Block.Block().StateRoot()
|
||||
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
payload, err := l.Block.Block().Body().Execution()
|
||||
require.NoError(t, err)
|
||||
|
||||
transactionsRoot, err := payload.TransactionsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
withdrawalsRoot, err := payload.WithdrawalsRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
blobGasUsed, err := payload.BlobGasUsed()
|
||||
require.NoError(t, err)
|
||||
|
||||
excessBlobGas, err := payload.ExcessBlobGas()
|
||||
require.NoError(t, err)
|
||||
|
||||
executionHeader := &v11.ExecutionPayloadHeaderDeneb{
|
||||
ParentHash: payload.ParentHash(),
|
||||
FeeRecipient: payload.FeeRecipient(),
|
||||
StateRoot: payload.StateRoot(),
|
||||
ReceiptsRoot: payload.ReceiptsRoot(),
|
||||
LogsBloom: payload.LogsBloom(),
|
||||
PrevRandao: payload.PrevRandao(),
|
||||
BlockNumber: payload.BlockNumber(),
|
||||
GasLimit: payload.GasLimit(),
|
||||
GasUsed: payload.GasUsed(),
|
||||
Timestamp: payload.Timestamp(),
|
||||
ExtraData: payload.ExtraData(),
|
||||
BaseFeePerGas: payload.BaseFeePerGas(),
|
||||
BlockHash: payload.BlockHash(),
|
||||
TransactionsRoot: transactionsRoot,
|
||||
WithdrawalsRoot: withdrawalsRoot,
|
||||
BlobGasUsed: blobGasUsed,
|
||||
ExcessBlobGas: excessBlobGas,
|
||||
}
|
||||
|
||||
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, l.Block.Block().Slot(), header.Beacon.Slot, "Slot is not equal")
|
||||
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon.ProposerIndex, "Proposer index is not equal")
|
||||
require.DeepSSZEqual(t, parentRoot[:], header.Beacon.ParentRoot, "Parent root is not equal")
|
||||
require.DeepSSZEqual(t, stateRoot[:], header.Beacon.StateRoot, "State root is not equal")
|
||||
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon.BodyRoot, "Body root is not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionHeader, header.Execution, "Execution headers are not equal")
|
||||
|
||||
require.DeepSSZEqual(t, executionPayloadProof, header.ExecutionBranch, "Execution payload proofs are not equal")
|
||||
}
|
||||
|
||||
// TODO - add finality update tests with non-nil finalized block for different versions
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -167,15 +167,6 @@ func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionDa
|
||||
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)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unknown execution data type")
|
||||
}
|
||||
@@ -268,9 +259,6 @@ 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{}
|
||||
}
|
||||
if pe >= params.BeaconConfig().DenebForkEpoch {
|
||||
return GetPayloadMethodV3, &pb.ExecutionPayloadDenebWithValueAndBlobsBundle{}
|
||||
}
|
||||
@@ -566,7 +554,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 +583,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 +705,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 +719,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))
|
||||
}
|
||||
|
||||
@@ -320,72 +320,6 @@ func TestClient_HTTP(t *testing.T) {
|
||||
blobs := [][]byte{bytesutil.PadTo([]byte("a"), fieldparams.BlobLength), bytesutil.PadTo([]byte("b"), fieldparams.BlobLength)}
|
||||
require.DeepEqual(t, blobs, resp.BlobsBundle.Blobs)
|
||||
})
|
||||
t.Run(GetPayloadMethodV4, func(t *testing.T) {
|
||||
payloadId := [8]byte{1}
|
||||
want, ok := fix["ExecutionPayloadElectraWithValue"].(*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")
|
||||
defer func() {
|
||||
require.NoError(t, r.Body.Close())
|
||||
}()
|
||||
enc, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
jsonRequestString := string(enc)
|
||||
|
||||
reqArg, err := json.Marshal(pb.PayloadIDBytes(payloadId))
|
||||
require.NoError(t, err)
|
||||
|
||||
// We expect the JSON string RPC request contains the right arguments.
|
||||
require.Equal(t, true, strings.Contains(
|
||||
jsonRequestString, string(reqArg),
|
||||
))
|
||||
resp := map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": want,
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
rpcClient, err := rpc.DialHTTP(srv.URL)
|
||||
require.NoError(t, err)
|
||||
defer rpcClient.Close()
|
||||
|
||||
client := &Service{}
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, resp.OverrideBuilder)
|
||||
g, err := resp.ExecutionData.ExcessBlobGas()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, uint64(3), g)
|
||||
g, err = resp.ExecutionData.BlobGasUsed()
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, uint64(2), g)
|
||||
|
||||
commitments := [][]byte{bytesutil.PadTo([]byte("commitment1"), fieldparams.BLSPubkeyLength), bytesutil.PadTo([]byte("commitment2"), fieldparams.BLSPubkeyLength)}
|
||||
require.DeepEqual(t, commitments, resp.BlobsBundle.KzgCommitments)
|
||||
proofs := [][]byte{bytesutil.PadTo([]byte("proof1"), fieldparams.BLSPubkeyLength), bytesutil.PadTo([]byte("proof2"), fieldparams.BLSPubkeyLength)}
|
||||
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)
|
||||
})
|
||||
t.Run(ForkchoiceUpdatedMethod+" VALID status", func(t *testing.T) {
|
||||
forkChoiceState := &pb.ForkchoiceState{
|
||||
HeadBlockHash: []byte("head"),
|
||||
@@ -568,20 +502,6 @@ func TestClient_HTTP(t *testing.T) {
|
||||
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)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
t.Run(NewPayloadMethod+" SYNCING status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
|
||||
require.Equal(t, true, ok)
|
||||
@@ -624,20 +544,6 @@ func TestClient_HTTP(t *testing.T) {
|
||||
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)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
t.Run(NewPayloadMethod+" INVALID_BLOCK_HASH status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
|
||||
require.Equal(t, true, ok)
|
||||
@@ -680,20 +586,6 @@ func TestClient_HTTP(t *testing.T) {
|
||||
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)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
|
||||
require.DeepEqual(t, []uint8(nil), resp)
|
||||
})
|
||||
t.Run(NewPayloadMethod+" INVALID status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
|
||||
require.Equal(t, true, ok)
|
||||
@@ -736,20 +628,6 @@ func TestClient_HTTP(t *testing.T) {
|
||||
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)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
|
||||
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
|
||||
require.DeepEqual(t, want.LatestValidHash, resp)
|
||||
})
|
||||
t.Run(NewPayloadMethod+" UNKNOWN status", func(t *testing.T) {
|
||||
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
|
||||
require.Equal(t, true, ok)
|
||||
@@ -1417,10 +1295,8 @@ 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,
|
||||
"ValidPayloadStatus": s.ValidPayloadStatus,
|
||||
"InvalidBlockHashStatus": s.InvalidBlockHashStatus,
|
||||
"AcceptedStatus": s.AcceptedStatus,
|
||||
@@ -1558,40 +1434,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,36 +1483,6 @@ func fixturesStruct() *payloadFixtures {
|
||||
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
|
||||
},
|
||||
}
|
||||
executionPayloadWithValueFixtureElectra := &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,
|
||||
},
|
||||
BlockValue: "0x11fffffffff",
|
||||
BlobsBundle: &pb.BlobBundleJSON{
|
||||
Commitments: []hexutil.Bytes{[]byte("commitment1"), []byte("commitment2")},
|
||||
Proofs: []hexutil.Bytes{[]byte("proof1"), []byte("proof2")},
|
||||
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
|
||||
},
|
||||
}
|
||||
parent := bytesutil.PadTo([]byte("parentHash"), fieldparams.RootLength)
|
||||
sha3Uncles := bytesutil.PadTo([]byte("sha3Uncles"), fieldparams.RootLength)
|
||||
miner := bytesutil.PadTo([]byte("miner"), fieldparams.FeeRecipientLength)
|
||||
@@ -1762,10 +1574,8 @@ func fixturesStruct() *payloadFixtures {
|
||||
ExecutionPayloadCapella: executionPayloadFixtureCapella,
|
||||
ExecutionPayloadDeneb: executionPayloadFixtureDeneb,
|
||||
EmptyExecutionPayloadDeneb: emptyExecutionPayloadDeneb,
|
||||
ExecutionPayloadElectra: executionPayloadFixtureElectra,
|
||||
ExecutionPayloadWithValueCapella: executionPayloadWithValueFixtureCapella,
|
||||
ExecutionPayloadWithValueDeneb: executionPayloadWithValueFixtureDeneb,
|
||||
ExecutionPayloadWithValueElectra: executionPayloadWithValueFixtureElectra,
|
||||
ValidPayloadStatus: validStatus,
|
||||
InvalidBlockHashStatus: inValidBlockHashStatus,
|
||||
AcceptedStatus: acceptedStatus,
|
||||
@@ -1787,10 +1597,8 @@ type payloadFixtures struct {
|
||||
ExecutionPayloadCapella *pb.ExecutionPayloadCapella
|
||||
EmptyExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
|
||||
ExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
|
||||
ExecutionPayloadElectra *pb.ExecutionPayloadElectra
|
||||
ExecutionPayloadWithValueCapella *pb.GetPayloadV2ResponseJson
|
||||
ExecutionPayloadWithValueDeneb *pb.GetPayloadV3ResponseJson
|
||||
ExecutionPayloadWithValueElectra *pb.GetPayloadV4ResponseJson
|
||||
ValidPayloadStatus *pb.PayloadStatus
|
||||
InvalidBlockHashStatus *pb.PayloadStatus
|
||||
AcceptedStatus *pb.PayloadStatus
|
||||
@@ -2149,40 +1957,6 @@ 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 {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
defer func() {
|
||||
require.NoError(t, r.Body.Close())
|
||||
}()
|
||||
enc, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
jsonRequestString := string(enc)
|
||||
|
||||
reqArg, 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),
|
||||
))
|
||||
resp := map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": status,
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(resp)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
|
||||
rpcClient, err := rpc.DialHTTP(srv.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
service := &Service{}
|
||||
service.rpcClient = rpcClient
|
||||
return service
|
||||
}
|
||||
|
||||
func TestReconstructBlindedBlockBatch(t *testing.T) {
|
||||
t.Run("empty response works", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -64,12 +64,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 +126,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 +158,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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -773,6 +773,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 +951,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 +1003,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,7 +33,6 @@ 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},
|
||||
@@ -69,7 +70,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 +115,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 +134,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)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1512,10 +1512,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 +1523,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 {
|
||||
|
||||
@@ -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,7 +493,29 @@ 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) {
|
||||
@@ -759,6 +813,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'})
|
||||
@@ -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()
|
||||
|
||||
@@ -29,12 +29,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 +53,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,11 +1,13 @@
|
||||
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"
|
||||
@@ -16,7 +18,7 @@ import (
|
||||
"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/monitoring/tracing/trace"
|
||||
@@ -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,83 @@ 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")
|
||||
)
|
||||
|
||||
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 +152,412 @@ 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 (es *eventStreamer) writeOutbox(ctx context.Context, w StreamingResponseWriter, first lazyReader) error {
|
||||
needKeepAlive := true
|
||||
if first != nil {
|
||||
if _, err := io.Copy(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 := io.Copy(w, rf()); err != nil {
|
||||
return err
|
||||
}
|
||||
needKeepAlive = false
|
||||
default:
|
||||
if needKeepAlive {
|
||||
if _, err := io.Copy(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 := blockchain.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 +575,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()),
|
||||
@@ -436,11 +586,11 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
case version.Deneb, version.Electra:
|
||||
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()),
|
||||
@@ -450,12 +600,12 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite
|
||||
ParentBeaconBlockRoot: hexutil.Encode(parentRoot[:]),
|
||||
}
|
||||
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 +617,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,35 @@ 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_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 +92,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 +132,18 @@ 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_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,44 +17,26 @@ 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:
|
||||
case version.Deneb:
|
||||
return createLightClientBootstrapDeneb(ctx, state, blk)
|
||||
}
|
||||
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,14 +48,44 @@ 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")
|
||||
@@ -84,38 +95,16 @@ func createLightClientBootstrapAltair(ctx context.Context, state state.BeaconSta
|
||||
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,14 +116,44 @@ 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")
|
||||
@@ -144,94 +163,16 @@ func createLightClientBootstrapCapella(ctx context.Context, state state.BeaconSt
|
||||
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,14 +184,44 @@ 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
|
||||
// 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.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")
|
||||
}
|
||||
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")
|
||||
@@ -260,86 +231,9 @@ func createLightClientBootstrapDeneb(ctx context.Context, state state.BeaconStat
|
||||
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
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -351,9 +245,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 +261,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 +277,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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -15,7 +15,9 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/eth/shared"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/httputil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -183,3 +185,52 @@ func (s *Server) GetChainHead(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
httputil.WriteJson(w, response)
|
||||
}
|
||||
|
||||
func (s *Server) PublishBlobs(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "beacon.PublishBlobs")
|
||||
defer span.End()
|
||||
if shared.IsSyncing(r.Context(), w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
|
||||
return
|
||||
}
|
||||
|
||||
var req structs.PublishBlobsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
httputil.HandleError(w, "Could not decode JSON request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.BlobSidecars == nil {
|
||||
httputil.HandleError(w, "Missing blob sidecars", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
root, err := bytesutil.DecodeHexWithLength(req.BlockRoot, 32)
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not decode block root: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
for _, blobSidecar := range req.BlobSidecars.Sidecars {
|
||||
sc, err := blobSidecar.ToConsensus()
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not decode blob sidecar: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
readOnlySc, err := blocks.NewROBlobWithRoot(sc, bytesutil.ToBytes32(root))
|
||||
if err != nil {
|
||||
httputil.HandleError(w, "Could not create read-only blob: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
verifiedBlob := blocks.NewVerifiedROBlob(readOnlySc)
|
||||
if err := s.BlobReceiver.ReceiveBlob(ctx, verifiedBlob); err != nil {
|
||||
httputil.HandleError(w, "Could not receive blob: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.Broadcaster.BroadcastBlob(ctx, sc.Index, sc); err != nil {
|
||||
httputil.HandleError(w, "Failed to broadcast blob: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,13 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
dbTest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
mockp2p "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
rpctesting "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/testing"
|
||||
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
mockstategen "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen/mock"
|
||||
mockSync "github.com/prysmaticlabs/prysm/v5/beacon-chain/sync/initial-sync/testing"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
@@ -870,3 +873,163 @@ func TestServer_GetChainHead(t *testing.T) {
|
||||
assert.DeepEqual(t, hexutil.Encode(fRoot[:]), ch.FinalizedBlockRoot, "Unexpected FinalizedBlockRoot")
|
||||
assert.Equal(t, false, ch.OptimisticStatus)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_InvalidJson(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.InvalidJson)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Could not decode JSON request body", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_MissingBlob(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestMissingBlob)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Could not decode blob sidecar", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_MissingSignedBlockHeader(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestMissingSignedBlockHeader)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Could not decode blob sidecar", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_MissingSidecars(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestMissingSidecars)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Missing blob sidecars", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_EmptySidecarsList(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestEmptySidecarsList)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_NullSidecar(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestNullSidecar)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Could not decode blob sidecar", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_SeveralFieldsMissing(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestSeveralFieldsMissing)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Could not decode blob sidecar", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs_BadBlockRoot(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequestBadBlockRoot)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusBadRequest, writer.Code)
|
||||
assert.StringContains(t, "Could not decode block root", writer.Body.String())
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 0)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), false)
|
||||
}
|
||||
|
||||
func TestPublishBlobs(t *testing.T) {
|
||||
server := &Server{
|
||||
BlobReceiver: &chainMock.ChainService{},
|
||||
Broadcaster: &mockp2p.MockBroadcaster{},
|
||||
SyncChecker: &mockSync.Sync{IsSyncing: false},
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "http://foo.example", bytes.NewReader([]byte(rpctesting.PublishBlobsRequest)))
|
||||
writer := httptest.NewRecorder()
|
||||
writer.Body = &bytes.Buffer{}
|
||||
server.PublishBlobs(writer, request)
|
||||
assert.Equal(t, http.StatusOK, writer.Code)
|
||||
|
||||
assert.Equal(t, len(server.BlobReceiver.(*chainMock.ChainService).Blobs), 1)
|
||||
assert.Equal(t, server.Broadcaster.(*mockp2p.MockBroadcaster).BroadcastCalled.Load(), true)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package beacon
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain"
|
||||
beacondb "github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/core"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/lookup"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
@@ -20,4 +21,6 @@ type Server struct {
|
||||
ChainInfoFetcher blockchain.ChainInfoFetcher
|
||||
FinalizationFetcher blockchain.FinalizationFetcher
|
||||
CoreService *core.Service
|
||||
Broadcaster p2p.Broadcaster
|
||||
BlobReceiver blockchain.BlobReceiver
|
||||
}
|
||||
|
||||
9
beacon-chain/rpc/prysm/testing/BUILD.bazel
Normal file
9
beacon-chain/rpc/prysm/testing/BUILD.bazel
Normal file
@@ -0,0 +1,9 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = ["json.go"],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/prysm/testing",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
210
beacon-chain/rpc/prysm/testing/json.go
Normal file
210
beacon-chain/rpc/prysm/testing/json.go
Normal file
File diff suppressed because one or more lines are too long
@@ -586,40 +586,6 @@ func TestServer_GetBeaconBlock_Electra(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
var scBits [fieldparams.SyncAggregateSyncCommitteeBytesLength]byte
|
||||
blk := ðpb.SignedBeaconBlockElectra{
|
||||
Block: ðpb.BeaconBlockElectra{
|
||||
Slot: electraSlot + 1,
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: genesis.Block.StateRoot,
|
||||
Body: ðpb.BeaconBlockBodyElectra{
|
||||
RandaoReveal: genesis.Block.Body.RandaoReveal,
|
||||
Graffiti: genesis.Block.Body.Graffiti,
|
||||
Eth1Data: genesis.Block.Body.Eth1Data,
|
||||
SyncAggregate: ðpb.SyncAggregate{SyncCommitteeBits: scBits[:], SyncCommitteeSignature: make([]byte, 96)},
|
||||
ExecutionPayload: &enginev1.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),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
blkRoot, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err, "Could not get signing root")
|
||||
require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state")
|
||||
require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state")
|
||||
|
||||
random, err := helpers.RandaoMix(beaconState, slots.ToEpoch(beaconState.Slot()))
|
||||
require.NoError(t, err)
|
||||
timeStamp, err := slots.ToTime(beaconState.GenesisTime(), electraSlot+1)
|
||||
require.NoError(t, err)
|
||||
dr := []*enginev1.DepositRequest{{
|
||||
Pubkey: bytesutil.PadTo(privKeys[0].PublicKey().Marshal(), 48),
|
||||
WithdrawalCredentials: bytesutil.PadTo([]byte("wc"), 32),
|
||||
@@ -641,21 +607,56 @@ func TestServer_GetBeaconBlock_Electra(t *testing.T) {
|
||||
TargetPubkey: bytesutil.PadTo(privKeys[2].PublicKey().Marshal(), 48),
|
||||
},
|
||||
}
|
||||
payload := &enginev1.ExecutionPayloadElectra{
|
||||
Timestamp: uint64(timeStamp.Unix()),
|
||||
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: random,
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
DepositRequests: dr,
|
||||
WithdrawalRequests: wr,
|
||||
ConsolidationRequests: cr,
|
||||
blk := ðpb.SignedBeaconBlockElectra{
|
||||
Block: ðpb.BeaconBlockElectra{
|
||||
Slot: electraSlot + 1,
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: genesis.Block.StateRoot,
|
||||
Body: ðpb.BeaconBlockBodyElectra{
|
||||
RandaoReveal: genesis.Block.Body.RandaoReveal,
|
||||
Graffiti: genesis.Block.Body.Graffiti,
|
||||
Eth1Data: genesis.Block.Body.Eth1Data,
|
||||
SyncAggregate: ðpb.SyncAggregate{SyncCommitteeBits: scBits[:], SyncCommitteeSignature: make([]byte, 96)},
|
||||
ExecutionPayload: &enginev1.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),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{
|
||||
Withdrawals: wr,
|
||||
Deposits: dr,
|
||||
Consolidations: cr,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
blkRoot, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err, "Could not get signing root")
|
||||
require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state")
|
||||
require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state")
|
||||
|
||||
random, err := helpers.RandaoMix(beaconState, slots.ToEpoch(beaconState.Slot()))
|
||||
require.NoError(t, err)
|
||||
timeStamp, err := slots.ToTime(beaconState.GenesisTime(), electraSlot+1)
|
||||
require.NoError(t, err)
|
||||
payload := &enginev1.ExecutionPayloadElectra{
|
||||
Timestamp: uint64(timeStamp.Unix()),
|
||||
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: random,
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
}
|
||||
proposerServer := getProposerServer(db, beaconState, parentRoot[:])
|
||||
ed, err := blocks.NewWrappedExecutionData(payload)
|
||||
require.NoError(t, err)
|
||||
@@ -675,11 +676,8 @@ func TestServer_GetBeaconBlock_Electra(t *testing.T) {
|
||||
Graffiti: graffiti[:],
|
||||
}
|
||||
|
||||
got, err := proposerServer.GetBeaconBlock(ctx, req)
|
||||
_, err = proposerServer.GetBeaconBlock(ctx, req)
|
||||
require.NoError(t, err)
|
||||
p := got.GetElectra().Block.Body.ExecutionPayload
|
||||
require.DeepEqual(t, dr, p.DepositRequests)
|
||||
require.DeepEqual(t, wr, p.WithdrawalRequests)
|
||||
}
|
||||
|
||||
func TestServer_GetBeaconBlock_Optimistic(t *testing.T) {
|
||||
|
||||
@@ -225,7 +225,7 @@ type ReadOnlySyncCommittee interface {
|
||||
type ReadOnlyDeposits interface {
|
||||
DepositBalanceToConsume() (primitives.Gwei, error)
|
||||
DepositRequestsStartIndex() (uint64, error)
|
||||
PendingBalanceDeposits() ([]*ethpb.PendingBalanceDeposit, error)
|
||||
PendingDeposits() ([]*ethpb.PendingDeposit, error)
|
||||
}
|
||||
|
||||
type ReadOnlyConsolidations interface {
|
||||
@@ -331,8 +331,8 @@ type WriteOnlyConsolidations interface {
|
||||
}
|
||||
|
||||
type WriteOnlyDeposits interface {
|
||||
AppendPendingBalanceDeposit(index primitives.ValidatorIndex, amount uint64) error
|
||||
AppendPendingDeposit(pd *ethpb.PendingDeposit) error
|
||||
SetDepositRequestsStartIndex(index uint64) error
|
||||
SetPendingBalanceDeposits(val []*ethpb.PendingBalanceDeposit) error
|
||||
SetPendingDeposits(val []*ethpb.PendingDeposit) error
|
||||
SetDepositBalanceToConsume(primitives.Gwei) error
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ go_library(
|
||||
"doc.go",
|
||||
"error.go",
|
||||
"getters_attestation.go",
|
||||
"getters_balance_deposits.go",
|
||||
"getters_block.go",
|
||||
"getters_checkpoint.go",
|
||||
"getters_consolidation.go",
|
||||
"getters_deposit_requests.go",
|
||||
"getters_deposits.go",
|
||||
"getters_eth1.go",
|
||||
"getters_exit.go",
|
||||
"getters_misc.go",
|
||||
@@ -27,12 +27,12 @@ go_library(
|
||||
"proofs.go",
|
||||
"readonly_validator.go",
|
||||
"setters_attestation.go",
|
||||
"setters_balance_deposits.go",
|
||||
"setters_block.go",
|
||||
"setters_checkpoint.go",
|
||||
"setters_churn.go",
|
||||
"setters_consolidation.go",
|
||||
"setters_deposit_requests.go",
|
||||
"setters_deposits.go",
|
||||
"setters_eth1.go",
|
||||
"setters_misc.go",
|
||||
"setters_participation.go",
|
||||
@@ -91,11 +91,11 @@ go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"getters_attestation_test.go",
|
||||
"getters_balance_deposits_test.go",
|
||||
"getters_block_test.go",
|
||||
"getters_checkpoint_test.go",
|
||||
"getters_consolidation_test.go",
|
||||
"getters_deposit_requests_test.go",
|
||||
"getters_deposits_test.go",
|
||||
"getters_exit_test.go",
|
||||
"getters_participation_test.go",
|
||||
"getters_test.go",
|
||||
@@ -107,10 +107,10 @@ go_test(
|
||||
"readonly_validator_test.go",
|
||||
"references_test.go",
|
||||
"setters_attestation_test.go",
|
||||
"setters_balance_deposits_test.go",
|
||||
"setters_churn_test.go",
|
||||
"setters_consolidation_test.go",
|
||||
"setters_deposit_requests_test.go",
|
||||
"setters_deposits_test.go",
|
||||
"setters_eth1_test.go",
|
||||
"setters_misc_test.go",
|
||||
"setters_participation_test.go",
|
||||
|
||||
@@ -55,7 +55,6 @@ type BeaconState struct {
|
||||
latestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader
|
||||
latestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella
|
||||
latestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb
|
||||
latestExecutionPayloadHeaderElectra *enginev1.ExecutionPayloadHeaderElectra
|
||||
nextWithdrawalIndex uint64
|
||||
nextWithdrawalValidatorIndex primitives.ValidatorIndex
|
||||
|
||||
@@ -66,7 +65,7 @@ type BeaconState struct {
|
||||
earliestExitEpoch primitives.Epoch
|
||||
consolidationBalanceToConsume primitives.Gwei
|
||||
earliestConsolidationEpoch primitives.Epoch
|
||||
pendingBalanceDeposits []*ethpb.PendingBalanceDeposit // pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT]
|
||||
pendingDeposits []*ethpb.PendingDeposit // pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT]
|
||||
pendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // pending_partial_withdrawals: List[PartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT]
|
||||
pendingConsolidations []*ethpb.PendingConsolidation // pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]
|
||||
|
||||
@@ -114,7 +113,6 @@ type beaconStateMarshalable struct {
|
||||
LatestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader `json:"latest_execution_payload_header" yaml:"latest_execution_payload_header"`
|
||||
LatestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella `json:"latest_execution_payload_header_capella" yaml:"latest_execution_payload_header_capella"`
|
||||
LatestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb `json:"latest_execution_payload_header_deneb" yaml:"latest_execution_payload_header_deneb"`
|
||||
LatestExecutionPayloadHeaderElectra *enginev1.ExecutionPayloadHeaderElectra `json:"latest_execution_payload_header_electra" yaml:"latest_execution_payload_header_electra"`
|
||||
NextWithdrawalIndex uint64 `json:"next_withdrawal_index" yaml:"next_withdrawal_index"`
|
||||
NextWithdrawalValidatorIndex primitives.ValidatorIndex `json:"next_withdrawal_validator_index" yaml:"next_withdrawal_validator_index"`
|
||||
DepositRequestsStartIndex uint64 `json:"deposit_requests_start_index" yaml:"deposit_requests_start_index"`
|
||||
@@ -123,7 +121,7 @@ type beaconStateMarshalable struct {
|
||||
EarliestExitEpoch primitives.Epoch `json:"earliest_exit_epoch" yaml:"earliest_exit_epoch"`
|
||||
ConsolidationBalanceToConsume primitives.Gwei `json:"consolidation_balance_to_consume" yaml:"consolidation_balance_to_consume"`
|
||||
EarliestConsolidationEpoch primitives.Epoch `json:"earliest_consolidation_epoch" yaml:"earliest_consolidation_epoch"`
|
||||
PendingBalanceDeposits []*ethpb.PendingBalanceDeposit `json:"pending_balance_deposits" yaml:"pending_balance_deposits"`
|
||||
PendingDeposits []*ethpb.PendingDeposit `json:"pending_deposits" yaml:"pending_deposits"`
|
||||
PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal `json:"pending_partial_withdrawals" yaml:"pending_partial_withdrawals"`
|
||||
PendingConsolidations []*ethpb.PendingConsolidation `json:"pending_consolidations" yaml:"pending_consolidations"`
|
||||
}
|
||||
@@ -184,7 +182,6 @@ func (b *BeaconState) MarshalJSON() ([]byte, error) {
|
||||
LatestExecutionPayloadHeader: b.latestExecutionPayloadHeader,
|
||||
LatestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapella,
|
||||
LatestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDeneb,
|
||||
LatestExecutionPayloadHeaderElectra: b.latestExecutionPayloadHeaderElectra,
|
||||
NextWithdrawalIndex: b.nextWithdrawalIndex,
|
||||
NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex,
|
||||
DepositRequestsStartIndex: b.depositRequestsStartIndex,
|
||||
@@ -193,7 +190,7 @@ func (b *BeaconState) MarshalJSON() ([]byte, error) {
|
||||
EarliestExitEpoch: b.earliestExitEpoch,
|
||||
ConsolidationBalanceToConsume: b.consolidationBalanceToConsume,
|
||||
EarliestConsolidationEpoch: b.earliestConsolidationEpoch,
|
||||
PendingBalanceDeposits: b.pendingBalanceDeposits,
|
||||
PendingDeposits: b.pendingDeposits,
|
||||
PendingPartialWithdrawals: b.pendingPartialWithdrawals,
|
||||
PendingConsolidations: b.pendingConsolidations,
|
||||
}
|
||||
|
||||
@@ -18,22 +18,22 @@ func (b *BeaconState) DepositBalanceToConsume() (primitives.Gwei, error) {
|
||||
return b.depositBalanceToConsume, nil
|
||||
}
|
||||
|
||||
// PendingBalanceDeposits is a non-mutating call to the beacon state which returns a deep copy of
|
||||
// PendingDeposits is a non-mutating call to the beacon state which returns a deep copy of
|
||||
// the pending balance deposit slice. This method requires access to the RLock on the state and
|
||||
// only applies in electra or later.
|
||||
func (b *BeaconState) PendingBalanceDeposits() ([]*ethpb.PendingBalanceDeposit, error) {
|
||||
func (b *BeaconState) PendingDeposits() ([]*ethpb.PendingDeposit, error) {
|
||||
if b.version < version.Electra {
|
||||
return nil, errNotSupported("PendingBalanceDeposits", b.version)
|
||||
return nil, errNotSupported("PendingDeposits", b.version)
|
||||
}
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
return b.pendingBalanceDepositsVal(), nil
|
||||
return b.pendingDepositsVal(), nil
|
||||
}
|
||||
|
||||
func (b *BeaconState) pendingBalanceDepositsVal() []*ethpb.PendingBalanceDeposit {
|
||||
if b.pendingBalanceDeposits == nil {
|
||||
func (b *BeaconState) pendingDepositsVal() []*ethpb.PendingDeposit {
|
||||
if b.pendingDeposits == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ethpb.CopySlice(b.pendingBalanceDeposits)
|
||||
return ethpb.CopySlice(b.pendingDeposits)
|
||||
}
|
||||
@@ -25,21 +25,40 @@ func TestDepositBalanceToConsume(t *testing.T) {
|
||||
require.ErrorContains(t, "not supported", err)
|
||||
}
|
||||
|
||||
func TestPendingBalanceDeposits(t *testing.T) {
|
||||
func TestPendingDeposits(t *testing.T) {
|
||||
s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{
|
||||
PendingBalanceDeposits: []*eth.PendingBalanceDeposit{
|
||||
{Index: 1, Amount: 2},
|
||||
{Index: 3, Amount: 4},
|
||||
PendingDeposits: []*eth.PendingDeposit{
|
||||
{
|
||||
PublicKey: []byte{1, 2, 3},
|
||||
WithdrawalCredentials: []byte{4, 5, 6},
|
||||
Amount: 2,
|
||||
Signature: []byte{7, 8, 9},
|
||||
Slot: 1,
|
||||
},
|
||||
{
|
||||
PublicKey: []byte{11, 22, 33},
|
||||
WithdrawalCredentials: []byte{44, 55, 66},
|
||||
Amount: 4,
|
||||
Signature: []byte{77, 88, 99},
|
||||
Slot: 2,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
pbd, err := s.PendingBalanceDeposits()
|
||||
pbd, err := s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(pbd))
|
||||
require.Equal(t, primitives.ValidatorIndex(1), pbd[0].Index)
|
||||
require.DeepEqual(t, []byte{1, 2, 3}, pbd[0].PublicKey)
|
||||
require.DeepEqual(t, []byte{4, 5, 6}, pbd[0].WithdrawalCredentials)
|
||||
require.Equal(t, uint64(2), pbd[0].Amount)
|
||||
require.Equal(t, primitives.ValidatorIndex(3), pbd[1].Index)
|
||||
require.DeepEqual(t, []byte{7, 8, 9}, pbd[0].Signature)
|
||||
require.Equal(t, primitives.Slot(1), pbd[0].Slot)
|
||||
|
||||
require.DeepEqual(t, []byte{11, 22, 33}, pbd[1].PublicKey)
|
||||
require.DeepEqual(t, []byte{44, 55, 66}, pbd[1].WithdrawalCredentials)
|
||||
require.Equal(t, uint64(4), pbd[1].Amount)
|
||||
require.DeepEqual(t, []byte{77, 88, 99}, pbd[1].Signature)
|
||||
require.Equal(t, primitives.Slot(2), pbd[1].Slot)
|
||||
|
||||
// Fails for older than electra state
|
||||
s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{})
|
||||
@@ -22,10 +22,8 @@ func (b *BeaconState) LatestExecutionPayloadHeader() (interfaces.ExecutionData,
|
||||
return blocks.WrappedExecutionPayloadHeader(b.latestExecutionPayloadHeader.Copy())
|
||||
case version.Capella:
|
||||
return blocks.WrappedExecutionPayloadHeaderCapella(b.latestExecutionPayloadHeaderCapella.Copy())
|
||||
case version.Deneb:
|
||||
case version.Deneb, version.Electra:
|
||||
return blocks.WrappedExecutionPayloadHeaderDeneb(b.latestExecutionPayloadHeaderDeneb.Copy())
|
||||
case version.Electra:
|
||||
return blocks.WrappedExecutionPayloadHeaderElectra(b.latestExecutionPayloadHeaderElectra.Copy())
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported version (%s) for latest execution payload header", version.String(b.version))
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func (b *BeaconState) ToProtoUnsafe() interface{} {
|
||||
InactivityScores: b.inactivityScoresVal(),
|
||||
CurrentSyncCommittee: b.currentSyncCommittee,
|
||||
NextSyncCommittee: b.nextSyncCommittee,
|
||||
LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderElectra,
|
||||
LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderDeneb,
|
||||
NextWithdrawalIndex: b.nextWithdrawalIndex,
|
||||
NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex,
|
||||
HistoricalSummaries: b.historicalSummaries,
|
||||
@@ -208,7 +208,7 @@ func (b *BeaconState) ToProtoUnsafe() interface{} {
|
||||
EarliestExitEpoch: b.earliestExitEpoch,
|
||||
ConsolidationBalanceToConsume: b.consolidationBalanceToConsume,
|
||||
EarliestConsolidationEpoch: b.earliestConsolidationEpoch,
|
||||
PendingBalanceDeposits: b.pendingBalanceDeposits,
|
||||
PendingDeposits: b.pendingDeposits,
|
||||
PendingPartialWithdrawals: b.pendingPartialWithdrawals,
|
||||
PendingConsolidations: b.pendingConsolidations,
|
||||
}
|
||||
@@ -404,7 +404,7 @@ func (b *BeaconState) ToProto() interface{} {
|
||||
InactivityScores: b.inactivityScoresVal(),
|
||||
CurrentSyncCommittee: b.currentSyncCommitteeVal(),
|
||||
NextSyncCommittee: b.nextSyncCommitteeVal(),
|
||||
LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderElectra.Copy(),
|
||||
LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderDeneb.Copy(),
|
||||
NextWithdrawalIndex: b.nextWithdrawalIndex,
|
||||
NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex,
|
||||
HistoricalSummaries: b.historicalSummariesVal(),
|
||||
@@ -414,7 +414,7 @@ func (b *BeaconState) ToProto() interface{} {
|
||||
EarliestExitEpoch: b.earliestExitEpoch,
|
||||
ConsolidationBalanceToConsume: b.consolidationBalanceToConsume,
|
||||
EarliestConsolidationEpoch: b.earliestConsolidationEpoch,
|
||||
PendingBalanceDeposits: b.pendingBalanceDepositsVal(),
|
||||
PendingDeposits: b.pendingDepositsVal(),
|
||||
PendingPartialWithdrawals: b.pendingPartialWithdrawalsVal(),
|
||||
PendingConsolidations: b.pendingConsolidationsVal(),
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
|
||||
epoch := slots.ToEpoch(b.slot)
|
||||
|
||||
// Electra partial withdrawals functionality.
|
||||
var partialWithdrawalsCount uint64
|
||||
if b.version >= version.Electra {
|
||||
for _, w := range b.pendingPartialWithdrawals {
|
||||
if w.WithdrawableEpoch > epoch || len(withdrawals) >= int(params.BeaconConfig().MaxPendingPartialsPerWithdrawalsSweep) {
|
||||
@@ -139,9 +140,9 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
|
||||
})
|
||||
withdrawalIndex++
|
||||
}
|
||||
partialWithdrawalsCount++
|
||||
}
|
||||
}
|
||||
partialWithdrawalsCount := uint64(len(withdrawals))
|
||||
|
||||
validatorsLen := b.validatorsLen()
|
||||
bound := mathutil.Min(uint64(validatorsLen), params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
|
||||
@@ -343,10 +343,28 @@ func TestExpectedWithdrawals(t *testing.T) {
|
||||
require.NoError(t, pb.UnmarshalSSZ(serializedSSZ))
|
||||
s, err := state_native.InitializeFromProtoElectra(pb)
|
||||
require.NoError(t, err)
|
||||
t.Log(s.NumPendingPartialWithdrawals())
|
||||
expected, partialWithdrawalsCount, err := s.ExpectedWithdrawals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 8, len(expected))
|
||||
require.Equal(t, uint64(8), partialWithdrawalsCount)
|
||||
})
|
||||
|
||||
t.Run("electra some pending partial withdrawals", func(t *testing.T) {
|
||||
// Load a serialized Electra state from disk.
|
||||
// This spectest has a fully hydrated beacon state with partial pending withdrawals.
|
||||
serializedBytes, err := util.BazelFileBytes("tests/mainnet/electra/operations/withdrawal_request/pyspec_tests/pending_withdrawals_consume_all_excess_balance/pre.ssz_snappy")
|
||||
require.NoError(t, err)
|
||||
serializedSSZ, err := snappy.Decode(nil /* dst */, serializedBytes)
|
||||
require.NoError(t, err)
|
||||
pb := ðpb.BeaconStateElectra{}
|
||||
require.NoError(t, pb.UnmarshalSSZ(serializedSSZ))
|
||||
s, err := state_native.InitializeFromProtoElectra(pb)
|
||||
require.NoError(t, err)
|
||||
p, err := s.PendingPartialWithdrawals()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.UpdateBalancesAtIndex(p[0].Index, 0)) // This should still count as partial withdrawal.
|
||||
_, partialWithdrawalsCount, err := s.ExpectedWithdrawals()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(10), partialWithdrawalsCount)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b
|
||||
fieldRoots[types.LatestExecutionPayloadHeaderCapella.RealPosition()] = executionPayloadRoot[:]
|
||||
}
|
||||
|
||||
if state.version == version.Deneb {
|
||||
if state.version >= version.Deneb {
|
||||
// Execution payload root.
|
||||
executionPayloadRoot, err := state.latestExecutionPayloadHeaderDeneb.HashTreeRoot()
|
||||
if err != nil {
|
||||
@@ -252,15 +252,6 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b
|
||||
fieldRoots[types.LatestExecutionPayloadHeaderDeneb.RealPosition()] = executionPayloadRoot[:]
|
||||
}
|
||||
|
||||
if state.version == version.Electra {
|
||||
// Execution payload root.
|
||||
executionPayloadRoot, err := state.latestExecutionPayloadHeaderElectra.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fieldRoots[types.LatestExecutionPayloadHeaderElectra.RealPosition()] = executionPayloadRoot[:]
|
||||
}
|
||||
|
||||
if state.version >= version.Capella {
|
||||
// Next withdrawal index root.
|
||||
nextWithdrawalIndexRoot := make([]byte, 32)
|
||||
@@ -305,12 +296,12 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b
|
||||
eceRoot := ssz.Uint64Root(uint64(state.earliestConsolidationEpoch))
|
||||
fieldRoots[types.EarliestConsolidationEpoch.RealPosition()] = eceRoot[:]
|
||||
|
||||
// PendingBalanceDeposits root.
|
||||
pbdRoot, err := stateutil.PendingBalanceDepositsRoot(state.pendingBalanceDeposits)
|
||||
// PendingDeposits root.
|
||||
pbdRoot, err := stateutil.PendingDepositsRoot(state.pendingDeposits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute pending balance deposits merkleization")
|
||||
}
|
||||
fieldRoots[types.PendingBalanceDeposits.RealPosition()] = pbdRoot[:]
|
||||
fieldRoots[types.PendingDeposits.RealPosition()] = pbdRoot[:]
|
||||
|
||||
// PendingPartialWithdrawals root.
|
||||
ppwRoot, err := stateutil.PendingPartialWithdrawalsRoot(state.pendingPartialWithdrawals)
|
||||
|
||||
@@ -8,43 +8,43 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
)
|
||||
|
||||
// AppendPendingBalanceDeposit is a mutating call to the beacon state to create and append a pending
|
||||
// AppendPendingDeposit is a mutating call to the beacon state to create and append a pending
|
||||
// balance deposit object on to the state. This method requires access to the Lock on the state and
|
||||
// only applies in electra or later.
|
||||
func (b *BeaconState) AppendPendingBalanceDeposit(index primitives.ValidatorIndex, amount uint64) error {
|
||||
func (b *BeaconState) AppendPendingDeposit(pd *ethpb.PendingDeposit) error {
|
||||
if b.version < version.Electra {
|
||||
return errNotSupported("AppendPendingBalanceDeposit", b.version)
|
||||
return errNotSupported("AppendPendingDeposit", b.version)
|
||||
}
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
b.sharedFieldReferences[types.PendingBalanceDeposits].MinusRef()
|
||||
b.sharedFieldReferences[types.PendingBalanceDeposits] = stateutil.NewRef(1)
|
||||
b.sharedFieldReferences[types.PendingDeposits].MinusRef()
|
||||
b.sharedFieldReferences[types.PendingDeposits] = stateutil.NewRef(1)
|
||||
|
||||
b.pendingBalanceDeposits = append(b.pendingBalanceDeposits, ðpb.PendingBalanceDeposit{Index: index, Amount: amount})
|
||||
b.pendingDeposits = append(b.pendingDeposits, pd)
|
||||
|
||||
b.markFieldAsDirty(types.PendingBalanceDeposits)
|
||||
b.rebuildTrie[types.PendingBalanceDeposits] = true
|
||||
b.markFieldAsDirty(types.PendingDeposits)
|
||||
b.rebuildTrie[types.PendingDeposits] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPendingBalanceDeposits is a mutating call to the beacon state which replaces the pending
|
||||
// SetPendingDeposits is a mutating call to the beacon state which replaces the pending
|
||||
// balance deposit slice with the provided value. This method requires access to the Lock on the
|
||||
// state and only applies in electra or later.
|
||||
func (b *BeaconState) SetPendingBalanceDeposits(val []*ethpb.PendingBalanceDeposit) error {
|
||||
func (b *BeaconState) SetPendingDeposits(val []*ethpb.PendingDeposit) error {
|
||||
if b.version < version.Electra {
|
||||
return errNotSupported("SetPendingBalanceDeposits", b.version)
|
||||
return errNotSupported("SetPendingDeposits", b.version)
|
||||
}
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
b.sharedFieldReferences[types.PendingBalanceDeposits].MinusRef()
|
||||
b.sharedFieldReferences[types.PendingBalanceDeposits] = stateutil.NewRef(1)
|
||||
b.sharedFieldReferences[types.PendingDeposits].MinusRef()
|
||||
b.sharedFieldReferences[types.PendingDeposits] = stateutil.NewRef(1)
|
||||
|
||||
b.pendingBalanceDeposits = val
|
||||
b.pendingDeposits = val
|
||||
|
||||
b.markFieldAsDirty(types.PendingBalanceDeposits)
|
||||
b.rebuildTrie[types.PendingBalanceDeposits] = true
|
||||
b.markFieldAsDirty(types.PendingDeposits)
|
||||
b.rebuildTrie[types.PendingDeposits] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,40 +9,52 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestAppendPendingBalanceDeposit(t *testing.T) {
|
||||
func TestAppendPendingDeposit(t *testing.T) {
|
||||
s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
pbd, err := s.PendingBalanceDeposits()
|
||||
pbd, err := s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pbd))
|
||||
require.NoError(t, s.AppendPendingBalanceDeposit(1, 10))
|
||||
pbd, err = s.PendingBalanceDeposits()
|
||||
creds := []byte{0xFA, 0xCC}
|
||||
pubkey := []byte{0xAA, 0xBB}
|
||||
sig := []byte{0xCC, 0xDD}
|
||||
require.NoError(t, s.AppendPendingDeposit(ð.PendingDeposit{
|
||||
PublicKey: pubkey,
|
||||
WithdrawalCredentials: creds,
|
||||
Amount: 10,
|
||||
Signature: sig,
|
||||
Slot: 1,
|
||||
}))
|
||||
pbd, err = s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(pbd))
|
||||
require.Equal(t, primitives.ValidatorIndex(1), pbd[0].Index)
|
||||
require.DeepEqual(t, pubkey, pbd[0].PublicKey)
|
||||
require.Equal(t, uint64(10), pbd[0].Amount)
|
||||
require.DeepEqual(t, creds, pbd[0].WithdrawalCredentials)
|
||||
require.Equal(t, primitives.Slot(1), pbd[0].Slot)
|
||||
require.DeepEqual(t, sig, pbd[0].Signature)
|
||||
|
||||
// Fails for versions older than electra
|
||||
s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{})
|
||||
require.NoError(t, err)
|
||||
require.ErrorContains(t, "not supported", s.AppendPendingBalanceDeposit(1, 1))
|
||||
require.ErrorContains(t, "not supported", s.AppendPendingDeposit(ð.PendingDeposit{}))
|
||||
}
|
||||
|
||||
func TestSetPendingBalanceDeposits(t *testing.T) {
|
||||
func TestSetPendingDeposits(t *testing.T) {
|
||||
s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
pbd, err := s.PendingBalanceDeposits()
|
||||
pbd, err := s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pbd))
|
||||
require.NoError(t, s.SetPendingBalanceDeposits([]*eth.PendingBalanceDeposit{{}, {}, {}}))
|
||||
pbd, err = s.PendingBalanceDeposits()
|
||||
require.NoError(t, s.SetPendingDeposits([]*eth.PendingDeposit{{}, {}, {}}))
|
||||
pbd, err = s.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(pbd))
|
||||
|
||||
// Fails for versions older than electra
|
||||
s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{})
|
||||
require.NoError(t, err)
|
||||
require.ErrorContains(t, "not supported", s.SetPendingBalanceDeposits([]*eth.PendingBalanceDeposit{{}, {}, {}}))
|
||||
require.ErrorContains(t, "not supported", s.SetPendingDeposits([]*eth.PendingDeposit{{}, {}, {}}))
|
||||
}
|
||||
|
||||
func TestSetDepositBalanceToConsume(t *testing.T) {
|
||||
@@ -45,7 +45,7 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadHeaderCapella)
|
||||
return nil
|
||||
case *enginev1.ExecutionPayloadDeneb:
|
||||
if b.version != version.Deneb {
|
||||
if b.version != version.Deneb && b.version != version.Electra {
|
||||
return fmt.Errorf("wrong state version (%s) for deneb execution payload", version.String(b.version))
|
||||
}
|
||||
latest, err := consensusblocks.PayloadToHeaderDeneb(val)
|
||||
@@ -55,21 +55,6 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa
|
||||
b.latestExecutionPayloadHeaderDeneb = latest
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb)
|
||||
return nil
|
||||
case *enginev1.ExecutionPayloadElectra:
|
||||
if b.version != version.Electra {
|
||||
return fmt.Errorf("wrong state version (%s) for electra execution payload", version.String(b.version))
|
||||
}
|
||||
eVal, ok := val.(interfaces.ExecutionDataElectra)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not cast %T to ExecutionDataElectra: %w", val, interfaces.ErrInvalidCast)
|
||||
}
|
||||
latest, err := consensusblocks.PayloadToHeaderElectra(eVal)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not convert payload to header")
|
||||
}
|
||||
b.latestExecutionPayloadHeaderElectra = latest
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadHeaderElectra)
|
||||
return nil
|
||||
case *enginev1.ExecutionPayloadHeader:
|
||||
if b.version != version.Bellatrix {
|
||||
return fmt.Errorf("wrong state version (%s) for bellatrix execution payload header", version.String(b.version))
|
||||
@@ -85,19 +70,12 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadHeaderCapella)
|
||||
return nil
|
||||
case *enginev1.ExecutionPayloadHeaderDeneb:
|
||||
if b.version != version.Deneb {
|
||||
if b.version != version.Deneb && b.version != version.Electra {
|
||||
return fmt.Errorf("wrong state version (%s) for deneb execution payload header", version.String(b.version))
|
||||
}
|
||||
b.latestExecutionPayloadHeaderDeneb = header
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb)
|
||||
return nil
|
||||
case *enginev1.ExecutionPayloadHeaderElectra:
|
||||
if b.version != version.Electra {
|
||||
return fmt.Errorf("wrong state version (%s) for electra execution payload header", version.String(b.version))
|
||||
}
|
||||
b.latestExecutionPayloadHeaderElectra = header
|
||||
b.markFieldAsDirty(types.LatestExecutionPayloadHeaderElectra)
|
||||
return nil
|
||||
default:
|
||||
return errors.New("value must be an execution payload header")
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestSetLatestExecutionPayloadHeader(t *testing.T) {
|
||||
}(),
|
||||
func() interfaces.ExecutionData {
|
||||
e := util.NewBlindedBeaconBlockElectra().Message.Body.ExecutionPayloadHeader
|
||||
ee, err := blocks.WrappedExecutionPayloadHeaderElectra(e)
|
||||
ee, err := blocks.WrappedExecutionPayloadHeaderDeneb(e)
|
||||
require.NoError(t, err)
|
||||
return ee
|
||||
}(),
|
||||
@@ -93,6 +93,13 @@ func TestSetLatestExecutionPayloadHeader(t *testing.T) {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
// Skip Deneb-Electra combinations
|
||||
if i == len(payloads)-1 && j == len(payloads)-2 {
|
||||
continue
|
||||
}
|
||||
if i == len(payloads)-2 && j == len(payloads)-1 {
|
||||
continue
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s state with %s payload", version.String(i+versionOffset), version.String(j+versionOffset)), func(t *testing.T) {
|
||||
s := state_native.EmptyStateFromVersion(t, i+versionOffset)
|
||||
p := payloads[j]
|
||||
|
||||
@@ -99,14 +99,14 @@ var electraFields = append(
|
||||
types.NextWithdrawalIndex,
|
||||
types.NextWithdrawalValidatorIndex,
|
||||
types.HistoricalSummaries,
|
||||
types.LatestExecutionPayloadHeaderElectra,
|
||||
types.LatestExecutionPayloadHeaderDeneb,
|
||||
types.DepositRequestsStartIndex,
|
||||
types.DepositBalanceToConsume,
|
||||
types.ExitBalanceToConsume,
|
||||
types.EarliestExitEpoch,
|
||||
types.ConsolidationBalanceToConsume,
|
||||
types.EarliestConsolidationEpoch,
|
||||
types.PendingBalanceDeposits,
|
||||
types.PendingDeposits,
|
||||
types.PendingPartialWithdrawals,
|
||||
types.PendingConsolidations,
|
||||
)
|
||||
@@ -726,38 +726,38 @@ func InitializeFromProtoUnsafeElectra(st *ethpb.BeaconStateElectra) (state.Beaco
|
||||
|
||||
fieldCount := params.BeaconConfig().BeaconStateElectraFieldCount
|
||||
b := &BeaconState{
|
||||
version: version.Electra,
|
||||
genesisTime: st.GenesisTime,
|
||||
genesisValidatorsRoot: bytesutil.ToBytes32(st.GenesisValidatorsRoot),
|
||||
slot: st.Slot,
|
||||
fork: st.Fork,
|
||||
latestBlockHeader: st.LatestBlockHeader,
|
||||
historicalRoots: hRoots,
|
||||
eth1Data: st.Eth1Data,
|
||||
eth1DataVotes: st.Eth1DataVotes,
|
||||
eth1DepositIndex: st.Eth1DepositIndex,
|
||||
slashings: st.Slashings,
|
||||
previousEpochParticipation: st.PreviousEpochParticipation,
|
||||
currentEpochParticipation: st.CurrentEpochParticipation,
|
||||
justificationBits: st.JustificationBits,
|
||||
previousJustifiedCheckpoint: st.PreviousJustifiedCheckpoint,
|
||||
currentJustifiedCheckpoint: st.CurrentJustifiedCheckpoint,
|
||||
finalizedCheckpoint: st.FinalizedCheckpoint,
|
||||
currentSyncCommittee: st.CurrentSyncCommittee,
|
||||
nextSyncCommittee: st.NextSyncCommittee,
|
||||
latestExecutionPayloadHeaderElectra: st.LatestExecutionPayloadHeader,
|
||||
nextWithdrawalIndex: st.NextWithdrawalIndex,
|
||||
nextWithdrawalValidatorIndex: st.NextWithdrawalValidatorIndex,
|
||||
historicalSummaries: st.HistoricalSummaries,
|
||||
depositRequestsStartIndex: st.DepositRequestsStartIndex,
|
||||
depositBalanceToConsume: st.DepositBalanceToConsume,
|
||||
exitBalanceToConsume: st.ExitBalanceToConsume,
|
||||
earliestExitEpoch: st.EarliestExitEpoch,
|
||||
consolidationBalanceToConsume: st.ConsolidationBalanceToConsume,
|
||||
earliestConsolidationEpoch: st.EarliestConsolidationEpoch,
|
||||
pendingBalanceDeposits: st.PendingBalanceDeposits,
|
||||
pendingPartialWithdrawals: st.PendingPartialWithdrawals,
|
||||
pendingConsolidations: st.PendingConsolidations,
|
||||
version: version.Electra,
|
||||
genesisTime: st.GenesisTime,
|
||||
genesisValidatorsRoot: bytesutil.ToBytes32(st.GenesisValidatorsRoot),
|
||||
slot: st.Slot,
|
||||
fork: st.Fork,
|
||||
latestBlockHeader: st.LatestBlockHeader,
|
||||
historicalRoots: hRoots,
|
||||
eth1Data: st.Eth1Data,
|
||||
eth1DataVotes: st.Eth1DataVotes,
|
||||
eth1DepositIndex: st.Eth1DepositIndex,
|
||||
slashings: st.Slashings,
|
||||
previousEpochParticipation: st.PreviousEpochParticipation,
|
||||
currentEpochParticipation: st.CurrentEpochParticipation,
|
||||
justificationBits: st.JustificationBits,
|
||||
previousJustifiedCheckpoint: st.PreviousJustifiedCheckpoint,
|
||||
currentJustifiedCheckpoint: st.CurrentJustifiedCheckpoint,
|
||||
finalizedCheckpoint: st.FinalizedCheckpoint,
|
||||
currentSyncCommittee: st.CurrentSyncCommittee,
|
||||
nextSyncCommittee: st.NextSyncCommittee,
|
||||
latestExecutionPayloadHeaderDeneb: st.LatestExecutionPayloadHeader,
|
||||
nextWithdrawalIndex: st.NextWithdrawalIndex,
|
||||
nextWithdrawalValidatorIndex: st.NextWithdrawalValidatorIndex,
|
||||
historicalSummaries: st.HistoricalSummaries,
|
||||
depositRequestsStartIndex: st.DepositRequestsStartIndex,
|
||||
depositBalanceToConsume: st.DepositBalanceToConsume,
|
||||
exitBalanceToConsume: st.ExitBalanceToConsume,
|
||||
earliestExitEpoch: st.EarliestExitEpoch,
|
||||
consolidationBalanceToConsume: st.ConsolidationBalanceToConsume,
|
||||
earliestConsolidationEpoch: st.EarliestConsolidationEpoch,
|
||||
pendingDeposits: st.PendingDeposits,
|
||||
pendingPartialWithdrawals: st.PendingPartialWithdrawals,
|
||||
pendingConsolidations: st.PendingConsolidations,
|
||||
|
||||
dirtyFields: make(map[types.FieldIndex]bool, fieldCount),
|
||||
dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount),
|
||||
@@ -818,11 +818,11 @@ func InitializeFromProtoUnsafeElectra(st *ethpb.BeaconStateElectra) (state.Beaco
|
||||
b.sharedFieldReferences[types.Slashings] = stateutil.NewRef(1)
|
||||
b.sharedFieldReferences[types.PreviousEpochParticipationBits] = stateutil.NewRef(1)
|
||||
b.sharedFieldReferences[types.CurrentEpochParticipationBits] = stateutil.NewRef(1)
|
||||
b.sharedFieldReferences[types.LatestExecutionPayloadHeaderElectra] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.HistoricalSummaries] = stateutil.NewRef(1) // New in Capella.
|
||||
b.sharedFieldReferences[types.PendingBalanceDeposits] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.PendingPartialWithdrawals] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.PendingConsolidations] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.LatestExecutionPayloadHeaderDeneb] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.HistoricalSummaries] = stateutil.NewRef(1) // New in Capella.
|
||||
b.sharedFieldReferences[types.PendingDeposits] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.PendingPartialWithdrawals] = stateutil.NewRef(1) // New in Electra.
|
||||
b.sharedFieldReferences[types.PendingConsolidations] = stateutil.NewRef(1) // New in Electra.
|
||||
if !features.Get().EnableExperimentalState {
|
||||
b.sharedFieldReferences[types.BlockRoots] = stateutil.NewRef(1)
|
||||
b.sharedFieldReferences[types.StateRoots] = stateutil.NewRef(1)
|
||||
@@ -898,7 +898,7 @@ func (b *BeaconState) Copy() state.BeaconState {
|
||||
currentEpochParticipation: b.currentEpochParticipation,
|
||||
inactivityScores: b.inactivityScores,
|
||||
inactivityScoresMultiValue: b.inactivityScoresMultiValue,
|
||||
pendingBalanceDeposits: b.pendingBalanceDeposits,
|
||||
pendingDeposits: b.pendingDeposits,
|
||||
pendingPartialWithdrawals: b.pendingPartialWithdrawals,
|
||||
pendingConsolidations: b.pendingConsolidations,
|
||||
|
||||
@@ -916,7 +916,6 @@ func (b *BeaconState) Copy() state.BeaconState {
|
||||
latestExecutionPayloadHeader: b.latestExecutionPayloadHeader.Copy(),
|
||||
latestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapella.Copy(),
|
||||
latestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDeneb.Copy(),
|
||||
latestExecutionPayloadHeaderElectra: b.latestExecutionPayloadHeaderElectra.Copy(),
|
||||
|
||||
id: types.Enumerator.Inc(),
|
||||
|
||||
@@ -1284,8 +1283,6 @@ func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex)
|
||||
return b.latestExecutionPayloadHeaderCapella.HashTreeRoot()
|
||||
case types.LatestExecutionPayloadHeaderDeneb:
|
||||
return b.latestExecutionPayloadHeaderDeneb.HashTreeRoot()
|
||||
case types.LatestExecutionPayloadHeaderElectra:
|
||||
return b.latestExecutionPayloadHeaderElectra.HashTreeRoot()
|
||||
case types.NextWithdrawalIndex:
|
||||
return ssz.Uint64Root(b.nextWithdrawalIndex), nil
|
||||
case types.NextWithdrawalValidatorIndex:
|
||||
@@ -1304,8 +1301,8 @@ func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex)
|
||||
return ssz.Uint64Root(uint64(b.consolidationBalanceToConsume)), nil
|
||||
case types.EarliestConsolidationEpoch:
|
||||
return ssz.Uint64Root(uint64(b.earliestConsolidationEpoch)), nil
|
||||
case types.PendingBalanceDeposits:
|
||||
return stateutil.PendingBalanceDepositsRoot(b.pendingBalanceDeposits)
|
||||
case types.PendingDeposits:
|
||||
return stateutil.PendingDepositsRoot(b.pendingDeposits)
|
||||
case types.PendingPartialWithdrawals:
|
||||
return stateutil.PendingPartialWithdrawalsRoot(b.pendingPartialWithdrawals)
|
||||
case types.PendingConsolidations:
|
||||
|
||||
@@ -88,8 +88,6 @@ func (f FieldIndex) String() string {
|
||||
return "latestExecutionPayloadHeaderCapella"
|
||||
case LatestExecutionPayloadHeaderDeneb:
|
||||
return "latestExecutionPayloadHeaderDeneb"
|
||||
case LatestExecutionPayloadHeaderElectra:
|
||||
return "latestExecutionPayloadHeaderElectra"
|
||||
case NextWithdrawalIndex:
|
||||
return "nextWithdrawalIndex"
|
||||
case NextWithdrawalValidatorIndex:
|
||||
@@ -108,8 +106,8 @@ func (f FieldIndex) String() string {
|
||||
return "consolidationBalanceToConsume"
|
||||
case EarliestConsolidationEpoch:
|
||||
return "earliestConsolidationEpoch"
|
||||
case PendingBalanceDeposits:
|
||||
return "pendingBalanceDeposits"
|
||||
case PendingDeposits:
|
||||
return "pendingDeposits"
|
||||
case PendingPartialWithdrawals:
|
||||
return "pendingPartialWithdrawals"
|
||||
case PendingConsolidations:
|
||||
@@ -171,7 +169,7 @@ func (f FieldIndex) RealPosition() int {
|
||||
return 22
|
||||
case NextSyncCommittee:
|
||||
return 23
|
||||
case LatestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella, LatestExecutionPayloadHeaderDeneb, LatestExecutionPayloadHeaderElectra:
|
||||
case LatestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella, LatestExecutionPayloadHeaderDeneb:
|
||||
return 24
|
||||
case NextWithdrawalIndex:
|
||||
return 25
|
||||
@@ -191,7 +189,7 @@ func (f FieldIndex) RealPosition() int {
|
||||
return 32
|
||||
case EarliestConsolidationEpoch:
|
||||
return 33
|
||||
case PendingBalanceDeposits:
|
||||
case PendingDeposits:
|
||||
return 34
|
||||
case PendingPartialWithdrawals:
|
||||
return 35
|
||||
@@ -249,7 +247,6 @@ const (
|
||||
LatestExecutionPayloadHeader
|
||||
LatestExecutionPayloadHeaderCapella
|
||||
LatestExecutionPayloadHeaderDeneb
|
||||
LatestExecutionPayloadHeaderElectra
|
||||
NextWithdrawalIndex
|
||||
NextWithdrawalValidatorIndex
|
||||
HistoricalSummaries
|
||||
@@ -259,7 +256,7 @@ const (
|
||||
EarliestExitEpoch // Electra: EIP-7251
|
||||
ConsolidationBalanceToConsume // Electra: EIP-7251
|
||||
EarliestConsolidationEpoch // Electra: EIP-7251
|
||||
PendingBalanceDeposits // Electra: EIP-7251
|
||||
PendingDeposits // Electra: EIP-7251
|
||||
PendingPartialWithdrawals // Electra: EIP-7251
|
||||
PendingConsolidations // Electra: EIP-7251
|
||||
)
|
||||
|
||||
@@ -72,18 +72,21 @@ go_test(
|
||||
"//beacon-chain/forkchoice/doubly-linked-tree: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/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/blocks/testing:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/mock:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
|
||||
@@ -4,15 +4,18 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
testDB "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
stateTesting "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensusblocks "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/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
@@ -209,13 +212,30 @@ func TestReplayBlocks_ProcessEpoch_Electra(t *testing.T) {
|
||||
beaconState, _ := util.DeterministicGenesisStateElectra(t, 1)
|
||||
require.NoError(t, beaconState.SetDepositBalanceToConsume(100))
|
||||
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
|
||||
require.NoError(t, beaconState.SetPendingBalanceDeposits([]*ethpb.PendingBalanceDeposit{
|
||||
genesisBlock := util.NewBeaconBlockElectra()
|
||||
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
ethAddress, err := hexutil.Decode("0x967646dCD8d34F4E02204faeDcbAe0cC96fB9245")
|
||||
require.NoError(t, err)
|
||||
newCredentials := make([]byte, 12)
|
||||
newCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
withdrawalCredentials := append(newCredentials, ethAddress...)
|
||||
ffe := params.BeaconConfig().FarFutureEpoch
|
||||
require.NoError(t, beaconState.SetValidators([]*ethpb.Validator{
|
||||
{
|
||||
Amount: uint64(amountAvailForProcessing) / 10,
|
||||
Index: primitives.ValidatorIndex(0),
|
||||
PublicKey: sk.PublicKey().Marshal(),
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
ExitEpoch: ffe,
|
||||
EffectiveBalance: params.BeaconConfig().MinActivationBalance,
|
||||
},
|
||||
}))
|
||||
genesisBlock := util.NewBeaconBlockElectra()
|
||||
beaconState.SaveValidatorIndices()
|
||||
|
||||
require.NoError(t, beaconState.SetPendingDeposits([]*ethpb.PendingDeposit{
|
||||
stateTesting.GeneratePendingDeposit(t, sk, uint64(amountAvailForProcessing)/10, bytesutil.ToBytes32(withdrawalCredentials), genesisBlock.Block.Slot),
|
||||
}))
|
||||
|
||||
bodyRoot, err := genesisBlock.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
err = beaconState.SetLatestBlockHeader(ðpb.BeaconBlockHeader{
|
||||
@@ -238,7 +258,7 @@ func TestReplayBlocks_ProcessEpoch_Electra(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Gwei(0), res)
|
||||
|
||||
remaining, err := newState.PendingBalanceDeposits()
|
||||
remaining, err := newState.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(remaining))
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ go_library(
|
||||
"historical_summaries_root.go",
|
||||
"participation_bit_root.go",
|
||||
"pending_attestation_root.go",
|
||||
"pending_balance_deposits_root.go",
|
||||
"pending_consolidations_root.go",
|
||||
"pending_deposits_root.go",
|
||||
"pending_partial_withdrawals_root.go",
|
||||
"reference.go",
|
||||
"sync_committee.root.go",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user