Compare commits

...

12 Commits

Author SHA1 Message Date
james-prysm
495056625e Validator block v4 (#16594)
**What type of PR is this?**

Feature

**What does this PR do? Why is it needed?**

Introduces the validator connection point for rest api to call block v4
and envelope endpoints

builds on https://github.com/OffchainLabs/prysm/pull/16488 and
https://github.com/OffchainLabs/prysm/pull/16522

testing
```
participants:
  - el_type: geth
    el_image: ethpandaops/geth:epbs-devnet-0
    cl_type: prysm
    cl_image: gcr.io/offchainlabs/prysm/beacon-chain:latest
    vc_image: gcr.io/offchainlabs/prysm/validator:latest
    supernode: true
    count: 2
    cl_extra_params:
      - --subscribe-all-subnets
      - --verbosity=debug
    vc_extra_params:
      - --enable-beacon-rest-api
      - --verbosity=debug

  - el_type: geth
    el_image: ethpandaops/geth:epbs-devnet-0
    cl_type: prysm
    cl_image: gcr.io/offchainlabs/prysm/beacon-chain:latest
    vc_image: gcr.io/offchainlabs/prysm/validator:latest
    validator_count: 63
    cl_extra_params:
      - --verbosity=debug
    vc_extra_params:
      - --enable-beacon-rest-api
      - --verbosity=debug

network_params:
  fulu_fork_epoch: 0
  gloas_fork_epoch: 2
  seconds_per_slot: 6
  genesis_delay: 40

additional_services:
  - dora

global_log_level: debug

dora_params:
  image: ethpandaops/dora:gloas-support
  ```

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [ ] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [ ] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [ ] I have added a description with sufficient context for reviewers to understand this PR.
- [ ] I have tested that my changes work as expected and I added a testing plan to the PR description (if applicable).

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 22:03:57 +00:00
james-prysm
c298c504ef fixing wrong path name in execution payload bid api (#16690)
**What type of PR is this?**

Bug fix

**What does this PR do? Why is it needed?**

fixing wrong path name from /eth/v2/beacon/execution_payload/bid to
/eth/v1/beacon/execution_payload_bid

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-17 21:07:35 +00:00
Manu NALEPA
486e479a99 Prevent expensive state replay when computing sync committees members for the current period (#16688)
**What type of PR is this?**
Bug fix

**What does this PR do? Why is it needed?**
Every `EPOCHS_PER_SYNC_COMMITTEE_PERIOD=256` epochs,
`SYNC_COMMITTEE_SIZE=512` validators are randomly chosen to be part of
the sync committee.

When calling the the
[/eth/v1/validator/duties/sync/epoch](https://ethereum.github.io/beacon-APIs/#/Validator/getSyncCommitteeDuties)
endpoint with `epoch` being set to the first epoch of the current
period, the Prysm beacon node:
1. Finds the youngest state in the DB before this epoch
2. Replays (expensive) states up the requested epoch

While this is technically correct, the step `2.` is very resource
consuming.

This pull request leverages the fact that the `current_sync_committee`
and `next_sync_committee` fields do not change within a period.

==> If the requested epoch and the current epoch are within the same
period, then we can fetch `current_sync_committee` and
`next_sync_committee` from the state corresponding to the current epoch,
which is way less expensive.

**Which issues(s) does this PR fix?**

Fixes:
- https://github.com/OffchainLabs/prysm/issues/16686

**Other notes for review**
Please read commit by commit.

With a Nimbus VC connected:
**Before this PR**
<img width="936" height="308" alt="image"
src="https://github.com/user-attachments/assets/b76f588d-dc95-4916-af93-6ea80b092609"
/>

**After this PR**
<img width="941" height="305" alt="image"
src="https://github.com/user-attachments/assets/65302c90-be33-4525-be5c-a13338335d39"
/>

**Test plan**
Read how to reproduce the issue in the linked issue, and check that the
same reproduction steps do not reproduce the issue with this PR.

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-17 14:16:12 +00:00
terence
fbb65f6700 Queue Gloas data column sidecars arriving before their block (#16653)
In Gloas, data column sidecars can arrive via gossip before the block
here is to queue instead of dropping and re-request later

### Design notes
  - Subnet is verified before queuing (reject bad subnet immediately)
- Columns are stored in a fixed-size `[128]` array per block root,
indexed by column index — duplicates for the same index are ignored
- When the block arrives (via `beaconBlockSubscriber` or
`processPendingBlocks`), queued columns are verified against the block's
bid commitments and saved to storage
- Peers that sent columns failing verification (slot mismatch, invalid
sidecar, bad KZG proof) are downscored (PeerID is tracked)
  - A slot ticker prunes entries from past slots every slot boundary 
- RPC column fetch is skipped for blocks that already have pending
gossip columns
- Each Gloas sidecar is ~44 KB at 21 max blobs (21 cells × 2048 bytes +
proofs). 128 columns per block = ~5.5 MB per block root. The slot ticker
ensures at most one slot's worth of pending roots exist at any time.
2026-04-16 16:36:51 +00:00
Manu NALEPA
f0c7633c87 Pubkey cache: Use map+mutex instead of LRU cache (#16654)
**What type of PR is this?**
Optimization

**What does this PR do? Why is it needed?**
Uncompressing/verifying a (validator) public key from bytes is an
expensive operation.
To avoid doing this operation multiple times for the same public key, a
cache mapping raw, compressed public key to uncompressed, verified ones
is created and populated at node start.

**Before this PR**, a LRU cache with a 2M capacity is used.
The issue with this design is the following:
1. If the cache capacity (2M) is higher than the current count of active
public keys, then keys in this cache are never evicted. ==> Using a LRU
cache is useless. This is the case for all devnets, testnets and
mainnet.
2. If the cache capacity (2M) is lower than the current count of active
public keys, because validators attetations and block proposals are
randomly distributed, some keys will be evicted, then very shortly after
re-inserted. (The only valid case for using a LRU here is sync
committees, that last ~27H). ==> Using a LRU cache is useless.

In both cases 1. and 2., using a LRU cache is useless, and could be
replaced by a map (+ mutex for concurrent accesses). Additionally,
compared to a simple map, a LRU cache consumes some extra heap memory.

**After this PR**, the LRU cache is removed and replaced by a map (+
mutex for concurrent accesses.)

**Cache memory usage (source: Hoodi Pyroscope)**
- Before this PR: 222 MB
- After this PR:  158 MB

**==> Gain: 64 MB**

That's not a lot, but it is an easy saving.

**Before this PR**
<img width="940" height="586" alt="image"
src="https://github.com/user-attachments/assets/ef92abf5-e781-44cb-83b0-7db7b52ef371"
/>

**After this PR**
<img width="1018" height="274" alt="image"
src="https://github.com/user-attachments/assets/8261e18c-3edc-4530-a55c-4c9eaa9a6d6c"
/>

<img width="1020" height="922" alt="image"
src="https://github.com/user-attachments/assets/afa07aac-0b28-417b-815e-f00936d04c02"
/>


**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-16 14:14:04 +00:00
Barnabas Busa
321828e775 Fix MaxBuildersPerWithdrawalsSweep in minimal preset (#16623)
## Summary
- The minimal preset was missing the `MaxBuildersPerWithdrawalsSweep`
override, causing it to inherit the mainnet value of `16384` instead of
the correct minimal value of `16`
- This aligns with the [consensus-specs minimal gloas
preset](https://github.com/ethereum/consensus-specs/blob/master/presets/minimal/gloas.yaml#L23)

## Test plan
- [x] `go build ./config/params/` passes
- [ ] Verify minimal preset spec tests pass with the corrected value

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2026-04-14 20:19:15 +00:00
james-prysm
e2ffb42abe allow proposer preferences on the same epoch (#16610)
**What type of PR is this?**

Feature

**What does this PR do? Why is it needed?**

based on https://github.com/ethereum/consensus-specs/pull/5035

adds capabilities on the validator client side to submit current epoch
attestations,
- for current epoch submissions, we skip slot 0 and start at slot 1 for
submissions, we also need a 1 slot buffer if we start the validator
client mid epoch and and need to propose during that epoch.
- current epoch submissions don't fire in fulu, only next epoch
submissions fire for gloas at the last epoch of fulu before gloas

kurtosis test by cherry picking changes on epbs-devnet-1 and running
```
participants:
  - el_type: geth
    el_image: ethpandaops/geth:epbs-devnet-0
    cl_type: prysm
    cl_image: gcr.io/offchainlabs/prysm/beacon-chain:latest
    vc_image: gcr.io/offchainlabs/prysm/validator:latest
    supernode: true
    count: 4
    vc_extra_params:
      - "--verbosity=debug"

network_params:
  fulu_fork_epoch: 0
  gloas_fork_epoch: 2
  seconds_per_slot: 6
  genesis_delay: 40

additional_services:
  - dora

global_log_level: debug

dora_params:
  image: ethpandaops/dora:gloas-support
```

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-14 19:45:40 +00:00
james-prysm
d1bb9018d3 reversing checkpoint api change (#16660)
**What type of PR is this?**

Bug fix

**What does this PR do? Why is it needed?**

https://github.com/OffchainLabs/prysm/pull/16635 will break clients
checkpoint sync if the first slot of the epoch is missed, but it was
added to resolve some changes in gloas.

With https://github.com/ethereum/consensus-specs/pull/5094 we will be
able to keep the old approach for checkpoint sync endpoints and so the
previous pr 16635 is no longer needed.


**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-14 19:03:51 +00:00
james-prysm
8c70e4bbb1 implementing envelope rest apis (#16522)
**What type of PR is this?**

Feature

**What does this PR do? Why is it needed?**

adds 

- GET /eth/v1/validator/execution_payload_envelope/{slot} endpoint
- POST /eth/v1/beacon/execution_payload_envelope endpoint

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).

---------

Co-authored-by: james-prysm <jhe@offchainlabs.com>
2026-04-14 17:31:40 +00:00
Rupam
c004abc89d return 404 for requests that ask for pre checkpoint sync state (#16615)
<!-- Thanks for sending a PR! Before submitting:

1. If this is your first PR, check out our contribution guide here
https://docs.prylabs.network/docs/contribute/contribution-guidelines
You will then need to sign our Contributor License Agreement (CLA),
which will show up as a comment from a bot in this pull request after
you open it. We cannot review code without a signed CLA.
2. Please file an associated tracking issue if this pull request is
non-trivial and requires context for our team to understand. All
features and most bug fixes should have
an associated issue with a design discussed and decided upon. Small bug
   fixes and documentation improvements don't need issues.
3. New features and bug fixes must have tests. Documentation may need to
be updated. If you're unsure what to update, send the PR, and we'll
discuss
   in review.
4. Note that PRs updating dependencies and new Go versions are not
accepted.
   Please file an issue instead.
5. A changelog entry is required for user facing issues.
-->

**What type of PR is this?**

Bug fix

**What does this PR do? Why is it needed?**

- Prevents invalid/time-expensive replay for unavailable historical
slots by returning Not Found instead of trying to replay from genesis.
- Handles replay no-data errors as Not Found, so missing historical data
no longer surfaces as Internal Server Error in HTTP paths (this might
not be required, I just added it for consistency, lmk if i should
remove)
- Adds unit tests for:
i) slot earlier than earliest available
ii) slot before backfill low slot
iii) replay no-data mapping to not-found
iv) shared HTTP error mapping for no-data to 404

**Which issues(s) does this PR fix?**

Addresses #16191

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).

---------

Co-authored-by: Bastin <43618253+Inspector-Butters@users.noreply.github.com>
2026-04-14 15:30:20 +00:00
Aliz Fara
85316c5d16 Fix event subscription timeout handling (#16681)
**What type of PR is this?**

Bug fix

**What does this PR do? Why is it needed?**

This excludes `/eth/v1/events` from the global `http.TimeoutHandler`
while keeping the timeout behavior for other HTTP routes unchanged.

When `--api-timeout` is set, the timeout wrapper is incompatible with
Prysm's SSE event stream handling and can return `200 OK` with an empty
response body instead of keeping the stream open.

**Which issues(s) does this PR fix?**

Fixes #15710

**Other notes for review**

- Adds focused coverage for the SSE bypass and the unchanged timeout
behavior for non-SSE routes.
- Stabilizes `TestServer_StartStop` by replacing a goroutine log
assertion with `require.Eventually`.
- Validation used during review:
`go test ./api/server/httprest -run 'TestServer_TimeoutHandler' -count=1
-v`

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-14 14:45:03 +00:00
james-prysm
9069afc6d0 Get block v4 (#16488)
**What type of PR is this?**

Feature

**What does this PR do? Why is it needed?**

implements the new GET /eth/v4/validator/blocks/{slot} endpoint, we
don't hook up the validator client to use it yet for post gloas in this
pr.

**Which issues(s) does this PR fix?**

Fixes https://github.com/ethereum/beacon-APIs/pull/580

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-04-14 03:23:42 +00:00
97 changed files with 4764 additions and 1513 deletions

View File

@@ -3,14 +3,15 @@ package api
import "net/http"
const (
VersionHeader = "Eth-Consensus-Version"
ExecutionPayloadBlindedHeader = "Eth-Execution-Payload-Blinded"
ExecutionPayloadValueHeader = "Eth-Execution-Payload-Value"
ConsensusBlockValueHeader = "Eth-Consensus-Block-Value"
JsonMediaType = "application/json"
OctetStreamMediaType = "application/octet-stream"
EventStreamMediaType = "text/event-stream"
KeepAlive = "keep-alive"
VersionHeader = "Eth-Consensus-Version"
ExecutionPayloadBlindedHeader = "Eth-Execution-Payload-Blinded"
ExecutionPayloadValueHeader = "Eth-Execution-Payload-Value"
ConsensusBlockValueHeader = "Eth-Consensus-Block-Value"
ExecutionPayloadIncludedHeader = "Eth-Execution-Payload-Included"
JsonMediaType = "application/json"
OctetStreamMediaType = "application/octet-stream"
EventStreamMediaType = "text/event-stream"
KeepAlive = "keep-alive"
)
// SetSSEHeaders sets the headers needed for a server-sent event response.

View File

@@ -29,6 +29,8 @@ type Server struct {
startFailure error
}
const eventStreamPath = "/eth/v1/events"
// New returns a new instance of the Server.
func New(ctx context.Context, opts ...Option) (*Server, error) {
g := &Server{
@@ -48,7 +50,17 @@ func New(ctx context.Context, opts ...Option) (*Server, error) {
handler = middleware.MiddlewareChain(g.cfg.router, g.cfg.middlewares)
if g.cfg.timeout > 0*time.Second {
defaultReadHeaderTimeout = g.cfg.timeout
handler = http.TimeoutHandler(handler, g.cfg.timeout, "request timed out")
baseHandler := handler
timeoutHandler := http.TimeoutHandler(baseHandler, g.cfg.timeout, "request timed out")
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// SSE streams stay open indefinitely, so the global timeout wrapper must not
// cancel `/eth/v1/events` before the handler starts streaming responses.
if r.URL != nil && r.URL.Path == eventStreamPath {
baseHandler.ServeHTTP(w, r)
return
}
timeoutHandler.ServeHTTP(w, r)
})
}
g.server = &http.Server{
Addr: g.cfg.httpAddr,

View File

@@ -7,7 +7,9 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
"github.com/OffchainLabs/prysm/v7/testing/assert"
@@ -37,10 +39,18 @@ func TestServer_StartStop(t *testing.T) {
require.NoError(t, err)
g.Start()
go func() {
require.LogsContain(t, hook, "Starting HTTP server")
require.LogsDoNotContain(t, hook, "Starting API middleware")
}()
require.Eventually(t, func() bool {
foundStart := false
for _, entry := range hook.AllEntries() {
if strings.Contains(entry.Message, "Starting HTTP server") {
foundStart = true
}
if strings.Contains(entry.Message, "Starting API middleware") {
return false
}
}
return foundStart
}, time.Second, 10*time.Millisecond)
err = g.Stop()
require.NoError(t, err)
}
@@ -68,3 +78,51 @@ func TestServer_NilHandler_NotFoundHandlerRegistered(t *testing.T) {
g.cfg.router.ServeHTTP(writer, &http.Request{Method: "GET", Host: "localhost", URL: &url.URL{Path: "/foo"}})
assert.Equal(t, http.StatusNotFound, writer.Code)
}
func TestServer_TimeoutHandlerBypassesSSE(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc(eventStreamPath, func(w http.ResponseWriter, _ *http.Request) {
time.Sleep(20 * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("stream-open"))
require.NoError(t, err)
})
g, err := New(t.Context(),
WithHTTPAddr("127.0.0.1:0"),
WithRouter(handler),
WithTimeout(5*time.Millisecond),
)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, eventStreamPath, nil)
writer := httptest.NewRecorder()
g.server.Handler.ServeHTTP(writer, req)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, "stream-open", writer.Body.String())
}
func TestServer_TimeoutHandlerStillAppliesToNonSSE(t *testing.T) {
handler := http.NewServeMux()
handler.HandleFunc("/foo", func(w http.ResponseWriter, _ *http.Request) {
time.Sleep(20 * time.Millisecond)
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("ok"))
require.NoError(t, err)
})
g, err := New(t.Context(),
WithHTTPAddr("127.0.0.1:0"),
WithRouter(handler),
WithTimeout(5*time.Millisecond),
)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
writer := httptest.NewRecorder()
g.server.Handler.ServeHTTP(writer, req)
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
assert.Equal(t, true, strings.Contains(writer.Body.String(), "request timed out"))
}

View File

@@ -54,6 +54,7 @@ go_test(
name = "go_default_test",
srcs = [
"conversions_block_execution_test.go",
"conversions_block_gloas_test.go",
"conversions_test.go",
],
embed = [":go_default_library"],

View File

@@ -584,6 +584,13 @@ func (s *SignedBeaconBlockGloas) SigString() string {
return s.Signature
}
type BlockContentsGloas struct {
Block *BeaconBlockGloas `json:"block"`
ExecutionPayloadEnvelope *ExecutionPayloadEnvelope `json:"execution_payload_envelope"`
KzgProofs []string `json:"kzg_proofs"`
Blobs []string `json:"blobs"`
}
type ExecutionPayloadEnvelope struct {
Payload *ExecutionPayloadDeneb `json:"payload"`
ExecutionRequests *ExecutionRequests `json:"execution_requests"`

View File

@@ -2983,6 +2983,19 @@ func PayloadAttestationDataFromConsensus(d *eth.PayloadAttestationData) *Payload
}
}
func (b *SignedBeaconBlockGloas) ToGeneric() (*eth.GenericSignedBeaconBlock, error) {
if b == nil {
return nil, errNilValue
}
signed, err := b.ToConsensus()
if err != nil {
return nil, err
}
return &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Gloas{Gloas: signed},
}, nil
}
func (b *SignedBeaconBlockGloas) ToConsensus() (*eth.SignedBeaconBlockGloas, error) {
if b == nil {
return nil, errNilValue
@@ -3137,6 +3150,14 @@ func (b *BeaconBlockBodyGloas) ToConsensus() (*eth.BeaconBlockBodyGloas, error)
}, nil
}
func (b *BeaconBlockGloas) ToGeneric() (*eth.GenericBeaconBlock, error) {
block, err := b.ToConsensus()
if err != nil {
return nil, errors.Wrap(err, "could not convert gloas block to consensus")
}
return &eth.GenericBeaconBlock{Block: &eth.GenericBeaconBlock_Gloas{Gloas: block}}, nil
}
func (b *SignedExecutionPayloadBid) ToConsensus() (*eth.SignedExecutionPayloadBid, error) {
if b == nil {
return nil, errNilValue
@@ -3284,25 +3305,113 @@ func (d *PayloadAttestationData) ToConsensus() (*eth.PayloadAttestationData, err
}, nil
}
// SignedExecutionPayloadEnvelopeFromConsensus converts a proto envelope to the API struct.
func SignedExecutionPayloadEnvelopeFromConsensus(e *eth.SignedExecutionPayloadEnvelope) (*SignedExecutionPayloadEnvelope, error) {
payload, err := ExecutionPayloadDenebFromConsensus(e.Message.Payload)
// ExecutionPayloadEnvelopeFromConsensus converts a proto envelope to the API struct.
func ExecutionPayloadEnvelopeFromConsensus(e *eth.ExecutionPayloadEnvelope) (*ExecutionPayloadEnvelope, error) {
payload, err := ExecutionPayloadDenebFromConsensus(e.Payload)
if err != nil {
return nil, err
}
var requests *ExecutionRequests
if e.Message.ExecutionRequests != nil {
requests = ExecutionRequestsFromConsensus(e.Message.ExecutionRequests)
if e.ExecutionRequests != nil {
requests = ExecutionRequestsFromConsensus(e.ExecutionRequests)
}
return &ExecutionPayloadEnvelope{
Payload: payload,
ExecutionRequests: requests,
BuilderIndex: fmt.Sprintf("%d", e.BuilderIndex),
BeaconBlockRoot: hexutil.Encode(e.BeaconBlockRoot),
Slot: fmt.Sprintf("%d", e.Slot),
StateRoot: hexutil.Encode(e.StateRoot),
}, nil
}
// SignedExecutionPayloadEnvelopeFromConsensus converts a signed proto envelope to the API struct.
func SignedExecutionPayloadEnvelopeFromConsensus(e *eth.SignedExecutionPayloadEnvelope) (*SignedExecutionPayloadEnvelope, error) {
envelope, err := ExecutionPayloadEnvelopeFromConsensus(e.Message)
if err != nil {
return nil, err
}
return &SignedExecutionPayloadEnvelope{
Message: &ExecutionPayloadEnvelope{
Payload: payload,
ExecutionRequests: requests,
BuilderIndex: fmt.Sprintf("%d", e.Message.BuilderIndex),
BeaconBlockRoot: hexutil.Encode(e.Message.BeaconBlockRoot),
Slot: fmt.Sprintf("%d", e.Message.Slot),
StateRoot: hexutil.Encode(e.Message.StateRoot),
},
Message: envelope,
Signature: hexutil.Encode(e.Signature),
}, nil
}
// BlockContentsGloasFromConsensus converts a proto Gloas block and envelope to the API struct.
func BlockContentsGloasFromConsensus(block *eth.BeaconBlockGloas, envelope *eth.ExecutionPayloadEnvelope) (*BlockContentsGloas, error) {
b, err := BeaconBlockGloasFromConsensus(block)
if err != nil {
return nil, err
}
env, err := ExecutionPayloadEnvelopeFromConsensus(envelope)
if err != nil {
return nil, err
}
return &BlockContentsGloas{
Block: b,
ExecutionPayloadEnvelope: env,
KzgProofs: []string{}, // TODO: populate from blobs bundle
Blobs: []string{}, // TODO: populate from blobs bundle
}, nil
}
// ToConsensus converts the API struct to a proto ExecutionPayloadEnvelope.
func (e *ExecutionPayloadEnvelope) ToConsensus() (*eth.ExecutionPayloadEnvelope, error) {
if e == nil {
return nil, server.NewDecodeError(errNilValue, "ExecutionPayloadEnvelope")
}
payload, err := e.Payload.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, "Payload")
}
var requests *enginev1.ExecutionRequests
if e.ExecutionRequests != nil {
requests, err = e.ExecutionRequests.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, "ExecutionRequests")
}
}
builderIndex, err := strconv.ParseUint(e.BuilderIndex, 10, 64)
if err != nil {
return nil, server.NewDecodeError(err, "BuilderIndex")
}
beaconBlockRoot, err := bytesutil.DecodeHexWithLength(e.BeaconBlockRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "BeaconBlockRoot")
}
slot, err := strconv.ParseUint(e.Slot, 10, 64)
if err != nil {
return nil, server.NewDecodeError(err, "Slot")
}
stateRoot, err := bytesutil.DecodeHexWithLength(e.StateRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "StateRoot")
}
return &eth.ExecutionPayloadEnvelope{
Payload: payload,
ExecutionRequests: requests,
BuilderIndex: primitives.BuilderIndex(builderIndex),
BeaconBlockRoot: beaconBlockRoot,
Slot: primitives.Slot(slot),
StateRoot: stateRoot,
}, nil
}
// ToConsensus converts the API struct to a proto SignedExecutionPayloadEnvelope.
func (e *SignedExecutionPayloadEnvelope) ToConsensus() (*eth.SignedExecutionPayloadEnvelope, error) {
if e == nil {
return nil, server.NewDecodeError(errNilValue, "SignedExecutionPayloadEnvelope")
}
msg, err := e.Message.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, "Message")
}
sig, err := bytesutil.DecodeHexWithLength(e.Signature, fieldparams.BLSSignatureLength)
if err != nil {
return nil, server.NewDecodeError(err, "Signature")
}
return &eth.SignedExecutionPayloadEnvelope{
Message: msg,
Signature: sig,
}, nil
}

View File

@@ -0,0 +1,67 @@
package structs
import (
"testing"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func testEnvelopeProto() *eth.ExecutionPayloadEnvelope {
return &eth.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: fillByteSlice(common.HashLength, 0xaa),
FeeRecipient: fillByteSlice(20, 0xbb),
StateRoot: fillByteSlice(32, 0xcc),
ReceiptsRoot: fillByteSlice(32, 0xdd),
LogsBloom: fillByteSlice(256, 0xee),
PrevRandao: fillByteSlice(32, 0xff),
BaseFeePerGas: fillByteSlice(32, 0x11),
BlockHash: fillByteSlice(common.HashLength, 0x22),
},
ExecutionRequests: &enginev1.ExecutionRequests{},
BuilderIndex: 7,
BeaconBlockRoot: fillByteSlice(32, 0x33),
Slot: 42,
StateRoot: fillByteSlice(32, 0x44),
}
}
func TestExecutionPayloadEnvelopeFromConsensus(t *testing.T) {
env := testEnvelopeProto()
result, err := ExecutionPayloadEnvelopeFromConsensus(env)
require.NoError(t, err)
require.NotNil(t, result.Payload)
require.Equal(t, hexutil.Encode(env.Payload.ParentHash), result.Payload.ParentHash)
require.Equal(t, "7", result.BuilderIndex)
require.Equal(t, hexutil.Encode(env.BeaconBlockRoot), result.BeaconBlockRoot)
require.Equal(t, "42", result.Slot)
require.Equal(t, hexutil.Encode(env.StateRoot), result.StateRoot)
require.NotNil(t, result.ExecutionRequests)
}
func TestExecutionPayloadEnvelopeFromConsensus_NilRequests(t *testing.T) {
env := testEnvelopeProto()
env.ExecutionRequests = nil
result, err := ExecutionPayloadEnvelopeFromConsensus(env)
require.NoError(t, err)
require.Equal(t, (*ExecutionRequests)(nil), result.ExecutionRequests)
}
func TestBlockContentsGloasFromConsensus(t *testing.T) {
block := util.NewBeaconBlockGloas().Block
env := testEnvelopeProto()
result, err := BlockContentsGloasFromConsensus(block, env)
require.NoError(t, err)
require.NotNil(t, result.Block)
require.NotNil(t, result.Block.Body)
require.NotNil(t, result.ExecutionPayloadEnvelope)
require.Equal(t, hexutil.Encode(env.BeaconBlockRoot), result.ExecutionPayloadEnvelope.BeaconBlockRoot)
require.Equal(t, 0, len(result.KzgProofs))
require.Equal(t, 0, len(result.Blobs))
}

View File

@@ -95,6 +95,14 @@ type ProduceBlockV3Response struct {
Data json.RawMessage `json:"data"` // represents the block values based on the version
}
// ProduceBlockV4Response is a wrapper json object for the returned block from the ProduceBlockV4 endpoint
type ProduceBlockV4Response struct {
Version string `json:"version"`
ConsensusBlockValue string `json:"consensus_block_value"`
ExecutionPayloadIncluded bool `json:"execution_payload_included"`
Data json.RawMessage `json:"data"`
}
type GetLivenessResponse struct {
Data []*Liveness `json:"data"`
}
@@ -151,6 +159,11 @@ type ValidatorParticipation struct {
PreviousEpochHeadAttestingGwei string `json:"previous_epoch_head_attesting_gwei"`
}
type GetValidatorExecutionPayloadEnvelopeResponse struct {
Version string `json:"version"`
Data *ExecutionPayloadEnvelope `json:"data"`
}
type ActiveSetChanges struct {
Epoch string `json:"epoch"`
ActivatedPublicKeys []string `json:"activated_public_keys"`

View File

@@ -116,17 +116,32 @@ func CalculateStateRoot(
rollback state.BeaconState,
signed interfaces.ReadOnlySignedBeaconBlock,
) ([32]byte, error) {
ctx, span := trace.StartSpan(ctx, "core.state.CalculateStateRoot")
st, err := CalculatePostState(ctx, rollback, signed)
if err != nil {
return [32]byte{}, err
}
return st.HashTreeRoot(ctx)
}
// CalculatePostState returns the post-block state after processing the given
// block on a copy of the input state. It is identical to CalculateStateRoot
// but returns the full state instead of just its hash tree root.
func CalculatePostState(
ctx context.Context,
rollback state.BeaconState,
signed interfaces.ReadOnlySignedBeaconBlock,
) (state.BeaconState, error) {
ctx, span := trace.StartSpan(ctx, "core.state.CalculatePostState")
defer span.End()
if ctx.Err() != nil {
tracing.AnnotateError(span, ctx.Err())
return [32]byte{}, ctx.Err()
return nil, ctx.Err()
}
if rollback == nil || rollback.IsNil() {
return [32]byte{}, errors.New("nil state")
return nil, errors.New("nil state")
}
if signed == nil || signed.IsNil() || signed.Block().IsNil() {
return [32]byte{}, errors.New("nil block")
return nil, errors.New("nil block")
}
// Copy state to avoid mutating the state reference.
@@ -136,22 +151,22 @@ func CalculateStateRoot(
var err error
state, err = ProcessSlotsForBlock(ctx, state, signed.Block())
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not process slots")
return nil, errors.Wrap(err, "could not process slots")
}
// Execute per block transition.
if features.Get().EnableProposerPreprocessing {
state, err = processBlockForProposing(ctx, state, signed)
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not process block for proposing")
return nil, errors.Wrap(err, "could not process block for proposing")
}
} else {
state, err = ProcessBlockForStateRoot(ctx, state, signed)
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not process block")
return nil, errors.Wrap(err, "could not process block")
}
}
return state.HashTreeRoot(ctx)
return state, nil
}
// processBlockVerifySigs processes the block and verifies the signatures within it. Block signatures are not verified as this block is not yet signed.

View File

@@ -138,24 +138,19 @@ func (s *Service) SyncCommitteeDuties(ctx context.Context, st state.BeaconState,
nextSyncCommitteeFirstEpoch := currentSyncCommitteeFirstEpoch + params.BeaconConfig().EpochsPerSyncCommitteePeriod
isCurrentCommittee := requestedEpoch < nextSyncCommitteeFirstEpoch
var committee [][]byte
syncCommitteeFunc := st.NextSyncCommittee
if isCurrentCommittee {
sc, err := st.CurrentSyncCommittee()
if err != nil {
return nil, &RpcError{Err: errors.Wrap(err, "could not get sync committee"), Reason: Internal}
}
committee = sc.Pubkeys
} else {
sc, err := st.NextSyncCommittee()
if err != nil {
return nil, &RpcError{Err: errors.Wrap(err, "could not get sync committee"), Reason: Internal}
}
committee = sc.Pubkeys
syncCommitteeFunc = st.CurrentSyncCommittee
}
sc, err := syncCommitteeFunc()
if err != nil {
return nil, &RpcError{Err: errors.Wrap(err, "could not get sync committee"), Reason: Internal}
}
// Build pubkey → positions map from committee pubkeys.
committeePubkeys := make(map[[fieldparams.BLSPubkeyLength]byte][]uint64)
for j, pk := range committee {
for j, pk := range sc.Pubkeys {
var pk48 [fieldparams.BLSPubkeyLength]byte
copy(pk48[:], pk)
committeePubkeys[pk48] = append(committeePubkeys[pk48], uint64(j))

View File

@@ -393,6 +393,16 @@ func (s *Service) validatorEndpoints(
handler: server.ProduceBlockV3,
methods: []string{http.MethodGet},
},
{
template: "/eth/v4/validator/blocks/{slot}",
name: namespace + ".ProduceBlockV4",
middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),
middleware.AcceptEncodingHeaderHandler(),
},
handler: server.ProduceBlockV4,
methods: []string{http.MethodGet},
},
{
template: "/eth/v1/validator/beacon_committee_selections",
name: namespace + ".BeaconCommitteeSelections",
@@ -411,6 +421,15 @@ func (s *Service) validatorEndpoints(
handler: server.SyncCommitteeSelections,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/validator/execution_payload_envelope/{slot}",
name: namespace + ".ExecutionPayloadEnvelope",
middleware: []middleware.Middleware{
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.ExecutionPayloadEnvelope,
methods: []string{http.MethodGet},
},
}
}
@@ -911,7 +930,17 @@ func (s *Service) beaconEndpoints(
methods: []string{http.MethodGet},
},
{
template: "/eth/v2/beacon/execution_payload/bid",
template: "/eth/v1/beacon/execution_payload_envelope",
name: namespace + ".PublishExecutionPayloadEnvelope",
middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType}),
middleware.AcceptHeaderHandler([]string{api.JsonMediaType}),
},
handler: server.PublishExecutionPayloadEnvelope,
methods: []string{http.MethodPost},
},
{
template: "/eth/v1/beacon/execution_payload_bid",
name: namespace + ".PublishSignedExecutionPayloadBid",
middleware: []middleware.Middleware{
middleware.ContentTypeHandler([]string{api.JsonMediaType, api.OctetStreamMediaType}),

View File

@@ -34,7 +34,8 @@ func Test_endpoints(t *testing.T) {
"/eth/v1/beacon/states/{state_id}/pending_consolidations": {http.MethodGet},
"/eth/v1/beacon/states/{state_id}/proposer_lookahead": {http.MethodGet},
"/eth/v1/beacon/execution_payload_envelope/{block_id}": {http.MethodGet},
"/eth/v2/beacon/execution_payload/bid": {http.MethodPost},
"/eth/v1/beacon/execution_payload_envelope": {http.MethodPost},
"/eth/v1/beacon/execution_payload_bid": {http.MethodPost},
"/eth/v1/beacon/headers": {http.MethodGet},
"/eth/v1/beacon/headers/{block_id}": {http.MethodGet},
"/eth/v2/beacon/blinded_blocks": {http.MethodPost},
@@ -94,24 +95,26 @@ func Test_endpoints(t *testing.T) {
}
validatorRoutes := map[string][]string{
"/eth/v1/validator/duties/attester/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/proposer/{epoch}": {http.MethodGet},
"/eth/v2/validator/duties/proposer/{epoch}": {http.MethodGet},
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/ptc/{epoch}": {http.MethodPost},
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
"/eth/v1/validator/attestation_data": {http.MethodGet},
"/eth/v2/validator/aggregate_attestation": {http.MethodGet},
"/eth/v2/validator/aggregate_and_proofs": {http.MethodPost},
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
"/eth/v1/validator/contribution_and_proofs": {http.MethodPost},
"/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost},
"/eth/v1/validator/register_validator": {http.MethodPost},
"/eth/v1/validator/liveness/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/attester/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/proposer/{epoch}": {http.MethodGet},
"/eth/v2/validator/duties/proposer/{epoch}": {http.MethodGet},
"/eth/v1/validator/duties/sync/{epoch}": {http.MethodPost},
"/eth/v1/validator/duties/ptc/{epoch}": {http.MethodPost},
"/eth/v3/validator/blocks/{slot}": {http.MethodGet},
"/eth/v4/validator/blocks/{slot}": {http.MethodGet},
"/eth/v1/validator/attestation_data": {http.MethodGet},
"/eth/v2/validator/aggregate_attestation": {http.MethodGet},
"/eth/v2/validator/aggregate_and_proofs": {http.MethodPost},
"/eth/v1/validator/beacon_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/sync_committee_subscriptions": {http.MethodPost},
"/eth/v1/validator/beacon_committee_selections": {http.MethodPost},
"/eth/v1/validator/sync_committee_selections": {http.MethodPost},
"/eth/v1/validator/execution_payload_envelope/{slot}": {http.MethodGet},
"/eth/v1/validator/sync_committee_contribution": {http.MethodGet},
"/eth/v1/validator/contribution_and_proofs": {http.MethodPost},
"/eth/v1/validator/prepare_beacon_proposer": {http.MethodPost},
"/eth/v1/validator/register_validator": {http.MethodPost},
"/eth/v1/validator/liveness/{epoch}": {http.MethodPost},
}
prysmBeaconRoutes := map[string][]string{

View File

@@ -66,6 +66,8 @@ go_library(
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
],
)
@@ -134,7 +136,10 @@ go_test(
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@com_github_stretchr_testify//mock:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//types/known/emptypb:go_default_library",
"@org_uber_go_mock//gomock:go_default_library",
],
)

View File

@@ -643,6 +643,7 @@ func (s *Server) publishBlockSSZ(ctx context.Context, w http.ResponseWriter, r *
}
var sszDecoders = map[string]blockDecoder{
version.String(version.Gloas): decodeGloasSSZ,
version.String(version.Fulu): decodeFuluSSZ,
version.String(version.Electra): decodeElectraSSZ,
version.String(version.Deneb): decodeDenebSSZ,
@@ -660,6 +661,18 @@ func decodeSSZToGenericBlock(versionHeader string, body []byte) (*eth.GenericSig
return nil, errors.New("body does not represent a valid block type")
}
func decodeGloasSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) {
gloasBlock := &eth.SignedBeaconBlockGloas{}
if err := gloasBlock.UnmarshalSSZ(body); err != nil {
return nil, decodingError(
version.String(version.Gloas), err,
)
}
return &eth.GenericSignedBeaconBlock{
Block: &eth.GenericSignedBeaconBlock_Gloas{Gloas: gloasBlock},
}, nil
}
func decodeFuluSSZ(body []byte) (*eth.GenericSignedBeaconBlock, error) {
fuluBlock := &eth.SignedBeaconBlockContentsFulu{}
if err := fuluBlock.UnmarshalSSZ(body); err != nil {
@@ -798,6 +811,7 @@ func (s *Server) publishBlock(ctx context.Context, w http.ResponseWriter, r *htt
}
var jsonDecoders = map[string]blockDecoder{
version.String(version.Gloas): decodeGloasJSON,
version.String(version.Fulu): decodeFuluJSON,
version.String(version.Electra): decodeElectraJSON,
version.String(version.Deneb): decodeDenebJSON,
@@ -815,6 +829,13 @@ func decodeJSONToGenericBlock(versionHeader string, body []byte) (*eth.GenericSi
return nil, fmt.Errorf("body does not represent a valid block type")
}
func decodeGloasJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) {
return decodeGenericJSON[*structs.SignedBeaconBlockGloas](
body,
version.String(version.Gloas),
)
}
func decodeFuluJSON(body []byte) (*eth.GenericSignedBeaconBlock, error) {
return decodeGenericJSON[*structs.SignedBeaconBlockContentsFulu](
body,

View File

@@ -14,6 +14,8 @@ import (
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// GetExecutionPayloadEnvelope retrieves a full execution payload envelope by beacon block root.
@@ -81,6 +83,48 @@ func (s *Server) GetExecutionPayloadEnvelope(w http.ResponseWriter, r *http.Requ
})
}
// PublishExecutionPayloadEnvelope broadcasts a signed execution payload envelope.
//
// Endpoint: POST /eth/v1/beacon/execution_payload_envelope
func (s *Server) PublishExecutionPayloadEnvelope(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.PublishExecutionPayloadEnvelope")
defer span.End()
body, err := io.ReadAll(r.Body)
if err != nil {
httputil.HandleError(w, "could not read request body: "+err.Error(), http.StatusInternalServerError)
return
}
var jsonEnvelope structs.SignedExecutionPayloadEnvelope
if err := json.Unmarshal(body, &jsonEnvelope); err != nil {
httputil.HandleError(w, "could not decode request body: "+err.Error(), http.StatusBadRequest)
return
}
consensus, err := jsonEnvelope.ToConsensus()
if err != nil {
httputil.HandleError(w, "invalid signed execution payload envelope: "+err.Error(), http.StatusBadRequest)
return
}
if _, err := s.V1Alpha1ValidatorServer.PublishExecutionPayloadEnvelope(ctx, consensus); err != nil {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.InvalidArgument:
httputil.HandleError(w, st.Message(), http.StatusBadRequest)
default:
httputil.HandleError(w, st.Message(), http.StatusInternalServerError)
}
return
}
httputil.HandleError(w, "could not publish execution payload envelope: "+err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
// PublishSignedExecutionPayloadBid broadcasts a signed execution payload bid to the P2P network.
func (s *Server) PublishSignedExecutionPayloadBid(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.PublishSignedExecutionPayloadBid")

View File

@@ -2,10 +2,12 @@ package beacon
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
chainMock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
dbTest "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
executiontesting "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
@@ -17,7 +19,12 @@ import (
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/testing/assert"
mock2 "github.com/OffchainLabs/prysm/v7/testing/mock"
"github.com/OffchainLabs/prysm/v7/testing/require"
"go.uber.org/mock/gomock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
func TestGetExecutionPayloadEnvelope_AcceptsSlotID(t *testing.T) {
@@ -105,3 +112,84 @@ func TestGetExecutionPayloadEnvelope_BlockNotFound(t *testing.T) {
require.Equal(t, http.StatusNotFound, w.Code)
assert.Equal(t, true, bytes.Contains(w.Body.Bytes(), []byte("Block not found")))
}
func testSignedEnvelope() *ethpb.SignedExecutionPayloadEnvelope {
return &ethpb.SignedExecutionPayloadEnvelope{
Message: &ethpb.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: bytesutil.PadTo([]byte("parent"), 32),
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
StateRoot: bytesutil.PadTo([]byte("state"), 32),
ReceiptsRoot: bytesutil.PadTo([]byte("receipts"), 32),
LogsBloom: make([]byte, 256),
PrevRandao: bytesutil.PadTo([]byte("randao"), 32),
BaseFeePerGas: bytesutil.PadTo([]byte{1}, 32),
BlockHash: bytesutil.PadTo([]byte("blockhash"), 32),
Transactions: [][]byte{},
Withdrawals: []*enginev1.Withdrawal{},
},
ExecutionRequests: &enginev1.ExecutionRequests{},
BuilderIndex: primitives.BuilderIndex(42),
BeaconBlockRoot: bytesutil.PadTo([]byte("beacon-root"), 32),
Slot: primitives.Slot(100),
StateRoot: bytesutil.PadTo([]byte("envelope-state"), 32),
},
Signature: bytesutil.PadTo([]byte("sig"), 96),
}
}
func TestPublishExecutionPayloadEnvelope_OK(t *testing.T) {
ctrl := gomock.NewController(t)
signed := testSignedEnvelope()
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().PublishExecutionPayloadEnvelope(
gomock.Any(), gomock.Any(),
).Return(&emptypb.Empty{}, nil)
jsonEnvelope, err := structs.SignedExecutionPayloadEnvelopeFromConsensus(signed)
require.NoError(t, err)
body, err := json.Marshal(jsonEnvelope)
require.NoError(t, err)
s := &Server{V1Alpha1ValidatorServer: v1alpha1Server}
req := httptest.NewRequest(http.MethodPost, "/eth/v1/beacon/execution_payload_envelope", bytes.NewReader(body))
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.PublishExecutionPayloadEnvelope(w, req)
require.Equal(t, http.StatusOK, w.Code)
}
func TestPublishExecutionPayloadEnvelope_InvalidBody(t *testing.T) {
s := &Server{}
req := httptest.NewRequest(http.MethodPost, "/eth/v1/beacon/execution_payload_envelope", bytes.NewReader([]byte("not json")))
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.PublishExecutionPayloadEnvelope(w, req)
require.Equal(t, http.StatusBadRequest, w.Code)
}
func TestPublishExecutionPayloadEnvelope_ServerError(t *testing.T) {
ctrl := gomock.NewController(t)
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().PublishExecutionPayloadEnvelope(
gomock.Any(), gomock.Any(),
).Return(nil, status.Error(codes.Internal, "broadcast failed"))
signed := testSignedEnvelope()
jsonEnvelope, err := structs.SignedExecutionPayloadEnvelopeFromConsensus(signed)
require.NoError(t, err)
body, err := json.Marshal(jsonEnvelope)
require.NoError(t, err)
s := &Server{V1Alpha1ValidatorServer: v1alpha1Server}
req := httptest.NewRequest(http.MethodPost, "/eth/v1/beacon/execution_payload_envelope", bytes.NewReader(body))
w := httptest.NewRecorder()
w.Body = &bytes.Buffer{}
s.PublishExecutionPayloadEnvelope(w, req)
require.Equal(t, http.StatusInternalServerError, w.Code)
}

View File

@@ -5,7 +5,6 @@ import (
"net/http"
"net/url"
"strconv"
"strings"
"github.com/OffchainLabs/prysm/v7/api"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
@@ -35,8 +34,7 @@ func (s *Server) Blobs(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
segments := strings.Split(r.URL.Path, "/")
blockId := segments[len(segments)-1]
blockId := r.PathValue("block_id")
verifiedBlobs, rpcErr := s.Blocker.BlobSidecars(ctx, blockId, options.WithIndices(indices))
if rpcErr != nil {
@@ -131,8 +129,7 @@ func (s *Server) GetBlobs(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.GetBlobs")
defer span.End()
segments := strings.Split(r.URL.Path, "/")
blockId := segments[len(segments)-1]
blockId := r.PathValue("block_id")
// Check if versioned_hashes parameter is provided
versionedHashesStr := r.URL.Query()["versioned_hashes"]

View File

@@ -64,6 +64,7 @@ func TestBlobs(t *testing.T) {
t.Run("genesis", func(t *testing.T) {
u := "http://foo.example/genesis"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "genesis")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
@@ -78,6 +79,7 @@ func TestBlobs(t *testing.T) {
t.Run("head", func(t *testing.T) {
u := "http://foo.example/head"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -126,6 +128,7 @@ func TestBlobs(t *testing.T) {
t.Run("finalized", func(t *testing.T) {
u := "http://foo.example/finalized"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -150,6 +153,7 @@ func TestBlobs(t *testing.T) {
t.Run("root", func(t *testing.T) {
u := "http://foo.example/" + hexutil.Encode(blockRoot[:])
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", hexutil.Encode(blockRoot[:]))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -174,6 +178,7 @@ func TestBlobs(t *testing.T) {
t.Run("slot", func(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", es)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -198,6 +203,7 @@ func TestBlobs(t *testing.T) {
t.Run("slot not found", func(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", es-1)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es-1))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -215,6 +221,7 @@ func TestBlobs(t *testing.T) {
t.Run("one blob only", func(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d?indices=2", es)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -246,6 +253,7 @@ func TestBlobs(t *testing.T) {
t.Run("no blobs returns an empty array", func(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", es)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -271,6 +279,7 @@ func TestBlobs(t *testing.T) {
overLimit := params.BeaconConfig().MaxBlobsPerBlock(ds)
u := fmt.Sprintf("http://foo.example/%d?indices=%d", es, overLimit)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
@@ -285,6 +294,7 @@ func TestBlobs(t *testing.T) {
t.Run("outside retention period returns 200 with what we have", func(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", es)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
moc := &mockChain.ChainService{FinalizedCheckPoint: &eth.Checkpoint{Root: blockRoot[:]}, Block: denebBlock}
@@ -315,6 +325,7 @@ func TestBlobs(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", es+128)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es+128))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -345,6 +356,7 @@ func TestBlobs(t *testing.T) {
u := "http://foo.example/31"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "31")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -367,6 +379,7 @@ func TestBlobs(t *testing.T) {
t.Run("malformed block ID", func(t *testing.T) {
u := "http://foo.example/foo"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "foo")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
@@ -381,6 +394,7 @@ func TestBlobs(t *testing.T) {
t.Run("ssz", func(t *testing.T) {
u := "http://foo.example/finalized?indices=0"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -404,6 +418,7 @@ func TestBlobs(t *testing.T) {
t.Run("ssz multiple blobs", func(t *testing.T) {
u := "http://foo.example/finalized"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -455,6 +470,7 @@ func TestBlobs_Electra(t *testing.T) {
t.Run("max blobs for electra", func(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", es)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -487,6 +503,7 @@ func TestBlobs_Electra(t *testing.T) {
limit := params.BeaconConfig().MaxBlobsPerBlock(es) - 1
u := fmt.Sprintf("http://foo.example/%d?indices=%d", es, limit)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -519,6 +536,7 @@ func TestBlobs_Electra(t *testing.T) {
overLimit := params.BeaconConfig().MaxBlobsPerBlock(es)
u := fmt.Sprintf("http://foo.example/%d?indices=%d", es, overLimit)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", es))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
@@ -617,6 +635,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("genesis", func(t *testing.T) {
u := "http://foo.example/genesis"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "genesis")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
@@ -631,6 +650,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("head", func(t *testing.T) {
u := "http://foo.example/head"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -665,6 +685,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("finalized", func(t *testing.T) {
u := "http://foo.example/finalized"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -688,6 +709,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("root", func(t *testing.T) {
u := "http://foo.example/" + hexutil.Encode(blockRoot[:])
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", hexutil.Encode(blockRoot[:]))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -711,6 +733,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("slot", func(t *testing.T) {
u := "http://foo.example/123"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "123")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -734,6 +757,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("slot not found", func(t *testing.T) {
u := "http://foo.example/122"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "122")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -751,6 +775,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("no blobs returns an empty array", func(t *testing.T) {
u := "http://foo.example/123"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "123")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -774,6 +799,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("outside retention period still returns 200 what we have in db ", func(t *testing.T) {
u := "http://foo.example/123"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "123")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
moc := &mockChain.ChainService{FinalizedCheckPoint: &eth.Checkpoint{Root: blockRoot[:]}, Block: denebBlock}
@@ -803,6 +829,7 @@ func TestGetBlobs(t *testing.T) {
u := "http://foo.example/333"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "333")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -832,6 +859,7 @@ func TestGetBlobs(t *testing.T) {
u := "http://foo.example/31"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "31")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -853,6 +881,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("malformed block ID", func(t *testing.T) {
u := "http://foo.example/foo"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "foo")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{}
@@ -867,6 +896,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("ssz", func(t *testing.T) {
u := "http://foo.example/finalized"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -889,6 +919,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("ssz multiple blobs", func(t *testing.T) {
u := "http://foo.example/finalized"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
request.Header.Add("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -910,6 +941,7 @@ func TestGetBlobs(t *testing.T) {
t.Run("versioned_hashes invalid hex", func(t *testing.T) {
u := "http://foo.example/finalized?versioned_hashes=invalidhex,invalid2hex"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -935,6 +967,7 @@ func TestGetBlobs(t *testing.T) {
shortHash := "0x1234567890abcdef1234567890abcdef"
u := fmt.Sprintf("http://foo.example/finalized?versioned_hashes=%s", shortHash)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -961,6 +994,7 @@ func TestGetBlobs(t *testing.T) {
u := fmt.Sprintf("http://foo.example/finalized?versioned_hashes=%s", hexutil.Encode(versionedHash[:]))
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -990,6 +1024,7 @@ func TestGetBlobs(t *testing.T) {
u := fmt.Sprintf("http://foo.example/finalized?versioned_hashes=%s&versioned_hashes=%s",
hexutil.Encode(versionedHash1[:]), hexutil.Encode(versionedHash3[:]))
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "finalized")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -1026,6 +1061,7 @@ func TestGetBlobs(t *testing.T) {
u := "http://foo.example/323"
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", "323")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.Blocker = &lookup.BeaconDbBlocker{
@@ -1063,6 +1099,7 @@ func TestGetBlobs(t *testing.T) {
u := fmt.Sprintf("http://foo.example/%d", fuluForkSlot)
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", fuluForkSlot))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
// Create an empty blob storage (won't be used but needs to be non-nil)
@@ -1117,6 +1154,7 @@ func TestGetBlobs(t *testing.T) {
hexutil.Encode(versionedHash1[:]),
hexutil.Encode(versionedHash2[:]))
request := httptest.NewRequest("GET", u, nil)
request.SetPathValue("block_id", fmt.Sprintf("%d", fuluForkSlot2))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
// Create an empty blob storage (won't be used but needs to be non-nil)

View File

@@ -258,8 +258,7 @@ func (s *Server) DataColumnSidecars(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, err.Error(), http.StatusBadRequest)
return
}
segments := strings.Split(r.URL.Path, "/")
blockId := segments[len(segments)-1]
blockId := r.PathValue("block_id")
verifiedDataColumns, rpcErr := s.Blocker.DataColumns(ctx, blockId, indices)
if rpcErr != nil {

View File

@@ -647,6 +647,7 @@ func TestDataColumnSidecars(t *testing.T) {
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/debug/beacon/data_column_sidecars/head", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -682,6 +683,7 @@ func TestDataColumnSidecars(t *testing.T) {
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/debug/beacon/data_column_sidecars/head", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -718,6 +720,7 @@ func TestDataColumnSidecars(t *testing.T) {
// Test with invalid index (out of range)
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/debug/beacon/data_column_sidecars/head?indices=9999", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -754,6 +757,7 @@ func TestDataColumnSidecars(t *testing.T) {
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/debug/beacon/data_column_sidecars/head", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -797,6 +801,7 @@ func TestDataColumnSidecars(t *testing.T) {
}
request := httptest.NewRequest(http.MethodGet, "http://example.com/eth/v1/debug/beacon/data_column_sidecars/head", nil)
request.SetPathValue("block_id", "head")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

View File

@@ -6,7 +6,6 @@ import (
"net/http"
"slices"
"strconv"
"strings"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/altair"
@@ -28,8 +27,7 @@ import (
func (s *Server) BlockRewards(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.BlockRewards")
defer span.End()
segments := strings.Split(r.URL.Path, "/")
blockId := segments[len(segments)-1]
blockId := r.PathValue("block_id")
blk, err := s.Blocker.Block(r.Context(), []byte(blockId))
if !shared.WriteBlockFetchError(w, blk, err) {
@@ -116,8 +114,7 @@ func (s *Server) AttestationRewards(w http.ResponseWriter, r *http.Request) {
func (s *Server) SyncCommitteeRewards(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "beacon.SyncCommitteeRewards")
defer span.End()
segments := strings.Split(r.URL.Path, "/")
blockId := segments[len(segments)-1]
blockId := r.PathValue("block_id")
blk, err := s.Blocker.Block(r.Context(), []byte(blockId))
if !shared.WriteBlockFetchError(w, blk, err) {
@@ -199,8 +196,7 @@ func (s *Server) SyncCommitteeRewards(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) attRewardsState(w http.ResponseWriter, r *http.Request) (state.BeaconState, bool) {
segments := strings.Split(r.URL.Path, "/")
requestedEpoch, err := strconv.ParseUint(segments[len(segments)-1], 10, 64)
requestedEpoch, err := strconv.ParseUint(r.PathValue("epoch"), 10, 64)
if err != nil {
httputil.HandleError(w, "Could not decode epoch: "+err.Error(), http.StatusBadRequest)
return nil, false

View File

@@ -268,6 +268,7 @@ func TestBlockRewards(t *testing.T) {
}
url := "http://only.the.slot.number.at.the.end.is.important/0"
request := httptest.NewRequest("GET", url, nil)
request.SetPathValue("block_id", "0")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -298,6 +299,7 @@ func TestBlockRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/2"
request := httptest.NewRequest("GET", url, nil)
request.SetPathValue("block_id", "2")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -334,6 +336,7 @@ func TestBlockRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/2"
request := httptest.NewRequest("GET", url, nil)
request.SetPathValue("block_id", "2")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -370,6 +373,7 @@ func TestBlockRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/2"
request := httptest.NewRequest("GET", url, nil)
request.SetPathValue("block_id", "2")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -406,6 +410,7 @@ func TestBlockRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/2"
request := httptest.NewRequest("GET", url, nil)
request.SetPathValue("block_id", "2")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -442,6 +447,7 @@ func TestBlockRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/2"
request := httptest.NewRequest("GET", url, nil)
request.SetPathValue("block_id", "2")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -511,6 +517,7 @@ func TestAttestationRewards(t *testing.T) {
t.Run("ideal rewards", func(t *testing.T) {
url := "http://only.the.epoch.number.at.the.end.is.important/1"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -540,6 +547,7 @@ func TestAttestationRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -563,6 +571,7 @@ func TestAttestationRewards(t *testing.T) {
t.Run("all vals", func(t *testing.T) {
url := "http://only.the.epoch.number.at.the.end.is.important/1"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -605,6 +614,7 @@ func TestAttestationRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -643,6 +653,7 @@ func TestAttestationRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -660,6 +671,7 @@ func TestAttestationRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -681,6 +693,7 @@ func TestAttestationRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -699,6 +712,7 @@ func TestAttestationRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("epoch", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -712,6 +726,7 @@ func TestAttestationRewards(t *testing.T) {
t.Run("phase 0", func(t *testing.T) {
url := "http://only.the.epoch.number.at.the.end.is.important/0"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("epoch", "0")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -725,6 +740,7 @@ func TestAttestationRewards(t *testing.T) {
t.Run("invalid epoch", func(t *testing.T) {
url := "http://only.the.epoch.number.at.the.end.is.important/foo"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("epoch", "foo")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -738,6 +754,7 @@ func TestAttestationRewards(t *testing.T) {
t.Run("previous epoch", func(t *testing.T) {
url := "http://only.the.epoch.number.at.the.end.is.important/2"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("epoch", "2")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -751,6 +768,7 @@ func TestAttestationRewards(t *testing.T) {
t.Run("epoch overflow", func(t *testing.T) {
url := "http://only.the.epoch.number.at.the.end.is.important/" + strconv.FormatUint(math.MaxUint64, 10)
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("epoch", strconv.FormatUint(math.MaxUint64, 10))
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -858,6 +876,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -885,6 +904,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/32"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -916,6 +936,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -947,6 +968,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -977,6 +999,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -1004,6 +1027,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -1028,6 +1052,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
_, err = body.Write(valIds)
require.NoError(t, err)
request := httptest.NewRequest("POST", url, &body)
request.SetPathValue("block_id", "32")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -1047,6 +1072,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
url := "http://only.the.slot.number.at.the.end.is.important/0"
request := httptest.NewRequest("POST", url, nil)
request.SetPathValue("block_id", "0")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

View File

@@ -12,6 +12,7 @@ go_library(
"//api/server/structs:go_default_library",
"//beacon-chain/blockchain:go_default_library",
"//beacon-chain/rpc/lookup:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//beacon-chain/sync:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library",
@@ -30,6 +31,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//beacon-chain/rpc/lookup:go_default_library",
"//beacon-chain/state/stategen:go_default_library",
"//network/httputil:go_default_library",
"//testing/assert:go_default_library",
"@com_github_pkg_errors//:go_default_library",

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/lookup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stategen"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/network/httputil"
@@ -14,7 +15,7 @@ import (
// The argument error should be a result of fetching state.
func WriteStateFetchError(w http.ResponseWriter, err error) {
var stateNotFoundError *lookup.StateNotFoundError
if errors.As(err, &stateNotFoundError) {
if errors.As(err, &stateNotFoundError) || errors.Is(err, stategen.ErrNoDataForSlot) {
httputil.HandleError(w, "State not found", http.StatusNotFound)
return
}

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/lookup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/stategen"
"github.com/OffchainLabs/prysm/v7/network/httputil"
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/pkg/errors"
@@ -31,6 +32,11 @@ func TestWriteStateFetchError(t *testing.T) {
expectedMessage: "Invalid state ID",
expectedCode: http.StatusBadRequest,
},
{
err: errors.Wrap(stategen.ErrNoDataForSlot, "no data for slot"),
expectedMessage: "State not found",
expectedCode: http.StatusNotFound,
},
{
err: errors.New("state not found"),
expectedMessage: "Could not get state",

View File

@@ -5,7 +5,7 @@ go_library(
srcs = [
"handlers.go",
"handlers_block.go",
"handlers_gloas.go",
"handlers_block_gloas.go",
"log.go",
"server.go",
],
@@ -49,6 +49,8 @@ go_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",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
"@org_golang_google_protobuf//types/known/wrapperspb:go_default_library",
],
)
@@ -56,6 +58,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"handlers_block_gloas_test.go",
"handlers_block_test.go",
"handlers_test.go",
],
@@ -88,6 +91,7 @@ go_test(
"//crypto/bls/common:go_default_library",
"//encoding/bytesutil:go_default_library",
"//network/httputil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",

View File

@@ -1189,9 +1189,28 @@ func (s *Server) GetSyncCommitteeDuties(w http.ResponseWriter, r *http.Request)
return
}
startingEpoch := min(requestedEpoch, currentEpoch)
currentCommitteeFirstEpoch, err := slots.SyncCommitteePeriodStartEpoch(currentEpoch)
if err != nil {
httputil.HandleError(w, "Could not get sync committee period start epoch: "+err.Error(), http.StatusInternalServerError)
return
}
st, err := s.Stater.StateByEpoch(ctx, startingEpoch)
requestedCommitteeFirstEpoch, err := slots.SyncCommitteePeriodStartEpoch(requestedEpoch)
if err != nil {
httputil.HandleError(w, "Could not get sync committee period start epoch: "+err.Error(), http.StatusInternalServerError)
return
}
// Sync committee assignments are computed at the start of the sync committee period and don't change during the period.
// - For the current period we use the current epoch to avoid expensive state replays.
// - For the next period we also use the current epoch and later read NextSyncCommittee (known one period in advance) to avoid fetching a future state.
// - For a past period we fall back to the first epoch of that period.
targetEpoch := currentEpoch
if requestedCommitteeFirstEpoch < currentCommitteeFirstEpoch {
targetEpoch = requestedCommitteeFirstEpoch
}
st, err := s.Stater.StateByEpoch(ctx, targetEpoch)
if err != nil {
shared.WriteStateFetchError(w, err)
return

View File

@@ -6,7 +6,6 @@ import (
"fmt"
"math/big"
"net/http"
"strings"
"github.com/OffchainLabs/prysm/v7/api"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
@@ -49,8 +48,7 @@ func (s *Server) ProduceBlockV3(w http.ResponseWriter, r *http.Request) {
return
}
segments := strings.Split(r.URL.Path, "/")
rawSlot := segments[len(segments)-1]
rawSlot := r.PathValue("slot")
rawRandaoReveal := r.URL.Query().Get("randao_reveal")
rawGraffiti := r.URL.Query().Get("graffiti")
rawSkipRandaoVerification := r.URL.Query().Get("skip_randao_verification")

View File

@@ -0,0 +1,238 @@
package validator
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/OffchainLabs/prysm/v7/api"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/eth/shared"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v7/network/httputil"
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/wrapperspb"
)
// ProduceBlockV4 requests a beacon node to produce a valid Gloas block.
// When include_payload=true (default), the response includes the execution payload
// envelope alongside the beacon block.
// Endpoint: GET /eth/v4/validator/blocks/{slot}
func (s *Server) ProduceBlockV4(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.ProduceBlockV4")
defer span.End()
if shared.IsSyncing(ctx, w, s.SyncChecker, s.HeadFetcher, s.TimeFetcher, s.OptimisticModeFetcher) {
return
}
rawSlot := r.PathValue("slot")
slot, valid := shared.ValidateUint(w, "slot", rawSlot)
if !valid {
return
}
if slots.ToEpoch(primitives.Slot(slot)) < params.BeaconConfig().GloasForkEpoch {
httputil.HandleError(w, "ProduceBlockV4 is only supported for Gloas and later forks", http.StatusBadRequest)
return
}
rawRandaoReveal := r.URL.Query().Get("randao_reveal")
rawGraffiti := r.URL.Query().Get("graffiti")
rawSkipRandaoVerification := r.URL.Query().Get("skip_randao_verification")
var bbFactor *wrapperspb.UInt64Value
rawBbFactor, bbValue, ok := shared.UintFromQuery(w, r, "builder_boost_factor", false)
if !ok {
return
}
if rawBbFactor != "" {
bbFactor = &wrapperspb.UInt64Value{Value: bbValue}
}
includePayload := true
if raw := r.URL.Query().Get("include_payload"); raw == "false" {
includePayload = false
}
var randaoReveal []byte
if rawSkipRandaoVerification == "true" {
randaoReveal = common.InfiniteSignature[:]
} else {
rr, err := bytesutil.DecodeHexWithLength(rawRandaoReveal, fieldparams.BLSSignatureLength)
if err != nil {
httputil.HandleError(w, errors.Wrap(err, "unable to decode randao reveal").Error(), http.StatusBadRequest)
return
}
randaoReveal = rr
}
var graffiti []byte
if rawGraffiti != "" {
g, err := bytesutil.DecodeHexWithLength(rawGraffiti, 32)
if err != nil {
httputil.HandleError(w, errors.Wrap(err, "unable to decode graffiti").Error(), http.StatusBadRequest)
return
}
graffiti = g
}
v1alpha1resp, err := s.V1Alpha1Server.GetBeaconBlock(ctx, &eth.BlockRequest{
Slot: primitives.Slot(slot),
RandaoReveal: randaoReveal,
Graffiti: graffiti,
SkipMevBoost: false,
BuilderBoostFactor: bbFactor,
EagerPayloadStateRoot: includePayload,
})
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
gloasBlock, ok := v1alpha1resp.Block.(*eth.GenericBeaconBlock_Gloas)
if !ok {
httputil.HandleError(w, fmt.Sprintf("expected Gloas block, got %T", v1alpha1resp.Block), http.StatusInternalServerError)
return
}
consensusBlockValue, httpError := getConsensusBlockValue(ctx, s.BlockRewardFetcher, v1alpha1resp.Block)
if httpError != nil {
log.WithError(httpError).Debug("Failed to get consensus block value")
consensusBlockValue = "0"
}
w.Header().Set(api.VersionHeader, version.String(version.Gloas))
w.Header().Set(api.ConsensusBlockValueHeader, consensusBlockValue)
w.Header().Set(api.ExecutionPayloadIncludedHeader, fmt.Sprintf("%v", includePayload))
isSSZ := httputil.RespondWithSsz(r)
if includePayload {
envelopeResp, err := s.V1Alpha1Server.GetExecutionPayloadEnvelope(ctx, &eth.ExecutionPayloadEnvelopeRequest{
Slot: primitives.Slot(slot),
})
if err != nil {
httputil.HandleError(w, errors.Wrap(err, "could not get execution payload envelope").Error(), http.StatusInternalServerError)
return
}
if isSSZ {
sszResp, err := (&eth.BeaconBlockContentsGloas{
Block: gloasBlock.Gloas,
ExecutionPayloadEnvelope: envelopeResp.Envelope,
}).MarshalSSZ()
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp)
return
}
blockContents, err := structs.BlockContentsGloasFromConsensus(gloasBlock.Gloas, envelopeResp.Envelope)
if err != nil {
httputil.HandleError(w, errors.Wrap(err, "could not convert block contents").Error(), http.StatusInternalServerError)
return
}
jsonBytes, err := json.Marshal(blockContents)
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteJson(w, &structs.ProduceBlockV4Response{
Version: version.String(version.Gloas),
ConsensusBlockValue: consensusBlockValue,
ExecutionPayloadIncluded: true,
Data: jsonBytes,
})
return
}
// include_payload=false: return only the beacon block.
if isSSZ {
sszResp, err := gloasBlock.Gloas.MarshalSSZ()
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteSsz(w, sszResp)
return
}
block, err := structs.BeaconBlockGloasFromConsensus(gloasBlock.Gloas)
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
jsonBytes, err := json.Marshal(block)
if err != nil {
httputil.HandleError(w, err.Error(), http.StatusInternalServerError)
return
}
httputil.WriteJson(w, &structs.ProduceBlockV4Response{
Version: version.String(version.Gloas),
ConsensusBlockValue: consensusBlockValue,
ExecutionPayloadIncluded: false,
Data: jsonBytes,
})
}
// ExecutionPayloadEnvelope retrieves a cached execution payload envelope.
//
// Endpoint: GET /eth/v1/validator/execution_payload_envelope/{slot}
func (s *Server) ExecutionPayloadEnvelope(w http.ResponseWriter, r *http.Request) {
ctx, span := trace.StartSpan(r.Context(), "validator.ExecutionPayloadEnvelope")
defer span.End()
rawSlot := r.PathValue("slot")
if rawSlot == "" {
httputil.HandleError(w, "slot is required in URL params", http.StatusBadRequest)
return
}
slot, err := strconv.ParseUint(rawSlot, 10, 64)
if err != nil {
httputil.HandleError(w, "invalid slot: "+err.Error(), http.StatusBadRequest)
return
}
resp, err := s.V1Alpha1Server.GetExecutionPayloadEnvelope(ctx, &eth.ExecutionPayloadEnvelopeRequest{
Slot: primitives.Slot(slot),
})
if err != nil {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.NotFound:
httputil.HandleError(w, st.Message(), http.StatusNotFound)
case codes.InvalidArgument:
httputil.HandleError(w, st.Message(), http.StatusBadRequest)
default:
httputil.HandleError(w, st.Message(), http.StatusInternalServerError)
}
return
}
httputil.HandleError(w, "could not get execution payload envelope: "+err.Error(), http.StatusInternalServerError)
return
}
jsonEnvelope, err := structs.ExecutionPayloadEnvelopeFromConsensus(resp.Envelope)
if err != nil {
httputil.HandleError(w, "could not convert envelope to JSON: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set(api.VersionHeader, version.String(version.Gloas))
httputil.WriteJson(w, &structs.GetValidatorExecutionPayloadEnvelopeResponse{
Version: version.String(version.Gloas),
Data: jsonEnvelope,
})
}

View File

@@ -0,0 +1,235 @@
package validator
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/OffchainLabs/prysm/v7/api"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
blockchainTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
rewardtesting "github.com/OffchainLabs/prysm/v7/beacon-chain/rpc/eth/rewards/testing"
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
"github.com/OffchainLabs/prysm/v7/config/params"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/assert"
mock2 "github.com/OffchainLabs/prysm/v7/testing/mock"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"go.uber.org/mock/gomock"
)
var (
testRandao = "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
testGraffiti = "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
)
func testEnvelope() *eth.ExecutionPayloadEnvelope {
return &eth.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: make([]byte, 32),
FeeRecipient: make([]byte, 20),
StateRoot: make([]byte, 32),
ReceiptsRoot: make([]byte, 32),
LogsBloom: make([]byte, 256),
PrevRandao: make([]byte, 32),
BaseFeePerGas: make([]byte, 32),
BlockHash: make([]byte, 32),
},
BuilderIndex: 0,
BeaconBlockRoot: make([]byte, 32),
Slot: 1,
StateRoot: make([]byte, 32),
}
}
func gloasGenericBlock() *eth.GenericBeaconBlock {
return &eth.GenericBeaconBlock{
Block: &eth.GenericBeaconBlock_Gloas{
Gloas: util.NewBeaconBlockGloas().Block,
},
}
}
func TestProduceBlockV4_IncludePayloadTrue(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), gomock.Any()).Return(gloasGenericBlock(), nil)
v1alpha1Server.EXPECT().GetExecutionPayloadEnvelope(gomock.Any(), gomock.Any()).Return(
&eth.ExecutionPayloadEnvelopeResponse{Envelope: testEnvelope()}, nil,
)
server := &Server{
V1Alpha1Server: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
OptimisticModeFetcher: &blockchainTesting.ChainService{},
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: &structs.BlockRewards{Total: "10"}},
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v4/validator/blocks/1?randao_reveal=%s&graffiti=%s", testRandao, testGraffiti), nil)
request.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV4(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
var resp structs.ProduceBlockV4Response
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
assert.Equal(t, "gloas", resp.Version)
assert.Equal(t, true, resp.ExecutionPayloadIncluded)
assert.Equal(t, "10000000000", resp.ConsensusBlockValue)
var blockContents structs.BlockContentsGloas
require.NoError(t, json.Unmarshal(resp.Data, &blockContents))
assert.NotNil(t, blockContents.Block)
assert.NotNil(t, blockContents.ExecutionPayloadEnvelope)
require.Equal(t, "gloas", writer.Header().Get(api.VersionHeader))
require.Equal(t, "10000000000", writer.Header().Get(api.ConsensusBlockValueHeader))
require.Equal(t, "true", writer.Header().Get(api.ExecutionPayloadIncludedHeader))
}
func TestProduceBlockV4_IncludePayloadFalse(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), gomock.Any()).Return(gloasGenericBlock(), nil)
server := &Server{
V1Alpha1Server: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
OptimisticModeFetcher: &blockchainTesting.ChainService{},
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: &structs.BlockRewards{Total: "10"}},
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v4/validator/blocks/1?randao_reveal=%s&graffiti=%s&include_payload=false", testRandao, testGraffiti), nil)
request.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV4(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
var resp structs.ProduceBlockV4Response
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
assert.Equal(t, "gloas", resp.Version)
assert.Equal(t, false, resp.ExecutionPayloadIncluded)
var block structs.BeaconBlockGloas
require.NoError(t, json.Unmarshal(resp.Data, &block))
assert.NotNil(t, block.Body)
require.Equal(t, "gloas", writer.Header().Get(api.VersionHeader))
require.Equal(t, "false", writer.Header().Get(api.ExecutionPayloadIncludedHeader))
}
func TestProduceBlockV4_PreGloasSlotRejected(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 100
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
server := &Server{
V1Alpha1Server: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
OptimisticModeFetcher: &blockchainTesting.ChainService{},
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v4/validator/blocks/1?randao_reveal=%s&graffiti=%s", testRandao, testGraffiti), nil)
request.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV4(writer, request)
assert.Equal(t, http.StatusBadRequest, writer.Code)
assert.StringContains(t, "only supported for Gloas", writer.Body.String())
}
func TestProduceBlockV4_Syncing(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
chainService := &blockchainTesting.ChainService{}
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
server := &Server{
V1Alpha1Server: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: true},
HeadFetcher: chainService,
TimeFetcher: chainService,
OptimisticModeFetcher: chainService,
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v4/validator/blocks/1?randao_reveal=%s&graffiti=%s", testRandao, testGraffiti), nil)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV4(writer, request)
assert.Equal(t, http.StatusServiceUnavailable, writer.Code)
}
func TestProduceBlockV4_SSZ_IncludePayloadTrue(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), gomock.Any()).Return(gloasGenericBlock(), nil)
v1alpha1Server.EXPECT().GetExecutionPayloadEnvelope(gomock.Any(), gomock.Any()).Return(
&eth.ExecutionPayloadEnvelopeResponse{Envelope: testEnvelope()}, nil,
)
server := &Server{
V1Alpha1Server: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
OptimisticModeFetcher: &blockchainTesting.ChainService{},
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: &structs.BlockRewards{Total: "10"}},
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v4/validator/blocks/1?randao_reveal=%s&graffiti=%s", testRandao, testGraffiti), nil)
request.SetPathValue("slot", "1")
request.Header.Set("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV4(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, "application/octet-stream", writer.Header().Get("Content-Type"))
assert.Equal(t, true, writer.Body.Len() > 0)
}
func TestProduceBlockV4_SSZ_IncludePayloadFalse(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
ctrl := gomock.NewController(t)
v1alpha1Server := mock2.NewMockBeaconNodeValidatorServer(ctrl)
v1alpha1Server.EXPECT().GetBeaconBlock(gomock.Any(), gomock.Any()).Return(gloasGenericBlock(), nil)
server := &Server{
V1Alpha1Server: v1alpha1Server,
SyncChecker: &mockSync.Sync{IsSyncing: false},
OptimisticModeFetcher: &blockchainTesting.ChainService{},
BlockRewardFetcher: &rewardtesting.MockBlockRewardFetcher{Rewards: &structs.BlockRewards{Total: "10"}},
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v4/validator/blocks/1?randao_reveal=%s&graffiti=%s&include_payload=false", testRandao, testGraffiti), nil)
request.SetPathValue("slot", "1")
request.Header.Set("Accept", "application/octet-stream")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV4(writer, request)
assert.Equal(t, http.StatusOK, writer.Code)
assert.Equal(t, "application/octet-stream", writer.Header().Get("Content-Type"))
}

View File

@@ -57,6 +57,7 @@ func TestProduceBlockV3(t *testing.T) {
SyncChecker: syncChecker,
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v3/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
request.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -93,6 +94,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -131,6 +133,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -169,6 +172,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -207,6 +211,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -245,6 +250,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -283,6 +289,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -321,6 +328,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -359,6 +367,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -397,6 +406,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -435,6 +445,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -473,6 +484,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -505,6 +517,7 @@ func TestProduceBlockV3(t *testing.T) {
SyncChecker: syncChecker,
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v3/validator/blocks/asdfsad", nil)
request.SetPathValue("slot", "asdfsad")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -518,6 +531,7 @@ func TestProduceBlockV3(t *testing.T) {
SyncChecker: syncChecker,
}
request := httptest.NewRequest(http.MethodGet, "http://foo.example/eth/v3/validator/blocks/1?randao_reveal=0x213123", nil)
request.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -566,6 +580,7 @@ func TestProduceBlockV3(t *testing.T) {
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.SetPathValue("slot", "1")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
server.ProduceBlockV3(writer, request)
@@ -611,6 +626,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
SyncChecker: syncChecker,
}
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://foo.example/eth/v3/validator/blocks/1?randao_reveal=%s&graffiti=%s", randao, graffiti), nil)
request.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -649,6 +665,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -691,6 +708,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -732,6 +750,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -773,6 +792,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -814,6 +834,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -855,6 +876,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -896,6 +918,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -937,6 +960,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -978,6 +1002,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -1019,6 +1044,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -1060,6 +1086,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
@@ -1103,6 +1130,7 @@ func TestProduceBlockV3SSZ(t *testing.T) {
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.SetPathValue("slot", "1")
request.Header.Set("Accept", api.OctetStreamMediaType)
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}

View File

@@ -1,31 +0,0 @@
package validator
import (
"net/http"
"github.com/OffchainLabs/prysm/v7/network/httputil"
)
// ProduceBlockV4 requests a beacon node to produce a valid Gloas block.
//
// TODO: Implement Gloas-specific block production.
// Endpoint: GET /eth/v4/validator/blocks/{slot}
func (s *Server) ProduceBlockV4(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, "ProduceBlockV4 not yet implemented", http.StatusNotImplemented)
}
// ExecutionPayloadEnvelope retrieves a cached execution payload envelope.
//
// TODO: Implement envelope retrieval from cache.
// Endpoint: GET /eth/v1/validator/execution_payload_envelope/{slot}/{builder_index}
func (s *Server) ExecutionPayloadEnvelope(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, "ExecutionPayloadEnvelope not yet implemented", http.StatusNotImplemented)
}
// PublishExecutionPayloadEnvelope broadcasts a signed execution payload envelope.
//
// TODO: Implement envelope validation and broadcast.
// Endpoint: POST /eth/v1/beacon/execution_payload_envelope
func (s *Server) PublishExecutionPayloadEnvelope(w http.ResponseWriter, r *http.Request) {
httputil.HandleError(w, "PublishExecutionPayloadEnvelope not yet implemented", http.StatusNotImplemented)
}

View File

@@ -3062,6 +3062,93 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
require.Equal(t, 1, len(duty.ValidatorSyncCommitteeIndices))
assert.Equal(t, "1", duty.ValidatorSyncCommitteeIndices[0])
})
t.Run("past sync committee period", func(t *testing.T) {
// Chain is two periods ahead, the request targets epoch 0 (a past period).
// The handler must load the state at the requested period's start epoch and
// use its CurrentSyncCommittee, NOT the current state's committee.
pastEpochStartSlot := primitives.Slot(0)
currentSlot := primitives.Slot(uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod) * 2 * uint64(params.BeaconConfig().SlotsPerEpoch))
pastSt, _ := util.DeterministicGenesisStateAltair(t, numVals)
require.NoError(t, pastSt.SetSlot(pastEpochStartSlot))
require.NoError(t, pastSt.SetGenesisTime(genesisTime))
pastVals := pastSt.Validators()
pastCommittee := &ethpbalpha.SyncCommittee{AggregatePubkey: make([]byte, 48)}
for i := range 5 {
pastCommittee.Pubkeys = append(pastCommittee.Pubkeys, pastVals[i].PublicKey)
}
require.NoError(t, pastSt.SetCurrentSyncCommittee(pastCommittee))
pastNextCommittee := &ethpbalpha.SyncCommittee{AggregatePubkey: make([]byte, 48)}
for i := 5; i < 10; i++ {
pastNextCommittee.Pubkeys = append(pastNextCommittee.Pubkeys, pastVals[i].PublicKey)
}
require.NoError(t, pastSt.SetNextSyncCommittee(pastNextCommittee))
// Current state has different sync committees so that if the handler used
// the current state instead of the past state, assertions below would fail.
currentSt, _ := util.DeterministicGenesisStateAltair(t, numVals)
require.NoError(t, currentSt.SetSlot(currentSlot))
require.NoError(t, currentSt.SetGenesisTime(genesisTime))
currentVals := currentSt.Validators()
currentCommittee := &ethpbalpha.SyncCommittee{AggregatePubkey: make([]byte, 48)}
for i := 5; i < 10; i++ {
currentCommittee.Pubkeys = append(currentCommittee.Pubkeys, currentVals[i].PublicKey)
}
require.NoError(t, currentSt.SetCurrentSyncCommittee(currentCommittee))
currentNextCommittee := &ethpbalpha.SyncCommittee{AggregatePubkey: make([]byte, 48)}
for i := range 5 {
currentNextCommittee.Pubkeys = append(currentNextCommittee.Pubkeys, currentVals[i].PublicKey)
}
require.NoError(t, currentSt.SetNextSyncCommittee(currentNextCommittee))
mockChainService := &mockChain.ChainService{Genesis: genesisTime, Slot: &currentSlot, State: currentSt}
s := &Server{
Stater: &testutil.MockStater{StatesByEpoch: map[primitives.Epoch]state.BeaconState{
0: pastSt,
primitives.Epoch(uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod) * 2): currentSt,
}},
SyncChecker: &mockSync.Sync{IsSyncing: false},
TimeFetcher: mockChainService,
HeadFetcher: mockChainService,
OptimisticModeFetcher: mockChainService,
}
var body bytes.Buffer
_, err := body.WriteString("[\"1\"]")
require.NoError(t, err)
request := httptest.NewRequest(http.MethodGet, "http://www.example.com/eth/v1/validator/duties/sync/{epoch}", &body)
request.SetPathValue("epoch", "0")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.GetSyncCommitteeDuties(writer, request)
require.Equal(t, http.StatusOK, writer.Code)
resp := &structs.GetSyncCommitteeDutiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 1, len(resp.Data))
duty := resp.Data[0]
// Validator 1 is in pastSt's CurrentSyncCommittee at index 1. If the handler
// had loaded currentSt instead, validator 1 would not appear in its
// CurrentSyncCommittee and no duty would be returned.
require.Equal(t, hexutil.Encode(pastVals[1].PublicKey), duty.Pubkey)
require.Equal(t, "1", duty.ValidatorIndex)
require.Equal(t, 1, len(duty.ValidatorSyncCommitteeIndices))
require.Equal(t, "1", duty.ValidatorSyncCommitteeIndices[0])
})
t.Run("epoch too far in the future", func(t *testing.T) {
var body bytes.Buffer
_, err := body.WriteString("[\"5\"]")

View File

@@ -58,10 +58,12 @@ go_test(
"//consensus-types/blocks:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/dbval: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/hexutil:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)

View File

@@ -140,13 +140,29 @@ func (p *BeaconDbStater) State(ctx context.Context, stateId []byte) (state.Beaco
}
case "finalized":
checkpoint := p.ChainInfoFetcher.FinalizedCheckpt()
s, err = p.StateGenService.StateByRoot(ctx, bytesutil.ToBytes32(checkpoint.Root))
targetSlot, err := slots.EpochStart(checkpoint.Epoch)
if err != nil {
return nil, errors.Wrap(err, "could not get start slot")
}
// We use the stategen replayer to fetch the finalized state and then
// replay it to the start slot of our checkpoint's epoch. The replayer
// only ever accesses our canonical history, so the state retrieved will
// always be the finalized state at that epoch.
s, err = p.ReplayerBuilder.ReplayerForSlot(targetSlot).ReplayToSlot(ctx, targetSlot)
if err != nil {
return nil, errors.Wrap(err, "could not get finalized state")
}
case "justified":
checkpoint := p.ChainInfoFetcher.CurrentJustifiedCheckpt()
s, err = p.StateGenService.StateByRoot(ctx, bytesutil.ToBytes32(checkpoint.Root))
targetSlot, err := slots.EpochStart(checkpoint.Epoch)
if err != nil {
return nil, errors.Wrap(err, "could not get start slot")
}
// We use the stategen replayer to fetch the justified state and then
// replay it to the start slot of our checkpoint's epoch. The replayer
// only ever accesses our canonical history, so the state retrieved will
// always be the justified state at that epoch.
s, err = p.ReplayerBuilder.ReplayerForSlot(targetSlot).ReplayToSlot(ctx, targetSlot)
if err != nil {
return nil, errors.Wrap(err, "could not get justified state")
}
@@ -275,8 +291,37 @@ func (p *BeaconDbStater) StateBySlot(ctx context.Context, target primitives.Slot
return nil, errors.New("requested slot is in the future")
}
if p.BeaconDB != nil {
earliestSlot, err := p.BeaconDB.EarliestSlot(ctx)
if err != nil && !errors.Is(err, db.ErrNotFound) {
return nil, errors.Wrap(err, "could not determine state availability")
}
if err == nil && target > 0 && target < earliestSlot {
return nil, &StateNotFoundError{
message: fmt.Sprintf("requested slot %d is unavailable; earliest available slot is %d", target, earliestSlot),
}
}
backfillStatus, err := p.BeaconDB.BackfillStatus(ctx)
if err != nil && !errors.Is(err, db.ErrNotFound) {
return nil, errors.Wrap(err, "could not determine state availability")
}
if err == nil && backfillStatus != nil {
if target > 0 && target < primitives.Slot(backfillStatus.LowSlot) {
return nil, &StateNotFoundError{
message: fmt.Sprintf("requested slot %d is unavailable; backfill starts at slot %d", target, backfillStatus.LowSlot),
}
}
}
}
st, err := p.ReplayerBuilder.ReplayerForSlot(target).ReplayBlocks(ctx)
if err != nil {
if errors.Is(err, stategen.ErrNoDataForSlot) {
return nil, &StateNotFoundError{
message: fmt.Sprintf("requested slot %d is unavailable; historical data not available", target),
}
}
msg := fmt.Sprintf("error while replaying history to slot=%d", target)
return nil, errors.Wrap(err, msg)
}

View File

@@ -1,6 +1,7 @@
package lookup
import (
"fmt"
"strconv"
"strings"
"testing"
@@ -15,11 +16,13 @@ import (
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/proto/dbval"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
)
func TestGetState(t *testing.T) {
@@ -91,20 +94,20 @@ func TestGetState(t *testing.T) {
})
t.Run("finalized", func(t *testing.T) {
// Use a block root distinct from the state root to verify
// we look up by checkpoint root, not by state root.
blockRoot := bytesutil.ToBytes32([]byte("finalized-block-root"))
stateGen := mockstategen.NewService()
stateGen.StatesByRoot[blockRoot] = newBeaconState
replayer := mockstategen.NewReplayerBuilder()
replayer.SetMockStateForSlot(newBeaconState, params.BeaconConfig().SlotsPerEpoch*10)
stateGen.StatesByRoot[stateRoot] = newBeaconState
p := BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{
FinalizedCheckPoint: &ethpb.Checkpoint{
Root: blockRoot[:],
Root: stateRoot[:],
Epoch: 10,
},
},
StateGenService: stateGen,
ReplayerBuilder: replayer,
}
s, err := p.State(ctx, []byte("finalized"))
@@ -115,18 +118,20 @@ func TestGetState(t *testing.T) {
})
t.Run("justified", func(t *testing.T) {
blockRoot := bytesutil.ToBytes32([]byte("justified-block-root"))
stateGen := mockstategen.NewService()
stateGen.StatesByRoot[blockRoot] = newBeaconState
replayer := mockstategen.NewReplayerBuilder()
replayer.SetMockStateForSlot(newBeaconState, params.BeaconConfig().SlotsPerEpoch*10)
stateGen.StatesByRoot[stateRoot] = newBeaconState
p := BeaconDbStater{
ChainInfoFetcher: &chainMock.ChainService{
CurrentJustifiedCheckPoint: &ethpb.Checkpoint{
Root: blockRoot[:],
Root: stateRoot[:],
Epoch: 10,
},
},
StateGenService: stateGen,
ReplayerBuilder: replayer,
}
s, err := p.State(ctx, []byte("justified"))
@@ -443,6 +448,72 @@ func TestStateBySlot_AfterHeadSlot(t *testing.T) {
assert.Equal(t, primitives.Slot(101), st.Slot())
}
func TestStateBySlot_EarlierThanEarliestAvailableSlot(t *testing.T) {
ctx := t.Context()
db := testDB.SetupDB(t)
target := primitives.Slot(100)
genesisRoot := bytesutil.ToBytes32([]byte("genesis"))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesisRoot))
b := util.NewBeaconBlock()
b.Block.ParentRoot = genesisRoot[:]
b.Block.Slot = target + 2
util.SaveBlock(t, ctx, db, b)
currentSlot := target + 3
p := BeaconDbStater{
BeaconDB: db,
GenesisTimeFetcher: &chainMock.ChainService{Slot: &currentSlot},
}
_, err := p.StateBySlot(ctx, target)
require.ErrorContains(t, fmt.Sprintf("earliest available slot is %d", b.Block.Slot), err)
var stateNotFoundErr *StateNotFoundError
require.Equal(t, true, errors.As(err, &stateNotFoundErr))
}
func TestStateBySlot_BeforeBackfillLowSlot(t *testing.T) {
ctx := t.Context()
db := testDB.SetupDB(t)
target := primitives.Slot(100)
genesisRoot := bytesutil.ToBytes32([]byte("genesis"))
require.NoError(t, db.SaveGenesisBlockRoot(ctx, genesisRoot))
lowSlot := target + 1
require.NoError(t, db.SaveBackfillStatus(ctx, &dbval.BackfillStatus{LowSlot: uint64(lowSlot)}))
currentSlot := lowSlot + 1
p := BeaconDbStater{
BeaconDB: db,
GenesisTimeFetcher: &chainMock.ChainService{Slot: &currentSlot},
}
_, err := p.StateBySlot(ctx, target)
require.ErrorContains(t, fmt.Sprintf("backfill starts at slot %d", lowSlot), err)
var stateNotFoundErr *StateNotFoundError
require.Equal(t, true, errors.As(err, &stateNotFoundErr))
}
func TestStateBySlot_ReplayNoDataForSlotReturnsNotFound(t *testing.T) {
target := primitives.Slot(100)
currentSlot := target + 1
mock := &chainMock.ChainService{Slot: &currentSlot}
mockReplayer := mockstategen.NewReplayerBuilder()
mockReplayer.SetMockSlotError(target, errors.Wrap(stategen.ErrNoDataForSlot, fmt.Sprintf("slot %d not in db due to checkpoint sync", target)))
p := BeaconDbStater{
GenesisTimeFetcher: mock,
ReplayerBuilder: mockReplayer,
}
_, err := p.StateBySlot(t.Context(), target)
require.ErrorContains(t, "historical data not available", err)
var stateNotFoundErr *StateNotFoundError
require.Equal(t, true, errors.As(err, &stateNotFoundErr))
}
func TestStateByEpoch(t *testing.T) {
ctx := t.Context()
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
@@ -105,8 +104,7 @@ func (s *Server) RemoveTrustedPeer(w http.ResponseWriter, r *http.Request) {
_, span := trace.StartSpan(r.Context(), "node.RemoveTrustedPeer")
defer span.End()
segments := strings.Split(r.URL.Path, "/")
id := segments[len(segments)-1]
id := r.PathValue("peer_id")
peerId, err := peer.Decode(id)
if err != nil {
errJson := &httputil.DefaultJsonError{

View File

@@ -227,6 +227,7 @@ func TestRemoveTrustedPeer(t *testing.T) {
url := "http://anything.is.fine.but.last.is.important/16Uiu2HAm1n583t4huDMMqEUUBuQs6bLts21mxCfX3tiqu9JfHvRJ"
request := httptest.NewRequest("DELETE", url, nil)
request.SetPathValue("peer_id", "16Uiu2HAm1n583t4huDMMqEUUBuQs6bLts21mxCfX3tiqu9JfHvRJ")
writer := httptest.NewRecorder()
writer.Body = &bytes.Buffer{}
s.RemoveTrustedPeer(writer, request)

View File

@@ -111,7 +111,7 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
builderBoostFactor = primitives.Gwei(req.BuilderBoostFactor.Value)
}
resp, err := vs.BuildBlockParallel(ctx, sBlk, head, req.SkipMevBoost, builderBoostFactor)
resp, err := vs.BuildBlockParallel(ctx, sBlk, head, req.SkipMevBoost, builderBoostFactor, req.EagerPayloadStateRoot)
l := log.WithFields(logrus.Fields{
"sinceSlotStartTime": time.Since(t),
"validator": sBlk.Block().ProposerIndex(),
@@ -202,7 +202,7 @@ func (vs *Server) getParentState(ctx context.Context, slot primitives.Slot) (sta
return head, parentRoot, err
}
func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.SignedBeaconBlock, head state.BeaconState, skipMevBoost bool, builderBoostFactor primitives.Gwei) (*ethpb.GenericBeaconBlock, error) {
func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.SignedBeaconBlock, head state.BeaconState, skipMevBoost bool, builderBoostFactor primitives.Gwei, eagerPayloadStateRoot bool) (*ethpb.GenericBeaconBlock, error) {
// Build consensus fields in background
var wg sync.WaitGroup
wg.Go(func() {
@@ -296,19 +296,29 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
wg.Wait()
sr, err := vs.computeStateRoot(ctx, sBlk)
sr, postBlockState, err := vs.computePostBlockStateAndRoot(ctx, sBlk)
if err != nil {
return nil, status.Errorf(codes.Internal, "Could not compute state root: %v", err)
}
sBlk.SetStateRoot(sr)
// For Gloas self-build, cache the execution payload envelope now that the
// block is fully built (state root set). The envelope needs the final block
// HTR as BeaconBlockRoot and the post-payload state root as StateRoot.
// For Gloas self-build, build and cache the execution payload envelope now
// that the block is fully built (state root set). The envelope needs the
// final block HTR as BeaconBlockRoot and the post-payload state root as
// StateRoot.
//
// When a remote P2P bid was selected, the winning builder is responsible
// for producing the envelope, so we must not cache a self-build one.
//
// When eagerPayloadStateRoot is true, the post-block state is passed so the
// envelope StateRoot is computed immediately. Otherwise it is left zeroed
// and computed lazily when GetExecutionPayloadEnvelope is called.
if sBlk.Version() >= version.Gloas && selfBuildEnvelope {
if err := vs.storeExecutionPayloadEnvelope(sBlk, local); err != nil {
var envelopeState state.BeaconState
if eagerPayloadStateRoot {
envelopeState = postBlockState
}
if err := vs.storeExecutionPayloadEnvelope(ctx, sBlk, local, envelopeState); err != nil {
return nil, status.Errorf(codes.Internal, "Could not build execution payload envelope: %v", err)
}
}
@@ -645,9 +655,25 @@ func (vs *Server) GetFeeRecipientByPubKey(ctx context.Context, request *ethpb.Fe
}, nil
}
// computeStateRoot computes the state root after a block has been processed through a state transition and
// returns it to the validator client.
func (vs *Server) computeStateRoot(ctx context.Context, block interfaces.SignedBeaconBlock) ([]byte, error) {
// computePostBlockStateAndRoot computes the state root after a block has been processed through a state transition and
// returns both the state root bytes and the full post-block state.
func (vs *Server) computePostBlockStateAndRoot(ctx context.Context, block interfaces.SignedBeaconBlock) ([]byte, state.BeaconState, error) {
st, err := vs.computePostBlockState(ctx, block)
if err != nil {
return nil, nil, err
}
root, err := st.HashTreeRoot(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "could not compute state root")
}
log.WithField("beaconStateRoot", fmt.Sprintf("%#x", root)).Debugf("Computed state root")
return root[:], st, nil
}
// computePostBlockState computes the post-block state by running the state transition.
// It uses the same logic as CalculateStateRoot (Copy, feature flags, slot processing)
// but returns the full state instead of just its hash.
func (vs *Server) computePostBlockState(ctx context.Context, block interfaces.SignedBeaconBlock) (state.BeaconState, error) {
roblock, err := blocks.NewROBlockWithRoot(block, [32]byte{}) // root is not used
if err != nil {
return nil, errors.Wrap(err, "could not create ROBlock")
@@ -656,17 +682,11 @@ func (vs *Server) computeStateRoot(ctx context.Context, block interfaces.SignedB
if err != nil {
return nil, errors.Wrap(err, "could not retrieve beacon state")
}
root, err := transition.CalculateStateRoot(
ctx,
beaconState,
block,
)
st, err := transition.CalculatePostState(ctx, beaconState, block)
if err != nil {
return vs.handleStateRootError(ctx, block, err)
return vs.handlePostBlockStateError(ctx, block, err)
}
log.WithField("beaconStateRoot", fmt.Sprintf("%#x", root)).Debugf("Computed state root")
return root[:], nil
return st, nil
}
type computeStateRootAttemptsKeyType string
@@ -674,8 +694,8 @@ type computeStateRootAttemptsKeyType string
const computeStateRootAttemptsKey = computeStateRootAttemptsKeyType("compute-state-root-attempts")
const maxComputeStateRootAttempts = 3
// handleStateRootError retries block construction in some error cases.
func (vs *Server) handleStateRootError(ctx context.Context, block interfaces.SignedBeaconBlock, err error) ([]byte, error) {
// handlePostBlockStateError retries block construction in some error cases.
func (vs *Server) handlePostBlockStateError(ctx context.Context, block interfaces.SignedBeaconBlock, err error) (state.BeaconState, error) {
if ctx.Err() != nil {
return nil, status.Errorf(codes.Canceled, "context error: %v", ctx.Err())
}
@@ -724,8 +744,8 @@ func (vs *Server) handleStateRootError(ctx context.Context, block interfaces.Sig
} else {
ctx = context.WithValue(ctx, computeStateRootAttemptsKey, v+1)
}
// recursive call to compute state root again
return vs.computeStateRoot(ctx, block)
// recursive call to compute post-block state again
return vs.computePostBlockState(ctx, block)
}
// Deprecated: The gRPC API will remain the default and fully supported through v8 (expected in 2026) but will be eventually removed in favor of REST API.

View File

@@ -7,6 +7,7 @@ import (
coregloas "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
@@ -24,13 +25,14 @@ import (
)
// storeExecutionPayloadEnvelope creates and caches the execution payload envelope
// after the block is fully built (state root set). The envelope is cached with a
// zeroed state root; the actual post-payload state root is computed lazily in
// GetExecutionPayloadEnvelope once the block has been submitted and the post-block
// state is available via StateGen.
// after the block is fully built (state root set). If postBlockState is non-nil,
// the envelope state root is eagerly computed; otherwise it is left zeroed for
// lazy computation by GetExecutionPayloadEnvelope.
func (vs *Server) storeExecutionPayloadEnvelope(
ctx context.Context,
sBlk interfaces.SignedBeaconBlock,
local *consensusblocks.GetPayloadResponse,
postBlockState state.BeaconState,
) error {
blockRoot, err := sBlk.Block().HashTreeRoot()
if err != nil {
@@ -45,7 +47,26 @@ func (vs *Server) storeExecutionPayloadEnvelope(
BuilderIndex: params.BeaconConfig().BuilderIndexSelfBuild,
BeaconBlockRoot: blockRoot[:],
Slot: sBlk.Block().Slot(),
StateRoot: make([]byte, 32), // zeroed; computed lazily in GetExecutionPayloadEnvelope
StateRoot: make([]byte, 32),
}
// When postBlockState is provided, eagerly compute the post-payload state
// root so the envelope is immediately usable by ProduceBlockV4.
// Otherwise, leave the state root zeroed for lazy computation later.
if postBlockState != nil {
stateCopy := postBlockState.Copy()
roEnvelope, err := consensusblocks.WrappedROExecutionPayloadEnvelope(envelope)
if err != nil {
return errors.Wrap(err, "could not wrap envelope")
}
if err := coregloas.ApplyExecutionPayload(ctx, stateCopy, roEnvelope); err != nil {
return errors.Wrap(err, "could not apply execution payload for envelope state root")
}
stateRoot, err := stateCopy.HashTreeRoot(ctx)
if err != nil {
return errors.Wrap(err, "could not compute post-payload state root")
}
envelope.StateRoot = stateRoot[:]
}
// Precompute data column sidecars now (inside ProposeBeaconBlock) so the

View File

@@ -17,6 +17,62 @@ import (
"github.com/OffchainLabs/prysm/v7/testing/util"
)
func testGloasBlock(t *testing.T) (*consensusblocks.GetPayloadResponse, interfaces.SignedBeaconBlock) {
t.Helper()
payload := &enginev1.ExecutionPayloadDeneb{
ParentHash: make([]byte, 32),
FeeRecipient: make([]byte, 20),
StateRoot: make([]byte, 32),
ReceiptsRoot: make([]byte, 32),
LogsBloom: make([]byte, 256),
PrevRandao: make([]byte, 32),
BaseFeePerGas: make([]byte, 32),
BlockHash: make([]byte, 32),
ExtraData: make([]byte, 0),
}
ed, err := consensusblocks.WrappedExecutionPayloadDeneb(payload)
require.NoError(t, err)
local := &consensusblocks.GetPayloadResponse{
ExecutionData: ed,
Bid: big.NewInt(0),
ExecutionRequests: &enginev1.ExecutionRequests{},
}
sBlk, err := consensusblocks.NewSignedBeaconBlock(util.NewBeaconBlockGloas())
require.NoError(t, err)
return local, sBlk
}
func TestStoreExecutionPayloadEnvelope_NilState(t *testing.T) {
local, sBlk := testGloasBlock(t)
vs := &Server{}
err := vs.storeExecutionPayloadEnvelope(t.Context(), sBlk, local, nil)
require.NoError(t, err)
envelope, found := vs.getExecutionPayloadEnvelope(sBlk.Block().Slot())
require.Equal(t, true, found)
require.NotNil(t, envelope.Payload)
require.Equal(t, sBlk.Block().Slot(), envelope.Slot)
require.DeepEqual(t, make([]byte, 32), envelope.StateRoot)
}
func TestStoreExecutionPayloadEnvelope_WithState(t *testing.T) {
local, sBlk := testGloasBlock(t)
vs := &Server{}
st, err := util.NewBeaconStateGloas()
require.NoError(t, err)
// The eager path is entered but ApplyExecutionPayload will fail because
// the default test state doesn't satisfy all transition checks.
err = vs.storeExecutionPayloadEnvelope(t.Context(), sBlk, local, st)
require.ErrorContains(t, "could not apply execution payload for envelope state root", err)
}
func TestExtractExecutionPayloadDeneb(t *testing.T) {
payload := &enginev1.ExecutionPayloadDeneb{
ParentHash: make([]byte, 32),

View File

@@ -49,15 +49,26 @@ func (vs *Server) SubmitSignedProposerPreferences(
)
}
if slots.ToEpoch(proposalSlot) != currentEpoch+1 {
proposalEpoch := slots.ToEpoch(proposalSlot)
if proposalEpoch < currentEpoch || proposalEpoch > currentEpoch.Add(1) {
return nil, status.Errorf(
codes.InvalidArgument,
"signed proposer preferences proposal slot must be in the next epoch: slot %d currentEpoch %d",
"signed proposer preferences proposal slot must be in the current or next epoch: slot %d currentEpoch %d",
proposalSlot,
currentEpoch,
)
}
currentSlot := vs.TimeFetcher.CurrentSlot()
if proposalSlot <= currentSlot {
return nil, status.Errorf(
codes.InvalidArgument,
"signed proposer preferences proposal slot has already passed: proposalSlot %d currentSlot %d",
proposalSlot,
currentSlot,
)
}
if vs.ProposerPreferencesCache.Has(proposalSlot) {
duplicate++
continue

View File

@@ -162,7 +162,7 @@ func TestSubmitSignedProposerPreferences_InvalidEpoch(t *testing.T) {
ProposerPreferencesCache: cache.NewProposerPreferencesCache(),
}
// Same epoch (current), not next epoch.
// Current slot (already passed) should fail.
req := &ethpb.SubmitSignedProposerPreferencesRequest{
SignedProposerPreferences: []*ethpb.SignedProposerPreferences{
{
@@ -177,12 +177,50 @@ func TestSubmitSignedProposerPreferences_InvalidEpoch(t *testing.T) {
},
}
_, err := vs.SubmitSignedProposerPreferences(t.Context(), req)
require.ErrorContains(t, "next epoch", err)
require.ErrorContains(t, "already passed", err)
// Two epochs ahead.
// Two epochs ahead should fail.
req.SignedProposerPreferences[0].Message.ProposalSlot = currentSlot + primitives.Slot(2*params.BeaconConfig().SlotsPerEpoch)
_, err = vs.SubmitSignedProposerPreferences(t.Context(), req)
require.ErrorContains(t, "next epoch", err)
require.ErrorContains(t, "current or next epoch", err)
}
func TestSubmitSignedProposerPreferences_CurrentEpochFutureSlot(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
params.OverrideBeaconConfig(cfg)
currentSlot := primitives.Slot(33)
proposalSlot := currentSlot + 1 // future slot in current epoch
chain := &chainMock.ChainService{Slot: &currentSlot}
p2p := &p2pmock.MockBroadcaster{}
cache := cache.NewProposerPreferencesCache()
vs := &Server{
SyncChecker: &mockSync.Sync{IsSyncing: false},
TimeFetcher: chain,
P2P: p2p,
ProposerPreferencesCache: cache,
}
req := &ethpb.SubmitSignedProposerPreferencesRequest{
SignedProposerPreferences: []*ethpb.SignedProposerPreferences{
{
Message: &ethpb.ProposerPreferences{
ProposalSlot: proposalSlot,
ValidatorIndex: 2,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
},
Signature: make([]byte, 96),
},
},
}
resp, err := vs.SubmitSignedProposerPreferences(t.Context(), req)
require.NoError(t, err)
require.DeepEqual(t, &emptypb.Empty{}, resp)
assert.Equal(t, true, p2p.BroadcastCalled.Load())
}
func TestSubmitSignedProposerPreferences_Syncing(t *testing.T) {

View File

@@ -95,7 +95,7 @@ func TestServer_GetBeaconBlock_Phase0(t *testing.T) {
proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:])
// Use a separate mock for BlockReceiver with an independent state copy.
// This mirrors production where computeStateRoot calls StateByRoot (fresh from DB),
// This mirrors production where computePostBlockStateAndRoot calls StateByRoot (fresh from DB),
// not the same head state object mutated by the getSlashings goroutine.
proposerServer.BlockReceiver = &mock.ChainService{
State: beaconState.Copy(),
@@ -1354,12 +1354,12 @@ func TestProposer_ComputeStateRoot_OK(t *testing.T) {
wsb, err := blocks.NewSignedBeaconBlock(req)
require.NoError(t, err)
_, err = proposerServer.computeStateRoot(t.Context(), wsb)
_, _, err = proposerServer.computePostBlockStateAndRoot(t.Context(), wsb)
require.NoError(t, err)
}
func TestHandleStateRootError_MaxAttemptsReached(t *testing.T) {
// Test that handleStateRootError returns an error when max attempts is reached
func TestHandlePostBlockStateError_MaxAttemptsReached(t *testing.T) {
// Test that handlePostBlockStateError returns an error when max attempts is reached
// instead of recursing infinitely.
ctx := t.Context()
vs := &Server{}
@@ -1372,15 +1372,15 @@ func TestHandleStateRootError_MaxAttemptsReached(t *testing.T) {
// Pre-seed the context with max attempts already reached
ctx = context.WithValue(ctx, computeStateRootAttemptsKey, maxComputeStateRootAttempts)
// Call handleStateRootError with a retryable error
_, err = vs.handleStateRootError(ctx, wsb, transition.ErrAttestationsSignatureInvalid)
// Call handlePostBlockStateError with a retryable error
_, err = vs.handlePostBlockStateError(ctx, wsb, transition.ErrAttestationsSignatureInvalid)
// Should return an error about max attempts instead of recursing
require.ErrorContains(t, "attempted max compute state root attempts", err)
}
func TestHandleStateRootError_IncrementsAttempts(t *testing.T) {
// Test that handleStateRootError properly increments the attempts counter
func TestHandlePostBlockStateError_IncrementsAttempts(t *testing.T) {
// Test that handlePostBlockStateError properly increments the attempts counter
// and eventually fails after max attempts.
db := dbutil.SetupDB(t)
ctx := t.Context()
@@ -1403,10 +1403,10 @@ func TestHandleStateRootError_IncrementsAttempts(t *testing.T) {
// Add a state for the parent root so StateByRoot succeeds
require.NoError(t, stateGen.SaveState(ctx, parentRoot, beaconState))
// Call handleStateRootError with a retryable error - it will recurse
// but eventually hit the max attempts limit since CalculateStateRoot
// Call handlePostBlockStateError with a retryable error - it will recurse
// but eventually hit the max attempts limit since CalculatePostState
// will keep failing (no valid attestations, randao, etc.)
_, err = vs.handleStateRootError(ctx, wsb, transition.ErrAttestationsSignatureInvalid)
_, err = vs.handlePostBlockStateError(ctx, wsb, transition.ErrAttestationsSignatureInvalid)
// Should eventually fail - either with max attempts or another error
require.NotNil(t, err)

View File

@@ -22,6 +22,7 @@ func NewRegularSyncFuzz(opts ...Option) *Service {
slotToPendingBlocks: gcache.New(time.Second, 2*time.Second),
seenPendingBlocks: make(map[[32]byte]bool),
blkRootToPendingAtts: make(map[[32]byte][]any),
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
r.rateLimiter = newRateLimiter(r.cfg.p2p)

View File

@@ -160,6 +160,7 @@ func (s *Service) processPendingBlocks(ctx context.Context) error {
// Process synchronously because it's likely that the next pending block depends on it.
s.processPendingPayloadEnvelope(ctx, blkRoot)
s.processPendingGloasColumns(blkRoot, b)
blkRoots = append(blkRoots, blkRoot)
// Remove the processed block from the queue.

View File

@@ -97,6 +97,11 @@ func (s *Service) requestAndSaveMissingDataColumnSidecars(blks []blocks.ROBlock)
return nil
}
// Process any gossip columns queued before the block arrived.
for _, blk := range blks {
s.processPendingGloasColumns(blk.Root(), blk)
}
samplesPerSlot := params.BeaconConfig().SamplesPerSlot
custodyGroupCount, err := s.cfg.p2p.CustodyGroupCount(s.ctx)

View File

@@ -160,6 +160,8 @@ type Service struct {
seenBlobLock sync.RWMutex
seenBlobCache *lru.Cache
seenDataColumnCache *slotAwareCache
pendingGloasColumnsLock sync.RWMutex
pendingGloasColumns map[[32]byte]*pendingGloasEntry
seenAggregatedAttestationLock sync.RWMutex
seenAggregatedAttestationCache *lru.Cache
seenUnAggregatedAttestationLock sync.RWMutex
@@ -219,6 +221,7 @@ func NewService(ctx context.Context, opts ...Option) *Service {
slotToPendingBlocks: gcache.New(pendingBlockExpTime /* exp time */, 0 /* disable janitor */),
seenPendingBlocks: make(map[[32]byte]bool),
blkRootToPendingAtts: make(map[[32]byte][]any),
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
dataColumnLogCh: make(chan dataColumnLogEntry, 1000),
reconstructionRandGen: rand.NewGenerator(),
payloadAttestationCache: &cache.PayloadAttestationCache{},
@@ -329,6 +332,8 @@ func (s *Service) Start() {
// Prune data column cache periodically on finalization.
async.RunEvery(s.ctx, 30*time.Second, s.pruneDataColumnCache)
go s.prunePendingGloasColumns()
if !params.FuluEnabled() {
return
}

View File

@@ -83,6 +83,8 @@ func (s *Service) beaconBlockSubscriber(ctx context.Context, msg proto.Message)
go s.processPendingPayloadEnvelope(s.ctx, root)
s.processPendingGloasColumns(root, signed)
return nil
}

View File

@@ -2,17 +2,39 @@ package sync
import (
"context"
"fmt"
"strings"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/logging"
"github.com/OffchainLabs/prysm/v7/time/slots"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// maxPendingGloasRoots caps the number of distinct block roots in the pending queue.
const maxPendingGloasRoots = 8
type pendingColumnEntry struct {
sidecar *ethpb.DataColumnSidecarGloas
peer peer.ID
}
type pendingGloasEntry struct {
slot primitives.Slot
columns [fieldparams.NumberOfColumns]*pendingColumnEntry
}
func (s *Service) validateDataColumnGloas(
ctx context.Context,
msg *pubsub.Message,
@@ -26,8 +48,12 @@ func (s *Service) validateDataColumnGloas(
// If not yet seen, a client MUST queue the sidecar for deferred validation and possible processing once
// the block is received or retrieved.
if s.cfg.chain == nil || !s.cfg.chain.HasBlock(ctx, roDataColumn.BlockRoot()) {
// TODO: Queue the sidecar for deferred validation and possible processing once the
// block is received or retrieved
actualSubnet := peerdas.ComputeSubnetForDataColumnSidecar(roDataColumn.Index())
expectedSubTopic := fmt.Sprintf(dataColumnSidecarSubTopic, actualSubnet)
if msg.Topic == nil || !strings.Contains(*msg.Topic+"/", expectedSubTopic) {
return blocks.VerifiedRODataColumn{}, errors.New("gloas data column on wrong subnet")
}
s.queuePendingGloasColumn(roDataColumn, peer.ID(msg.GetFrom()))
return blocks.VerifiedRODataColumn{}, ignoreValidation(errors.New("gloas data column block not yet seen"))
}
@@ -97,6 +123,160 @@ func (s *Service) setSeenDataColumnRootIndex(root [fieldparams.RootLength]byte,
s.seenDataColumnCache.Add(slot, key, true)
}
func (s *Service) queuePendingGloasColumn(roCol blocks.RODataColumn, pid peer.ID) {
dc := roCol.DataColumnSidecarGloas()
if dc == nil {
return
}
idx := roCol.Index()
if idx >= fieldparams.NumberOfColumns {
return
}
root := roCol.BlockRoot()
slot := roCol.Slot()
s.pendingGloasColumnsLock.Lock()
defer s.pendingGloasColumnsLock.Unlock()
entry := s.pendingGloasColumns[root]
if entry == nil {
if len(s.pendingGloasColumns) >= maxPendingGloasRoots {
return
}
entry = &pendingGloasEntry{slot: slot}
s.pendingGloasColumns[root] = entry
}
if entry.columns[idx] != nil {
return
}
entry.columns[idx] = &pendingColumnEntry{sidecar: dc, peer: pid}
}
func (s *Service) processPendingGloasColumns(root [fieldparams.RootLength]byte, blk interfaces.ReadOnlySignedBeaconBlock) {
if blk == nil || blk.IsNil() {
return
}
s.pendingGloasColumnsLock.Lock()
entry := s.pendingGloasColumns[root]
delete(s.pendingGloasColumns, root)
s.pendingGloasColumnsLock.Unlock()
if entry == nil {
return
}
commitments, err := blk.Block().Body().BlobKzgCommitments()
if err != nil {
log.WithError(err).WithField("root", fmt.Sprintf("%#x", root)).Warn("Failed to get bid commitments for pending Gloas columns")
return
}
// Count pending sidecars for pre-allocation.
count := 0
for _, pe := range entry.columns {
if pe != nil {
count++
}
}
verified := make([]blocks.VerifiedRODataColumn, 0, count)
var skipped int
badPeers := make(map[peer.ID]bool)
for _, pe := range entry.columns {
if pe == nil {
continue
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(pe.sidecar, root)
if err != nil {
log.WithError(err).WithField("root", fmt.Sprintf("%#x", root)).Error("Failed to wrap pending Gloas column")
skipped++
continue
}
roCol.SetBidCommitments(commitments)
if s.hasSeenDataColumnRootIndex(root, roCol.Index()) {
continue
}
verifier := verification.NewGloasDataColumnVerifier(roCol, blk.Block(), verification.PendingGloasColumnRequirements)
if err := verifier.VerifyDataColumnSidecarSlotMatchesBlockGloas(); err != nil {
skipped++
badPeers[pe.peer] = true
continue
}
if err := verifier.VerifyDataColumnSidecarGloas(); err != nil {
skipped++
badPeers[pe.peer] = true
continue
}
if err := verifier.VerifyDataColumnSidecarKzgProofsGloas(); err != nil {
skipped++
badPeers[pe.peer] = true
continue
}
v, err := verifier.VerifiedRODataColumn()
if err != nil {
log.WithError(err).WithField("root", fmt.Sprintf("%#x", root)).Error("Failed to get verified pending Gloas column")
skipped++
continue
}
v.SetBidCommitments(commitments)
s.setSeenDataColumnRootIndex(root, v.Index(), v.Slot())
verified = append(verified, v)
}
for pid := range badPeers {
s.cfg.p2p.Peers().Scorers().BadResponsesScorer().Increment(pid)
}
if len(verified) > 0 {
if err := s.cfg.dataColumnStorage.Save(verified); err != nil {
log.WithError(err).WithField("root", fmt.Sprintf("%#x", root)).Warn("Failed to save pending Gloas columns")
return
}
log.WithFields(logrus.Fields{
"root": fmt.Sprintf("%#x", root),
"count": len(verified),
"skipped": skipped,
"slot": entry.slot,
}).Debug("Processed pending Gloas data columns")
}
}
func (s *Service) hasPendingGloasColumns(root [fieldparams.RootLength]byte) bool {
s.pendingGloasColumnsLock.RLock()
defer s.pendingGloasColumnsLock.RUnlock()
_, ok := s.pendingGloasColumns[root]
return ok
}
// prunePendingGloasColumns removes stale entries every slot.
func (s *Service) prunePendingGloasColumns() {
slotTicker := slots.NewSlotTicker(s.cfg.clock.GenesisTime(), params.BeaconConfig().SecondsPerSlot)
defer slotTicker.Done()
for {
select {
case currentSlot := <-slotTicker.C():
s.pendingGloasColumnsLock.Lock()
for r, e := range s.pendingGloasColumns {
if e.slot+1 < currentSlot {
delete(s.pendingGloasColumns, r)
}
}
s.pendingGloasColumnsLock.Unlock()
case <-s.ctx.Done():
return
}
}
}
func computeRootIndexCacheKey(root [fieldparams.RootLength]byte, index uint64) string {
key := make([]byte, 0, fieldparams.RootLength+32)
key = append(key, root[:]...)

View File

@@ -10,12 +10,14 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/filesystem"
dbtest "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
p2ptest "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
@@ -25,9 +27,48 @@ import (
"github.com/OffchainLabs/prysm/v7/testing/util"
pubsub "github.com/libp2p/go-libp2p-pubsub"
pb "github.com/libp2p/go-libp2p-pubsub/pb"
"github.com/libp2p/go-libp2p/core/peer"
ssz "github.com/prysmaticlabs/fastssz"
)
func gloasFixture(t *testing.T) (*ethpb.DataColumnSidecarGloas, interfaces.ReadOnlySignedBeaconBlock) {
t.Helper()
roBlock, roSidecars, _ := util.GenerateTestFuluBlockWithSidecars(t, 1, util.WithSlot(1))
require.Equal(t, true, len(roSidecars) > 0)
base := roSidecars[0]
bid := util.GenerateTestSignedExecutionPayloadBid(base.Slot())
comms, err := roBlock.Block().Body().BlobKzgCommitments()
require.NoError(t, err)
bid.Message.BlobKzgCommitments = bytesutil.SafeCopy2dBytes(comms)
pb := util.NewBeaconBlockGloas()
pb.Block.Slot = base.Slot()
pb.Block.ProposerIndex = roBlock.Block().ProposerIndex()
parentRoot := roBlock.Block().ParentRoot()
pb.Block.ParentRoot = parentRoot[:]
stateRoot := roBlock.Block().StateRoot()
pb.Block.StateRoot = stateRoot[:]
pb.Block.Body.SignedExecutionPayloadBid = bid
signedBlock, err := blocks.NewSignedBeaconBlock(pb)
require.NoError(t, err)
blockRoot, err := signedBlock.Block().HashTreeRoot()
require.NoError(t, err)
sidecar := &ethpb.DataColumnSidecarGloas{
Index: base.Index(),
Column: bytesutil.SafeCopy2dBytes(base.Column()),
KzgProofs: bytesutil.SafeCopy2dBytes(base.KzgProofs()),
Slot: base.Slot(),
BeaconBlockRoot: blockRoot[:],
}
return sidecar, signedBlock
}
func TestValidateDataColumnGloas(t *testing.T) {
err := kzg.Start()
require.NoError(t, err)
@@ -50,6 +91,7 @@ func TestValidateDataColumnGloas(t *testing.T) {
ctx: ctx,
newColumnsVerifier: newDataColumnsVerifier,
seenDataColumnCache: newSlotAwareCache(seenDataColumnSize),
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
buf := new(bytes.Buffer)
@@ -67,44 +109,6 @@ func TestValidateDataColumnGloas(t *testing.T) {
return service, message
}
gloasFixture := func(t *testing.T) (*ethpb.DataColumnSidecarGloas, interfaces.ReadOnlySignedBeaconBlock) {
t.Helper()
roBlock, roSidecars, _ := util.GenerateTestFuluBlockWithSidecars(t, 1, util.WithSlot(1))
require.Equal(t, true, len(roSidecars) > 0)
base := roSidecars[0]
bid := util.GenerateTestSignedExecutionPayloadBid(base.Slot())
comms, err := roBlock.Block().Body().BlobKzgCommitments()
require.NoError(t, err)
bid.Message.BlobKzgCommitments = bytesutil.SafeCopy2dBytes(comms)
pb := util.NewBeaconBlockGloas()
pb.Block.Slot = base.Slot()
pb.Block.ProposerIndex = roBlock.Block().ProposerIndex()
parentRoot := roBlock.Block().ParentRoot()
pb.Block.ParentRoot = parentRoot[:]
stateRoot := roBlock.Block().StateRoot()
pb.Block.StateRoot = stateRoot[:]
pb.Block.Body.SignedExecutionPayloadBid = bid
signedBlock, err := blocks.NewSignedBeaconBlock(pb)
require.NoError(t, err)
blockRoot, err := signedBlock.Block().HashTreeRoot()
require.NoError(t, err)
sidecar := &ethpb.DataColumnSidecarGloas{
Index: base.Index(),
Column: bytesutil.SafeCopy2dBytes(base.Column()),
KzgProofs: bytesutil.SafeCopy2dBytes(base.KzgProofs()),
Slot: base.Slot(),
BeaconBlockRoot: blockRoot[:],
}
return sidecar, signedBlock
}
t.Run("ignores unseen block", func(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
@@ -186,3 +190,268 @@ func TestValidateDataColumnGloas(t *testing.T) {
require.ErrorContains(t, "slot does not match block slot", err)
})
}
func TestPendingGloasColumns(t *testing.T) {
clock := startup.NewClock(time.Now(), [32]byte{})
t.Run("queue and retrieve", func(t *testing.T) {
s := &Service{
cfg: &config{clock: clock},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
root := [32]byte{0xaa}
dc := &ethpb.DataColumnSidecarGloas{
Index: 5,
Slot: clock.CurrentSlot(),
BeaconBlockRoot: root[:],
Column: [][]byte{make([]byte, 2048)},
KzgProofs: [][]byte{make([]byte, 48)},
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(dc, root)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "peer1")
require.Equal(t, true, s.hasPendingGloasColumns(root))
entry := s.pendingGloasColumns[root]
require.NotNil(t, entry)
require.NotNil(t, entry.columns[5])
require.Equal(t, peer.ID("peer1"), entry.columns[5].peer)
})
t.Run("dedup by index", func(t *testing.T) {
s := &Service{
cfg: &config{clock: clock},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
root := [32]byte{0xbb}
dc := &ethpb.DataColumnSidecarGloas{
Index: 10,
Slot: clock.CurrentSlot(),
BeaconBlockRoot: root[:],
Column: [][]byte{make([]byte, 2048)},
KzgProofs: [][]byte{make([]byte, 48)},
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(dc, root)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "peer1")
s.queuePendingGloasColumn(roCol, "peer2")
require.Equal(t, peer.ID("peer1"), s.pendingGloasColumns[root].columns[10].peer)
})
t.Run("nil block is no-op", func(t *testing.T) {
s := &Service{
cfg: &config{clock: clock},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
root := [32]byte{0xcc}
s.pendingGloasColumns[root] = &pendingGloasEntry{slot: clock.CurrentSlot()}
s.processPendingGloasColumns(root, nil)
// Entry should remain because the block was nil.
require.Equal(t, true, s.hasPendingGloasColumns(root))
})
t.Run("index out of bounds rejected", func(t *testing.T) {
s := &Service{
cfg: &config{clock: clock},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
root := [32]byte{0xee}
dc := &ethpb.DataColumnSidecarGloas{
Index: fieldparams.NumberOfColumns + 1,
Slot: clock.CurrentSlot(),
BeaconBlockRoot: root[:],
Column: [][]byte{make([]byte, 2048)},
KzgProofs: [][]byte{make([]byte, 48)},
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(dc, root)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "peer1")
require.Equal(t, false, s.hasPendingGloasColumns(root))
})
t.Run("map capped at maxPendingGloasRoots", func(t *testing.T) {
s := &Service{
cfg: &config{clock: clock},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
// Fill up to the cap.
for i := range maxPendingGloasRoots {
root := [32]byte{byte(i)}
dc := &ethpb.DataColumnSidecarGloas{
Index: 0,
Slot: clock.CurrentSlot(),
BeaconBlockRoot: root[:],
Column: [][]byte{make([]byte, 2048)},
KzgProofs: [][]byte{make([]byte, 48)},
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(dc, root)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "peer1")
}
require.Equal(t, maxPendingGloasRoots, len(s.pendingGloasColumns))
// One more should be dropped.
overflowRoot := [32]byte{0xff}
dc := &ethpb.DataColumnSidecarGloas{
Index: 0,
Slot: clock.CurrentSlot(),
BeaconBlockRoot: overflowRoot[:],
Column: [][]byte{make([]byte, 2048)},
KzgProofs: [][]byte{make([]byte, 48)},
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(dc, overflowRoot)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "peer1")
require.Equal(t, false, s.hasPendingGloasColumns(overflowRoot))
// Adding to an existing root should still work.
existingRoot := [32]byte{0x00}
dc2 := &ethpb.DataColumnSidecarGloas{
Index: 1,
Slot: clock.CurrentSlot(),
BeaconBlockRoot: existingRoot[:],
Column: [][]byte{make([]byte, 2048)},
KzgProofs: [][]byte{make([]byte, 48)},
}
roCol2, err := blocks.NewRODataColumnGloasWithRoot(dc2, existingRoot)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol2, "peer1")
require.NotNil(t, s.pendingGloasColumns[existingRoot].columns[1])
})
t.Run("process verifies and saves valid columns", func(t *testing.T) {
err := kzg.Start()
require.NoError(t, err)
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.FuluForkEpoch = 0
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
p := p2ptest.NewTestP2P(t)
dcs := filesystem.NewEphemeralDataColumnStorage(t)
sidecar, signedBlock := gloasFixture(t)
blockRoot, err := signedBlock.Block().HashTreeRoot()
require.NoError(t, err)
s := &Service{
cfg: &config{
p2p: p,
clock: clock,
dataColumnStorage: dcs,
},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
seenDataColumnCache: newSlotAwareCache(seenDataColumnSize),
}
// Queue the sidecar.
roCol, err := blocks.NewRODataColumnGloasWithRoot(sidecar, blockRoot)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "peer1")
require.Equal(t, true, s.hasPendingGloasColumns(blockRoot))
// Process with the block.
s.processPendingGloasColumns(blockRoot, signedBlock)
require.Equal(t, false, s.hasPendingGloasColumns(blockRoot))
// Column should be marked as seen.
require.Equal(t, true, s.hasSeenDataColumnRootIndex(blockRoot, sidecar.Index))
})
t.Run("process downscores bad peer for slot mismatch", func(t *testing.T) {
err := kzg.Start()
require.NoError(t, err)
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig()
cfg.FuluForkEpoch = 0
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
p := p2ptest.NewTestP2P(t)
dcs := filesystem.NewEphemeralDataColumnStorage(t)
sidecar, signedBlock := gloasFixture(t)
blockRoot, err := signedBlock.Block().HashTreeRoot()
require.NoError(t, err)
// Mismatch the slot.
sidecar.Slot = sidecar.Slot + 10
s := &Service{
cfg: &config{
p2p: p,
clock: clock,
dataColumnStorage: dcs,
},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
seenDataColumnCache: newSlotAwareCache(seenDataColumnSize),
}
roCol, err := blocks.NewRODataColumnGloasWithRoot(sidecar, blockRoot)
require.NoError(t, err)
s.queuePendingGloasColumn(roCol, "badpeer")
s.processPendingGloasColumns(blockRoot, signedBlock)
require.Equal(t, false, s.hasPendingGloasColumns(blockRoot))
// Column should NOT be marked as seen (it was invalid).
require.Equal(t, false, s.hasSeenDataColumnRootIndex(blockRoot, sidecar.Index))
})
t.Run("no entry is no-op", func(t *testing.T) {
p := p2ptest.NewTestP2P(t)
s := &Service{
cfg: &config{
p2p: p,
clock: clock,
},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
seenDataColumnCache: newSlotAwareCache(seenDataColumnSize),
}
root := [32]byte{0xdd}
pb := util.NewBeaconBlockGloas()
blk, err := blocks.NewSignedBeaconBlock(pb)
require.NoError(t, err)
// Should not panic.
s.processPendingGloasColumns(root, blk)
})
t.Run("prune keeps current and next slot", func(t *testing.T) {
s := &Service{
cfg: &config{clock: clock},
pendingGloasColumns: make(map[[32]byte]*pendingGloasEntry),
}
currentSlot := clock.CurrentSlot()
if currentSlot < 3 {
t.Skip("need slot >= 3")
}
staleRoot := [32]byte{0x01}
currentRoot := [32]byte{0x02}
prevRoot := [32]byte{0x03}
s.pendingGloasColumns[staleRoot] = &pendingGloasEntry{slot: currentSlot - 3}
s.pendingGloasColumns[currentRoot] = &pendingGloasEntry{slot: currentSlot}
s.pendingGloasColumns[prevRoot] = &pendingGloasEntry{slot: currentSlot - 1}
// Simulate what the ticker does.
s.pendingGloasColumnsLock.Lock()
for r, e := range s.pendingGloasColumns {
if e.slot+1 < currentSlot {
delete(s.pendingGloasColumns, r)
}
}
s.pendingGloasColumnsLock.Unlock()
// Stale should be pruned, current and prev should remain.
require.Equal(t, false, s.hasPendingGloasColumns(staleRoot))
require.Equal(t, true, s.hasPendingGloasColumns(currentRoot))
require.Equal(t, true, s.hasPendingGloasColumns(prevRoot))
})
}

View File

@@ -3,6 +3,7 @@ package sync
import (
"context"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
@@ -44,14 +45,26 @@ func (s *Service) validateSignedProposerPreferencesGossip(ctx context.Context, p
if err != nil {
return pubsub.ValidationIgnore, err
}
headRoot, err := s.cfg.chain.HeadRoot(ctx)
if err != nil {
return pubsub.ValidationIgnore, err
}
// HeadStateReadOnly returns the current head state as stored, which may still
// be on the previous slot or epoch until the next block arrives. Advance it to
// the wall-clock slot so current-epoch proposer preferences are validated
// against the same epoch/ProposerLookahead view used elsewhere.
st, err = transition.ProcessSlotsIfNeeded(ctx, st, headRoot, s.cfg.clock.CurrentSlot())
if err != nil {
return pubsub.ValidationIgnore, err
}
v := s.newSignedProposerPreferencesVerifier(signedPreferences, verification.SignedProposerPreferencesGossipRequirements)
// [IGNORE] preferences.proposal_slot is in the next epoch.
if err := v.VerifyNextEpoch(st); err != nil {
// [IGNORE] preferences.proposal_slot is in the current or next epoch and has not already passed.
if err := v.VerifyCurrentOrNextEpoch(st); err != nil {
return pubsub.ValidationIgnore, err
}
// [REJECT] preferences.validator_index is present at the correct slot in the
// next epoch's portion of state.proposer_lookahead.
// current or next epoch's portion of state.proposer_lookahead.
if err := v.VerifyValidProposalSlot(st); err != nil {
return pubsub.ValidationReject, err
}

View File

@@ -58,8 +58,8 @@ func TestValidateSignedProposerPreferencesGossip_ErrorPathsWithMock(t *testing.T
wantError bool
}{
{
name: "not next epoch",
verifier: mockSignedProposerPreferencesVerifier{errNextEpoch: errors.New("wrong epoch")},
name: "not current or next epoch",
verifier: mockSignedProposerPreferencesVerifier{errCurrentOrNextEpoch: errors.New("wrong epoch")},
result: pubsub.ValidationIgnore,
wantError: true,
},
@@ -134,22 +134,26 @@ func TestSignedProposerPreferencesSubscriber_HappyPath(t *testing.T) {
}
type mockSignedProposerPreferencesVerifier struct {
errNextEpoch error
errValidProposalSlot error
errSignature error
errCurrentOrNextEpoch error
errValidProposalSlot error
errSignature error
lastStateSlot primitives.Slot
}
var _ verification.SignedProposerPreferencesVerifier = &mockSignedProposerPreferencesVerifier{}
func (m *mockSignedProposerPreferencesVerifier) VerifyNextEpoch(state.ReadOnlyBeaconState) error {
return m.errNextEpoch
func (m *mockSignedProposerPreferencesVerifier) VerifyCurrentOrNextEpoch(st state.ReadOnlyBeaconState) error {
m.lastStateSlot = st.Slot()
return m.errCurrentOrNextEpoch
}
func (m *mockSignedProposerPreferencesVerifier) VerifyValidProposalSlot(state.ReadOnlyBeaconState) error {
func (m *mockSignedProposerPreferencesVerifier) VerifyValidProposalSlot(st state.ReadOnlyBeaconState) error {
m.lastStateSlot = st.Slot()
return m.errValidProposalSlot
}
func (m *mockSignedProposerPreferencesVerifier) VerifySignature(state.ReadOnlyBeaconState) error {
func (m *mockSignedProposerPreferencesVerifier) VerifySignature(st state.ReadOnlyBeaconState) error {
m.lastStateSlot = st.Slot()
return m.errSignature
}
@@ -162,6 +166,36 @@ func testNewSignedProposerPreferencesVerifier(m mockSignedProposerPreferencesVer
}
}
func testNewSignedProposerPreferencesVerifierCapture(slot *primitives.Slot) verification.NewSignedProposerPreferencesVerifier {
return func(*ethpb.SignedProposerPreferences, []verification.Requirement) verification.SignedProposerPreferencesVerifier {
return &mockSignedProposerPreferencesVerifierWithCapture{slot: slot}
}
}
type mockSignedProposerPreferencesVerifierWithCapture struct {
slot *primitives.Slot
}
var _ verification.SignedProposerPreferencesVerifier = &mockSignedProposerPreferencesVerifierWithCapture{}
func (m *mockSignedProposerPreferencesVerifierWithCapture) VerifyCurrentOrNextEpoch(st state.ReadOnlyBeaconState) error {
*m.slot = st.Slot()
return nil
}
func (m *mockSignedProposerPreferencesVerifierWithCapture) VerifyValidProposalSlot(st state.ReadOnlyBeaconState) error {
*m.slot = st.Slot()
return nil
}
func (m *mockSignedProposerPreferencesVerifierWithCapture) VerifySignature(st state.ReadOnlyBeaconState) error {
*m.slot = st.Slot()
return nil
}
func (*mockSignedProposerPreferencesVerifierWithCapture) SatisfyRequirement(verification.Requirement) {
}
func setupSignedProposerPreferencesService(t *testing.T) (*Service, *pubsub.Message, *ethpb.SignedProposerPreferences) {
t.Helper()
@@ -194,6 +228,28 @@ func setupSignedProposerPreferencesService(t *testing.T) (*Service, *pubsub.Mess
return s, msg, signedPreferences
}
func TestValidateSignedProposerPreferencesGossip_AdvancesHeadStateToCurrentSlot(t *testing.T) {
ctx := context.Background()
s, _, signedPreferences := setupSignedProposerPreferencesService(t)
chainService, ok := s.cfg.chain.(*mock.ChainService)
require.Equal(t, true, ok)
st, _ := util.DeterministicGenesisStateFulu(t, 64)
require.NoError(t, st.SetSlot(31))
chainService.State = st
currentSlot := primitives.Slot(32)
s.cfg.clock = startup.NewClock(s.cfg.chain.GenesisTime(), s.cfg.chain.GenesisValidatorsRoot(), startup.WithSlotAsNow(currentSlot))
msg := signedProposerPreferencesToPubsub(t, s, s.cfg.p2p, signedPreferences)
var verifiedSlot primitives.Slot
s.newSignedProposerPreferencesVerifier = testNewSignedProposerPreferencesVerifierCapture(&verifiedSlot)
result, err := s.validateSignedProposerPreferencesGossip(ctx, "", msg)
require.NoError(t, err)
require.Equal(t, pubsub.ValidationAccept, result)
require.Equal(t, currentSlot, verifiedSlot)
}
func signedProposerPreferencesToPubsub(t *testing.T, s *Service, p p2p.P2P, preferences *ethpb.SignedProposerPreferences) *pubsub.Message {
t.Helper()

View File

@@ -21,6 +21,13 @@ var GossipDataColumnSidecarRequirementsGloas = []Requirement{
RequireNotSeenGloas,
}
// PendingGloasColumnRequirements defines the requirements for columns queued before their block arrived.
var PendingGloasColumnRequirements = []Requirement{
RequireSlotMatchesBlockGloas,
RequireValidFieldsGloas,
RequireSidecarKzgProofVerifiedGloas,
}
type ROGloasDataColumnVerifier struct {
sidecar blocks.RODataColumn
block interfaces.ReadOnlyBeaconBlock

View File

@@ -85,7 +85,7 @@ type NewPayloadAttestationMsgVerifier func(pa payloadattestation.ROMessage, reqs
// SignedProposerPreferencesVerifier defines the methods implemented by the signed proposer preferences verifier.
type SignedProposerPreferencesVerifier interface {
VerifyNextEpoch(state.ReadOnlyBeaconState) error
VerifyCurrentOrNextEpoch(state.ReadOnlyBeaconState) error
VerifyValidProposalSlot(state.ReadOnlyBeaconState) error
VerifySignature(state.ReadOnlyBeaconState) error
SatisfyRequirement(Requirement)

View File

@@ -49,7 +49,7 @@ const (
RequireBidSignatureValid
// Signed proposer preferences specific.
RequireProposerPreferencesNextEpoch
RequireProposerPreferencesCurrentOrNextEpoch
RequireProposerPreferencesProposalSlotValid
RequireProposerPreferencesSignatureValid
)

View File

@@ -83,8 +83,8 @@ func (r Requirement) String() string {
return "RequireBidBuilderCanCover"
case RequireBidSignatureValid:
return "RequireBidSignatureValid"
case RequireProposerPreferencesNextEpoch:
return "RequireProposerPreferencesNextEpoch"
case RequireProposerPreferencesCurrentOrNextEpoch:
return "RequireProposerPreferencesCurrentOrNextEpoch"
case RequireProposerPreferencesProposalSlotValid:
return "RequireProposerPreferencesProposalSlotValid"
case RequireProposerPreferencesSignatureValid:

View File

@@ -6,6 +6,7 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
@@ -14,14 +15,15 @@ import (
// SignedProposerPreferencesGossipRequirements is the requirement list for gossip
// signed proposer preferences.
var SignedProposerPreferencesGossipRequirements = requirementList([]Requirement{
RequireProposerPreferencesNextEpoch,
RequireProposerPreferencesCurrentOrNextEpoch,
RequireProposerPreferencesProposalSlotValid,
RequireProposerPreferencesSignatureValid,
})
var (
ErrProposerPreferencesNotNextEpoch = errors.New("proposer preferences proposal slot is not in the next epoch")
ErrProposerPreferencesInvalidProposalSlot = errors.New("proposer preferences validator is not assigned to the proposal slot")
ErrProposerPreferencesNotCurrentOrNextEpoch = errors.New("proposer preferences proposal slot is not in the current or next epoch")
ErrProposerPreferencesSlotAlreadyPassed = errors.New("proposer preferences proposal slot has already passed")
ErrProposerPreferencesInvalidProposalSlot = errors.New("proposer preferences validator is not assigned to the proposal slot")
)
var _ SignedProposerPreferencesVerifier = &ProposerPreferencesVerifier{}
@@ -33,17 +35,25 @@ type ProposerPreferencesVerifier struct {
p *ethpb.SignedProposerPreferences
}
// VerifyNextEpoch verifies the proposal slot is in the next epoch relative to
// the state epoch, keeping it consistent with the ProposerLookahead index.
func (v *ProposerPreferencesVerifier) VerifyNextEpoch(st state.ReadOnlyBeaconState) (err error) {
defer v.record(RequireProposerPreferencesNextEpoch, &err)
// VerifyCurrentOrNextEpoch verifies the proposal slot is in the current or next
// epoch relative to the state epoch and has not already passed.
func (v *ProposerPreferencesVerifier) VerifyCurrentOrNextEpoch(st state.ReadOnlyBeaconState) (err error) {
defer v.record(RequireProposerPreferencesCurrentOrNextEpoch, &err)
msg := v.message()
stateEpoch := slots.ToEpoch(st.Slot())
currentSlot := st.Slot()
if v.clock != nil && v.clock.CurrentSlot() > currentSlot {
currentSlot = v.clock.CurrentSlot()
}
currentEpoch := slots.ToEpoch(currentSlot)
proposalEpoch := slots.ToEpoch(msg.ProposalSlot)
if proposalEpoch != stateEpoch.Add(1) {
return fmt.Errorf("%w: proposal epoch %d, state epoch %d",
ErrProposerPreferencesNotNextEpoch, proposalEpoch, stateEpoch)
if proposalEpoch < currentEpoch || proposalEpoch > currentEpoch.Add(1) {
return fmt.Errorf("%w: proposal epoch %d, current epoch %d",
ErrProposerPreferencesNotCurrentOrNextEpoch, proposalEpoch, currentEpoch)
}
if msg.ProposalSlot <= currentSlot {
return fmt.Errorf("%w: proposal slot %d <= current slot %d",
ErrProposerPreferencesSlotAlreadyPassed, msg.ProposalSlot, currentSlot)
}
return nil
}
@@ -59,7 +69,9 @@ func (v *ProposerPreferencesVerifier) VerifyValidProposalSlot(st state.ReadOnlyB
return errors.Wrap(err, "failed to get proposer lookahead")
}
slotIndex := params.BeaconConfig().SlotsPerEpoch + (msg.ProposalSlot % params.BeaconConfig().SlotsPerEpoch)
currentEpoch := slots.ToEpoch(st.Slot())
proposalEpoch := slots.ToEpoch(msg.ProposalSlot)
slotIndex := primitives.Slot(proposalEpoch.Sub(uint64(currentEpoch)))*params.BeaconConfig().SlotsPerEpoch + (msg.ProposalSlot % params.BeaconConfig().SlotsPerEpoch)
if uint64(len(lookahead)) <= uint64(slotIndex) {
return fmt.Errorf("%w: proposer lookahead index %d out of bounds", ErrProposerPreferencesInvalidProposalSlot, slotIndex)
}

View File

@@ -3,8 +3,10 @@ package verification
import (
"bytes"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
@@ -15,15 +17,36 @@ import (
"github.com/OffchainLabs/prysm/v7/time/slots"
)
func TestProposerPreferencesVerifier_VerifyNextEpoch(t *testing.T) {
func TestProposerPreferencesVerifier_VerifyCurrentOrNextEpoch(t *testing.T) {
// Next epoch future slot is accepted.
st, _, signed := newSignedProposerPreferencesState(t, 31, 40, 0)
verifier := &ProposerPreferencesVerifier{sharedResources: &sharedResources{clock: testClockAtSlotForProposerPreferences(t, st.Slot())}, results: newResults(RequireProposerPreferencesCurrentOrNextEpoch), p: signed}
require.NoError(t, verifier.VerifyCurrentOrNextEpoch(st))
verifier := &ProposerPreferencesVerifier{sharedResources: &sharedResources{}, results: newResults(RequireProposerPreferencesNextEpoch), p: signed}
require.NoError(t, verifier.VerifyNextEpoch(st))
// Current epoch future slot is accepted.
signed.Message.ProposalSlot = st.Slot() + 1
verifier = &ProposerPreferencesVerifier{sharedResources: &sharedResources{clock: testClockAtSlotForProposerPreferences(t, st.Slot())}, results: newResults(RequireProposerPreferencesCurrentOrNextEpoch), p: signed}
require.NoError(t, verifier.VerifyCurrentOrNextEpoch(st))
// Current slot (already passed) is rejected.
signed.Message.ProposalSlot = st.Slot()
verifier = &ProposerPreferencesVerifier{sharedResources: &sharedResources{}, results: newResults(RequireProposerPreferencesNextEpoch), p: signed}
require.ErrorIs(t, verifier.VerifyNextEpoch(st), ErrProposerPreferencesNotNextEpoch)
verifier = &ProposerPreferencesVerifier{sharedResources: &sharedResources{clock: testClockAtSlotForProposerPreferences(t, st.Slot())}, results: newResults(RequireProposerPreferencesCurrentOrNextEpoch), p: signed}
require.ErrorIs(t, verifier.VerifyCurrentOrNextEpoch(st), ErrProposerPreferencesSlotAlreadyPassed)
// Same-epoch future slot with more room.
st2, _, signed2 := newSignedProposerPreferencesState(t, 24, 28, 0)
verifier = &ProposerPreferencesVerifier{sharedResources: &sharedResources{clock: testClockAtSlotForProposerPreferences(t, st2.Slot())}, results: newResults(RequireProposerPreferencesCurrentOrNextEpoch), p: signed2}
require.NoError(t, verifier.VerifyCurrentOrNextEpoch(st2))
}
func TestProposerPreferencesVerifier_VerifyCurrentOrNextEpoch_UsesClockWhenStateLags(t *testing.T) {
st, _, signed := newSignedProposerPreferencesState(t, 31, 32, 0)
verifier := &ProposerPreferencesVerifier{
sharedResources: &sharedResources{clock: testClockAtSlotForProposerPreferences(t, 32)},
results: newResults(RequireProposerPreferencesCurrentOrNextEpoch),
p: signed,
}
require.ErrorIs(t, verifier.VerifyCurrentOrNextEpoch(st), ErrProposerPreferencesSlotAlreadyPassed)
}
func TestProposerPreferencesVerifier_VerifyValidProposalSlot(t *testing.T) {
@@ -107,7 +130,9 @@ func newSignedProposerPreferencesState(t *testing.T, currentSlot, proposalSlot p
lookaheadSize := int(uint64(params.BeaconConfig().MinSeedLookahead+1) * uint64(params.BeaconConfig().SlotsPerEpoch))
lookahead := make([]primitives.ValidatorIndex, lookaheadSize)
index := params.BeaconConfig().SlotsPerEpoch + (proposalSlot % params.BeaconConfig().SlotsPerEpoch)
currentEpoch := slots.ToEpoch(currentSlot)
proposalEpoch := slots.ToEpoch(proposalSlot)
index := primitives.Slot(proposalEpoch-currentEpoch)*params.BeaconConfig().SlotsPerEpoch + (proposalSlot % params.BeaconConfig().SlotsPerEpoch)
lookahead[index] = validatorIndex
require.NoError(t, st.SetProposerLookahead(lookahead))
@@ -135,3 +160,12 @@ func signProposerPreferencesWithConfigFork(t *testing.T, sk bls.SecretKey, prefe
require.NoError(t, err)
return sig
}
func testClockAtSlotForProposerPreferences(t *testing.T, slot primitives.Slot) *startup.Clock {
t.Helper()
genesis := time.Unix(1_700_000_000, 0)
now, err := slots.StartTime(genesis, slot)
require.NoError(t, err)
return startup.NewClock(genesis, [32]byte{}, startup.WithNower(func() time.Time { return now }))
}

View File

@@ -0,0 +1,3 @@
### Fixed
- Prevent `/eth/v1/events` subscriptions from timing out under the global HTTP timeout handler.

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixed `MaxBuildersPerWithdrawalsSweep` in the minimal preset to match the consensus-specs minimal gloas preset value of `16`.

View File

@@ -0,0 +1,3 @@
### Ignored
- refactors calculate state root and breaks up into calculate post state function.

View File

@@ -0,0 +1,4 @@
### Added
- GET /eth/v1/validator/execution_payload_envelope/{slot} endpoint
- POST /eth/v1/beacon/execution_payload_envelope endpoint

View File

@@ -0,0 +1,3 @@
### Changed
- changed POST incorrect endpoint /eth/v2/beacon/execution_payload/bid to /eth/v1/beacon/execution_payload_bid

View File

@@ -0,0 +1,3 @@
### Added
- GET /eth/v4/validator/blocks/{slot}

View File

@@ -0,0 +1,3 @@
### Ignored
- reverses pr#16635.

View File

@@ -0,0 +1,3 @@
### Added
- allow and send same epoch proposer preferences from https://github.com/ethereum/consensus-specs/pull/5035.

View File

@@ -0,0 +1,3 @@
### Added
- added validator client call to blocks v4 and payload envelope endpoints post gloas.

View File

@@ -0,0 +1,3 @@
### Changed
- Replace LRU cache with map+mutex for BLS public key cache.

View File

@@ -0,0 +1,3 @@
### Changed
- `GetSyncCommitteeDuties` now fetches the state at the current epoch for current and next-period requests to avoid expensive state replays.

View File

@@ -1,2 +0,0 @@
### Fixed
- Fixed finalized and justified state endpoint to not advance the slot.

View File

@@ -0,0 +1,3 @@
### Fixed
- Prevent replays and return 404 for Beacon-API requests that ask for pre checkpoint sync state

View File

@@ -0,0 +1,3 @@
### Added
- Queue Gloas data column sidecars that arrive before their block for deferred validation, with per-index deduplication, slot-based pruning, and peer downscoring on verification failure.

View File

@@ -80,6 +80,7 @@ func MinimalSpecConfig() *BeaconChainConfig {
minimalConfig.MaxWithdrawalsPerPayload = 4
minimalConfig.MaxBlsToExecutionChanges = 16
minimalConfig.MaxValidatorsPerWithdrawalsSweep = 16
minimalConfig.MaxBuildersPerWithdrawalsSweep = 16
// Signature domains
minimalConfig.DomainBeaconProposer = bytesutil.ToBytes4(bytesutil.Bytes4(0))

View File

@@ -18,7 +18,6 @@ go_library(
visibility = ["//visibility:public"],
deps = select({
"@io_bazel_rules_go//go/platform:android_amd64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -27,7 +26,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:android_arm64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -36,7 +34,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin_amd64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -45,7 +42,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:darwin_arm64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -54,7 +50,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:ios_amd64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -63,7 +58,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:ios_arm64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -72,7 +66,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux_amd64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -81,7 +74,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux_arm64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -90,7 +82,6 @@ go_library(
"@com_github_supranational_blst//:go_default_library",
],
"@io_bazel_rules_go//go/platform:windows_amd64": [
"//cache/nonblocking:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//crypto/bls/common:go_default_library",
@@ -109,7 +100,6 @@ go_test(
"public_key_test.go",
"secret_key_test.go",
"signature_test.go",
"test_helper_test.go",
],
embed = [":go_default_library"],
deps = select({

View File

@@ -3,11 +3,8 @@
package blst
import (
"fmt"
"runtime"
"github.com/OffchainLabs/prysm/v7/cache/nonblocking"
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
blst "github.com/supranational/blst/bindings/go"
)
@@ -18,10 +15,4 @@ func init() {
maxProcs = 1
}
blst.SetMaxProcs(maxProcs)
onEvict := func(_ [48]byte, _ common.PublicKey) {}
keysCache, err := nonblocking.NewLRU(maxKeys, onEvict)
if err != nil {
panic(fmt.Sprintf("Could not initiate public keys cache: %v", err))
}
pubkeyCache = keysCache
}

View File

@@ -4,16 +4,37 @@ package blst
import (
"fmt"
"sync"
"github.com/OffchainLabs/prysm/v7/cache/nonblocking"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
"github.com/pkg/errors"
)
var maxKeys = 2_000_000
var pubkeyCache *nonblocking.LRU[[48]byte, common.PublicKey]
var pubkeyCache = &pubkeyCacheMap{
items: make(map[[fieldparams.BLSPubkeyLength]byte]common.PublicKey),
}
type pubkeyCacheMap struct {
mu sync.RWMutex
items map[[fieldparams.BLSPubkeyLength]byte]common.PublicKey
}
func (c *pubkeyCacheMap) pubkey(key [fieldparams.BLSPubkeyLength]byte) (common.PublicKey, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.items[key]
return v, ok
}
func (c *pubkeyCacheMap) setPubkey(key [fieldparams.BLSPubkeyLength]byte, value common.PublicKey) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}
// PublicKey used in the BLS signature scheme.
type PublicKey struct {
@@ -30,7 +51,7 @@ func publicKeyFromBytes(pubKey []byte, cacheCopy bool) (common.PublicKey, error)
return nil, fmt.Errorf("public key must be %d bytes", params.BeaconConfig().BLSPubkeyLength)
}
newKey := (*[fieldparams.BLSPubkeyLength]byte)(pubKey)
if cv, ok := pubkeyCache.Get(*newKey); ok {
if cv, ok := pubkeyCache.pubkey(*newKey); ok {
if cacheCopy {
return cv.Copy(), nil
}
@@ -48,8 +69,7 @@ func publicKeyFromBytes(pubKey []byte, cacheCopy bool) (common.PublicKey, error)
}
pubKeyObj := &PublicKey{p: p}
copiedKey := pubKeyObj.Copy()
cacheKey := *newKey
pubkeyCache.Add(cacheKey, copiedKey)
pubkeyCache.setPubkey(*newKey, copiedKey)
return pubKeyObj, nil
}

View File

@@ -162,27 +162,3 @@ func TestPublicKeysEmpty(t *testing.T) {
_, err := blst.AggregatePublicKeys(pubs)
require.ErrorContains(t, "nil or empty public keys", err)
}
func BenchmarkPublicKeyFromBytes(b *testing.B) {
priv, err := blst.RandKey()
require.NoError(b, err)
pubkey := priv.PublicKey()
pubkeyBytes := pubkey.Marshal()
b.Run("cache on", func(b *testing.B) {
blst.EnableCaches()
for b.Loop() {
_, err := blst.PublicKeyFromBytes(pubkeyBytes)
require.NoError(b, err)
}
})
b.Run("cache off", func(b *testing.B) {
blst.DisableCaches()
for b.Loop() {
_, err := blst.PublicKeyFromBytes(pubkeyBytes)
require.NoError(b, err)
}
})
}

View File

@@ -1,13 +0,0 @@
package blst
// Note: These functions are for tests to access private globals, such as pubkeyCache.
// DisableCaches sets the cache sizes to 0.
func DisableCaches() {
pubkeyCache.Resize(0)
}
// EnableCaches sets the cache sizes to the default values.
func EnableCaches() {
pubkeyCache.Resize(maxKeys)
}

View File

@@ -212,6 +212,7 @@ ssz_gloas_objs = [
"SignedBlindedExecutionPayloadEnvelope",
"SignedExecutionPayloadEnvelope",
"BeaconBlockGloas",
"BeaconBlockContentsGloas",
"SignedBeaconBlockGloas",
"BeaconStateGloas",
]

View File

@@ -1261,6 +1261,74 @@ func (x *PTCs) GetValidatorIndices() []github_com_OffchainLabs_prysm_v7_consensu
return []github_com_OffchainLabs_prysm_v7_consensus_types_primitives.ValidatorIndex(nil)
}
type BeaconBlockContentsGloas struct {
state protoimpl.MessageState `protogen:"open.v1"`
Block *BeaconBlockGloas `protobuf:"bytes,1,opt,name=block,proto3" json:"block,omitempty"`
ExecutionPayloadEnvelope *ExecutionPayloadEnvelope `protobuf:"bytes,2,opt,name=execution_payload_envelope,json=executionPayloadEnvelope,proto3" json:"execution_payload_envelope,omitempty"`
KzgProofs [][]byte `protobuf:"bytes,3,rep,name=kzg_proofs,json=kzgProofs,proto3" json:"kzg_proofs,omitempty" ssz-max:"4096" ssz-size:"?,48"`
Blobs [][]byte `protobuf:"bytes,4,rep,name=blobs,proto3" json:"blobs,omitempty" ssz-max:"4096" ssz-size:"?,131072"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BeaconBlockContentsGloas) Reset() {
*x = BeaconBlockContentsGloas{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BeaconBlockContentsGloas) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BeaconBlockContentsGloas) ProtoMessage() {}
func (x *BeaconBlockContentsGloas) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BeaconBlockContentsGloas.ProtoReflect.Descriptor instead.
func (*BeaconBlockContentsGloas) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{13}
}
func (x *BeaconBlockContentsGloas) GetBlock() *BeaconBlockGloas {
if x != nil {
return x.Block
}
return nil
}
func (x *BeaconBlockContentsGloas) GetExecutionPayloadEnvelope() *ExecutionPayloadEnvelope {
if x != nil {
return x.ExecutionPayloadEnvelope
}
return nil
}
func (x *BeaconBlockContentsGloas) GetKzgProofs() [][]byte {
if x != nil {
return x.KzgProofs
}
return nil
}
func (x *BeaconBlockContentsGloas) GetBlobs() [][]byte {
if x != nil {
return x.Blobs
}
return nil
}
type BuilderPendingPayment struct {
state protoimpl.MessageState `protogen:"open.v1"`
Weight github_com_OffchainLabs_prysm_v7_consensus_types_primitives.Gwei `protobuf:"varint,1,opt,name=weight,proto3" json:"weight,omitempty" cast-type:"github.com/OffchainLabs/prysm/v7/consensus-types/primitives.Gwei"`
@@ -1271,7 +1339,7 @@ type BuilderPendingPayment struct {
func (x *BuilderPendingPayment) Reset() {
*x = BuilderPendingPayment{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[13]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1283,7 +1351,7 @@ func (x *BuilderPendingPayment) String() string {
func (*BuilderPendingPayment) ProtoMessage() {}
func (x *BuilderPendingPayment) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[13]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1296,7 +1364,7 @@ func (x *BuilderPendingPayment) ProtoReflect() protoreflect.Message {
// Deprecated: Use BuilderPendingPayment.ProtoReflect.Descriptor instead.
func (*BuilderPendingPayment) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{13}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{14}
}
func (x *BuilderPendingPayment) GetWeight() github_com_OffchainLabs_prysm_v7_consensus_types_primitives.Gwei {
@@ -1324,7 +1392,7 @@ type BuilderPendingWithdrawal struct {
func (x *BuilderPendingWithdrawal) Reset() {
*x = BuilderPendingWithdrawal{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[14]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1336,7 +1404,7 @@ func (x *BuilderPendingWithdrawal) String() string {
func (*BuilderPendingWithdrawal) ProtoMessage() {}
func (x *BuilderPendingWithdrawal) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[14]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1349,7 +1417,7 @@ func (x *BuilderPendingWithdrawal) ProtoReflect() protoreflect.Message {
// Deprecated: Use BuilderPendingWithdrawal.ProtoReflect.Descriptor instead.
func (*BuilderPendingWithdrawal) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{14}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{15}
}
func (x *BuilderPendingWithdrawal) GetFeeRecipient() []byte {
@@ -1386,7 +1454,7 @@ type DataColumnSidecarGloas struct {
func (x *DataColumnSidecarGloas) Reset() {
*x = DataColumnSidecarGloas{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[15]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1398,7 +1466,7 @@ func (x *DataColumnSidecarGloas) String() string {
func (*DataColumnSidecarGloas) ProtoMessage() {}
func (x *DataColumnSidecarGloas) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[15]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1411,7 +1479,7 @@ func (x *DataColumnSidecarGloas) ProtoReflect() protoreflect.Message {
// Deprecated: Use DataColumnSidecarGloas.ProtoReflect.Descriptor instead.
func (*DataColumnSidecarGloas) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{15}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{16}
}
func (x *DataColumnSidecarGloas) GetIndex() uint64 {
@@ -1463,7 +1531,7 @@ type ExecutionPayloadEnvelope struct {
func (x *ExecutionPayloadEnvelope) Reset() {
*x = ExecutionPayloadEnvelope{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[16]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1475,7 +1543,7 @@ func (x *ExecutionPayloadEnvelope) String() string {
func (*ExecutionPayloadEnvelope) ProtoMessage() {}
func (x *ExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[16]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1488,7 +1556,7 @@ func (x *ExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
// Deprecated: Use ExecutionPayloadEnvelope.ProtoReflect.Descriptor instead.
func (*ExecutionPayloadEnvelope) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{16}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{17}
}
func (x *ExecutionPayloadEnvelope) GetPayload() *v1.ExecutionPayloadDeneb {
@@ -1543,7 +1611,7 @@ type SignedExecutionPayloadEnvelope struct {
func (x *SignedExecutionPayloadEnvelope) Reset() {
*x = SignedExecutionPayloadEnvelope{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[17]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1555,7 +1623,7 @@ func (x *SignedExecutionPayloadEnvelope) String() string {
func (*SignedExecutionPayloadEnvelope) ProtoMessage() {}
func (x *SignedExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[17]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1568,7 +1636,7 @@ func (x *SignedExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
// Deprecated: Use SignedExecutionPayloadEnvelope.ProtoReflect.Descriptor instead.
func (*SignedExecutionPayloadEnvelope) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{17}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{18}
}
func (x *SignedExecutionPayloadEnvelope) GetMessage() *ExecutionPayloadEnvelope {
@@ -1600,7 +1668,7 @@ type BlindedExecutionPayloadEnvelope struct {
func (x *BlindedExecutionPayloadEnvelope) Reset() {
*x = BlindedExecutionPayloadEnvelope{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[18]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1612,7 +1680,7 @@ func (x *BlindedExecutionPayloadEnvelope) String() string {
func (*BlindedExecutionPayloadEnvelope) ProtoMessage() {}
func (x *BlindedExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[18]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1625,7 +1693,7 @@ func (x *BlindedExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
// Deprecated: Use BlindedExecutionPayloadEnvelope.ProtoReflect.Descriptor instead.
func (*BlindedExecutionPayloadEnvelope) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{18}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{19}
}
func (x *BlindedExecutionPayloadEnvelope) GetBlockHash() []byte {
@@ -1687,7 +1755,7 @@ type SignedBlindedExecutionPayloadEnvelope struct {
func (x *SignedBlindedExecutionPayloadEnvelope) Reset() {
*x = SignedBlindedExecutionPayloadEnvelope{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[19]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1699,7 +1767,7 @@ func (x *SignedBlindedExecutionPayloadEnvelope) String() string {
func (*SignedBlindedExecutionPayloadEnvelope) ProtoMessage() {}
func (x *SignedBlindedExecutionPayloadEnvelope) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[19]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1712,7 +1780,7 @@ func (x *SignedBlindedExecutionPayloadEnvelope) ProtoReflect() protoreflect.Mess
// Deprecated: Use SignedBlindedExecutionPayloadEnvelope.ProtoReflect.Descriptor instead.
func (*SignedBlindedExecutionPayloadEnvelope) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{19}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{20}
}
func (x *SignedBlindedExecutionPayloadEnvelope) GetMessage() *BlindedExecutionPayloadEnvelope {
@@ -1743,7 +1811,7 @@ type Builder struct {
func (x *Builder) Reset() {
*x = Builder{}
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[20]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1755,7 +1823,7 @@ func (x *Builder) String() string {
func (*Builder) ProtoMessage() {}
func (x *Builder) ProtoReflect() protoreflect.Message {
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[20]
mi := &file_proto_prysm_v1alpha1_gloas_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1768,7 +1836,7 @@ func (x *Builder) ProtoReflect() protoreflect.Message {
// Deprecated: Use Builder.ProtoReflect.Descriptor instead.
func (*Builder) Descriptor() ([]byte, []int) {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{20}
return file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP(), []int{21}
}
func (x *Builder) GetPubkey() []byte {
@@ -2341,168 +2409,187 @@ var file_proto_prysm_v1alpha1_gloas_proto_rawDesc = []byte{
0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x69,
0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x8a, 0xb5, 0x18, 0x03, 0x35, 0x31,
0x32, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x64, 0x69,
0x63, 0x65, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x15, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x50,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x5c, 0x0a,
0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82,
0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66,
0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d,
0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x47,
0x77, 0x65, 0x69, 0x52, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x4f, 0x0a, 0x0a, 0x77,
0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x50,
0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c,
0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x22, 0x98, 0x02, 0x0a,
0x18, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x57,
0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x12, 0x2b, 0x0a, 0x0d, 0x66, 0x65, 0x65,
0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x32, 0x30, 0x52, 0x0c, 0x66, 0x65, 0x65, 0x52, 0x65, 0x63,
0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x5c, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c,
0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e,
0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69,
0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x47, 0x77, 0x65, 0x69, 0x52, 0x06, 0x61, 0x6d,
0x6f, 0x75, 0x6e, 0x74, 0x12, 0x71, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f,
0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x4c, 0x82, 0xb5, 0x18,
0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63,
0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76,
0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65,
0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x42, 0x75, 0x69,
0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64,
0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x99, 0x02, 0x0a, 0x16, 0x44, 0x61, 0x74, 0x61,
0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x53, 0x69, 0x64, 0x65, 0x63, 0x61, 0x72, 0x47, 0x6c, 0x6f,
0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28,
0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2a, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75,
0x6d, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x12, 0x8a, 0xb5, 0x18, 0x06, 0x3f, 0x2c,
0x32, 0x30, 0x34, 0x38, 0x92, 0xb5, 0x18, 0x04, 0x34, 0x30, 0x39, 0x36, 0x52, 0x06, 0x63, 0x6f,
0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x2f, 0x0a, 0x0a, 0x6b, 0x7a, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x6f,
0x66, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x10, 0x8a, 0xb5, 0x18, 0x04, 0x3f, 0x2c,
0x34, 0x38, 0x92, 0xb5, 0x18, 0x04, 0x34, 0x30, 0x39, 0x36, 0x52, 0x09, 0x6b, 0x7a, 0x67, 0x50,
0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12, 0x58, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x05, 0x20,
0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x65, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x18, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x47, 0x6c, 0x6f, 0x61, 0x73,
0x12, 0x3d, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x27, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x47, 0x6c, 0x6f, 0x61, 0x73, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12,
0x6d, 0x0a, 0x1a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x79,
0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65,
0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63,
0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65,
0x6c, 0x6f, 0x70, 0x65, 0x52, 0x18, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x2f,
0x0a, 0x0a, 0x6b, 0x7a, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0c, 0x42, 0x10, 0x8a, 0xb5, 0x18, 0x04, 0x3f, 0x2c, 0x34, 0x38, 0x92, 0xb5, 0x18, 0x04,
0x34, 0x30, 0x39, 0x36, 0x52, 0x09, 0x6b, 0x7a, 0x67, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12,
0x2a, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x14,
0x8a, 0xb5, 0x18, 0x08, 0x3f, 0x2c, 0x31, 0x33, 0x31, 0x30, 0x37, 0x32, 0x92, 0xb5, 0x18, 0x04,
0x34, 0x30, 0x39, 0x36, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x22, 0xc6, 0x01, 0x0a, 0x15,
0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61,
0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x5c, 0x0a, 0x06, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61,
0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73,
0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d,
0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x47, 0x77, 0x65, 0x69, 0x52, 0x06, 0x77, 0x65, 0x69,
0x67, 0x68, 0x74, 0x12, 0x4f, 0x0a, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61,
0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65,
0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x57, 0x69,
0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72,
0x61, 0x77, 0x61, 0x6c, 0x22, 0x98, 0x02, 0x0a, 0x18, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72,
0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61,
0x6c, 0x12, 0x2b, 0x0a, 0x0d, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65,
0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x32, 0x30,
0x52, 0x0c, 0x66, 0x65, 0x65, 0x52, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x5c,
0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44,
0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f,
0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73,
0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74,
0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e,
0x47, 0x77, 0x65, 0x69, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x71, 0x0a, 0x0d,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20,
0x01, 0x28, 0x04, 0x42, 0x4c, 0x82, 0xb5, 0x18, 0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73,
0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e,
0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74,
0x69, 0x76, 0x65, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12,
0x32, 0x0a, 0x11, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f,
0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02,
0x33, 0x32, 0x52, 0x0f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52,
0x6f, 0x6f, 0x74, 0x22, 0xdd, 0x03, 0x0a, 0x18, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f,
0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65,
0x12, 0x43, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x6e, 0x67,
0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e,
0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x44, 0x65, 0x6e, 0x65, 0x62, 0x52, 0x07, 0x70, 0x61,
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x54, 0x0a, 0x12, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69,
0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x6e, 0x67,
0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x52, 0x11, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x71, 0x0a, 0x0d, 0x62,
0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01,
0x28, 0x04, 0x42, 0x4c, 0x82, 0xb5, 0x18, 0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f,
0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73,
0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69,
0x76, 0x65, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x32,
0x0a, 0x11, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72,
0x6f, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33,
0x32, 0x52, 0x0f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f,
0x6f, 0x74, 0x12, 0x58, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,
0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72,
0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73,
0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65,
0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12, 0x25, 0x0a, 0x0a,
0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c,
0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52,
0x6f, 0x6f, 0x74, 0x22, 0x91, 0x01, 0x0a, 0x1e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x45, 0x78,
0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e,
0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x49, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65,
0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
0x69, 0x76, 0x65, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65,
0x78, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22,
0x99, 0x02, 0x0a, 0x16, 0x44, 0x61, 0x74, 0x61, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x53, 0x69,
0x64, 0x65, 0x63, 0x61, 0x72, 0x47, 0x6c, 0x6f, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78,
0x12, 0x2a, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c,
0x42, 0x12, 0x8a, 0xb5, 0x18, 0x06, 0x3f, 0x2c, 0x32, 0x30, 0x34, 0x38, 0x92, 0xb5, 0x18, 0x04,
0x34, 0x30, 0x39, 0x36, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x2f, 0x0a, 0x0a,
0x6b, 0x7a, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c,
0x42, 0x10, 0x8a, 0xb5, 0x18, 0x04, 0x3f, 0x2c, 0x34, 0x38, 0x92, 0xb5, 0x18, 0x04, 0x34, 0x30,
0x39, 0x36, 0x52, 0x09, 0x6b, 0x7a, 0x67, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x12, 0x58, 0x0a,
0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18,
0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63,
0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76,
0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65,
0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x53, 0x6c, 0x6f,
0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12, 0x32, 0x0a, 0x11, 0x62, 0x65, 0x61, 0x63, 0x6f,
0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01,
0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0f, 0x62, 0x65, 0x61, 0x63,
0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0xdd, 0x03, 0x0a, 0x18,
0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x12, 0x24, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x39, 0x36, 0x52, 0x09, 0x73, 0x69,
0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xfa, 0x03, 0x0a, 0x1f, 0x42, 0x6c, 0x69, 0x6e,
0x64, 0x65, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c,
0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0a, 0x62,
0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42,
0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61,
0x73, 0x68, 0x12, 0x54, 0x0a, 0x12, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25,
0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65,
0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x73, 0x52, 0x11, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x71, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c,
0x64, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42,
0x4c, 0x82, 0xb5, 0x18, 0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79,
0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d,
0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73,
0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x0c, 0x62,
0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x32, 0x0a, 0x11, 0x62,
0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x6f, 0x6f, 0x74,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0f,
0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x12,
0x58, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82,
0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66,
0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d,
0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79,
0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x53,
0x6c, 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x74, 0x61,
0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a,
0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74,
0x12, 0x32, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18,
0x02, 0x33, 0x32, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
0x48, 0x61, 0x73, 0x68, 0x22, 0x9f, 0x01, 0x0a, 0x25, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42,
0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x50,
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x36, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x45,
0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45,
0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x12, 0x24, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x39, 0x36, 0x52, 0x09, 0x73, 0x69, 0x67,
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xc1, 0x03, 0x0a, 0x07, 0x42, 0x75, 0x69, 0x6c, 0x64,
0x65, 0x72, 0x12, 0x1e, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x34, 0x38, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b,
0x65, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0c, 0x42, 0x05, 0x8a, 0xb5, 0x18, 0x01, 0x31, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x11, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e,
0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06,
0x8a, 0xb5, 0x18, 0x02, 0x32, 0x30, 0x52, 0x10, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f,
0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x5e, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61,
0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f,
0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f,
0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x47, 0x77, 0x65, 0x69, 0x52,
0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x0d, 0x64, 0x65, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42,
0x45, 0x82, 0xb5, 0x18, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79,
0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d,
0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73,
0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x45,
0x70, 0x6f, 0x63, 0x68, 0x12, 0x74, 0x0a, 0x12, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77,
0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04,
0x42, 0x45, 0x82, 0xb5, 0x18, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72,
0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73,
0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65,
0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52, 0x11, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61,
0x77, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69,
0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c,
0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x65, 0x74, 0x68, 0x65,
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45,
0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x44,
0x65, 0x6e, 0x65, 0x62, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x54, 0x0a,
0x12, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65,
0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45,
0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73,
0x52, 0x11, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x73, 0x12, 0x71, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x69,
0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x4c, 0x82, 0xb5, 0x18, 0x48,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68,
0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37,
0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73,
0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c,
0x64, 0x65, 0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65,
0x72, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x32, 0x0a, 0x11, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e,
0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0f, 0x62, 0x65, 0x61, 0x63, 0x6f,
0x6e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x58, 0x0a, 0x04, 0x73, 0x6c,
0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69,
0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70,
0x68, 0x61, 0x31, 0x3b, 0x65, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63,
0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70,
0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x04,
0x73, 0x6c, 0x6f, 0x74, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f,
0x6f, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32,
0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x22, 0x91, 0x01, 0x0a, 0x1e,
0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x49,
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76,
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f,
0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65,
0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x09, 0x73, 0x69, 0x67,
0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5,
0x18, 0x02, 0x39, 0x36, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22,
0xfa, 0x03, 0x0a, 0x1f, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75,
0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c,
0x6f, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73,
0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52,
0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x54, 0x0a, 0x12, 0x65, 0x78,
0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75,
0x6d, 0x2e, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63,
0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x52, 0x11, 0x65,
0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73,
0x12, 0x71, 0x0a, 0x0d, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, 0x65,
0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x4c, 0x82, 0xb5, 0x18, 0x48, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f,
0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72,
0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72,
0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x49, 0x6e,
0x64, 0x65, 0x78, 0x12, 0x32, 0x0a, 0x11, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x5f, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06,
0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0f, 0x62, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x42, 0x6c,
0x6f, 0x63, 0x6b, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x58, 0x0a, 0x04, 0x73, 0x6c, 0x6f, 0x74, 0x18,
0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61,
0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73,
0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d,
0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x53, 0x6c, 0x6f, 0x74, 0x52, 0x04, 0x73, 0x6c, 0x6f,
0x74, 0x12, 0x25, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18,
0x06, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x09, 0x73,
0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x32, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x07, 0x20,
0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0f, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x9f, 0x01, 0x0a,
0x25, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x45, 0x78,
0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e,
0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x50, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65,
0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e,
0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x52,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e,
0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18,
0x02, 0x39, 0x36, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0xc1,
0x03, 0x0a, 0x07, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x06, 0x70, 0x75,
0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02,
0x34, 0x38, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x05, 0x8a, 0xb5, 0x18,
0x01, 0x31, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x11, 0x65,
0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x32, 0x30, 0x52, 0x10,
0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x12, 0x5e, 0x0a, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x04, 0x42, 0x44, 0x82, 0xb5, 0x18, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70,
0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75,
0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76,
0x65, 0x73, 0x2e, 0x47, 0x77, 0x65, 0x69, 0x52, 0x07, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x12, 0x6a, 0x0a, 0x0d, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63,
0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x45, 0x82, 0xb5, 0x18, 0x41, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63, 0x6f,
0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72,
0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52, 0x0c,
0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x74, 0x0a, 0x12,
0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x70, 0x6f,
0x63, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x42, 0x45, 0x82, 0xb5, 0x18, 0x41, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69,
0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x63,
0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70,
0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52,
0x11, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x70, 0x6f,
0x63, 0x68, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72,
0x79, 0x73, 0x6d, 0x2f, 0x76, 0x37, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79,
0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x65, 0x74, 0x68, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -2517,7 +2604,7 @@ func file_proto_prysm_v1alpha1_gloas_proto_rawDescGZIP() []byte {
return file_proto_prysm_v1alpha1_gloas_proto_rawDescData
}
var file_proto_prysm_v1alpha1_gloas_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
var file_proto_prysm_v1alpha1_gloas_proto_msgTypes = make([]protoimpl.MessageInfo, 22)
var file_proto_prysm_v1alpha1_gloas_proto_goTypes = []any{
(*ExecutionPayloadBid)(nil), // 0: ethereum.eth.v1alpha1.ExecutionPayloadBid
(*SignedExecutionPayloadBid)(nil), // 1: ethereum.eth.v1alpha1.SignedExecutionPayloadBid
@@ -2532,34 +2619,35 @@ var file_proto_prysm_v1alpha1_gloas_proto_goTypes = []any{
(*SignedBeaconBlockGloas)(nil), // 10: ethereum.eth.v1alpha1.SignedBeaconBlockGloas
(*BeaconStateGloas)(nil), // 11: ethereum.eth.v1alpha1.BeaconStateGloas
(*PTCs)(nil), // 12: ethereum.eth.v1alpha1.PTCs
(*BuilderPendingPayment)(nil), // 13: ethereum.eth.v1alpha1.BuilderPendingPayment
(*BuilderPendingWithdrawal)(nil), // 14: ethereum.eth.v1alpha1.BuilderPendingWithdrawal
(*DataColumnSidecarGloas)(nil), // 15: ethereum.eth.v1alpha1.DataColumnSidecarGloas
(*ExecutionPayloadEnvelope)(nil), // 16: ethereum.eth.v1alpha1.ExecutionPayloadEnvelope
(*SignedExecutionPayloadEnvelope)(nil), // 17: ethereum.eth.v1alpha1.SignedExecutionPayloadEnvelope
(*BlindedExecutionPayloadEnvelope)(nil), // 18: ethereum.eth.v1alpha1.BlindedExecutionPayloadEnvelope
(*SignedBlindedExecutionPayloadEnvelope)(nil), // 19: ethereum.eth.v1alpha1.SignedBlindedExecutionPayloadEnvelope
(*Builder)(nil), // 20: ethereum.eth.v1alpha1.Builder
(*Eth1Data)(nil), // 21: ethereum.eth.v1alpha1.Eth1Data
(*ProposerSlashing)(nil), // 22: ethereum.eth.v1alpha1.ProposerSlashing
(*AttesterSlashingElectra)(nil), // 23: ethereum.eth.v1alpha1.AttesterSlashingElectra
(*AttestationElectra)(nil), // 24: ethereum.eth.v1alpha1.AttestationElectra
(*Deposit)(nil), // 25: ethereum.eth.v1alpha1.Deposit
(*SignedVoluntaryExit)(nil), // 26: ethereum.eth.v1alpha1.SignedVoluntaryExit
(*SyncAggregate)(nil), // 27: ethereum.eth.v1alpha1.SyncAggregate
(*SignedBLSToExecutionChange)(nil), // 28: ethereum.eth.v1alpha1.SignedBLSToExecutionChange
(*Fork)(nil), // 29: ethereum.eth.v1alpha1.Fork
(*BeaconBlockHeader)(nil), // 30: ethereum.eth.v1alpha1.BeaconBlockHeader
(*Validator)(nil), // 31: ethereum.eth.v1alpha1.Validator
(*Checkpoint)(nil), // 32: ethereum.eth.v1alpha1.Checkpoint
(*SyncCommittee)(nil), // 33: ethereum.eth.v1alpha1.SyncCommittee
(*HistoricalSummary)(nil), // 34: ethereum.eth.v1alpha1.HistoricalSummary
(*PendingDeposit)(nil), // 35: ethereum.eth.v1alpha1.PendingDeposit
(*PendingPartialWithdrawal)(nil), // 36: ethereum.eth.v1alpha1.PendingPartialWithdrawal
(*PendingConsolidation)(nil), // 37: ethereum.eth.v1alpha1.PendingConsolidation
(*v1.Withdrawal)(nil), // 38: ethereum.engine.v1.Withdrawal
(*v1.ExecutionPayloadDeneb)(nil), // 39: ethereum.engine.v1.ExecutionPayloadDeneb
(*v1.ExecutionRequests)(nil), // 40: ethereum.engine.v1.ExecutionRequests
(*BeaconBlockContentsGloas)(nil), // 13: ethereum.eth.v1alpha1.BeaconBlockContentsGloas
(*BuilderPendingPayment)(nil), // 14: ethereum.eth.v1alpha1.BuilderPendingPayment
(*BuilderPendingWithdrawal)(nil), // 15: ethereum.eth.v1alpha1.BuilderPendingWithdrawal
(*DataColumnSidecarGloas)(nil), // 16: ethereum.eth.v1alpha1.DataColumnSidecarGloas
(*ExecutionPayloadEnvelope)(nil), // 17: ethereum.eth.v1alpha1.ExecutionPayloadEnvelope
(*SignedExecutionPayloadEnvelope)(nil), // 18: ethereum.eth.v1alpha1.SignedExecutionPayloadEnvelope
(*BlindedExecutionPayloadEnvelope)(nil), // 19: ethereum.eth.v1alpha1.BlindedExecutionPayloadEnvelope
(*SignedBlindedExecutionPayloadEnvelope)(nil), // 20: ethereum.eth.v1alpha1.SignedBlindedExecutionPayloadEnvelope
(*Builder)(nil), // 21: ethereum.eth.v1alpha1.Builder
(*Eth1Data)(nil), // 22: ethereum.eth.v1alpha1.Eth1Data
(*ProposerSlashing)(nil), // 23: ethereum.eth.v1alpha1.ProposerSlashing
(*AttesterSlashingElectra)(nil), // 24: ethereum.eth.v1alpha1.AttesterSlashingElectra
(*AttestationElectra)(nil), // 25: ethereum.eth.v1alpha1.AttestationElectra
(*Deposit)(nil), // 26: ethereum.eth.v1alpha1.Deposit
(*SignedVoluntaryExit)(nil), // 27: ethereum.eth.v1alpha1.SignedVoluntaryExit
(*SyncAggregate)(nil), // 28: ethereum.eth.v1alpha1.SyncAggregate
(*SignedBLSToExecutionChange)(nil), // 29: ethereum.eth.v1alpha1.SignedBLSToExecutionChange
(*Fork)(nil), // 30: ethereum.eth.v1alpha1.Fork
(*BeaconBlockHeader)(nil), // 31: ethereum.eth.v1alpha1.BeaconBlockHeader
(*Validator)(nil), // 32: ethereum.eth.v1alpha1.Validator
(*Checkpoint)(nil), // 33: ethereum.eth.v1alpha1.Checkpoint
(*SyncCommittee)(nil), // 34: ethereum.eth.v1alpha1.SyncCommittee
(*HistoricalSummary)(nil), // 35: ethereum.eth.v1alpha1.HistoricalSummary
(*PendingDeposit)(nil), // 36: ethereum.eth.v1alpha1.PendingDeposit
(*PendingPartialWithdrawal)(nil), // 37: ethereum.eth.v1alpha1.PendingPartialWithdrawal
(*PendingConsolidation)(nil), // 38: ethereum.eth.v1alpha1.PendingConsolidation
(*v1.Withdrawal)(nil), // 39: ethereum.engine.v1.Withdrawal
(*v1.ExecutionPayloadDeneb)(nil), // 40: ethereum.engine.v1.ExecutionPayloadDeneb
(*v1.ExecutionRequests)(nil), // 41: ethereum.engine.v1.ExecutionRequests
}
var file_proto_prysm_v1alpha1_gloas_proto_depIdxs = []int32{
0, // 0: ethereum.eth.v1alpha1.SignedExecutionPayloadBid.message:type_name -> ethereum.eth.v1alpha1.ExecutionPayloadBid
@@ -2568,48 +2656,50 @@ var file_proto_prysm_v1alpha1_gloas_proto_depIdxs = []int32{
5, // 3: ethereum.eth.v1alpha1.PayloadAttestation.data:type_name -> ethereum.eth.v1alpha1.PayloadAttestationData
5, // 4: ethereum.eth.v1alpha1.PayloadAttestationMessage.data:type_name -> ethereum.eth.v1alpha1.PayloadAttestationData
9, // 5: ethereum.eth.v1alpha1.BeaconBlockGloas.body:type_name -> ethereum.eth.v1alpha1.BeaconBlockBodyGloas
21, // 6: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.eth1_data:type_name -> ethereum.eth.v1alpha1.Eth1Data
22, // 7: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.proposer_slashings:type_name -> ethereum.eth.v1alpha1.ProposerSlashing
23, // 8: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.attester_slashings:type_name -> ethereum.eth.v1alpha1.AttesterSlashingElectra
24, // 9: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.attestations:type_name -> ethereum.eth.v1alpha1.AttestationElectra
25, // 10: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.deposits:type_name -> ethereum.eth.v1alpha1.Deposit
26, // 11: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.voluntary_exits:type_name -> ethereum.eth.v1alpha1.SignedVoluntaryExit
27, // 12: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.sync_aggregate:type_name -> ethereum.eth.v1alpha1.SyncAggregate
28, // 13: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.bls_to_execution_changes:type_name -> ethereum.eth.v1alpha1.SignedBLSToExecutionChange
22, // 6: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.eth1_data:type_name -> ethereum.eth.v1alpha1.Eth1Data
23, // 7: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.proposer_slashings:type_name -> ethereum.eth.v1alpha1.ProposerSlashing
24, // 8: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.attester_slashings:type_name -> ethereum.eth.v1alpha1.AttesterSlashingElectra
25, // 9: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.attestations:type_name -> ethereum.eth.v1alpha1.AttestationElectra
26, // 10: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.deposits:type_name -> ethereum.eth.v1alpha1.Deposit
27, // 11: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.voluntary_exits:type_name -> ethereum.eth.v1alpha1.SignedVoluntaryExit
28, // 12: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.sync_aggregate:type_name -> ethereum.eth.v1alpha1.SyncAggregate
29, // 13: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.bls_to_execution_changes:type_name -> ethereum.eth.v1alpha1.SignedBLSToExecutionChange
1, // 14: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.signed_execution_payload_bid:type_name -> ethereum.eth.v1alpha1.SignedExecutionPayloadBid
6, // 15: ethereum.eth.v1alpha1.BeaconBlockBodyGloas.payload_attestations:type_name -> ethereum.eth.v1alpha1.PayloadAttestation
8, // 16: ethereum.eth.v1alpha1.SignedBeaconBlockGloas.block:type_name -> ethereum.eth.v1alpha1.BeaconBlockGloas
29, // 17: ethereum.eth.v1alpha1.BeaconStateGloas.fork:type_name -> ethereum.eth.v1alpha1.Fork
30, // 18: ethereum.eth.v1alpha1.BeaconStateGloas.latest_block_header:type_name -> ethereum.eth.v1alpha1.BeaconBlockHeader
21, // 19: ethereum.eth.v1alpha1.BeaconStateGloas.eth1_data:type_name -> ethereum.eth.v1alpha1.Eth1Data
21, // 20: ethereum.eth.v1alpha1.BeaconStateGloas.eth1_data_votes:type_name -> ethereum.eth.v1alpha1.Eth1Data
31, // 21: ethereum.eth.v1alpha1.BeaconStateGloas.validators:type_name -> ethereum.eth.v1alpha1.Validator
32, // 22: ethereum.eth.v1alpha1.BeaconStateGloas.previous_justified_checkpoint:type_name -> ethereum.eth.v1alpha1.Checkpoint
32, // 23: ethereum.eth.v1alpha1.BeaconStateGloas.current_justified_checkpoint:type_name -> ethereum.eth.v1alpha1.Checkpoint
32, // 24: ethereum.eth.v1alpha1.BeaconStateGloas.finalized_checkpoint:type_name -> ethereum.eth.v1alpha1.Checkpoint
33, // 25: ethereum.eth.v1alpha1.BeaconStateGloas.current_sync_committee:type_name -> ethereum.eth.v1alpha1.SyncCommittee
33, // 26: ethereum.eth.v1alpha1.BeaconStateGloas.next_sync_committee:type_name -> ethereum.eth.v1alpha1.SyncCommittee
30, // 17: ethereum.eth.v1alpha1.BeaconStateGloas.fork:type_name -> ethereum.eth.v1alpha1.Fork
31, // 18: ethereum.eth.v1alpha1.BeaconStateGloas.latest_block_header:type_name -> ethereum.eth.v1alpha1.BeaconBlockHeader
22, // 19: ethereum.eth.v1alpha1.BeaconStateGloas.eth1_data:type_name -> ethereum.eth.v1alpha1.Eth1Data
22, // 20: ethereum.eth.v1alpha1.BeaconStateGloas.eth1_data_votes:type_name -> ethereum.eth.v1alpha1.Eth1Data
32, // 21: ethereum.eth.v1alpha1.BeaconStateGloas.validators:type_name -> ethereum.eth.v1alpha1.Validator
33, // 22: ethereum.eth.v1alpha1.BeaconStateGloas.previous_justified_checkpoint:type_name -> ethereum.eth.v1alpha1.Checkpoint
33, // 23: ethereum.eth.v1alpha1.BeaconStateGloas.current_justified_checkpoint:type_name -> ethereum.eth.v1alpha1.Checkpoint
33, // 24: ethereum.eth.v1alpha1.BeaconStateGloas.finalized_checkpoint:type_name -> ethereum.eth.v1alpha1.Checkpoint
34, // 25: ethereum.eth.v1alpha1.BeaconStateGloas.current_sync_committee:type_name -> ethereum.eth.v1alpha1.SyncCommittee
34, // 26: ethereum.eth.v1alpha1.BeaconStateGloas.next_sync_committee:type_name -> ethereum.eth.v1alpha1.SyncCommittee
0, // 27: ethereum.eth.v1alpha1.BeaconStateGloas.latest_execution_payload_bid:type_name -> ethereum.eth.v1alpha1.ExecutionPayloadBid
34, // 28: ethereum.eth.v1alpha1.BeaconStateGloas.historical_summaries:type_name -> ethereum.eth.v1alpha1.HistoricalSummary
35, // 29: ethereum.eth.v1alpha1.BeaconStateGloas.pending_deposits:type_name -> ethereum.eth.v1alpha1.PendingDeposit
36, // 30: ethereum.eth.v1alpha1.BeaconStateGloas.pending_partial_withdrawals:type_name -> ethereum.eth.v1alpha1.PendingPartialWithdrawal
37, // 31: ethereum.eth.v1alpha1.BeaconStateGloas.pending_consolidations:type_name -> ethereum.eth.v1alpha1.PendingConsolidation
20, // 32: ethereum.eth.v1alpha1.BeaconStateGloas.builders:type_name -> ethereum.eth.v1alpha1.Builder
13, // 33: ethereum.eth.v1alpha1.BeaconStateGloas.builder_pending_payments:type_name -> ethereum.eth.v1alpha1.BuilderPendingPayment
14, // 34: ethereum.eth.v1alpha1.BeaconStateGloas.builder_pending_withdrawals:type_name -> ethereum.eth.v1alpha1.BuilderPendingWithdrawal
38, // 35: ethereum.eth.v1alpha1.BeaconStateGloas.payload_expected_withdrawals:type_name -> ethereum.engine.v1.Withdrawal
35, // 28: ethereum.eth.v1alpha1.BeaconStateGloas.historical_summaries:type_name -> ethereum.eth.v1alpha1.HistoricalSummary
36, // 29: ethereum.eth.v1alpha1.BeaconStateGloas.pending_deposits:type_name -> ethereum.eth.v1alpha1.PendingDeposit
37, // 30: ethereum.eth.v1alpha1.BeaconStateGloas.pending_partial_withdrawals:type_name -> ethereum.eth.v1alpha1.PendingPartialWithdrawal
38, // 31: ethereum.eth.v1alpha1.BeaconStateGloas.pending_consolidations:type_name -> ethereum.eth.v1alpha1.PendingConsolidation
21, // 32: ethereum.eth.v1alpha1.BeaconStateGloas.builders:type_name -> ethereum.eth.v1alpha1.Builder
14, // 33: ethereum.eth.v1alpha1.BeaconStateGloas.builder_pending_payments:type_name -> ethereum.eth.v1alpha1.BuilderPendingPayment
15, // 34: ethereum.eth.v1alpha1.BeaconStateGloas.builder_pending_withdrawals:type_name -> ethereum.eth.v1alpha1.BuilderPendingWithdrawal
39, // 35: ethereum.eth.v1alpha1.BeaconStateGloas.payload_expected_withdrawals:type_name -> ethereum.engine.v1.Withdrawal
12, // 36: ethereum.eth.v1alpha1.BeaconStateGloas.ptc_window:type_name -> ethereum.eth.v1alpha1.PTCs
14, // 37: ethereum.eth.v1alpha1.BuilderPendingPayment.withdrawal:type_name -> ethereum.eth.v1alpha1.BuilderPendingWithdrawal
39, // 38: ethereum.eth.v1alpha1.ExecutionPayloadEnvelope.payload:type_name -> ethereum.engine.v1.ExecutionPayloadDeneb
40, // 39: ethereum.eth.v1alpha1.ExecutionPayloadEnvelope.execution_requests:type_name -> ethereum.engine.v1.ExecutionRequests
16, // 40: ethereum.eth.v1alpha1.SignedExecutionPayloadEnvelope.message:type_name -> ethereum.eth.v1alpha1.ExecutionPayloadEnvelope
40, // 41: ethereum.eth.v1alpha1.BlindedExecutionPayloadEnvelope.execution_requests:type_name -> ethereum.engine.v1.ExecutionRequests
18, // 42: ethereum.eth.v1alpha1.SignedBlindedExecutionPayloadEnvelope.message:type_name -> ethereum.eth.v1alpha1.BlindedExecutionPayloadEnvelope
43, // [43:43] is the sub-list for method output_type
43, // [43:43] is the sub-list for method input_type
43, // [43:43] is the sub-list for extension type_name
43, // [43:43] is the sub-list for extension extendee
0, // [0:43] is the sub-list for field type_name
8, // 37: ethereum.eth.v1alpha1.BeaconBlockContentsGloas.block:type_name -> ethereum.eth.v1alpha1.BeaconBlockGloas
17, // 38: ethereum.eth.v1alpha1.BeaconBlockContentsGloas.execution_payload_envelope:type_name -> ethereum.eth.v1alpha1.ExecutionPayloadEnvelope
15, // 39: ethereum.eth.v1alpha1.BuilderPendingPayment.withdrawal:type_name -> ethereum.eth.v1alpha1.BuilderPendingWithdrawal
40, // 40: ethereum.eth.v1alpha1.ExecutionPayloadEnvelope.payload:type_name -> ethereum.engine.v1.ExecutionPayloadDeneb
41, // 41: ethereum.eth.v1alpha1.ExecutionPayloadEnvelope.execution_requests:type_name -> ethereum.engine.v1.ExecutionRequests
17, // 42: ethereum.eth.v1alpha1.SignedExecutionPayloadEnvelope.message:type_name -> ethereum.eth.v1alpha1.ExecutionPayloadEnvelope
41, // 43: ethereum.eth.v1alpha1.BlindedExecutionPayloadEnvelope.execution_requests:type_name -> ethereum.engine.v1.ExecutionRequests
19, // 44: ethereum.eth.v1alpha1.SignedBlindedExecutionPayloadEnvelope.message:type_name -> ethereum.eth.v1alpha1.BlindedExecutionPayloadEnvelope
45, // [45:45] is the sub-list for method output_type
45, // [45:45] is the sub-list for method input_type
45, // [45:45] is the sub-list for extension type_name
45, // [45:45] is the sub-list for extension extendee
0, // [0:45] is the sub-list for field type_name
}
func init() { file_proto_prysm_v1alpha1_gloas_proto_init() }
@@ -2627,7 +2717,7 @@ func file_proto_prysm_v1alpha1_gloas_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_prysm_v1alpha1_gloas_proto_rawDesc,
NumEnums: 0,
NumMessages: 21,
NumMessages: 22,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -398,6 +398,22 @@ message PTCs {
];
}
// BeaconBlockContentsGloas bundles a Gloas beacon block with the execution
// payload envelope and associated blob data. This is used for block production
// when include_payload=true.
message BeaconBlockContentsGloas {
BeaconBlockGloas block = 1;
ExecutionPayloadEnvelope execution_payload_envelope = 2;
repeated bytes kzg_proofs = 3 [
(ethereum.eth.ext.ssz_size) = "?,48",
(ethereum.eth.ext.ssz_max) = "max_blob_commitments.size"
];
repeated bytes blobs = 4 [
(ethereum.eth.ext.ssz_size) = "?,blob.size",
(ethereum.eth.ext.ssz_max) = "max_blob_commitments.size"
];
}
// BuilderPendingPayment represents a pending payment to a builder.
//
// Spec:

View File

@@ -3206,6 +3206,254 @@ func (p *PTCs) HashTreeRootWith(hh *ssz.Hasher) (err error) {
return
}
// MarshalSSZ ssz marshals the BeaconBlockContentsGloas object
func (b *BeaconBlockContentsGloas) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(b)
}
// MarshalSSZTo ssz marshals the BeaconBlockContentsGloas object to a target array
func (b *BeaconBlockContentsGloas) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf
offset := int(16)
// Offset (0) 'Block'
dst = ssz.WriteOffset(dst, offset)
if b.Block == nil {
b.Block = new(BeaconBlockGloas)
}
offset += b.Block.SizeSSZ()
// Offset (1) 'ExecutionPayloadEnvelope'
dst = ssz.WriteOffset(dst, offset)
if b.ExecutionPayloadEnvelope == nil {
b.ExecutionPayloadEnvelope = new(ExecutionPayloadEnvelope)
}
offset += b.ExecutionPayloadEnvelope.SizeSSZ()
// Offset (2) 'KzgProofs'
dst = ssz.WriteOffset(dst, offset)
offset += len(b.KzgProofs) * 48
// Offset (3) 'Blobs'
dst = ssz.WriteOffset(dst, offset)
offset += len(b.Blobs) * 131072
// Field (0) 'Block'
if dst, err = b.Block.MarshalSSZTo(dst); err != nil {
return
}
// Field (1) 'ExecutionPayloadEnvelope'
if dst, err = b.ExecutionPayloadEnvelope.MarshalSSZTo(dst); err != nil {
return
}
// Field (2) 'KzgProofs'
if size := len(b.KzgProofs); size > 4096 {
err = ssz.ErrListTooBigFn("--.KzgProofs", size, 4096)
return
}
for ii := 0; ii < len(b.KzgProofs); ii++ {
if size := len(b.KzgProofs[ii]); size != 48 {
err = ssz.ErrBytesLengthFn("--.KzgProofs[ii]", size, 48)
return
}
dst = append(dst, b.KzgProofs[ii]...)
}
// Field (3) 'Blobs'
if size := len(b.Blobs); size > 4096 {
err = ssz.ErrListTooBigFn("--.Blobs", size, 4096)
return
}
for ii := 0; ii < len(b.Blobs); ii++ {
if size := len(b.Blobs[ii]); size != 131072 {
err = ssz.ErrBytesLengthFn("--.Blobs[ii]", size, 131072)
return
}
dst = append(dst, b.Blobs[ii]...)
}
return
}
// UnmarshalSSZ ssz unmarshals the BeaconBlockContentsGloas object
func (b *BeaconBlockContentsGloas) UnmarshalSSZ(buf []byte) error {
var err error
size := uint64(len(buf))
if size < 16 {
return ssz.ErrSize
}
tail := buf
var o0, o1, o2, o3 uint64
// Offset (0) 'Block'
if o0 = ssz.ReadOffset(buf[0:4]); o0 > size {
return ssz.ErrOffset
}
if o0 != 16 {
return ssz.ErrInvalidVariableOffset
}
// Offset (1) 'ExecutionPayloadEnvelope'
if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 {
return ssz.ErrOffset
}
// Offset (2) 'KzgProofs'
if o2 = ssz.ReadOffset(buf[8:12]); o2 > size || o1 > o2 {
return ssz.ErrOffset
}
// Offset (3) 'Blobs'
if o3 = ssz.ReadOffset(buf[12:16]); o3 > size || o2 > o3 {
return ssz.ErrOffset
}
// Field (0) 'Block'
{
buf = tail[o0:o1]
if b.Block == nil {
b.Block = new(BeaconBlockGloas)
}
if err = b.Block.UnmarshalSSZ(buf); err != nil {
return err
}
}
// Field (1) 'ExecutionPayloadEnvelope'
{
buf = tail[o1:o2]
if b.ExecutionPayloadEnvelope == nil {
b.ExecutionPayloadEnvelope = new(ExecutionPayloadEnvelope)
}
if err = b.ExecutionPayloadEnvelope.UnmarshalSSZ(buf); err != nil {
return err
}
}
// Field (2) 'KzgProofs'
{
buf = tail[o2:o3]
num, err := ssz.DivideInt2(len(buf), 48, 4096)
if err != nil {
return err
}
b.KzgProofs = make([][]byte, num)
for ii := 0; ii < num; ii++ {
if cap(b.KzgProofs[ii]) == 0 {
b.KzgProofs[ii] = make([]byte, 0, len(buf[ii*48:(ii+1)*48]))
}
b.KzgProofs[ii] = append(b.KzgProofs[ii], buf[ii*48:(ii+1)*48]...)
}
}
// Field (3) 'Blobs'
{
buf = tail[o3:]
num, err := ssz.DivideInt2(len(buf), 131072, 4096)
if err != nil {
return err
}
b.Blobs = make([][]byte, num)
for ii := 0; ii < num; ii++ {
if cap(b.Blobs[ii]) == 0 {
b.Blobs[ii] = make([]byte, 0, len(buf[ii*131072:(ii+1)*131072]))
}
b.Blobs[ii] = append(b.Blobs[ii], buf[ii*131072:(ii+1)*131072]...)
}
}
return err
}
// SizeSSZ returns the ssz encoded size in bytes for the BeaconBlockContentsGloas object
func (b *BeaconBlockContentsGloas) SizeSSZ() (size int) {
size = 16
// Field (0) 'Block'
if b.Block == nil {
b.Block = new(BeaconBlockGloas)
}
size += b.Block.SizeSSZ()
// Field (1) 'ExecutionPayloadEnvelope'
if b.ExecutionPayloadEnvelope == nil {
b.ExecutionPayloadEnvelope = new(ExecutionPayloadEnvelope)
}
size += b.ExecutionPayloadEnvelope.SizeSSZ()
// Field (2) 'KzgProofs'
size += len(b.KzgProofs) * 48
// Field (3) 'Blobs'
size += len(b.Blobs) * 131072
return
}
// HashTreeRoot ssz hashes the BeaconBlockContentsGloas object
func (b *BeaconBlockContentsGloas) HashTreeRoot() ([32]byte, error) {
return ssz.HashWithDefaultHasher(b)
}
// HashTreeRootWith ssz hashes the BeaconBlockContentsGloas object with a hasher
func (b *BeaconBlockContentsGloas) HashTreeRootWith(hh *ssz.Hasher) (err error) {
indx := hh.Index()
// Field (0) 'Block'
if err = b.Block.HashTreeRootWith(hh); err != nil {
return
}
// Field (1) 'ExecutionPayloadEnvelope'
if err = b.ExecutionPayloadEnvelope.HashTreeRootWith(hh); err != nil {
return
}
// Field (2) 'KzgProofs'
{
if size := len(b.KzgProofs); size > 4096 {
err = ssz.ErrListTooBigFn("--.KzgProofs", size, 4096)
return
}
subIndx := hh.Index()
for _, i := range b.KzgProofs {
if len(i) != 48 {
err = ssz.ErrBytesLength
return
}
hh.PutBytes(i)
}
numItems := uint64(len(b.KzgProofs))
hh.MerkleizeWithMixin(subIndx, numItems, 4096)
}
// Field (3) 'Blobs'
{
if size := len(b.Blobs); size > 4096 {
err = ssz.ErrListTooBigFn("--.Blobs", size, 4096)
return
}
subIndx := hh.Index()
for _, i := range b.Blobs {
if len(i) != 131072 {
err = ssz.ErrBytesLength
return
}
hh.PutBytes(i)
}
numItems := uint64(len(b.Blobs))
hh.MerkleizeWithMixin(subIndx, numItems, 4096)
}
hh.Merkleize(indx)
return
}
// MarshalSSZ ssz marshals the BuilderPendingPayment object
func (b *BuilderPendingPayment) MarshalSSZ() ([]byte, error) {
return ssz.MarshalSSZ(b)

File diff suppressed because it is too large Load Diff

View File

@@ -961,6 +961,12 @@ message BlockRequest {
// Percentage multiplier to apply to the builder's payload value when choosing
// between a builder payload header and payload from the paired execution node
google.protobuf.UInt64Value builder_boost_factor = 5;
// When true, eagerly compute the post-payload state root in the envelope
// during block production. Used by the V4 produce-block endpoint when
// include_payload=true so the envelope is immediately available with a
// populated state root.
bool eager_payload_state_root = 6;
}
message ProposeResponse {

View File

@@ -990,7 +990,7 @@
- name: compute_new_state_root#phase0
sources:
- file: beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go
search: func (vs *Server) computeStateRoot(
search: func (vs *Server) computePostBlockStateAndRoot(
spec: |
<spec fn="compute_new_state_root" fork="phase0" hash="ad3c03b7">
def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root:

View File

@@ -89,6 +89,7 @@ go_test(
"domain_data_test.go",
"doppelganger_test.go",
"duties_test.go",
"execution_payload_envelope_test.go",
"genesis_test.go",
"get_beacon_block_test.go",
"index_test.go",

View File

@@ -1,28 +1,51 @@
package beacon_api
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
)
// TODO: Implement Gloas beacon API client methods.
// getExecutionPayloadEnvelope retrieves the execution payload envelope for the given slot.
func (c *beaconApiValidatorClient) getExecutionPayloadEnvelope(
ctx context.Context,
slot primitives.Slot,
) (*ethpb.ExecutionPayloadEnvelope, error) {
return nil, errors.New("getExecutionPayloadEnvelope not yet implemented")
endpoint := fmt.Sprintf("/eth/v1/validator/execution_payload_envelope/%d", slot)
var resp structs.GetValidatorExecutionPayloadEnvelopeResponse
if err := c.handler.Get(ctx, endpoint, &resp); err != nil {
return nil, errors.Wrap(err, "could not get execution payload envelope")
}
if resp.Data == nil {
return nil, errors.New("execution payload envelope data is nil")
}
envelope, err := resp.Data.ToConsensus()
if err != nil {
return nil, errors.Wrap(err, "could not convert execution payload envelope to consensus")
}
return envelope, nil
}
// publishExecutionPayloadEnvelope broadcasts a signed execution payload envelope.
func (c *beaconApiValidatorClient) publishExecutionPayloadEnvelope(
ctx context.Context,
envelope *ethpb.SignedExecutionPayloadEnvelope,
) (*empty.Empty, error) {
return nil, errors.New("publishExecutionPayloadEnvelope not yet implemented")
jsonEnvelope, err := structs.SignedExecutionPayloadEnvelopeFromConsensus(envelope)
if err != nil {
return nil, errors.Wrap(err, "could not convert envelope to JSON")
}
body, err := json.Marshal(jsonEnvelope)
if err != nil {
return nil, errors.Wrap(err, "could not marshal envelope")
}
if err := c.handler.Post(ctx, "/eth/v1/beacon/execution_payload_envelope", nil, bytes.NewBuffer(body), nil); err != nil {
return nil, errors.Wrap(err, "could not publish execution payload envelope")
}
return &empty.Empty{}, nil
}

View File

@@ -0,0 +1,170 @@
package beacon_api
import (
"bytes"
"encoding/json"
"testing"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/validator/client/beacon-api/mock"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"go.uber.org/mock/gomock"
)
func testProtoEnvelope() *ethpb.ExecutionPayloadEnvelope {
return &ethpb.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: bytesutil.PadTo([]byte("parent"), 32),
FeeRecipient: bytesutil.PadTo([]byte("fee"), 20),
StateRoot: bytesutil.PadTo([]byte("state"), 32),
ReceiptsRoot: bytesutil.PadTo([]byte("receipts"), 32),
LogsBloom: make([]byte, 256),
PrevRandao: bytesutil.PadTo([]byte("randao"), 32),
BaseFeePerGas: bytesutil.PadTo([]byte{1}, 32),
BlockHash: bytesutil.PadTo([]byte("blockhash"), 32),
Transactions: [][]byte{},
Withdrawals: []*enginev1.Withdrawal{},
},
ExecutionRequests: &enginev1.ExecutionRequests{},
BuilderIndex: primitives.BuilderIndex(42),
BeaconBlockRoot: bytesutil.PadTo([]byte("beacon-root"), 32),
Slot: primitives.Slot(100),
StateRoot: bytesutil.PadTo([]byte("envelope-state"), 32),
}
}
func TestGetExecutionPayloadEnvelope_Valid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
envelope := testProtoEnvelope()
jsonEnvelope, err := structs.ExecutionPayloadEnvelopeFromConsensus(envelope)
require.NoError(t, err)
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Get(
gomock.Any(),
"/eth/v1/validator/execution_payload_envelope/100",
gomock.Any(),
).SetArg(
2,
structs.GetValidatorExecutionPayloadEnvelopeResponse{
Version: "gloas",
Data: jsonEnvelope,
},
).Return(nil)
client := &beaconApiValidatorClient{handler: handler}
resp, err := client.getExecutionPayloadEnvelope(t.Context(), 100)
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, primitives.BuilderIndex(42), resp.BuilderIndex)
assert.Equal(t, primitives.Slot(100), resp.Slot)
assert.DeepEqual(t, envelope.BeaconBlockRoot, resp.BeaconBlockRoot)
}
func TestGetExecutionPayloadEnvelope_Error(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Get(
gomock.Any(), gomock.Any(), gomock.Any(),
).Return(errors.New("not found"))
client := &beaconApiValidatorClient{handler: handler}
_, err := client.getExecutionPayloadEnvelope(t.Context(), 999)
assert.ErrorContains(t, "not found", err)
}
func TestGetExecutionPayloadEnvelope_NilData(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Get(
gomock.Any(), gomock.Any(), gomock.Any(),
).SetArg(
2,
structs.GetValidatorExecutionPayloadEnvelopeResponse{
Version: "gloas",
Data: nil,
},
).Return(nil)
client := &beaconApiValidatorClient{handler: handler}
_, err := client.getExecutionPayloadEnvelope(t.Context(), 100)
assert.ErrorContains(t, "execution payload envelope data is nil", err)
}
func TestPublishExecutionPayloadEnvelope_Valid(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
envelope := testProtoEnvelope()
signed := &ethpb.SignedExecutionPayloadEnvelope{
Message: envelope,
Signature: bytesutil.PadTo([]byte("sig"), 96),
}
jsonEnvelope, err := structs.SignedExecutionPayloadEnvelopeFromConsensus(signed)
require.NoError(t, err)
expectedBody, err := json.Marshal(jsonEnvelope)
require.NoError(t, err)
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Post(
gomock.Any(),
"/eth/v1/beacon/execution_payload_envelope",
nil,
bytes.NewBuffer(expectedBody),
nil,
).Return(nil)
client := &beaconApiValidatorClient{handler: handler}
resp, err := client.publishExecutionPayloadEnvelope(t.Context(), signed)
require.NoError(t, err)
require.NotNil(t, resp)
}
func TestPublishExecutionPayloadEnvelope_Error(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
envelope := testProtoEnvelope()
signed := &ethpb.SignedExecutionPayloadEnvelope{
Message: envelope,
Signature: bytesutil.PadTo([]byte("sig"), 96),
}
handler := mock.NewMockJsonRestHandler(ctrl)
handler.EXPECT().Post(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(errors.New("server error"))
client := &beaconApiValidatorClient{handler: handler}
_, err := client.publishExecutionPayloadEnvelope(t.Context(), signed)
assert.ErrorContains(t, "server error", err)
}
func TestEnvelopeRoundTrip(t *testing.T) {
envelope := testProtoEnvelope()
jsonEnvelope, err := structs.ExecutionPayloadEnvelopeFromConsensus(envelope)
require.NoError(t, err)
roundTripped, err := jsonEnvelope.ToConsensus()
require.NoError(t, err)
assert.Equal(t, envelope.BuilderIndex, roundTripped.BuilderIndex)
assert.Equal(t, envelope.Slot, roundTripped.Slot)
assert.DeepEqual(t, envelope.BeaconBlockRoot, roundTripped.BeaconBlockRoot)
assert.DeepEqual(t, envelope.StateRoot, roundTripped.StateRoot)
assert.Equal(t, hexutil.Encode(envelope.Payload.BlockHash), hexutil.Encode(roundTripped.Payload.BlockHash))
}

View File

@@ -12,9 +12,11 @@ import (
"github.com/OffchainLabs/prysm/v7/api"
"github.com/OffchainLabs/prysm/v7/api/apiutil"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
)
@@ -25,6 +27,11 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
if len(graffiti) > 0 {
queryParams.Add("graffiti", hexutil.Encode(graffiti))
}
if slots.ToEpoch(slot) >= params.BeaconConfig().GloasForkEpoch {
return c.beaconBlockV4(ctx, slot, queryParams)
}
queryUrl := apiutil.BuildURL(fmt.Sprintf("/eth/v3/validator/blocks/%d", slot), queryParams)
data, header, err := c.handler.GetSSZ(ctx, queryUrl)
if err != nil {
@@ -55,6 +62,58 @@ func (c *beaconApiValidatorClient) beaconBlock(ctx context.Context, slot primiti
}
}
func (c *beaconApiValidatorClient) beaconBlockV4(ctx context.Context, slot primitives.Slot, queryParams neturl.Values) (*ethpb.GenericBeaconBlock, error) {
queryUrl := apiutil.BuildURL(fmt.Sprintf("/eth/v4/validator/blocks/%d", slot), queryParams)
data, header, err := c.handler.GetSSZ(ctx, queryUrl)
if err != nil {
return nil, errors.Wrap(err, "could not get v4 beacon block")
}
if strings.Contains(header.Get("Content-Type"), api.OctetStreamMediaType) {
payloadIncluded := header.Get(api.ExecutionPayloadIncludedHeader) == "true"
if payloadIncluded {
contents := &ethpb.BeaconBlockContentsGloas{}
if err := contents.UnmarshalSSZ(data); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal gloas block contents SSZ")
}
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Gloas{Gloas: contents.Block}}, nil
}
block := &ethpb.BeaconBlockGloas{}
if err := block.UnmarshalSSZ(data); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal gloas block SSZ")
}
return &ethpb.GenericBeaconBlock{Block: &ethpb.GenericBeaconBlock_Gloas{Gloas: block}}, nil
}
resp := structs.ProduceBlockV4Response{}
if err := json.Unmarshal(data, &resp); err != nil {
return nil, errors.Wrapf(err, "failed to decode v4 response body for %s", queryUrl)
}
if resp.ExecutionPayloadIncluded {
contents := &structs.BlockContentsGloas{}
if err := json.Unmarshal(resp.Data, contents); err != nil {
return nil, errors.Wrap(err, "failed to decode gloas block contents")
}
if contents.Block == nil {
return nil, errors.New("gloas block contents has nil block")
}
blk, err := contents.Block.ToGeneric()
if err != nil {
return nil, errors.Wrap(err, "could not convert gloas block contents to generic")
}
return blk, nil
}
block := &structs.BeaconBlockGloas{}
if err := json.Unmarshal(resp.Data, block); err != nil {
return nil, errors.Wrap(err, "failed to decode gloas block")
}
blk, err := block.ToGeneric()
if err != nil {
return nil, errors.Wrap(err, "could not convert gloas block to generic")
}
return blk, nil
}
// sszBlockCodec defines SSZ unmarshalers for a fork's block and blinded block types.
type sszBlockCodec struct {
unmarshalBlock func([]byte) (*ethpb.GenericBeaconBlock, error)

View File

@@ -9,6 +9,7 @@ import (
"github.com/OffchainLabs/prysm/v7/api"
"github.com/OffchainLabs/prysm/v7/api/server/structs"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
@@ -1346,3 +1347,198 @@ func TestGetBeaconBlock_BlindedElectraValid(t *testing.T) {
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}
func setupGloasConfig(t *testing.T) {
t.Helper()
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
}
func TestGetBeaconBlock_GloasValid_JSON_WithPayload(t *testing.T) {
setupGloasConfig(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
proto := testhelpers.GenerateProtoGloasBeaconBlock()
block := testhelpers.GenerateJsonGloasBeaconBlock()
blockContents := &structs.BlockContentsGloas{
Block: block,
ExecutionPayloadEnvelope: nil,
KzgProofs: []string{},
Blobs: []string{},
}
dataBytes, err := json.Marshal(blockContents)
require.NoError(t, err)
const slot = primitives.Slot(1)
randaoReveal := []byte{2}
graffiti := []byte{3}
ctx := t.Context()
b, err := json.Marshal(structs.ProduceBlockV4Response{
Version: "gloas",
ConsensusBlockValue: "0",
ExecutionPayloadIncluded: true,
Data: dataBytes,
})
require.NoError(t, err)
handler := mock.NewMockHandler(ctrl)
handler.EXPECT().GetSSZ(
gomock.Any(),
fmt.Sprintf("/eth/v4/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)),
).Return(
b,
http.Header{"Content-Type": []string{"application/json"}},
nil,
).Times(1)
validatorClient := &beaconApiValidatorClient{handler: handler}
beaconBlock, err := validatorClient.beaconBlock(ctx, slot, randaoReveal, graffiti)
require.NoError(t, err)
expectedBeaconBlock := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Gloas{
Gloas: proto,
},
}
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}
func TestGetBeaconBlock_GloasValid_JSON_WithoutPayload(t *testing.T) {
setupGloasConfig(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
proto := testhelpers.GenerateProtoGloasBeaconBlock()
block := testhelpers.GenerateJsonGloasBeaconBlock()
dataBytes, err := json.Marshal(block)
require.NoError(t, err)
const slot = primitives.Slot(1)
randaoReveal := []byte{2}
graffiti := []byte{3}
ctx := t.Context()
b, err := json.Marshal(structs.ProduceBlockV4Response{
Version: "gloas",
ConsensusBlockValue: "0",
ExecutionPayloadIncluded: false,
Data: dataBytes,
})
require.NoError(t, err)
handler := mock.NewMockHandler(ctrl)
handler.EXPECT().GetSSZ(
gomock.Any(),
fmt.Sprintf("/eth/v4/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)),
).Return(
b,
http.Header{"Content-Type": []string{"application/json"}},
nil,
).Times(1)
validatorClient := &beaconApiValidatorClient{handler: handler}
beaconBlock, err := validatorClient.beaconBlock(ctx, slot, randaoReveal, graffiti)
require.NoError(t, err)
expectedBeaconBlock := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Gloas{
Gloas: proto,
},
}
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}
func TestGetBeaconBlock_GloasValid_SSZ_WithPayload(t *testing.T) {
setupGloasConfig(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
proto := testhelpers.GenerateProtoGloasBeaconBlock()
contents := &ethpb.BeaconBlockContentsGloas{
Block: proto,
ExecutionPayloadEnvelope: testhelpers.GenerateProtoExecutionPayloadEnvelope(),
}
sszBytes, err := contents.MarshalSSZ()
require.NoError(t, err)
const slot = primitives.Slot(1)
randaoReveal := []byte{2}
graffiti := []byte{3}
ctx := t.Context()
handler := mock.NewMockHandler(ctrl)
handler.EXPECT().GetSSZ(
gomock.Any(),
fmt.Sprintf("/eth/v4/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)),
).Return(
sszBytes,
http.Header{
"Content-Type": []string{api.OctetStreamMediaType},
api.VersionHeader: []string{"gloas"},
api.ExecutionPayloadIncludedHeader: []string{"true"},
},
nil,
).Times(1)
validatorClient := &beaconApiValidatorClient{handler: handler}
beaconBlock, err := validatorClient.beaconBlock(ctx, slot, randaoReveal, graffiti)
require.NoError(t, err)
expectedBeaconBlock := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Gloas{
Gloas: proto,
},
}
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}
func TestGetBeaconBlock_GloasValid_SSZ_WithoutPayload(t *testing.T) {
setupGloasConfig(t)
ctrl := gomock.NewController(t)
defer ctrl.Finish()
proto := testhelpers.GenerateProtoGloasBeaconBlock()
sszBytes, err := proto.MarshalSSZ()
require.NoError(t, err)
const slot = primitives.Slot(1)
randaoReveal := []byte{2}
graffiti := []byte{3}
ctx := t.Context()
handler := mock.NewMockHandler(ctrl)
handler.EXPECT().GetSSZ(
gomock.Any(),
fmt.Sprintf("/eth/v4/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)),
).Return(
sszBytes,
http.Header{
"Content-Type": []string{api.OctetStreamMediaType},
api.VersionHeader: []string{"gloas"},
api.ExecutionPayloadIncludedHeader: []string{"false"},
},
nil,
).Times(1)
validatorClient := &beaconApiValidatorClient{handler: handler}
beaconBlock, err := validatorClient.beaconBlock(ctx, slot, randaoReveal, graffiti)
require.NoError(t, err)
expectedBeaconBlock := &ethpb.GenericBeaconBlock{
Block: &ethpb.GenericBeaconBlock_Gloas{
Gloas: proto,
},
}
assert.DeepEqual(t, expectedBeaconBlock, beaconBlock)
}

View File

@@ -144,6 +144,14 @@ func (c *beaconApiValidatorClient) proposeBeaconBlock(ctx context.Context, in *e
}
return json.Marshal(signedBlock)
})
case *ethpb.GenericSignedBeaconBlock_Gloas:
res, err = buildBlockResult("gloas", false, blockType.Gloas, blockType.Gloas.Block, func() ([]byte, error) {
signedBlock, err := structs.SignedBeaconBlockGloasFromConsensus(blockType.Gloas)
if err != nil {
return nil, errors.Wrap(err, "failed to convert gloas beacon block")
}
return json.Marshal(signedBlock)
})
default:
return nil, errors.Errorf("unsupported block type %T", in.Block)
}

View File

@@ -10,6 +10,7 @@ go_library(
"deneb_beacon_block_test_helpers.go",
"electra_beacon_block_test_helpers.go",
"fulu_beacon_block_test_helpers.go",
"gloas_beacon_block_test_helpers.go",
"phase0_beacon_block_test_helpers.go",
"test_helpers.go",
],

View File

@@ -0,0 +1,165 @@
package test_helpers
import (
"github.com/OffchainLabs/prysm/v7/api/server/structs"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func GenerateProtoGloasBeaconBlock() *ethpb.BeaconBlockGloas {
return &ethpb.BeaconBlockGloas{
Slot: 1,
ProposerIndex: 2,
ParentRoot: FillByteSlice(32, 3),
StateRoot: FillByteSlice(32, 4),
Body: &ethpb.BeaconBlockBodyGloas{
RandaoReveal: FillByteSlice(96, 5),
Eth1Data: &ethpb.Eth1Data{
DepositRoot: FillByteSlice(32, 6),
DepositCount: 7,
BlockHash: FillByteSlice(32, 8),
},
Graffiti: FillByteSlice(32, 9),
ProposerSlashings: []*ethpb.ProposerSlashing{
{
Header_1: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 11,
ParentRoot: FillByteSlice(32, 12),
StateRoot: FillByteSlice(32, 13),
BodyRoot: FillByteSlice(32, 14),
},
Signature: FillByteSlice(96, 15),
},
Header_2: &ethpb.SignedBeaconBlockHeader{
Header: &ethpb.BeaconBlockHeader{
Slot: 16,
ProposerIndex: 17,
ParentRoot: FillByteSlice(32, 18),
StateRoot: FillByteSlice(32, 19),
BodyRoot: FillByteSlice(32, 20),
},
Signature: FillByteSlice(96, 21),
},
},
},
AttesterSlashings: []*ethpb.AttesterSlashingElectra{},
Attestations: []*ethpb.AttestationElectra{},
Deposits: []*ethpb.Deposit{},
VoluntaryExits: []*ethpb.SignedVoluntaryExit{},
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: FillByteSlice(64, 100),
SyncCommitteeSignature: FillByteSlice(96, 101),
},
BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{},
SignedExecutionPayloadBid: &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
ParentBlockHash: FillByteSlice(32, 110),
ParentBlockRoot: FillByteSlice(32, 111),
BlockHash: FillByteSlice(32, 112),
PrevRandao: FillByteSlice(32, 113),
FeeRecipient: FillByteSlice(20, 114),
GasLimit: 120,
BuilderIndex: 121,
Slot: 1,
Value: 123,
ExecutionPayment: 124,
BlobKzgCommitments: [][]byte{},
},
Signature: FillByteSlice(96, 130),
},
PayloadAttestations: []*ethpb.PayloadAttestation{},
},
}
}
func GenerateJsonGloasBeaconBlock() *structs.BeaconBlockGloas {
return &structs.BeaconBlockGloas{
Slot: "1",
ProposerIndex: "2",
ParentRoot: hexutil.Encode(FillByteSlice(32, 3)),
StateRoot: hexutil.Encode(FillByteSlice(32, 4)),
Body: &structs.BeaconBlockBodyGloas{
RandaoReveal: hexutil.Encode(FillByteSlice(96, 5)),
Eth1Data: &structs.Eth1Data{
DepositRoot: hexutil.Encode(FillByteSlice(32, 6)),
DepositCount: "7",
BlockHash: hexutil.Encode(FillByteSlice(32, 8)),
},
Graffiti: hexutil.Encode(FillByteSlice(32, 9)),
ProposerSlashings: []*structs.ProposerSlashing{
{
SignedHeader1: &structs.SignedBeaconBlockHeader{
Message: &structs.BeaconBlockHeader{
Slot: "10",
ProposerIndex: "11",
ParentRoot: hexutil.Encode(FillByteSlice(32, 12)),
StateRoot: hexutil.Encode(FillByteSlice(32, 13)),
BodyRoot: hexutil.Encode(FillByteSlice(32, 14)),
},
Signature: hexutil.Encode(FillByteSlice(96, 15)),
},
SignedHeader2: &structs.SignedBeaconBlockHeader{
Message: &structs.BeaconBlockHeader{
Slot: "16",
ProposerIndex: "17",
ParentRoot: hexutil.Encode(FillByteSlice(32, 18)),
StateRoot: hexutil.Encode(FillByteSlice(32, 19)),
BodyRoot: hexutil.Encode(FillByteSlice(32, 20)),
},
Signature: hexutil.Encode(FillByteSlice(96, 21)),
},
},
},
AttesterSlashings: []*structs.AttesterSlashingElectra{},
Attestations: []*structs.AttestationElectra{},
Deposits: []*structs.Deposit{},
VoluntaryExits: []*structs.SignedVoluntaryExit{},
SyncAggregate: &structs.SyncAggregate{
SyncCommitteeBits: hexutil.Encode(FillByteSlice(64, 100)),
SyncCommitteeSignature: hexutil.Encode(FillByteSlice(96, 101)),
},
BLSToExecutionChanges: []*structs.SignedBLSToExecutionChange{},
SignedExecutionPayloadBid: &structs.SignedExecutionPayloadBid{
Message: &structs.ExecutionPayloadBid{
ParentBlockHash: hexutil.Encode(FillByteSlice(32, 110)),
ParentBlockRoot: hexutil.Encode(FillByteSlice(32, 111)),
BlockHash: hexutil.Encode(FillByteSlice(32, 112)),
PrevRandao: hexutil.Encode(FillByteSlice(32, 113)),
FeeRecipient: hexutil.Encode(FillByteSlice(20, 114)),
GasLimit: "120",
BuilderIndex: "121",
Slot: "1",
Value: "123",
ExecutionPayment: "124",
BlobKzgCommitments: []string{},
},
Signature: hexutil.Encode(FillByteSlice(96, 130)),
},
PayloadAttestations: []*structs.PayloadAttestation{},
},
}
}
func GenerateProtoExecutionPayloadEnvelope() *ethpb.ExecutionPayloadEnvelope {
return &ethpb.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: FillByteSlice(32, 200),
FeeRecipient: FillByteSlice(20, 201),
StateRoot: FillByteSlice(32, 202),
ReceiptsRoot: FillByteSlice(32, 203),
LogsBloom: FillByteSlice(256, 204),
PrevRandao: FillByteSlice(32, 205),
BaseFeePerGas: FillByteSlice(32, 206),
BlockHash: FillByteSlice(32, 207),
ExtraData: make([]byte, 0),
},
ExecutionRequests: &enginev1.ExecutionRequests{},
BuilderIndex: 121,
BeaconBlockRoot: FillByteSlice(32, 210),
Slot: 1,
StateRoot: FillByteSlice(32, 211),
}
}

View File

@@ -223,6 +223,7 @@ func (v *ValidatorService) Start() {
emitAccountMetrics: v.emitAccountMetrics,
enableAPI: v.enableAPI,
duties: &dutyStore{},
submittedPrefSlots: make(map[primitives.Slot]bool),
distributed: v.distributed,
disableDutiesPolling: v.disableDutiesPolling,
accountsChangedChannel: make(chan [][fieldparams.BLSPubkeyLength]byte, 1),

View File

@@ -89,6 +89,7 @@ type validator struct {
interopKeysConfig *local.InteropKeymanagerConfig
duties *dutyStore
signedValidatorRegistrations map[[fieldparams.BLSPubkeyLength]byte]*ethpb.SignedValidatorRegistrationV1
submittedPrefSlots map[primitives.Slot]bool
proposerSettings *proposer.Settings
web3SignerConfig *remoteweb3signer.SetupConfig
startBalances map[[fieldparams.BLSPubkeyLength]byte]uint64
@@ -1027,7 +1028,15 @@ func (v *validator) buildProposerSettingsRequests(
}
// buildProposerPreferences creates signed proposer preferences for validators
// that have proposer slots in the next epoch.
// that have proposer slots in the current epoch (future slots) or next epoch.
//
// Current-epoch preferences are submitted after the first slot of the epoch
// (slot 0 is skipped to avoid stale state after epoch transition). If the
// validator client starts mid-epoch, preferences are submitted for all
// remaining future slots in the epoch.
// Next-epoch preferences are submitted at or after mid-epoch to ensure beacon
// nodes have processed the epoch transition.
// Already-submitted slots are tracked to avoid duplicate signing and RPC calls.
func (v *validator) buildProposerPreferences(
ctx context.Context,
km keymanager.IKeymanager,
@@ -1038,27 +1047,23 @@ func (v *validator) buildProposerPreferences(
if currentEpoch+1 < gloasEpoch {
return nil
}
// Send once per epoch at mid-epoch so beacon nodes have processed the
// epoch transition and updated their ProposerLookahead.
epochStart, err := slots.EpochStart(currentEpoch)
if err != nil {
return nil
}
midEpoch := epochStart + params.BeaconConfig().SlotsPerEpoch/2
if slot != midEpoch {
return nil
for s := range v.submittedPrefSlots {
if s < epochStart {
delete(v.submittedPrefSlots, s)
}
}
v.dutiesLock.RLock()
defer v.dutiesLock.RUnlock()
if !v.duties.IsInitialized() {
log.Debug("Duties not yet initialized, skipping proposer preferences")
return nil
}
nextDuties := v.duties.NextEpochDuties()
if len(nextDuties) == 0 {
return nil
}
@@ -1066,55 +1071,99 @@ func (v *validator) buildProposerPreferences(
var signedPrefs []*ethpb.SignedProposerPreferences
var sigFailCount int
for pk, duty := range nextDuties {
if len(duty.ProposerSlots) == 0 {
continue
}
if duty.Status != ethpb.ValidatorStatus_ACTIVE && duty.Status != ethpb.ValidatorStatus_EXITING {
continue
}
feeRecipient := common.HexToAddress(params.BeaconConfig().EthBurnAddressHex)
gasLimit := params.BeaconConfig().DefaultBuilderGasLimit
if ps != nil && ps.DefaultConfig != nil {
if ps.DefaultConfig.FeeRecipientConfig != nil {
feeRecipient = ps.DefaultConfig.FeeRecipientConfig.FeeRecipient
}
if ps.DefaultConfig.BuilderConfig != nil && ps.DefaultConfig.BuilderConfig.Enabled {
gasLimit = uint64(ps.DefaultConfig.BuilderConfig.GasLimit)
}
}
if ps != nil && ps.ProposeConfig != nil {
if config, ok := ps.ProposeConfig[pk]; ok && config != nil {
if config.FeeRecipientConfig != nil {
feeRecipient = config.FeeRecipientConfig.FeeRecipient
}
if config.BuilderConfig != nil && config.BuilderConfig.Enabled {
gasLimit = uint64(config.BuilderConfig.GasLimit)
}
}
}
for _, proposalSlot := range duty.ProposerSlots {
pref := &ethpb.ProposerPreferences{
ProposalSlot: proposalSlot,
ValidatorIndex: duty.ValidatorIndex,
FeeRecipient: feeRecipient[:],
GasLimit: gasLimit,
}
signedPref, err := v.signProposerPreferences(ctx, km, pk, pref)
if err != nil {
sigFailCount++
processDuties := func(duties map[pubkey]*ethpb.ValidatorDuty, isNextEpoch bool) {
for pk, duty := range duties {
if len(duty.ProposerSlots) == 0 {
continue
}
signedPrefs = append(signedPrefs, signedPref)
if duty.Status != ethpb.ValidatorStatus_ACTIVE && duty.Status != ethpb.ValidatorStatus_EXITING {
continue
}
feeRecipient := common.HexToAddress(params.BeaconConfig().EthBurnAddressHex)
gasLimit := params.BeaconConfig().DefaultBuilderGasLimit
if ps != nil && ps.DefaultConfig != nil {
if ps.DefaultConfig.FeeRecipientConfig != nil {
feeRecipient = ps.DefaultConfig.FeeRecipientConfig.FeeRecipient
}
if ps.DefaultConfig.BuilderConfig != nil && ps.DefaultConfig.BuilderConfig.Enabled {
gasLimit = uint64(ps.DefaultConfig.BuilderConfig.GasLimit)
}
}
if ps != nil && ps.ProposeConfig != nil {
if config, ok := ps.ProposeConfig[pk]; ok && config != nil {
if config.FeeRecipientConfig != nil {
feeRecipient = config.FeeRecipientConfig.FeeRecipient
}
if config.BuilderConfig != nil && config.BuilderConfig.Enabled {
gasLimit = uint64(config.BuilderConfig.GasLimit)
}
}
}
for _, proposalSlot := range duty.ProposerSlots {
if v.submittedPrefSlots[proposalSlot] {
continue
}
// Skip slots that have passed or are too close. Preferences are
// submitted at mid-slot, so the proposer needs to be at least 1
// full slot away for the beacon node to receive them in time.
if !isNextEpoch && proposalSlot <= slot+1 {
continue
}
pref := &ethpb.ProposerPreferences{
ProposalSlot: proposalSlot,
ValidatorIndex: duty.ValidatorIndex,
FeeRecipient: feeRecipient[:],
GasLimit: gasLimit,
}
signedPref, err := v.signProposerPreferences(ctx, km, pk, pref)
if err != nil {
sigFailCount++
continue
}
signedPrefs = append(signedPrefs, signedPref)
v.submittedPrefSlots[proposalSlot] = true
}
}
}
currentDuties := v.duties.CurrentEpochDuties()
nextDuties := v.duties.NextEpochDuties()
var currentProposerCount, nextProposerCount int
for _, d := range currentDuties {
currentProposerCount += len(d.ProposerSlots)
}
for _, d := range nextDuties {
nextProposerCount += len(d.ProposerSlots)
}
// Current-epoch: submit after first slot of epoch to avoid stale state.
// Only post-gloas — current-epoch prefs before gloas would be rejected.
if currentEpoch >= gloasEpoch && slot > epochStart {
processDuties(currentDuties, false)
}
// Next-epoch: submit at or after mid-epoch.
if slot >= midEpoch {
processDuties(nextDuties, true)
}
if sigFailCount > 0 {
log.WithField("count", sigFailCount).Warn("Failed to sign proposer preferences")
}
log.WithFields(logrus.Fields{
"slot": slot,
"epoch": currentEpoch,
"epochStart": epochStart,
"midEpoch": midEpoch,
"currentProposerSlots": currentProposerCount,
"nextProposerSlots": nextProposerCount,
"prefsBuilt": len(signedPrefs),
"alreadySubmitted": len(v.submittedPrefSlots),
}).Debug("Build proposer preferences result")
return signedPrefs
}

View File

@@ -2157,7 +2157,8 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
index: 1,
},
},
duties: &dutyStore{},
duties: &dutyStore{},
submittedPrefSlots: make(map[primitives.Slot]bool),
}
t.Run("pre-gloas returns nil", func(t *testing.T) {
@@ -2185,6 +2186,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2212,6 +2214,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2252,6 +2255,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2281,6 +2285,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2314,6 +2319,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
slot2 := params.BeaconConfig().SlotsPerEpoch + 5
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2348,6 +2354,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2400,6 +2407,7 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
}
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
@@ -2437,6 +2445,237 @@ func TestValidator_buildProposerPreferences(t *testing.T) {
},
}
})
t.Run("current epoch proposer slot included", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
currentEpochSlot := primitives.Slot(3)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{currentEpochSlot},
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
},
},
})
// Slot 1 (past epoch start) allows current-epoch preferences.
prefs := v.buildProposerPreferences(t.Context(), km, 1)
require.Equal(t, 1, len(prefs))
require.Equal(t, currentEpochSlot, prefs[0].Message.ProposalSlot)
})
t.Run("both current and next epoch proposer slots included", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
// Current-epoch slot must be after midEpoch so it's still in the future.
currentEpochSlot := midEpochSlot + 3
nextEpochSlot := params.BeaconConfig().SlotsPerEpoch + 2
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{currentEpochSlot},
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{nextEpochSlot},
},
},
})
// At mid-epoch, both current and next epoch preferences are eligible.
prefs := v.buildProposerPreferences(t.Context(), km, midEpochSlot)
require.Equal(t, 2, len(prefs))
gotSlots := []primitives.Slot{prefs[0].Message.ProposalSlot, prefs[1].Message.ProposalSlot}
slices.Sort(gotSlots)
require.Equal(t, currentEpochSlot, gotSlots[0])
require.Equal(t, nextEpochSlot, gotSlots[1])
})
t.Run("epoch start skips current epoch preferences", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{3},
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
},
},
})
// Slot 0 (epoch start) skips current-epoch preferences.
prefs := v.buildProposerPreferences(t.Context(), km, 0)
require.Equal(t, 0, len(prefs))
})
t.Run("second call deduplicates already submitted slots", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{5},
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
},
},
})
prefs := v.buildProposerPreferences(t.Context(), km, 1)
require.Equal(t, 1, len(prefs))
// Second call returns nothing — slot already submitted.
prefs = v.buildProposerPreferences(t.Context(), km, 2)
require.Equal(t, 0, len(prefs))
})
t.Run("new validator added after initial submission", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
kp2 := randKeypair(t)
require.NoError(t, km.add(kp2))
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{5},
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
},
},
})
prefs := v.buildProposerPreferences(t.Context(), km, 1)
require.Equal(t, 1, len(prefs))
// Simulate new validator added with a different proposal slot.
v.pubkeyToStatus[kp2.pub] = &validatorStatus{
publicKey: kp2.pub[:],
status: &ethpb.ValidatorStatusResponse{Status: ethpb.ValidatorStatus_ACTIVE},
index: 2,
}
v.duties = &dutyStore{}
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{5},
},
{
PublicKey: kp2.pub[:],
ValidatorIndex: 2,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{7},
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{},
})
// Only the new validator's slot is submitted.
prefs = v.buildProposerPreferences(t.Context(), km, 2)
require.Equal(t, 1, len(prefs))
require.Equal(t, primitives.Slot(7), prefs[0].Message.ProposalSlot)
delete(v.pubkeyToStatus, kp2.pub)
delete(km.keysMap, kp2.pub)
})
t.Run("next epoch before mid-epoch returns nil", func(t *testing.T) {
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
v.duties = &dutyStore{}
v.submittedPrefSlots = make(map[primitives.Slot]bool)
v.duties.SetFromCombinedDutiesResponse(&ethpb.ValidatorDutiesContainer{
CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
},
},
NextEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: kp.pub[:],
ValidatorIndex: 1,
Status: ethpb.ValidatorStatus_ACTIVE,
ProposerSlots: []primitives.Slot{nextEpochProposerSlot},
},
},
})
// Slot 1 is before mid-epoch, next-epoch prefs should not be sent.
prefs := v.buildProposerPreferences(t.Context(), km, 1)
require.Equal(t, 0, len(prefs))
})
}
func TestValidator_buildSignedRegReqs_DefaultConfigDisabled(t *testing.T) {