Compare commits

...

19 Commits

Author SHA1 Message Date
terence
e716037f31 Review feedback round 1 2026-04-17 14:38:58 -07:00
terence
d8c2a1f610 Add spec tests 2026-04-16 15:04:21 -07:00
terence
ae65871466 Update API structs, test utilities, and changelog
Mechanical updates for deferred payload processing:
- Update API structs for parent_execution_requests in block body
- Update test block/state utilities for new proto fields
- Update assertion helpers
- Add changelog entry
2026-04-16 15:04:21 -07:00
terence
f7c9fa6041 Update gossip validation for deferred processing
- Add validateParentExecutionRequests in Gloas block gossip validation:
  verifies parent_execution_requests against parent bid
- Use ProcessSlotsUsingNextSlotCache directly in validate_beacon_blocks
- Simplify envelope validation (no state mutations)
- Use shared IsEmptyExecutionRequests from core/gloas
- Update verification bid tests for new proto fields
2026-04-16 15:04:21 -07:00
terence
c338473fa1 Update proposer for deferred payload processing
- Set parent_execution_requests in block body during proposal
- Add computePostPayloadStateRoot for lazy envelope state root
- Update bid construction with parent_block_hash and
  execution_requests_root
- Simplify envelope handling (verification only, no state mutations)
- Remove PayloadContentLookup usage from head state lookup
- Remove dead parameter from handleSuccesfulReorgAttempt
2026-04-16 15:04:21 -07:00
terence
2059e19083 Remove dual-key state access infrastructure
With deferred processing, states and caches are always keyed by beacon
block root. The old pattern of sometimes using execution block hash
(accessRoot) is no longer needed.

Removed:
- PayloadContentLookup from forkchoice interfaces and implementations
- accessRoot parameter from getPayloadAttribute, getPayloadAttributeGloas,
  refreshCaches, updateCachesAndEpochBoundary
- loadStateByBlockHash, blockRootForExecHash from stategen
- executionPayloadEnvelopeProvider interface from replayer
- SaveStateSummary with execution block hash in batch processing
- handleEpochBoundary with block hash in postPayloadTasks

Changed:
- FCU notifications use forkchoice BlockHash instead of st.LatestBlockHash
- getPayloadAttribute always uses headRoot for NSC lookup
2026-04-16 15:04:21 -07:00
terence
134a64adc3 Wire ProcessParentExecutionPayload into block state transition
- Call ProcessParentExecutionPayload in ProcessBlockForStateRoot before
  process_block_header (per spec ordering)
- Remove ProcessSlotsForBlock: callers use ProcessSlotsUsingNextSlotCache
  with ParentRoot directly. The dual-key NSC logic (block root vs block
  hash) is no longer needed
- Rename accessRoot parameter to parentRoot in ProcessSlotsIfNeeded
2026-04-16 15:04:21 -07:00
terence
eea5870cba Make ProcessExecutionPayload verification-only
With deferred processing, ProcessExecutionPayload no longer mutates
state. It only verifies the payload against the execution engine.

- Strip state mutations from ProcessExecutionPayload
- Remove ApplyExecutionPayload (state mutations moved to
  ProcessParentExecutionPayload in commit 2)
- Simplify savePostPayload: only save envelope to DB, no state save
  with execution block hash
- Remove head.full field: payload receipt no longer changes the head
- Remove state_root computation from envelope processing
- Update verification pipeline with new requirements
2026-04-16 15:04:21 -07:00
terence
1aced9e85d Implement ProcessParentExecutionPayload
Add the core spec function for deferred payload processing. When a
block is processed, it checks if its parent had a full payload by
comparing bid.parent_block_hash with the parent bid's block_hash.

If the parent was full:
- Set execution payload availability bit
- Verify and process parent execution requests (deposits, withdrawals,
  consolidations)
- Queue builder payment for the parent slot
- Update latest_block_hash

If the parent was empty:
- Assert parent_execution_requests is empty
- Return early

State:
- Add QueueBuilderPaymentForSlot implementation
- Refactor QueueBuilderPayment to share logic via queueBuilderPaymentAtIndex

Also update UpgradeToGloas to set ExecutionRequestsRoot on the seeded bid.
2026-04-16 15:04:21 -07:00
terence
4c207aa068 Add proto fields and types for deferred payload processing
Under deferred processing (consensus-specs#5094), execution requests
are no longer applied when the payload envelope arrives. Instead the
next block includes the parent's execution requests in its body.

Proto:
- Add execution_requests_root to ExecutionPayloadBid
- Add parent_execution_requests to BeaconBlockBodyGloas
- Remove state_root from ExecutionPayloadEnvelope
- Deep copy ParentExecutionRequests in cloner

Consensus types:
- Add ParentBlockHash() getter on signed execution payload bid
- Add ParentExecutionRequests() to beacon block body interface
- Add setter for parent execution requests on block body
- Remove ExecutionRequestsHash from envelope interface

State:
- Add QueueBuilderPaymentForSlot to state interface
2026-04-16 15:04:21 -07: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
178 changed files with 5686 additions and 4323 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

@@ -509,17 +509,18 @@ func (s *SignedBlindedBeaconBlockFulu) SigString() string {
// ----------------------------------------------------------------------------
type ExecutionPayloadBid struct {
ParentBlockHash string `json:"parent_block_hash"`
ParentBlockRoot string `json:"parent_block_root"`
BlockHash string `json:"block_hash"`
PrevRandao string `json:"prev_randao"`
FeeRecipient string `json:"fee_recipient"`
GasLimit string `json:"gas_limit"`
BuilderIndex string `json:"builder_index"`
Slot string `json:"slot"`
Value string `json:"value"`
ExecutionPayment string `json:"execution_payment"`
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
ParentBlockHash string `json:"parent_block_hash"`
ParentBlockRoot string `json:"parent_block_root"`
BlockHash string `json:"block_hash"`
PrevRandao string `json:"prev_randao"`
FeeRecipient string `json:"fee_recipient"`
GasLimit string `json:"gas_limit"`
BuilderIndex string `json:"builder_index"`
Slot string `json:"slot"`
Value string `json:"value"`
ExecutionPayment string `json:"execution_payment"`
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
ExecutionRequestsRoot string `json:"execution_requests_root"`
}
type SignedExecutionPayloadBid struct {
@@ -559,6 +560,7 @@ type BeaconBlockBodyGloas struct {
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
SignedExecutionPayloadBid *SignedExecutionPayloadBid `json:"signed_execution_payload_bid"`
PayloadAttestations []*PayloadAttestation `json:"payload_attestations"`
ParentExecutionRequests *ExecutionRequests `json:"parent_execution_requests"`
}
type BeaconBlockGloas struct {
@@ -584,13 +586,19 @@ 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"`
BuilderIndex string `json:"builder_index"`
BeaconBlockRoot string `json:"beacon_block_root"`
Slot string `json:"slot"`
StateRoot string `json:"state_root"`
}
type SignedExecutionPayloadEnvelope struct {

View File

@@ -2927,6 +2927,7 @@ func BeaconBlockGloasFromConsensus(b *eth.BeaconBlockGloas) (*BeaconBlockGloas,
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
SignedExecutionPayloadBid: SignedExecutionPayloadBidFromConsensus(b.Body.SignedExecutionPayloadBid),
PayloadAttestations: payloadAttestations,
ParentExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ParentExecutionRequests),
},
}, nil
}
@@ -2944,17 +2945,18 @@ func ExecutionPayloadBidFromConsensus(b *eth.ExecutionPayloadBid) *ExecutionPayl
blobKzgCommitments[i] = hexutil.Encode(b.BlobKzgCommitments[i])
}
return &ExecutionPayloadBid{
ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
BlockHash: hexutil.Encode(b.BlockHash),
PrevRandao: hexutil.Encode(b.PrevRandao),
FeeRecipient: hexutil.Encode(b.FeeRecipient),
GasLimit: fmt.Sprintf("%d", b.GasLimit),
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
Slot: fmt.Sprintf("%d", b.Slot),
Value: fmt.Sprintf("%d", b.Value),
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
BlobKzgCommitments: blobKzgCommitments,
ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
BlockHash: hexutil.Encode(b.BlockHash),
PrevRandao: hexutil.Encode(b.PrevRandao),
FeeRecipient: hexutil.Encode(b.FeeRecipient),
GasLimit: fmt.Sprintf("%d", b.GasLimit),
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
Slot: fmt.Sprintf("%d", b.Slot),
Value: fmt.Sprintf("%d", b.Value),
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequestsRoot: hexutil.Encode(b.ExecutionRequestsRoot),
}
}
@@ -2983,6 +2985,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
@@ -3113,6 +3128,13 @@ func (b *BeaconBlockBodyGloas) ToConsensus() (*eth.BeaconBlockBodyGloas, error)
if err != nil {
return nil, server.NewDecodeError(err, "PayloadAttestations")
}
var parentExecutionRequests *enginev1.ExecutionRequests
if b.ParentExecutionRequests != nil {
parentExecutionRequests, err = b.ParentExecutionRequests.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, "ParentExecutionRequests")
}
}
return &eth.BeaconBlockBodyGloas{
RandaoReveal: randaoReveal,
@@ -3134,6 +3156,7 @@ func (b *BeaconBlockBodyGloas) ToConsensus() (*eth.BeaconBlockBodyGloas, error)
BlsToExecutionChanges: blsChanges,
SignedExecutionPayloadBid: signedBid,
PayloadAttestations: payloadAttestations,
ParentExecutionRequests: parentExecutionRequests,
}, nil
}
@@ -3211,18 +3234,23 @@ func (b *ExecutionPayloadBid) ToConsensus() (*eth.ExecutionPayloadBid, error) {
}
blobKzgCommitments[i] = kzg
}
executionRequestsRoot, err := bytesutil.DecodeHexWithLength(b.ExecutionRequestsRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "ExecutionRequestsRoot")
}
return &eth.ExecutionPayloadBid{
ParentBlockHash: parentBlockHash,
ParentBlockRoot: parentBlockRoot,
BlockHash: blockHash,
PrevRandao: prevRandao,
FeeRecipient: feeRecipient,
GasLimit: gasLimit,
BuilderIndex: primitives.BuilderIndex(builderIndex),
Slot: primitives.Slot(slot),
Value: primitives.Gwei(value),
ExecutionPayment: primitives.Gwei(executionPayment),
BlobKzgCommitments: blobKzgCommitments,
ParentBlockHash: parentBlockHash,
ParentBlockRoot: parentBlockRoot,
BlockHash: blockHash,
PrevRandao: prevRandao,
FeeRecipient: feeRecipient,
GasLimit: gasLimit,
BuilderIndex: primitives.BuilderIndex(builderIndex),
Slot: primitives.Slot(slot),
Value: primitives.Gwei(value),
ExecutionPayment: primitives.Gwei(executionPayment),
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequestsRoot: executionRequestsRoot,
}, nil
}
@@ -3284,25 +3312,107 @@ 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),
}, 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")
}
return &eth.ExecutionPayloadEnvelope{
Payload: payload,
ExecutionRequests: requests,
BuilderIndex: primitives.BuilderIndex(builderIndex),
BeaconBlockRoot: beaconBlockRoot,
Slot: primitives.Slot(slot),
}, 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, err
}
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,65 @@
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,
}
}
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.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

@@ -23,18 +23,20 @@ func ROExecutionPayloadBidFromConsensus(b interfaces.ROExecutionPayloadBid) *Exe
for _, commitment := range commitments {
blobKzgCommitments = append(blobKzgCommitments, hexutil.Encode(commitment))
}
erRoot := b.ExecutionRequestsRoot()
return &ExecutionPayloadBid{
ParentBlockHash: hexutil.Encode(pbh[:]),
ParentBlockRoot: hexutil.Encode(pbr[:]),
BlockHash: hexutil.Encode(bh[:]),
PrevRandao: hexutil.Encode(pr[:]),
FeeRecipient: hexutil.Encode(fr[:]),
GasLimit: fmt.Sprintf("%d", b.GasLimit()),
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex()),
Slot: fmt.Sprintf("%d", b.Slot()),
Value: fmt.Sprintf("%d", b.Value()),
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment()),
BlobKzgCommitments: blobKzgCommitments,
ParentBlockHash: hexutil.Encode(pbh[:]),
ParentBlockRoot: hexutil.Encode(pbr[:]),
BlockHash: hexutil.Encode(bh[:]),
PrevRandao: hexutil.Encode(pr[:]),
FeeRecipient: hexutil.Encode(fr[:]),
GasLimit: fmt.Sprintf("%d", b.GasLimit()),
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex()),
Slot: fmt.Sprintf("%d", b.Slot()),
Value: fmt.Sprintf("%d", b.Value()),
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment()),
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequestsRoot: hexutil.Encode(erRoot[:]),
}
}

View File

@@ -364,51 +364,54 @@ func TestIndexedAttestation_ToConsensus(t *testing.T) {
func TestROExecutionPayloadBidFromConsensus(t *testing.T) {
t.Run("empty blobkzg commitments", func(t *testing.T) {
bid := &eth.ExecutionPayloadBid{
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
BlockHash: bytes.Repeat([]byte{0x03}, 32),
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
GasLimit: 100,
BuilderIndex: 7,
Slot: 9,
Value: 11,
ExecutionPayment: 22,
BlobKzgCommitments: [][]byte{},
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
BlockHash: bytes.Repeat([]byte{0x03}, 32),
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
GasLimit: 100,
BuilderIndex: 7,
Slot: 9,
Value: 11,
ExecutionPayment: 22,
BlobKzgCommitments: [][]byte{},
ExecutionRequestsRoot: bytes.Repeat([]byte{0x07}, 32),
}
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
require.NoError(t, err)
got := ROExecutionPayloadBidFromConsensus(roBid)
want := &ExecutionPayloadBid{
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
BlockHash: hexutil.Encode(bid.BlockHash),
PrevRandao: hexutil.Encode(bid.PrevRandao),
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
GasLimit: "100",
BuilderIndex: "7",
Slot: "9",
Value: "11",
ExecutionPayment: "22",
BlobKzgCommitments: []string{},
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
BlockHash: hexutil.Encode(bid.BlockHash),
PrevRandao: hexutil.Encode(bid.PrevRandao),
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
GasLimit: "100",
BuilderIndex: "7",
Slot: "9",
Value: "11",
ExecutionPayment: "22",
BlobKzgCommitments: []string{},
ExecutionRequestsRoot: hexutil.Encode(bid.ExecutionRequestsRoot),
}
assert.DeepEqual(t, want, got)
})
t.Run("default", func(t *testing.T) {
bid := &eth.ExecutionPayloadBid{
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
BlockHash: bytes.Repeat([]byte{0x03}, 32),
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
GasLimit: 100,
BuilderIndex: 7,
Slot: 9,
Value: 11,
ExecutionPayment: 22,
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x06}, 48)},
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
BlockHash: bytes.Repeat([]byte{0x03}, 32),
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
GasLimit: 100,
BuilderIndex: 7,
Slot: 9,
Value: 11,
ExecutionPayment: 22,
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x06}, 48)},
ExecutionRequestsRoot: bytes.Repeat([]byte{0x07}, 32),
}
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
require.NoError(t, err)
@@ -420,17 +423,18 @@ func TestROExecutionPayloadBidFromConsensus(t *testing.T) {
got := ROExecutionPayloadBidFromConsensus(roBid)
want := &ExecutionPayloadBid{
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
BlockHash: hexutil.Encode(bid.BlockHash),
PrevRandao: hexutil.Encode(bid.PrevRandao),
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
GasLimit: "100",
BuilderIndex: "7",
Slot: "9",
Value: "11",
ExecutionPayment: "22",
BlobKzgCommitments: bkcs,
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
BlockHash: hexutil.Encode(bid.BlockHash),
PrevRandao: hexutil.Encode(bid.PrevRandao),
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
GasLimit: "100",
BuilderIndex: "7",
Slot: "9",
Value: "11",
ExecutionPayment: "22",
BlobKzgCommitments: bkcs,
ExecutionRequestsRoot: hexutil.Encode(bid.ExecutionRequestsRoot),
}
assert.DeepEqual(t, want, got)
})
@@ -491,17 +495,18 @@ func TestBeaconStateGloasFromConsensus(t *testing.T) {
state.Slot = 5
state.ProposerLookahead = []primitives.ValidatorIndex{1, 2}
state.LatestExecutionPayloadBid = &eth.ExecutionPayloadBid{
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32),
ParentBlockRoot: bytes.Repeat([]byte{0x12}, 32),
BlockHash: bytes.Repeat([]byte{0x13}, 32),
PrevRandao: bytes.Repeat([]byte{0x14}, 32),
FeeRecipient: bytes.Repeat([]byte{0x15}, 20),
GasLimit: 64,
BuilderIndex: 3,
Slot: 5,
Value: 99,
ExecutionPayment: 7,
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x16}, 48)},
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32),
ParentBlockRoot: bytes.Repeat([]byte{0x12}, 32),
BlockHash: bytes.Repeat([]byte{0x13}, 32),
PrevRandao: bytes.Repeat([]byte{0x14}, 32),
FeeRecipient: bytes.Repeat([]byte{0x15}, 20),
GasLimit: 64,
BuilderIndex: 3,
Slot: 5,
Value: 99,
ExecutionPayment: 7,
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x16}, 48)},
ExecutionRequestsRoot: make([]byte, 32),
}
state.Builders = []*eth.Builder{
{

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

@@ -48,7 +48,6 @@ type ForkchoiceFetcher interface {
HighestReceivedBlockSlot() primitives.Slot
HighestReceivedBlockRoot() [32]byte
HasFullNode([32]byte) bool
PayloadContentLookup([32]byte) ([32]byte, bool)
ReceivedBlocksLastEpoch() (uint64, error)
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
InsertPayload(interfaces.ROExecutionPayloadEnvelope) error
@@ -77,6 +76,7 @@ type GenesisFetcher interface {
// directly retrieve head related data.
type HeadFetcher interface {
HeadSlot() primitives.Slot
HeadFull() bool
HeadRoot(ctx context.Context) ([]byte, error)
HeadBlock(ctx context.Context) (interfaces.ReadOnlySignedBeaconBlock, error)
HeadState(ctx context.Context) (state.BeaconState, error)

View File

@@ -63,13 +63,6 @@ func (s *Service) HasFullNode(root [32]byte) bool {
return s.cfg.ForkChoiceStore.HasFullNode(root)
}
// PayloadContentLookup returns the preferred payload-content lookup key from forkchoice.
func (s *Service) PayloadContentLookup(root [32]byte) ([32]byte, bool) {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.PayloadContentLookup(root)
}
// ReceivedBlocksLastEpoch returns the corresponding value from forkchoice
func (s *Service) ReceivedBlocksLastEpoch() (uint64, error) {
s.cfg.ForkChoiceStore.RLock()

View File

@@ -321,7 +321,7 @@ func (s *Service) pruneInvalidBlock(ctx context.Context, root, parentRoot, paren
// getPayloadAttributes returns the payload attributes for the given state and slot.
// The attribute is required to initiate a payload build process in the context of an `engine_forkchoiceUpdated` call.
func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, slot primitives.Slot, headRoot, accessRoot []byte) payloadattribute.Attributer {
func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, slot primitives.Slot, headRoot []byte) payloadattribute.Attributer {
emptyAttri := payloadattribute.EmptyWithVersion(st.Version())
// If it is an epoch boundary then process slots to get the right
@@ -343,7 +343,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
// right proposer index pre-Fulu, either way we need to copy the state to process it.
st = st.Copy()
var err error
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, st, accessRoot, slot)
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, st, headRoot, slot)
if err != nil {
log.WithError(err).Error("Could not process slots to get payload attribute")
return emptyAttri
@@ -373,7 +373,12 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
v := st.Version()
switch {
case v >= version.Gloas:
return payloadAttributesGloas(st, uint64(t.Unix()), prevRando, val.FeeRecipient[:], headRoot)
withdrawals, err := s.computePayloadWithdrawals(ctx, st, bytesutil.ToBytes32(headRoot))
if err != nil {
log.WithError(err).Error("Could not get withdrawals for payload attribute")
return emptyAttri
}
return payloadAttributesGloas(uint64(t.Unix()), prevRando, val.FeeRecipient[:], headRoot, withdrawals)
case v >= version.Deneb:
return payloadAttributesDeneb(st, uint64(t.Unix()), prevRando, val.FeeRecipient[:], headRoot)
case v >= version.Capella:
@@ -386,12 +391,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
}
}
func payloadAttributesGloas(st state.BeaconState, timestamp uint64, prevRandao, feeRecipient, parentBeaconBlockRoot []byte) payloadattribute.Attributer {
withdrawals, err := st.WithdrawalsForPayload()
if err != nil {
log.WithError(err).Error("Could not get payload withdrawals to get payload attribute")
return payloadattribute.EmptyWithVersion(st.Version())
}
func payloadAttributesGloas(timestamp uint64, prevRandao, feeRecipient, parentBeaconBlockRoot []byte, withdrawals []*enginev1.Withdrawal) payloadattribute.Attributer {
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV3{
Timestamp: timestamp,
PrevRandao: prevRandao,
@@ -401,7 +401,7 @@ func payloadAttributesGloas(st state.BeaconState, timestamp uint64, prevRandao,
})
if err != nil {
log.WithError(err).Error("Could not get payload attribute")
return payloadattribute.EmptyWithVersion(st.Version())
return payloadattribute.EmptyWithVersion(version.Gloas)
}
return attr
}

View File

@@ -717,14 +717,14 @@ func Test_GetPayloadAttribute(t *testing.T) {
ctx := tr.ctx
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
attr := service.getPayloadAttribute(ctx, st, 0, []byte{}, []byte{})
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
require.Equal(t, true, attr.IsEmpty())
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
// Cache hit, advance state, no fee recipient
slot := primitives.Slot(1)
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
@@ -732,7 +732,7 @@ func Test_GetPayloadAttribute(t *testing.T) {
suggestedAddr := common.HexToAddress("123")
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
}
@@ -747,7 +747,7 @@ func Test_GetPayloadAttribute_PrepareAllPayloads(t *testing.T) {
ctx := tr.ctx
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
attr := service.getPayloadAttribute(ctx, st, 0, []byte{}, []byte{})
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
}
@@ -757,14 +757,14 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
ctx := tr.ctx
st, _ := util.DeterministicGenesisStateCapella(t, 1)
attr := service.getPayloadAttribute(ctx, st, 0, []byte{}, []byte{})
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
require.Equal(t, true, attr.IsEmpty())
// Cache hit, advance state, no fee recipient
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
slot := primitives.Slot(1)
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
a, err := attr.Withdrawals()
@@ -775,7 +775,7 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
suggestedAddr := common.HexToAddress("123")
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
a, err = attr.Withdrawals()
@@ -809,14 +809,14 @@ func Test_GetPayloadAttributeV3(t *testing.T) {
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
ctx := tr.ctx
attr := service.getPayloadAttribute(ctx, test.st, 0, []byte{}, []byte{})
attr := service.getPayloadAttribute(ctx, test.st, 0, []byte{})
require.Equal(t, true, attr.IsEmpty())
// Cache hit, advance state, no fee recipient
slot := primitives.Slot(1)
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:])
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
a, err := attr.Withdrawals()
@@ -827,7 +827,7 @@ func Test_GetPayloadAttributeV3(t *testing.T) {
suggestedAddr := common.HexToAddress("123")
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:])
require.Equal(t, false, attr.IsEmpty())
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
a, err = attr.Withdrawals()

View File

@@ -59,14 +59,13 @@ type fcuConfig struct {
// when processing an incoming block during regular sync. It
// always updates the shuffling caches and handles epoch transitions .
func (s *Service) sendFCU(cfg *postBlockProcessConfig) {
// Release forkchoice lock; attribute computation acquires RLock internally.
s.ForkChoicer().Unlock()
if cfg.postState.Version() < version.Fulu {
// update the caches to compute the right proposer index
// this function is called under a forkchoice lock which we need to release.
s.ForkChoicer().Unlock()
s.updateCachesPostBlockProcessing(cfg)
s.ForkChoicer().Lock()
}
fcuArgs, err := s.getFCUArgs(cfg)
s.ForkChoicer().Lock()
if err != nil {
log.WithError(err).Error("Could not get forkchoice update argument")
return

View File

@@ -22,33 +22,19 @@ func TestService_isNewHead(t *testing.T) {
// Zero root is always a new head
require.Equal(t, true, service.isNewHead([32]byte{}, false))
require.Equal(t, true, service.isNewHead([32]byte{}, true))
// Different root is a new head
service.head = &head{root: [32]byte{1}}
require.Equal(t, true, service.isNewHead([32]byte{2}, false))
// Same root and same full status is not a new head
// Same root is not a new head.
require.Equal(t, false, service.isNewHead([32]byte{1}, false))
// Same root but different full status is a new head
require.Equal(t, true, service.isNewHead([32]byte{1}, true))
// Same root and both full is not a new head
service.head = &head{root: [32]byte{1}, full: true}
require.Equal(t, false, service.isNewHead([32]byte{1}, true))
// Same root, head is full but incoming is not full, is a new head
require.Equal(t, true, service.isNewHead([32]byte{1}, false))
// Nil head should use origin root
service.head = nil
service.originBlockRoot = [32]byte{3}
require.Equal(t, true, service.isNewHead([32]byte{2}, false))
require.Equal(t, false, service.isNewHead([32]byte{3}, false))
// Nil head with full=true is always a new head (originBlockRoot has full=false)
require.Equal(t, true, service.isNewHead([32]byte{3}, true))
}
func TestService_getHeadStateAndBlock(t *testing.T) {

View File

@@ -5,12 +5,12 @@ import (
"math"
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
coregloas "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
payloadattribute "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attribute"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
@@ -37,42 +37,6 @@ func (s *Service) waitUntilEpoch(target primitives.Epoch, secondsPerSlot uint64)
}
}
// getLookupParentRoot returns the root that serves as key to generate the parent state for the passed beacon block.
// if it is based on empty or it is pre-Gloas, it is the parent root of the block, otherwise if it is based on full it is
// the parent hash.
// The caller of this function should not hold a lock on forkchoice.
func (s *Service) getLookupParentRoot(b consensus_blocks.ROBlock) ([32]byte, error) {
bl := b.Block()
parentRoot := bl.ParentRoot()
if b.Version() < version.Gloas {
return parentRoot, nil
}
parentSlot, err := s.cfg.ForkChoiceStore.Slot(parentRoot)
if err != nil {
return [32]byte{}, errors.Wrap(err, "failed to get slot for parent root")
}
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch {
return parentRoot, nil
}
blockHash, err := s.cfg.ForkChoiceStore.BlockHash(parentRoot)
if err != nil {
return [32]byte{}, errors.Wrap(err, "failed to get block hash for parent root")
}
bid, err := bl.Body().SignedExecutionPayloadBid()
if err != nil {
return [32]byte{}, errors.Wrap(err, "failed to get signed execution payload bid from block body")
}
if bid == nil || bid.Message == nil || len(bid.Message.ParentBlockHash) != 32 {
return [32]byte{}, errors.New("invalid signed execution payload bid message")
}
parentHash := [32]byte(bid.Message.ParentBlockHash)
if blockHash == parentHash {
return parentHash, nil
}
return parentRoot, nil
}
func (s *Service) runLatePayloadTasks() {
if err := s.waitForSync(); err != nil {
log.WithError(err).Error("Failed to wait for initial sync")
@@ -109,36 +73,76 @@ func (s *Service) checkIfProposing(st state.ReadOnlyBeaconState, slot primitives
return cache.TrackedValidator{}, false
}
// computePayloadWithdrawals returns the withdrawals for the next payload.
// If the parent's payload was delivered (full), it applies the parent's
// execution requests on a state copy before computing withdrawals.
// If the parent was empty, it returns the existing payload_expected_withdrawals.
func (s *Service) computePayloadWithdrawals(ctx context.Context, st state.BeaconState, parentRoot [32]byte) ([]*enginev1.Withdrawal, error) {
parentSlot, err := s.RecentBlockSlot(parentRoot)
if err != nil {
return nil, errors.Wrap(err, "could not get parent block slot")
}
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch {
result, err := st.ExpectedWithdrawalsGloas()
if err != nil {
return nil, errors.Wrap(err, "could not compute expected withdrawals")
}
return result.Withdrawals, nil
}
if !s.HeadFull() {
return st.PayloadExpectedWithdrawals()
}
// TODO: replace DB lookup with a single-entry cache (blockroot → envelope).
envelope, err := s.cfg.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
if err != nil {
return nil, errors.Wrap(err, "could not get parent execution payload envelope")
}
if err := coregloas.ApplyParentExecutionPayload(ctx, st, envelope.Message.ExecutionRequests); err != nil {
return nil, errors.Wrap(err, "could not apply parent execution payload")
}
result, err := st.ExpectedWithdrawalsGloas()
if err != nil {
return nil, errors.Wrap(err, "could not compute expected withdrawals")
}
return result.Withdrawals, nil
}
// This is a Gloas version of getPayloadAttribute that avoids all the clutter that was originally due to the proposer Index.
// It is guaranteed to be called for the current slot + 1 and the head state to have been advanced to at least the current epoch.
func (s *Service) getPayloadAttributeGloas(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot, headRoot, accessRoot []byte) payloadattribute.Attributer {
func (s *Service) getLatePayloadAttribute(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot, headRoot []byte) payloadattribute.Attributer {
emptyAttri := payloadattribute.EmptyWithVersion(st.Version())
val, proposing := s.checkIfProposing(st, slot)
if !proposing {
return emptyAttri
}
st, err := transition.ProcessSlotsIfNeeded(ctx, st, accessRoot, slot)
if err != nil {
log.WithError(err).Error("Could not process slots to get payload attribute")
return emptyAttri
var err error
if slot > st.Slot() {
writable, ok := st.(state.BeaconState)
if !ok {
log.Error("head state is not writable; cannot advance slots")
return emptyAttri
}
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, writable, headRoot, slot)
if err != nil {
log.WithError(err).Error("Could not process slots to get payload attribute")
return emptyAttri
}
}
// Get previous randao.
prevRando, err := helpers.RandaoMix(st, time.CurrentEpoch(st))
if err != nil {
log.WithError(err).Error("Could not get randao mix to get payload attribute")
return emptyAttri
}
// Get timestamp.
t, err := slots.StartTime(s.genesisTime, slot)
if err != nil {
log.WithError(err).Error("Could not get timestamp to get payload attribute")
return emptyAttri
}
withdrawals, err := st.WithdrawalsForPayload()
withdrawals, err := st.PayloadExpectedWithdrawals()
if err != nil {
log.WithError(err).Error("Could not get payload withdrawals to get payload attribute")
return emptyAttri
@@ -158,9 +162,8 @@ func (s *Service) getPayloadAttributeGloas(ctx context.Context, st state.ReadOnl
return attr
}
// latePayloadTasks updates the NSC and epoch boundary caches when there is no payload in the current slot (and there is a block)
// latePayloadTasks sends an FCU when no payload arrived for the current slot's block.
// The case where the block was also missing would have been dealt by lateBlockTasks already.
// We call FCU only if we are proposing next slot, as the execution head is assumed to not have changed.
func (s *Service) latePayloadTasks(ctx context.Context) {
currentSlot := s.CurrentSlot()
if currentSlot != s.HeadSlot() {
@@ -187,15 +190,14 @@ func (s *Service) latePayloadTasks(ctx context.Context) {
if !s.inRegularSync() {
return
}
attr := s.getPayloadAttributeGloas(ctx, st, currentSlot+1, r, r)
attr := s.getLatePayloadAttribute(ctx, st, currentSlot+1, r)
if attr == nil || attr.IsEmpty() {
return
}
beaconLatePayloadTaskTriggeredTotal.Inc()
// Head is the empty block.
bh, err := st.LatestBlockHash()
if err != nil {
log.WithError(err).Error("Could not get latest block hash to notify engine")
log.WithError(err).Error("Could not get latest block hash")
return
}
pid, err := s.notifyForkchoiceUpdateGloas(ctx, bh, attr)

View File

@@ -21,7 +21,6 @@ import (
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/OffchainLabs/prysm/v7/time/slots"
logTest "github.com/sirupsen/logrus/hooks/test"
)
@@ -63,12 +62,13 @@ func prepareGloasForkchoiceState(
FinalizedCheckpoint: finalizedCheckpoint,
LatestBlockHeader: blockHeader,
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{
BlockHash: blockHash[:],
ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
BlockHash: blockHash[:],
ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
ExecutionRequestsRoot: make([]byte, 32),
},
Builders: make([]*ethpb.Builder, 0),
BuilderPendingPayments: builderPendingPayments,
@@ -134,12 +134,13 @@ func testGloasState(t *testing.T, slot primitives.Slot, parentRoot [32]byte, blo
BlockHash: make([]byte, 32),
},
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{
BlockHash: blockHash[:],
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
BlockHash: blockHash[:],
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
ExecutionRequestsRoot: make([]byte, 32),
},
Builders: make([]*ethpb.Builder, 0),
BuilderPendingPayments: builderPendingPayments,
@@ -186,7 +187,6 @@ func testSignedEnvelope(t *testing.T, blockRoot [32]byte, slot primitives.Slot,
BuilderIndex: 0,
BeaconBlockRoot: blockRoot[:],
Slot: slot,
StateRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}
@@ -383,15 +383,11 @@ func TestSavePostPayload(t *testing.T) {
blockRoot := bytesutil.ToBytes32([]byte("root1"))
blockHash := bytesutil.ToBytes32([]byte("hash1"))
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
require.NoError(t, err)
protoEnv := testSignedEnvelope(t, blockRoot, 1, blockHash[:])
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(protoEnv)
require.NoError(t, err)
require.NoError(t, s.savePostPayload(ctx, signed, st))
require.NoError(t, s.savePostPayload(ctx, signed))
// Verify the envelope was saved in the DB.
require.Equal(t, true, s.cfg.BeaconDB.HasExecutionPayloadEnvelope(ctx, blockRoot))
@@ -423,7 +419,7 @@ func TestValidateExecutionOnEnvelope_Valid(t *testing.T) {
require.Equal(t, true, isValid)
}
func TestPostPayloadHeadUpdate_NotHead(t *testing.T) {
func TestPostPayloadTasks_NotHead(t *testing.T) {
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
ctx := t.Context()
@@ -443,10 +439,10 @@ func TestPostPayloadHeadUpdate_NotHead(t *testing.T) {
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
require.NoError(t, err)
require.NoError(t, s.postPayloadHeadUpdate(ctx, envelope, st, root, headRoot[:]))
require.NoError(t, s.postPayloadTasks(ctx, envelope, st, root, headRoot))
}
func TestPostPayloadHeadUpdate_SetsHeadFull(t *testing.T) {
func TestPostPayloadTasks_DoesNotMutateHead(t *testing.T) {
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
ctx := t.Context()
@@ -456,11 +452,14 @@ func TestPostPayloadHeadUpdate_SetsHeadFull(t *testing.T) {
base, blk := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
require.NoError(t, err)
oldBase, _ := testGloasState(t, 0, params.BeaconConfig().ZeroHash, blockHash)
oldSt, err := state_native.InitializeFromProtoUnsafeGloas(oldBase)
require.NoError(t, err)
signed, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
s.head = &head{root: root, block: signed, state: st, slot: 1}
require.Equal(t, false, s.head.full)
s.head.state = oldSt
env := &ethpb.ExecutionPayloadEnvelope{
BeaconBlockRoot: root[:],
@@ -470,183 +469,14 @@ func TestPostPayloadHeadUpdate_SetsHeadFull(t *testing.T) {
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
require.NoError(t, err)
require.NoError(t, s.postPayloadHeadUpdate(ctx, envelope, st, root, root[:]))
require.NoError(t, s.postPayloadTasks(ctx, envelope, st, root, root))
s.headLock.RLock()
require.Equal(t, true, s.head.full)
require.Equal(t, root, s.head.root)
require.Equal(t, primitives.Slot(0), s.head.state.Slot())
s.headLock.RUnlock()
}
func TestGetLookupParentRoot_PreGloas(t *testing.T) {
service, _ := minimalTestService(t)
parentRoot := [32]byte{1}
blk := util.HydrateSignedBeaconBlockDeneb(&ethpb.SignedBeaconBlockDeneb{
Block: &ethpb.BeaconBlockDeneb{
ParentRoot: parentRoot[:],
},
})
wsb, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
roblock, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
got, err := service.getLookupParentRoot(roblock)
require.NoError(t, err)
require.Equal(t, parentRoot, got)
}
func TestGetLookupParentRoot_GloasBuildsOnEmpty(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
cfg.InitializeForkSchedule()
params.OverrideBeaconConfig(cfg)
service, req := minimalTestService(t)
ctx := t.Context()
parentRoot := [32]byte{1}
parentBlockHash := [32]byte{20}
parentNodeBlockHash := [32]byte{99} // Different from parentBlockHash => builds on empty
// Insert a Gloas node for the parent so BlockHash works.
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, params.BeaconConfig().ZeroHash, parentNodeBlockHash, params.BeaconConfig().ZeroHash, 0, 0)
require.NoError(t, err)
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
blockHash := [32]byte{10}
bid := util.HydrateSignedExecutionPayloadBid(&ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
BlockHash: blockHash[:],
ParentBlockHash: parentBlockHash[:],
},
})
blk := util.HydrateSignedBeaconBlockGloas(&ethpb.SignedBeaconBlockGloas{
Block: &ethpb.BeaconBlockGloas{
Slot: 2,
ParentRoot: parentRoot[:],
Body: &ethpb.BeaconBlockBodyGloas{
SignedExecutionPayloadBid: bid,
},
},
})
wsb, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
roblock, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
got, err := service.getLookupParentRoot(roblock)
require.NoError(t, err)
// parentBlockHash != parentNodeBlockHash, so it builds on empty => returns parentRoot
require.Equal(t, parentRoot, got)
}
func TestGetLookupParentRoot_GloasBuildsOnFull(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
cfg.InitializeForkSchedule()
params.OverrideBeaconConfig(cfg)
service, req := minimalTestService(t)
ctx := t.Context()
parentRoot := [32]byte{1}
parentNodeBlockHash := [32]byte{10}
// Insert a Gloas node for the parent so BlockHash works.
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, params.BeaconConfig().ZeroHash, parentNodeBlockHash, params.BeaconConfig().ZeroHash, 0, 0)
require.NoError(t, err)
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
// Set parentBlockHash in the bid to match the parent's blockHash.
blockHash := [32]byte{20}
bid := util.HydrateSignedExecutionPayloadBid(&ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
BlockHash: blockHash[:],
ParentBlockHash: parentNodeBlockHash[:],
},
})
blk := util.HydrateSignedBeaconBlockGloas(&ethpb.SignedBeaconBlockGloas{
Block: &ethpb.BeaconBlockGloas{
Slot: 2,
ParentRoot: parentRoot[:],
Body: &ethpb.BeaconBlockBodyGloas{
SignedExecutionPayloadBid: bid,
},
},
})
wsb, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
roblock, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
got, err := service.getLookupParentRoot(roblock)
require.NoError(t, err)
// parentBlockHash == parentNodeBlockHash, so it builds on full => returns parentBlockHash
require.Equal(t, parentNodeBlockHash, got)
}
func TestGetLookupParentRoot_GloasParentPreForkEpoch(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 2
cfg.InitializeForkSchedule()
params.OverrideBeaconConfig(cfg)
service, req := minimalTestService(t)
ctx := t.Context()
parentRoot := [32]byte{1}
parentNodeBlockHash := [32]byte{10}
parentSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
require.NoError(t, err)
parentSlot = parentSlot - 1
st, parentROBlock, err := prepareGloasForkchoiceState(
ctx,
parentSlot,
parentRoot,
params.BeaconConfig().ZeroHash,
parentNodeBlockHash,
params.BeaconConfig().ZeroHash,
0,
0,
)
require.NoError(t, err)
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
blockHash := [32]byte{20}
bid := util.HydrateSignedExecutionPayloadBid(&ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
BlockHash: blockHash[:],
ParentBlockHash: parentNodeBlockHash[:],
},
})
blk := util.HydrateSignedBeaconBlockGloas(&ethpb.SignedBeaconBlockGloas{
Block: &ethpb.BeaconBlockGloas{
Slot: parentSlot + 1,
ParentRoot: parentRoot[:],
Body: &ethpb.BeaconBlockBodyGloas{
SignedExecutionPayloadBid: bid,
},
},
})
wsb, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
roblock, err := blocks.NewROBlock(wsb)
require.NoError(t, err)
got, err := service.getLookupParentRoot(roblock)
require.NoError(t, err)
// Parent slot is pre-fork, so always return parentRoot.
require.Equal(t, parentRoot, got)
}
func TestLatePayloadTasks_ReturnsEarlyWhenBlockLate(t *testing.T) {
logHook := logTest.NewGlobal()
service, tr := setupGloasService(t, &mockExecution.EngineClient{})
@@ -693,6 +523,7 @@ func TestLatePayloadTasks_SendsFCU(t *testing.T) {
require.NoError(t, err)
headRoot := bytesutil.ToBytes32([]byte("headroot"))
insertGloasBlock(t, service, base, blk, headRoot)
service.head = &head{
root: headRoot,
block: signed,
@@ -723,12 +554,13 @@ func TestLateBlockTasks_GloasFCU(t *testing.T) {
service, tr := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
blockHash := bytesutil.ToBytes32([]byte("hash1"))
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
base, blk := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
base.LatestBlockHash = blockHash[:]
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
require.NoError(t, err)
headRoot := bytesutil.ToBytes32([]byte("headroot"))
insertGloasBlock(t, service, base, blk, headRoot)
service.head = &head{
root: headRoot,
state: st,
@@ -748,174 +580,8 @@ func TestLateBlockTasks_GloasFCU(t *testing.T) {
require.Equal(t, primitives.PayloadID(pid[:]), cachedPid)
}
// TestSaveHead_GloasForkBoundary_PreforkBidForcesEmptyHead verifies that saveHead does not
// treat the head as "full" when the latest execution payload bid was issued in a pre-fork epoch.
// This guards against the Fulu->Gloas upgrade-seeded bid (bid.BlockHash == latestBlockHash,
// bid.Slot == 0) causing a spurious full=true head before any real Gloas bid has been processed.
func TestSaveHead_GloasForkBoundary_PreforkBidForcesEmptyHead(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
cfg.InitializeForkSchedule()
params.OverrideBeaconConfig(cfg)
service, _ := setupGloasService(t, &mockExecution.EngineClient{})
ctx := t.Context()
blockRoot := bytesutil.ToBytes32([]byte("root1"))
parentRoot := params.BeaconConfig().ZeroHash
blockHash := bytesutil.ToBytes32([]byte("hash1"))
// Create a Gloas state where IsParentBlockFull()==true (bid.BlockHash == LatestBlockHash)
// but bid.Slot is 0 (epoch 0, pre-fork). This mimics the upgrade-seeded state.
base, blk := testGloasState(t, 1, parentRoot, blockHash)
base.LatestBlockHash = blockHash[:]
// bid.Slot defaults to 0, which is before GloasForkEpoch=1.
// Set a valid initial head so saveHead's headBlock() call does not panic.
// We do NOT insert the old block into forkchoice because insertGloasBlock
// would claim the tree root slot; the target block (parentRoot=ZeroHash) must
// be the first node inserted so it can become the tree root.
oldBlk := util.HydrateSignedBeaconBlockGloas(&ethpb.SignedBeaconBlockGloas{})
oldSigned, err2 := blocks.NewSignedBeaconBlock(oldBlk)
require.NoError(t, err2)
oldSt, err2 := state_native.InitializeFromProtoUnsafeGloas(&ethpb.BeaconStateGloas{
Slot: 0,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
CurrentJustifiedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
FinalizedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
LatestBlockHeader: &ethpb.BeaconBlockHeader{ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32)},
Eth1Data: &ethpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{BlockHash: make([]byte, 32), ParentBlockHash: make([]byte, 32), ParentBlockRoot: make([]byte, 32), PrevRandao: make([]byte, 32), FeeRecipient: make([]byte, 20), BlobKzgCommitments: [][]byte{make([]byte, 48)}},
BuilderPendingPayments: func() []*ethpb.BuilderPendingPayment {
pp := make([]*ethpb.BuilderPendingPayment, 64)
for i := range pp {
pp[i] = &ethpb.BuilderPendingPayment{Withdrawal: &ethpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)}}
}
return pp
}(),
ExecutionPayloadAvailability: make([]byte, 1024),
LatestBlockHash: make([]byte, 32),
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
ProposerLookahead: make([]primitives.ValidatorIndex, 64),
})
require.NoError(t, err2)
oldRoot := bytesutil.ToBytes32([]byte("oldroot1"))
service.head = &head{root: oldRoot, block: oldSigned, state: oldSt, slot: 0}
insertGloasBlock(t, service, base, blk, blockRoot)
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
require.NoError(t, err)
// Verify precondition: IsParentBlockFull() is true.
full, err := st.IsParentBlockFull()
require.NoError(t, err)
require.Equal(t, true, full, "precondition: IsParentBlockFull must be true")
// Verify guard precondition: bid.Slot is pre-fork.
bid, err := st.LatestExecutionPayloadBid()
require.NoError(t, err)
isPrefork := slots.ToEpoch(bid.Slot()) < params.BeaconConfig().GloasForkEpoch
require.Equal(t, true, isPrefork, "precondition: bid.Slot must be pre-fork")
ssigned, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
// saveHead should NOT mark the head as full because bid.Slot < GloasForkEpoch.
require.NoError(t, service.saveHead(ctx, blockRoot, ssigned, st))
service.headLock.RLock()
headFull := service.head.full
service.headLock.RUnlock()
require.Equal(t, false, headFull, "head must not be full for upgrade-seeded bid")
}
// TestSaveHead_GloasForkBoundary_PostforkBidSetsFullHead verifies that saveHead correctly
// marks the head as full when the latest bid is from a post-fork epoch.
func TestSaveHead_GloasForkBoundary_PostforkBidSetsFullHead(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 1
cfg.InitializeForkSchedule()
params.OverrideBeaconConfig(cfg)
service, _ := setupGloasService(t, &mockExecution.EngineClient{})
ctx := t.Context()
forkSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
require.NoError(t, err)
blockRoot := bytesutil.ToBytes32([]byte("root1"))
parentRoot := params.BeaconConfig().ZeroHash
blockHash := bytesutil.ToBytes32([]byte("hash1"))
// Set a valid initial head so saveHead's headBlock() call does not panic.
// Do NOT use insertGloasBlock for the old block — the target block must be
// the first node inserted so it can claim the tree root (parentRoot=ZeroHash).
oldBlk2 := util.HydrateSignedBeaconBlockGloas(&ethpb.SignedBeaconBlockGloas{})
oldSigned2, err2 := blocks.NewSignedBeaconBlock(oldBlk2)
require.NoError(t, err2)
oldSt2, err2 := state_native.InitializeFromProtoUnsafeGloas(&ethpb.BeaconStateGloas{
Slot: 0,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
CurrentJustifiedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
FinalizedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
LatestBlockHeader: &ethpb.BeaconBlockHeader{ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32)},
Eth1Data: &ethpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{BlockHash: make([]byte, 32), ParentBlockHash: make([]byte, 32), ParentBlockRoot: make([]byte, 32), PrevRandao: make([]byte, 32), FeeRecipient: make([]byte, 20), BlobKzgCommitments: [][]byte{make([]byte, 48)}},
BuilderPendingPayments: func() []*ethpb.BuilderPendingPayment {
pp := make([]*ethpb.BuilderPendingPayment, 64)
for i := range pp {
pp[i] = &ethpb.BuilderPendingPayment{Withdrawal: &ethpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)}}
}
return pp
}(),
ExecutionPayloadAvailability: make([]byte, 1024),
LatestBlockHash: make([]byte, 32),
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
ProposerLookahead: make([]primitives.ValidatorIndex, 64),
})
require.NoError(t, err2)
oldRoot2 := bytesutil.ToBytes32([]byte("oldroot2"))
service.head = &head{root: oldRoot2, block: oldSigned2, state: oldSt2, slot: 0}
base, blk := testGloasState(t, forkSlot+1, parentRoot, blockHash)
base.LatestBlockHash = blockHash[:]
// Set bid.Slot to a post-fork epoch slot.
base.LatestExecutionPayloadBid.Slot = forkSlot + 1
insertGloasBlock(t, service, base, blk, blockRoot)
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
require.NoError(t, err)
// Verify preconditions.
full, err := st.IsParentBlockFull()
require.NoError(t, err)
require.Equal(t, true, full, "precondition: IsParentBlockFull must be true")
bid, err := st.LatestExecutionPayloadBid()
require.NoError(t, err)
isPostfork := slots.ToEpoch(bid.Slot()) >= params.BeaconConfig().GloasForkEpoch
require.Equal(t, true, isPostfork, "precondition: bid.Slot must be post-fork")
ssigned, err := blocks.NewSignedBeaconBlock(blk)
require.NoError(t, err)
// saveHead SHOULD mark the head as full because bid.Slot >= GloasForkEpoch.
require.NoError(t, service.saveHead(ctx, blockRoot, ssigned, st))
service.headLock.RLock()
headFull := service.head.full
service.headLock.RUnlock()
require.Equal(t, true, headFull, "head must be full for real post-fork bid")
}
// TestLateBlockTasks_GloasForkBoundary_PreforkBidUsesHeadRoot verifies that lateBlockTasks
// uses headRoot (not LatestBlockHash) as the accessRoot when the bid is pre-fork epoch.
// Without this guard, the upgrade-seeded bid would cause lateBlockTasks to use the wrong
// access root for the next-slot cache.
// uses headRoot for the next-slot cache lookup even at the fork boundary.
func TestLateBlockTasks_GloasForkBoundary_PreforkBidUsesHeadRoot(t *testing.T) {
logHook := logTest.NewGlobal()
resetCfg := features.InitWithReset(&features.Flags{
@@ -933,15 +599,16 @@ func TestLateBlockTasks_GloasForkBoundary_PreforkBidUsesHeadRoot(t *testing.T) {
service, tr := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
blockHash := bytesutil.ToBytes32([]byte("hash1"))
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
// Make IsParentBlockFull() true: bid.BlockHash == LatestBlockHash.
base, blk := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
// Make LatestBlockHashMatchesBidBlockHash() true: bid.BlockHash == LatestBlockHash.
base.LatestBlockHash = blockHash[:]
// bid.Slot is 0 (pre-fork epoch): the epoch guard should prevent using LatestBlockHash as accessRoot.
// bid.Slot is 0 (pre-fork epoch).
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
require.NoError(t, err)
headRoot := bytesutil.ToBytes32([]byte("headroot"))
insertGloasBlock(t, service, base, blk, headRoot)
service.head = &head{
root: headRoot,
state: st,

View File

@@ -50,7 +50,7 @@ type head struct {
block interfaces.ReadOnlySignedBeaconBlock // current head block.
state state.BeaconState // current head state.
slot primitives.Slot // the head block slot number
full bool // whether the head is post-CL or post-EL after Gloas
full bool // whether the head's execution payload has been delivered (post-Gloas)
optimistic bool // optimistic status when saved head
}
@@ -61,23 +61,7 @@ func (s *Service) saveHead(ctx context.Context, newHeadRoot [32]byte, headBlock
ctx, span := trace.StartSpan(ctx, "blockChain.saveHead")
defer span.End()
// Pre-Gloas we use empty for head because we still key states by blockroot
var full bool
var err error
if headState.Version() >= version.Gloas {
gloasFirstSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
if err != nil {
return errors.Wrap(err, "could not compute gloas first slot")
}
if headState.Slot() > gloasFirstSlot {
full, err = headState.IsParentBlockFull()
if err != nil {
return errors.Wrap(err, "could not determine if head is full or not")
}
}
}
// Do nothing if head hasn't changed.
full := s.cfg.ForkChoiceStore.IsFullNode(newHeadRoot)
if !s.isNewHead(newHeadRoot, full) {
return nil
}
@@ -235,9 +219,9 @@ func (s *Service) setHead(newHead *head) error {
root: newHead.root,
block: bCp,
state: newHead.state.Copy(),
full: newHead.full,
optimistic: newHead.optimistic,
slot: newHead.slot,
full: newHead.full,
}
return nil
}
@@ -290,6 +274,16 @@ func (s *Service) headBlock() (interfaces.ReadOnlySignedBeaconBlock, error) {
return s.head.block.Copy()
}
// HeadFull returns whether the current head's execution payload has been delivered.
func (s *Service) HeadFull() bool {
s.headLock.RLock()
defer s.headLock.RUnlock()
if s.head == nil {
return false
}
return s.head.full
}
// This returns the head state.
// It does a full copy on head state for immutability.
// This is a lock free version.

View File

@@ -145,50 +145,6 @@ func getStateVersionAndPayload(st state.BeaconState) (int, interfaces.ExecutionD
return preStateVersion, preStateHeader, nil
}
// applyPayloadIfNeeded applies the parent block's execution payload envelope to
// preState when the current block's bid indicates it built on a full parent.
func (s *Service) applyPayloadIfNeeded(ctx context.Context, b interfaces.ReadOnlyBeaconBlock, parentRoot [32]byte, preState state.BeaconState) error {
if b.Version() < version.Gloas || parentRoot == [32]byte{} {
return nil
}
parentBlock, err := s.cfg.BeaconDB.Block(ctx, parentRoot)
if err != nil {
return errors.Wrapf(err, "could not get parent block with root %#x", parentRoot)
}
if parentBlock.Version() < version.Gloas {
return nil
}
sb, err := b.Body().SignedExecutionPayloadBid()
if err != nil {
return errors.Wrap(err, "could not get execution payload bid for block")
}
if sb == nil || sb.Message == nil {
return fmt.Errorf("missing execution payload bid for block at slot %d", b.Slot())
}
parentBid, err := parentBlock.Block().Body().SignedExecutionPayloadBid()
if err != nil {
return errors.Wrapf(err, "could not get execution payload bid for parent block with root %#x", parentRoot)
}
if parentBid == nil || parentBid.Message == nil {
return fmt.Errorf("missing execution payload bid for parent block with root %#x", parentRoot)
}
if !bytes.Equal(sb.Message.ParentBlockHash, parentBid.Message.BlockHash) {
return nil
}
signedEnvelope, err := s.cfg.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
if err != nil {
return errors.Wrapf(err, "could not get execution payload envelope for parent block with root %#x", parentRoot)
}
if signedEnvelope == nil || signedEnvelope.Message == nil {
return nil
}
envelope, err := consensusblocks.WrappedROBlindedExecutionPayloadEnvelope(signedEnvelope.Message)
if err != nil {
return errors.Wrapf(err, "could not wrap blinded execution payload envelope for parent block with root %#x", parentRoot)
}
return gloas.ProcessBlindedExecutionPayload(ctx, preState, parentBlock.Block().StateRoot(), envelope)
}
// getBatchPrestate returns the pre-state to apply to the first beacon block in the batch and returns true if it applied the first envelope before
func (s *Service) getBatchPrestate(ctx context.Context, b consensusblocks.ROBlock, envelopes []interfaces.ROSignedExecutionPayloadEnvelope) (state.BeaconState, bool, error) {
if len(envelopes) == 0 || b.Version() < version.Gloas {
@@ -210,37 +166,15 @@ func (s *Service) getBatchPrestate(ctx context.Context, b consensusblocks.ROBloc
if !full {
return blockPreState, false, nil
}
parentBlock, err := s.cfg.BeaconDB.Block(ctx, parentRoot)
if err != nil {
return nil, false, errors.Wrap(err, "could not get parent block")
}
if s.cfg.BeaconDB.HasExecutionPayloadEnvelope(ctx, parentRoot) {
// The parent envelope was already saved by a previous batch but the
// replayed state may not include it (replay skips the last block's
// envelope). Load the blinded form from DB and apply it.
blindedEnv, err := s.cfg.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
if !s.cfg.BeaconDB.HasExecutionPayloadEnvelope(ctx, parentRoot) {
env, err := envelopes[0].Envelope()
if err != nil {
return nil, false, errors.Wrap(err, "could not load parent blinded envelope from DB")
return nil, false, err
}
wrappedEnv, err := consensusblocks.WrappedROBlindedExecutionPayloadEnvelope(blindedEnv.Message)
if err != nil {
return nil, false, errors.Wrap(err, "could not wrap blinded envelope")
if _, err := s.notifyNewEnvelope(ctx, blockPreState, env); err != nil {
return nil, false, err
}
if err := gloas.ProcessBlindedExecutionPayload(ctx, blockPreState, parentBlock.Block().StateRoot(), wrappedEnv); err != nil {
return nil, false, errors.Wrap(err, "could not apply parent blinded envelope from DB")
}
return blockPreState, true, nil
}
env, err := envelopes[0].Envelope()
if err != nil {
return nil, false, err
}
// notify the engine of the new envelope
if _, err := s.notifyNewEnvelope(ctx, blockPreState, env); err != nil {
return nil, false, err
}
if err := gloas.ProcessBlindedExecutionPayload(ctx, blockPreState, parentBlock.Block().StateRoot(), env); err != nil {
return nil, false, err
}
return blockPreState, true, nil
}
@@ -323,7 +257,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
return invalidBlock{error: err}
}
if b.Root() == br && eidx < len(envelopes) {
envSigSet, err := gloas.ProcessExecutionPayloadWithDeferredSig(ctx, preState, b.Block().StateRoot(), envelopes[eidx])
envSigSet, err := gloas.VerifyExecutionPayloadEnvelopeWithDeferredSig(ctx, preState, envelopes[eidx])
if err != nil {
return err
}
@@ -462,14 +396,6 @@ func (s *Service) notifyEngineAndSaveData(
return nil, false, errors.Wrap(err, "could not notify new envelope from block")
}
args.HasPayload = true
bh := env.BlockHash()
if err := s.cfg.BeaconDB.SaveStateSummary(ctx, &ethpb.StateSummary{
Slot: b.Block().Slot(),
Root: bh[:],
}); err != nil {
tracing.AnnotateError(span, err)
return nil, false, err
}
}
}
if err := s.areSidecarsAvailable(ctx, avs, b); err != nil {
@@ -585,40 +511,38 @@ func (s *Service) updateEpochBoundaryCaches(ctx context.Context, st state.Beacon
// refreshCaches updates the next slot state cache and epoch boundary caches.
// Before Fulu this is done synchronously, after Fulu it is deferred to a goroutine.
func (s *Service) refreshCaches(ctx context.Context, currentSlot primitives.Slot, headRoot [32]byte, headState state.BeaconState, accessRoot [32]byte) {
func (s *Service) refreshCaches(ctx context.Context, currentSlot primitives.Slot, headRoot [32]byte, headState state.BeaconState) {
lastRoot, lastState := transition.LastCachedState()
if lastState == nil {
lastRoot, lastState = headRoot[:], headState
}
if lastState.Version() < version.Fulu {
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, accessRoot, lastRoot, lastState)
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, headRoot, lastRoot, lastState)
} else {
go func() {
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
defer cancel()
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, accessRoot, lastRoot, lastState)
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, headRoot, lastRoot, lastState)
}()
}
}
// updateCachesAndEpochBoundary updates the next slot state cache and handles
// epoch boundary processing. If the lastRoot matches accessRoot, the cached
// epoch boundary processing. If the lastRoot matches headRoot, the cached
// last state is reused; otherwise, the head state is advanced instead.
func (s *Service) updateCachesAndEpochBoundary(ctx context.Context, currentSlot primitives.Slot, headState state.BeaconState, accessRoot [32]byte, lastRoot []byte, lastState state.BeaconState) {
if bytes.Equal(lastRoot, accessRoot[:]) {
// Happy case, the last advanced state is head, we thus keep it
func (s *Service) updateCachesAndEpochBoundary(ctx context.Context, currentSlot primitives.Slot, headState state.BeaconState, headRoot [32]byte, lastRoot []byte, lastState state.BeaconState) {
if bytes.Equal(lastRoot, headRoot[:]) {
lastState.CopyAllTries()
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
log.WithError(err).Debug("Could not update next slot state cache")
}
} else {
// Last advanced state was not head, we do not advance this but rather use headstate
headState.CopyAllTries()
if err := transition.UpdateNextSlotCache(ctx, accessRoot[:], headState); err != nil {
if err := transition.UpdateNextSlotCache(ctx, headRoot[:], headState); err != nil {
log.WithError(err).Debug("Could not update next slot state cache")
}
}
if err := s.handleEpochBoundary(ctx, currentSlot, headState, accessRoot[:]); err != nil {
if err := s.handleEpochBoundary(ctx, currentSlot, headState, headRoot[:]); err != nil {
log.WithError(err).Error("Could not update epoch boundary caches")
}
}
@@ -1217,19 +1141,7 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
headState := s.headState(ctx)
s.headLock.RUnlock()
var accessRoot [32]byte
isFull, err := headState.IsParentBlockFull()
gloasFirstSlot, _ := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
if err != nil || !isFull || headState.Slot() <= gloasFirstSlot {
accessRoot = headRoot
} else {
accessRoot, err = headState.LatestBlockHash()
if err != nil {
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash, using head root as access root")
accessRoot = headRoot
}
}
s.refreshCaches(ctx, currentSlot, headRoot, headState, accessRoot)
s.refreshCaches(ctx, currentSlot, headRoot, headState)
// return early if we already started building a block for the current
// head root
_, has := s.cfg.PayloadIDCache.PayloadID(s.CurrentSlot()+1, headRoot)
@@ -1237,18 +1149,22 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
return
}
attribute := s.getPayloadAttribute(ctx, headState, s.CurrentSlot()+1, headRoot[:], accessRoot[:])
attribute := s.getPayloadAttribute(ctx, headState, s.CurrentSlot()+1, headRoot[:])
// return early if we are not proposing next slot
if attribute.IsEmpty() {
return
}
if headState.Version() >= version.Gloas {
bh, err := headState.LatestBlockHash()
bid, err := headState.LatestExecutionPayloadBid()
if err != nil {
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash")
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve execution payload bid")
return
}
bh := bid.ParentBlockHash()
if s.HasFullNode(headRoot) {
bh = bid.BlockHash()
}
id, err := s.notifyForkchoiceUpdateGloas(ctx, bh, attribute)
if err != nil {
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")

View File

@@ -46,7 +46,7 @@ func (s *Service) getFCUArgs(cfg *postBlockProcessConfig) (*fcuConfig, error) {
if err != nil {
return nil, err
}
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:], cfg.headRoot[:])
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:])
return fcuArgs, nil
}
@@ -195,25 +195,14 @@ func (s *Service) updateCachesPostBlockProcessing(cfg *postBlockProcessConfig) {
}
}
// reportProcessingTime reports the metric of how long it took to process the
// current block
func reportProcessingTime(startTime time.Time) {
onBlockProcessingTime.Observe(float64(time.Since(startTime).Milliseconds()))
}
// GetPrestateToPropose returns the pre-state for a proposer to base its block on.
// It is similar to GetBlockPreState but it lacks unnecessary verifications.
func (s *Service) GetPrestateToPropose(ctx context.Context, b consensus_blocks.ROBlock) (state.BeaconState, error) {
ctx, span := trace.StartSpan(ctx, "blockChain.GetPreStateToPropose")
defer span.End()
accessRoot, err := s.getLookupParentRoot(b)
if err != nil {
return nil, errors.Wrap(err, "could not get lookup parent root")
}
parentRoot := b.Block().ParentRoot()
bl := b.Block()
preState, err := s.cfg.StateGen.StateByRoot(ctx, accessRoot)
preState, err := s.cfg.StateGen.StateByRoot(ctx, parentRoot)
if err != nil {
return nil, errors.Wrapf(err, "could not get pre state for slot %d", bl.Slot())
}
@@ -223,6 +212,12 @@ func (s *Service) GetPrestateToPropose(ctx context.Context, b consensus_blocks.R
return preState, nil
}
// reportProcessingTime reports the metric of how long it took to process the
// current block
func reportProcessingTime(startTime time.Time) {
onBlockProcessingTime.Observe(float64(time.Since(startTime).Milliseconds()))
}
// GetBlockPreState returns the pre state of an incoming block. It uses the parent root of the block
// to retrieve the state in DB. It verifies the pre state's validity and the incoming block
// is in the correct time window.
@@ -230,17 +225,14 @@ func (s *Service) GetBlockPreState(ctx context.Context, b consensus_blocks.ROBlo
ctx, span := trace.StartSpan(ctx, "blockChain.getBlockPreState")
defer span.End()
accessRoot, err := s.getLookupParentRoot(b)
if err != nil {
return nil, errors.Wrap(err, "could not get lookup parent root")
}
parentRoot := b.Block().ParentRoot()
// Verify incoming block has a valid pre state.
if err := s.verifyBlkPreState(ctx, accessRoot); err != nil {
if err := s.verifyBlkPreState(ctx, parentRoot); err != nil {
return nil, err
}
bl := b.Block()
preState, err := s.cfg.StateGen.StateByRoot(ctx, accessRoot)
preState, err := s.cfg.StateGen.StateByRoot(ctx, parentRoot)
if err != nil {
return nil, errors.Wrapf(err, "could not get pre state for slot %d", bl.Slot())
}

View File

@@ -3642,11 +3642,11 @@ func TestHandleBlockPayloadAttestations(t *testing.T) {
func TestUpdateCachesAndEpochBoundary_MatchingRoots(t *testing.T) {
service := testServiceNoDB(t)
st, _ := util.DeterministicGenesisState(t, 1)
accessRoot := [32]byte{'a'}
headRoot := [32]byte{'a'}
service.updateCachesAndEpochBoundary(t.Context(), 1, st, accessRoot, accessRoot[:], st)
service.updateCachesAndEpochBoundary(t.Context(), 1, st, headRoot, headRoot[:], st)
cached := transition.NextSlotState(accessRoot[:], 1)
cached := transition.NextSlotState(headRoot[:], 1)
require.NotNil(t, cached)
require.Equal(t, primitives.Slot(1), cached.Slot())
}
@@ -3655,13 +3655,12 @@ func TestUpdateCachesAndEpochBoundary_DifferentRoots(t *testing.T) {
service := testServiceNoDB(t)
headState, _ := util.DeterministicGenesisState(t, 1)
lastState, _ := util.DeterministicGenesisState(t, 1)
accessRoot := [32]byte{'a'}
headRoot := [32]byte{'a'}
lastRoot := [32]byte{'b'}
service.updateCachesAndEpochBoundary(t.Context(), 1, headState, accessRoot, lastRoot[:], lastState)
service.updateCachesAndEpochBoundary(t.Context(), 1, headState, headRoot, lastRoot[:], lastState)
// Cache should be keyed by accessRoot, not lastRoot.
cached := transition.NextSlotState(accessRoot[:], 1)
cached := transition.NextSlotState(headRoot[:], 1)
require.NotNil(t, cached)
require.Equal(t, primitives.Slot(1), cached.Slot())
@@ -3674,25 +3673,24 @@ func TestRefreshCaches_NoCachedState(t *testing.T) {
st, _ := util.DeterministicGenesisState(t, 1)
headRoot := [32]byte{'h'}
service.refreshCaches(t.Context(), 1, headRoot, st, headRoot)
service.refreshCaches(t.Context(), 1, headRoot, st)
cached := transition.NextSlotState(headRoot[:], 1)
require.NotNil(t, cached)
require.Equal(t, primitives.Slot(1), cached.Slot())
}
func TestRefreshCaches_CachedStateMatchesAccessRoot(t *testing.T) {
func TestRefreshCaches_CachedStateMatchesHeadRoot(t *testing.T) {
service := testServiceNoDB(t)
st, _ := util.DeterministicGenesisState(t, 1)
accessRoot := [32]byte{'a'}
headRoot := [32]byte{'h'}
// Pre-populate the cache with accessRoot.
require.NoError(t, transition.UpdateNextSlotCache(t.Context(), accessRoot[:], st))
// Pre-populate the cache with headRoot.
require.NoError(t, transition.UpdateNextSlotCache(t.Context(), headRoot[:], st))
service.refreshCaches(t.Context(), 1, headRoot, st, accessRoot)
service.refreshCaches(t.Context(), 1, headRoot, st)
cached := transition.NextSlotState(accessRoot[:], 1)
cached := transition.NextSlotState(headRoot[:], 1)
require.NotNil(t, cached)
require.Equal(t, primitives.Slot(1), cached.Slot())
}

View File

@@ -133,8 +133,7 @@ func (s *Service) UpdateHead(ctx context.Context, proposingSlot primitives.Slot)
processAttsElapsedTime.Observe(float64(time.Since(start).Milliseconds()))
start = time.Now()
// return early if we haven't changed head
newHeadRoot, newHeadBlockHash, full, err := s.cfg.ForkChoiceStore.FullHead(ctx)
newHeadRoot, _, full, err := s.cfg.ForkChoiceStore.FullHead(ctx)
if err != nil {
log.WithError(err).Error("Could not compute head from new attestations")
return
@@ -143,40 +142,39 @@ func (s *Service) UpdateHead(ctx context.Context, proposingSlot primitives.Slot)
return
}
log.WithField("newHeadRoot", fmt.Sprintf("%#x", newHeadRoot)).Debug("Head changed due to attestations")
var accessRoot [32]byte
postGloas := slots.ToEpoch(proposingSlot) >= params.BeaconConfig().GloasForkEpoch
if full && postGloas {
accessRoot = newHeadBlockHash
} else {
accessRoot = newHeadRoot
}
headState, headBlock, err := s.getStateAndBlock(ctx, newHeadRoot, accessRoot)
headState, headBlock, err := s.getStateAndBlock(ctx, newHeadRoot, newHeadRoot)
if err != nil {
log.WithError(err).Error("Could not get head block and state")
return
}
newAttHeadElapsedTime.Observe(float64(time.Since(start).Milliseconds()))
if s.inRegularSync() {
attr := s.getPayloadAttribute(ctx, headState, proposingSlot, newHeadRoot[:], accessRoot[:])
attr := s.getPayloadAttribute(ctx, headState, proposingSlot, newHeadRoot[:])
if attr != nil && s.shouldOverrideFCU(newHeadRoot, proposingSlot) {
return
}
postGloas := slots.ToEpoch(proposingSlot) >= params.BeaconConfig().GloasForkEpoch
if postGloas {
go func() {
pid, err := s.notifyForkchoiceUpdateGloas(s.ctx, newHeadBlockHash, attr)
if err != nil {
log.WithError(err).Error("Could not update forkchoice with engine")
}
if pid == nil {
if attr != nil {
log.Warn("Engine did not return a payload ID for the fork choice update with attributes")
blockHash, hashErr := s.cfg.ForkChoiceStore.BlockHash(newHeadRoot)
if hashErr != nil {
log.WithError(hashErr).Error("Could not get block hash from forkchoice for FCU")
} else {
go func() {
pid, err := s.notifyForkchoiceUpdateGloas(s.ctx, blockHash, attr)
if err != nil {
log.WithError(err).Error("Could not update forkchoice with engine")
}
return
}
var pId [8]byte
copy(pId[:], pid[:])
s.cfg.PayloadIDCache.Set(proposingSlot, newHeadRoot, pId)
}()
if pid == nil {
if attr != nil {
log.Warn("Engine did not return a payload ID for the fork choice update with attributes")
}
return
}
var pId [8]byte
copy(pId[:], pid[:])
s.cfg.PayloadIDCache.Set(proposingSlot, newHeadRoot, pId)
}()
}
} else {
fcuArgs := &fcuConfig{
headState: headState,

View File

@@ -1,7 +1,6 @@
package blockchain
import (
"bytes"
"context"
"fmt"
"time"
@@ -9,7 +8,6 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed"
statefeed "github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/state"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
@@ -59,7 +57,7 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed in
}
defer s.payloadBeingSynced.unset(root)
preState, err := s.getPayloadEnvelopePrestate(ctx, envelope)
blockState, err := s.getPayloadEnvelopePrestate(ctx, envelope)
if err != nil {
return err
}
@@ -68,12 +66,12 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed in
g, gCtx := errgroup.WithContext(ctx)
g.Go(func() error {
return gloas.ProcessExecutionPayload(gCtx, preState, signed)
return gloas.VerifyExecutionPayloadEnvelope(gCtx, blockState, signed)
})
g.Go(func() error {
var elErr error
isValidPayload, elErr = s.validateExecutionOnEnvelope(gCtx, preState, envelope)
isValidPayload, elErr = s.validateExecutionOnEnvelope(gCtx, blockState, envelope)
return elErr
})
@@ -82,7 +80,7 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed in
}
// DA check: verify data columns are available before inserting payload.
bid, err := preState.LatestExecutionPayloadBid()
bid, err := blockState.LatestExecutionPayloadBid()
if err != nil {
return errors.Wrap(err, "could not get latest execution payload bid")
}
@@ -91,7 +89,7 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed in
return errors.Wrap(err, "data availability check failed for payload envelope")
}
}
if err := s.savePostPayload(ctx, signed, preState); err != nil {
if err := s.savePostPayload(ctx, signed); err != nil {
return err
}
if err := s.InsertPayload(envelope); err != nil {
@@ -106,12 +104,13 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed in
s.cfg.ForkChoiceStore.Unlock()
}
headRoot, err := s.HeadRoot(ctx)
headRootSlice, err := s.HeadRoot(ctx)
if err != nil {
log.WithError(err).Error("Could not get head root")
return nil
}
if err := s.postPayloadHeadUpdate(ctx, envelope, preState, root, headRoot); err != nil {
headRoot := bytesutil.ToBytes32(headRootSlice)
if err := s.postPayloadTasks(ctx, envelope, blockState, root, headRoot); err != nil {
return err
}
@@ -138,8 +137,8 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed in
return nil
}
func (s *Service) postPayloadHeadUpdate(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, st state.BeaconState, root [32]byte, headRoot []byte) error {
if !bytes.Equal(headRoot, root[:]) {
func (s *Service) postPayloadTasks(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, st state.BeaconState, root, headRoot [32]byte) error {
if headRoot != root {
return nil
}
payload, err := envelope.Execution()
@@ -149,22 +148,12 @@ func (s *Service) postPayloadHeadUpdate(ctx context.Context, envelope interfaces
blockHash := bytesutil.ToBytes32(payload.BlockHash())
s.headLock.Lock()
s.head.state = st
s.head.full = true
if s.head != nil && s.head.root == root {
s.head.full = true
}
s.headLock.Unlock()
go func() {
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
defer cancel()
if err := transition.UpdateNextSlotCache(ctx, blockHash[:], st); err != nil {
log.WithError(err).Error("Could not update next slot cache")
}
if err := s.handleEpochBoundary(ctx, envelope.Slot(), st, blockHash[:]); err != nil {
log.WithError(err).Error("Could not handle epoch boundary")
}
}()
attr := s.getPayloadAttribute(ctx, st, envelope.Slot()+1, headRoot, blockHash[:])
attr := s.getPayloadAttribute(ctx, st, envelope.Slot()+1, headRoot[:])
if s.inRegularSync() {
go func() {
pid, err := s.notifyForkchoiceUpdateGloas(s.ctx, blockHash, attr)
@@ -287,7 +276,7 @@ func (s *Service) validateExecutionOnEnvelope(ctx context.Context, st state.Beac
return false, s.handleInvalidExecutionError(ctx, err, blockRoot, parentRoot, parentHash)
}
func (s *Service) savePostPayload(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope, st state.BeaconState) error {
func (s *Service) savePostPayload(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope) error {
ctx, span := trace.StartSpan(ctx, "blockChain.savePostPayload")
defer span.End()
@@ -295,19 +284,7 @@ func (s *Service) savePostPayload(ctx context.Context, signed interfaces.ROSigne
if !ok {
return errors.New("could not type assert signed envelope to proto")
}
if err := s.cfg.BeaconDB.SaveExecutionPayloadEnvelope(ctx, protoEnv); err != nil {
return errors.Wrap(err, "could not save execution payload envelope")
}
envelope, err := signed.Envelope()
if err != nil {
return errors.Wrap(err, "could not get envelope")
}
payload, err := envelope.Execution()
if err != nil {
return errors.Wrap(err, "could not get execution payload from envelope")
}
return s.cfg.StateGen.SaveState(ctx, bytesutil.ToBytes32(payload.BlockHash()), st)
return s.cfg.BeaconDB.SaveExecutionPayloadEnvelope(ctx, protoEnv)
}
// notifyForkchoiceUpdateGloas takes the block hash directly because Gloas

View File

@@ -345,7 +345,7 @@ func (s *Service) initializeHead(ctx context.Context, st state.BeaconState) erro
return errors.Wrap(err, "could not get head state")
}
}
if err := s.setHead(&head{root, blk, st, blk.Block().Slot(), false, false}); err != nil {
if err := s.setHead(&head{root: root, block: blk, state: st, slot: blk.Block().Slot(), optimistic: false}); err != nil {
return errors.Wrap(err, "could not set head")
}
log.WithFields(logrus.Fields{
@@ -429,12 +429,11 @@ func (s *Service) saveGenesisData(ctx context.Context, genesisState state.Beacon
s.cfg.ForkChoiceStore.SetGenesisTime(s.genesisTime)
if err := s.setHead(&head{
genesisBlkRoot,
genesisBlk,
genesisState,
genesisBlk.Block().Slot(),
false,
false,
root: genesisBlkRoot,
block: genesisBlk,
state: genesisState,
slot: genesisBlk.Block().Slot(),
optimistic: false,
}); err != nil {
log.WithError(err).Fatal("Could not set head")
}

View File

@@ -38,6 +38,7 @@ var ErrNilState = errors.New("nil state")
type ChainService struct {
NotFinalized bool
Optimistic bool
FullHead bool
ValidAttestation bool
ValidatorsRoot [32]byte
PublicKey [fieldparams.BLSPubkeyLength]byte
@@ -80,11 +81,10 @@ type ChainService struct {
DependentRootCB func([32]byte, primitives.Epoch) ([32]byte, error)
MockCanonicalRoots map[primitives.Slot][32]byte
MockCanonicalFull map[primitives.Slot]bool
MockPayloadContentLookup map[[32]byte][32]byte
MockPayloadContentIsFull map[[32]byte]bool
ParentPayloadReadyVal *bool
ForkchoiceRoots map[[32]byte]bool
ForkchoiceBlockHashes map[[32]byte][32]byte
ParentPayloadReadyVal *bool
ForkchoiceRoots map[[32]byte]bool
ForkchoiceBlockHashes map[[32]byte][32]byte
}
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
@@ -352,6 +352,11 @@ func (s *ChainService) GetPrestateToPropose(_ context.Context, _ blocks.ROBlock)
return s.State.Copy(), nil
}
// HeadFull mocks HeadFull method in chain service.
func (s *ChainService) HeadFull() bool {
return s.FullHead
}
// HeadSlot mocks HeadSlot method in chain service.
func (s *ChainService) HeadSlot() primitives.Slot {
if s.MockHeadSlot != nil {
@@ -758,22 +763,17 @@ func (s *ChainService) HasFullNode(root [32]byte) bool {
return false
}
// ShouldIgnoreData returns true if the data for the given parent root and slot should be ignored.
func (s *ChainService) ShouldIgnoreData(_ [32]byte, _ primitives.Slot) bool {
// IsFullNode mocks the same method in the chain service.
func (s *ChainService) IsFullNode(root [32]byte) bool {
if s.ForkChoiceStore != nil {
return s.ForkChoiceStore.IsFullNode(root)
}
return false
}
// PayloadContentLookup mocks the same method in the chain service.
func (s *ChainService) PayloadContentLookup(root [32]byte) ([32]byte, bool) {
if s.ForkChoiceStore != nil {
return s.ForkChoiceStore.PayloadContentLookup(root)
}
if s.MockPayloadContentLookup != nil {
if value, ok := s.MockPayloadContentLookup[root]; ok {
return value, s.MockPayloadContentIsFull[root]
}
}
return root, false
// ShouldIgnoreData returns true if the data for the given parent root and slot should be ignored.
func (s *ChainService) ShouldIgnoreData(_ [32]byte, _ primitives.Slot) bool {
return false
}
// InsertNode mocks the same method in the chain service

View File

@@ -89,16 +89,17 @@ func testSignedExecutionPayloadBid(
) *ethpb.SignedExecutionPayloadBid {
return &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
Slot: slot,
ParentBlockHash: bytes.Clone(parentHash[:]),
ParentBlockRoot: bytes.Clone(parentRoot[:]),
BlockHash: bytes.Repeat([]byte{0x03}, 32),
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
GasLimit: 30_000_000,
BuilderIndex: 1,
Value: primitives.Gwei(value),
ExecutionPayment: 10,
Slot: slot,
ParentBlockHash: bytes.Clone(parentHash[:]),
ParentBlockRoot: bytes.Clone(parentRoot[:]),
BlockHash: bytes.Repeat([]byte{0x03}, 32),
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
GasLimit: 30_000_000,
BuilderIndex: 1,
Value: primitives.Gwei(value),
ExecutionPayment: 10,
ExecutionRequestsRoot: make([]byte, 32),
},
Signature: bytes.Repeat([]byte{0x06}, 96),
}

View File

@@ -9,6 +9,7 @@ go_library(
"deposit_request.go",
"log.go",
"metrics.go",
"parent_payload.go",
"payload.go",
"payload_attestation.go",
"pending_payment.go",

View File

@@ -53,6 +53,9 @@ func (s stubBlockBody) PayloadAttestations() ([]*ethpb.PayloadAttestation, error
func (s stubBlockBody) SignedExecutionPayloadBid() (*ethpb.SignedExecutionPayloadBid, error) {
return s.signedBid, nil
}
func (s stubBlockBody) ParentExecutionRequests() (*enginev1.ExecutionRequests, error) {
return nil, nil
}
func (s stubBlockBody) MarshalSSZ() ([]byte, error) { return nil, nil }
func (s stubBlockBody) MarshalSSZTo([]byte) ([]byte, error) { return nil, nil }
func (s stubBlockBody) UnmarshalSSZ([]byte) error { return nil }
@@ -216,17 +219,18 @@ func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 0,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 0,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
signed := &ethpb.SignedExecutionPayloadBid{
Message: bid,
@@ -258,16 +262,17 @@ func TestProcessExecutionPayloadBid_SelfBuildNonZeroAmountFails(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, [48]byte{})
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
PrevRandao: randao[:],
BuilderIndex: builderIdx,
Slot: slot,
Value: 10,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
PrevRandao: randao[:],
BuilderIndex: builderIdx,
Slot: slot,
Value: 10,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
signed := &ethpb.SignedExecutionPayloadBid{
Message: bid,
@@ -302,17 +307,18 @@ func TestProcessExecutionPayloadBid_PendingPaymentAndCacheBid(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 500_000,
ExecutionPayment: 1,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 500_000,
ExecutionPayment: 1,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
@@ -363,17 +369,18 @@ func TestProcessExecutionPayloadBid_BuilderNotActive(t *testing.T) {
state = stateIface.(*state_native.BeaconState)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
BlockHash: bytes.Repeat([]byte{0x04}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 10,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
BlockHash: bytes.Repeat([]byte{0x04}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 10,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -416,17 +423,18 @@ func TestProcessExecutionPayloadBid_CannotCoverBid(t *testing.T) {
state = stateIface.(*state_native.BeaconState)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 25,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 25,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -458,17 +466,18 @@ func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 10,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 10,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
// Use an invalid signature.
invalidSig := [96]byte{1}
@@ -495,14 +504,15 @@ func TestProcessExecutionPayloadBid_TooManyBlobCommitments(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
BuilderIndex: builderIdx,
Slot: slot,
BlobKzgCommitments: tooManyBlobCommitmentsForSlot(slot),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:],
BuilderIndex: builderIdx,
Slot: slot,
BlobKzgCommitments: tooManyBlobCommitmentsForSlot(slot),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
signed := &ethpb.SignedExecutionPayloadBid{
Message: bid,
@@ -536,17 +546,18 @@ func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot + 1, // mismatch
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot + 1, // mismatch
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -578,17 +589,18 @@ func TestProcessExecutionPayloadBid_ParentHashMismatch(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -621,17 +633,18 @@ func TestProcessExecutionPayloadBid_ParentRootMismatch(t *testing.T) {
parentRoot := bytes.Repeat([]byte{0x22}, 32)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: parentRoot,
BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: parentRoot,
BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: randao[:],
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -663,17 +676,18 @@ func TestProcessExecutionPayloadBid_PrevRandaoMismatch(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 1,
ExecutionPayment: 0,
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
ExecutionRequestsRoot: make([]byte, 32),
}
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis)

View File

@@ -0,0 +1,112 @@
package gloas
import (
"bytes"
"context"
requests "github.com/OffchainLabs/prysm/v7/beacon-chain/core/requests"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
"github.com/pkg/errors"
)
// ProcessParentExecutionPayload must run before process_block_header and
// process_execution_payload_bid, which overwrite the state fields it reads.
//
// <spec fn="process_parent_execution_payload" fork="gloas" hash="defer_payload">
func ProcessParentExecutionPayload(ctx context.Context, st state.BeaconState, blk interfaces.ReadOnlyBeaconBlock) error {
body := blk.Body()
signedBid, err := body.SignedExecutionPayloadBid()
if err != nil {
return errors.Wrap(err, "could not get signed execution payload bid")
}
bid := signedBid.Message
parentBid, err := st.LatestExecutionPayloadBid()
if err != nil {
return errors.Wrap(err, "could not get parent execution payload bid")
}
parentExecutionRequests, err := body.ParentExecutionRequests()
if err != nil {
return errors.Wrap(err, "could not get parent execution requests")
}
parentBidBlockHash := parentBid.BlockHash()
isParentFull := bytes.Equal(bid.ParentBlockHash, parentBidBlockHash[:])
if !isParentFull {
if !IsEmptyExecutionRequests(parentExecutionRequests) {
return errors.New("parent was empty but parent_execution_requests is non-empty")
}
return nil
}
requestsRoot, err := parentExecutionRequests.HashTreeRoot()
if err != nil {
return errors.Wrap(err, "could not compute parent execution requests root")
}
parentBidRequestRoot := parentBid.ExecutionRequestsRoot()
if requestsRoot != parentBidRequestRoot {
return errors.Errorf("parent execution requests root mismatch: block=%#x, bid=%#x", requestsRoot, parentBidRequestRoot)
}
return ApplyParentExecutionPayload(ctx, st, parentExecutionRequests)
}
// ApplyParentExecutionPayload reads parent_bid from state.latest_execution_payload_bid
// and mutates st. Called by ProcessParentExecutionPayload and by the validator during
// block production before computing withdrawals.
//
// <spec fn="apply_parent_execution_payload" fork="gloas" hash="defer_payload">
func ApplyParentExecutionPayload(
ctx context.Context,
st state.BeaconState,
reqs *enginev1.ExecutionRequests,
) error {
parentBid, err := st.LatestExecutionPayloadBid()
if err != nil {
return errors.Wrap(err, "could not get latest execution payload bid")
}
parentSlot := parentBid.Slot()
if err := processExecutionRequests(ctx, st, reqs); err != nil {
return errors.Wrap(err, "could not process parent execution requests")
}
if err := st.QueueBuilderPaymentForSlot(parentSlot); err != nil {
return errors.Wrap(err, "could not queue builder payment")
}
if err := st.SetExecutionPayloadAvailability(parentSlot, true); err != nil {
return errors.Wrap(err, "could not set parent execution payload availability")
}
blockHash := parentBid.BlockHash()
if err := st.SetLatestBlockHash(blockHash); err != nil {
return errors.Wrap(err, "could not set latest block hash")
}
return nil
}
func processExecutionRequests(ctx context.Context, st state.BeaconState, rqs *enginev1.ExecutionRequests) error {
if err := processDepositRequests(ctx, st, rqs.Deposits); err != nil {
return errors.Wrap(err, "could not process deposit requests")
}
var err error
st, err = requests.ProcessWithdrawalRequests(ctx, st, rqs.Withdrawals)
if err != nil {
return errors.Wrap(err, "could not process withdrawal requests")
}
return requests.ProcessConsolidationRequests(ctx, st, rqs.Consolidations)
}
// IsEmptyExecutionRequests returns true if the execution requests contain no entries.
func IsEmptyExecutionRequests(r *enginev1.ExecutionRequests) bool {
if r == nil {
return true
}
return len(r.Deposits) == 0 && len(r.Withdrawals) == 0 && len(r.Consolidations) == 0
}

View File

@@ -5,106 +5,50 @@ import (
"context"
"fmt"
requests "github.com/OffchainLabs/prysm/v7/beacon-chain/core/requests"
"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/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/crypto/bls"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
)
// ProcessExecutionPayload is the gossip entry point: verify signature, validate
// consistency, apply state mutations, and verify the post-payload state root.
// VerifyExecutionPayloadEnvelope is a verification function called by fork-choice when
// importing a signed execution payload. It verifies the payload against the
// execution engine without processing execution requests or updating state.
// Actual state mutations are deferred to process_parent_execution_payload in
// the next block.
//
// <spec fn="process_execution_payload" fork="gloas" hash="36bd3af3">
// def process_execution_payload(
// state: BeaconState,
// # [Modified in Gloas:EIP7732]
// # Removed `body`
// # [New in Gloas:EIP7732]
// signed_envelope: SignedExecutionPayloadEnvelope,
// execution_engine: ExecutionEngine,
// # [New in Gloas:EIP7732]
// verify: bool = True,
// ) -> None:
// <spec fn="verify_execution_payload_envelope" fork="gloas" hash="defer_payload">
// def verify_execution_payload_envelope(state, signed_envelope, execution_engine):
// envelope = signed_envelope.message
// payload = envelope.payload
//
// # Verify signature
// if verify:
// assert verify_execution_payload_envelope_signature(state, signed_envelope)
//
// # Cache latest block header state root
// previous_state_root = hash_tree_root(state)
// if state.latest_block_header.state_root == Root():
// state.latest_block_header.state_root = previous_state_root
//
// # Verify consistency with the beacon block
// assert envelope.beacon_block_root == hash_tree_root(state.latest_block_header)
// assert verify_execution_payload_envelope_signature(state, signed_envelope)
// header = copy(state.latest_block_header)
// header.state_root = hash_tree_root(state)
// assert envelope.beacon_block_root == hash_tree_root(header)
// assert envelope.slot == state.slot
//
// # Verify consistency with the committed bid
// committed_bid = state.latest_execution_payload_bid
// assert envelope.builder_index == committed_bid.builder_index
// assert committed_bid.prev_randao == payload.prev_randao
//
// # Verify consistency with expected withdrawals
// assert hash_tree_root(envelope.execution_requests) == committed_bid.execution_requests_root
// assert hash_tree_root(payload.withdrawals) == hash_tree_root(state.payload_expected_withdrawals)
//
// # Verify the gas_limit
// assert committed_bid.gas_limit == payload.gas_limit
// # Verify the block hash
// assert committed_bid.block_hash == payload.block_hash
// # Verify consistency of the parent hash with respect to the previous execution payload
// assert payload.parent_hash == state.latest_block_hash
// # Verify timestamp
// assert payload.timestamp == compute_time_at_slot(state, state.slot)
// # Verify the execution payload is valid
// versioned_hashes = [
// kzg_commitment_to_versioned_hash(commitment)
// # [Modified in Gloas:EIP7732]
// for commitment in committed_bid.blob_kzg_commitments
// ]
// requests = envelope.execution_requests
// assert execution_engine.verify_and_notify_new_payload(
// NewPayloadRequest(
// execution_payload=payload,
// versioned_hashes=versioned_hashes,
// versioned_hashes=[kzg_commitment_to_versioned_hash(c) for c in committed_bid.blob_kzg_commitments],
// parent_beacon_block_root=state.latest_block_header.parent_root,
// execution_requests=requests,
// execution_requests=envelope.execution_requests,
// )
// )
//
// def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
// for operation in operations:
// fn(state, operation)
//
// for_ops(requests.deposits, process_deposit_request)
// for_ops(requests.withdrawals, process_withdrawal_request)
// for_ops(requests.consolidations, process_consolidation_request)
//
// # Queue the builder payment
// payment = state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH]
// amount = payment.withdrawal.amount
// if amount > 0:
// state.builder_pending_withdrawals.append(payment.withdrawal)
// state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH] = (
// BuilderPendingPayment()
// )
//
// # Cache the execution payload hash
// state.execution_payload_availability[state.slot % SLOTS_PER_HISTORICAL_ROOT] = 0b1
// state.latest_block_hash = payload.block_hash
//
// # Verify the state root
// if verify:
// assert envelope.state_root == hash_tree_root(state)
// </spec>
func ProcessExecutionPayload(
func VerifyExecutionPayloadEnvelope(
ctx context.Context,
st state.BeaconState,
signedEnvelope interfaces.ROSignedExecutionPayloadEnvelope,
@@ -118,26 +62,15 @@ func ProcessExecutionPayload(
return errors.Wrap(err, "could not get envelope from signed envelope")
}
if err := cacheLatestBlockHeaderStateRoot(ctx, st); err != nil {
return err
}
if err := validatePayloadConsistency(st, envelope); err != nil {
return err
}
if err := applyExecutionPayloadStateMutations(ctx, st, envelope.ExecutionRequests(), envelope.BlockHash()); err != nil {
return err
}
return verifyPostStateRoot(ctx, st, envelope)
return validatePayloadConsistency(ctx, st, envelope)
}
// ProcessExecutionPayloadWithDeferredSig is the init-sync entry point: extract the
// signature for deferred verification, validate consistency, apply state
// mutations, and verify the post-payload state root. The caller provides the
// previousStateRoot to avoid recomputing it.
func ProcessExecutionPayloadWithDeferredSig(
// VerifyExecutionPayloadEnvelopeWithDeferredSig is the init-sync entry point: extract
// the signature for deferred batch verification and validate consistency.
// No state mutations are performed.
func VerifyExecutionPayloadEnvelopeWithDeferredSig(
ctx context.Context,
st state.BeaconState,
previousStateRoot [32]byte,
signedEnvelope interfaces.ROSignedExecutionPayloadEnvelope,
) (*bls.SignatureBatch, error) {
sigBatch, err := ExecutionPayloadEnvelopeSignatureBatch(st, signedEnvelope)
@@ -150,128 +83,15 @@ func ProcessExecutionPayloadWithDeferredSig(
return nil, errors.Wrap(err, "could not get envelope from signed envelope")
}
if err := setLatestBlockHeaderStateRoot(st, previousStateRoot); err != nil {
return nil, errors.Wrap(err, "could not set latest block header state root")
}
if err := validatePayloadConsistency(st, envelope); err != nil {
return nil, err
}
if err := applyExecutionPayloadStateMutations(ctx, st, envelope.ExecutionRequests(), envelope.BlockHash()); err != nil {
return nil, err
}
if err := verifyPostStateRoot(ctx, st, envelope); err != nil {
if err := validatePayloadConsistency(ctx, st, envelope); err != nil {
return nil, err
}
return sigBatch, nil
}
// ProcessBlindedExecutionPayload is the replay/stategen entry
// point: patch the block header, do minimal bid consistency checks, and apply
// state mutations. No payload data is available — only the blinded envelope.
// A nil envelope is a no-op (the payload was not delivered for that slot).
func ProcessBlindedExecutionPayload(
ctx context.Context,
st state.BeaconState,
previousStateRoot [32]byte,
envelope interfaces.ROBlindedExecutionPayloadEnvelope,
) error {
if envelope == nil {
return nil
}
if err := setLatestBlockHeaderStateRoot(st, previousStateRoot); err != nil {
return errors.Wrap(err, "could not set latest block header state root")
}
if envelope.Slot() != st.Slot() {
return errors.Errorf("blinded envelope slot does not match state slot: envelope=%d, state=%d", envelope.Slot(), st.Slot())
}
latestBid, err := st.LatestExecutionPayloadBid()
if err != nil {
return errors.Wrap(err, "could not get latest execution payload bid")
}
if latestBid == nil {
return errors.New("latest execution payload bid is nil")
}
if envelope.BuilderIndex() != latestBid.BuilderIndex() {
return errors.Errorf(
"blinded envelope builder index does not match committed bid builder index: envelope=%d, bid=%d",
envelope.BuilderIndex(),
latestBid.BuilderIndex(),
)
}
bidBlockHash := latestBid.BlockHash()
envelopeBlockHash := envelope.BlockHash()
if bidBlockHash != envelopeBlockHash {
return errors.Errorf(
"blinded envelope block hash does not match committed bid block hash: envelope=%#x, bid=%#x",
envelopeBlockHash,
bidBlockHash,
)
}
return applyExecutionPayloadStateMutations(ctx, st, envelope.ExecutionRequests(), envelopeBlockHash)
}
// ApplyExecutionPayload patches the block header state root, validates
// consistency, and applies state mutations. No signature or post-state-root
// verification is performed. Used by the proposer path to compute the
// post-payload state root for the envelope.
func ApplyExecutionPayload(
ctx context.Context,
st state.BeaconState,
envelope interfaces.ROExecutionPayloadEnvelope,
) error {
if err := cacheLatestBlockHeaderStateRoot(ctx, st); err != nil {
return err
}
if err := validatePayloadConsistency(st, envelope); err != nil {
return err
}
return applyExecutionPayloadStateMutations(ctx, st, envelope.ExecutionRequests(), envelope.BlockHash())
}
func setLatestBlockHeaderStateRoot(st state.BeaconState, root [32]byte) error {
latestHeader := st.LatestBlockHeader()
latestHeader.StateRoot = root[:]
return st.SetLatestBlockHeader(latestHeader)
}
// cacheLatestBlockHeaderStateRoot fills in the state root on the latest block
// header if it hasn't been set yet (the spec's "cache latest block header
// state root" step).
func cacheLatestBlockHeaderStateRoot(ctx context.Context, st state.BeaconState) error {
latestHeader := st.LatestBlockHeader()
if len(latestHeader.StateRoot) == 0 || bytes.Equal(latestHeader.StateRoot, make([]byte, 32)) {
previousStateRoot, err := st.HashTreeRoot(ctx)
if err != nil {
return errors.Wrap(err, "could not compute state root")
}
latestHeader.StateRoot = previousStateRoot[:]
if err := st.SetLatestBlockHeader(latestHeader); err != nil {
return errors.Wrap(err, "could not set latest block header")
}
}
return nil
}
// validatePayloadConsistency checks that the envelope and payload are consistent
// with the beacon block header, the committed bid, and the current state.
func validatePayloadConsistency(st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) error {
latestHeader := st.LatestBlockHeader()
blockHeaderRoot, err := latestHeader.HashTreeRoot()
if err != nil {
return errors.Wrap(err, "could not compute block header root")
}
beaconBlockRoot := envelope.BeaconBlockRoot()
if !bytes.Equal(beaconBlockRoot[:], blockHeaderRoot[:]) {
return errors.Errorf("envelope beacon block root does not match state latest block header root: envelope=%#x, header=%#x", beaconBlockRoot, blockHeaderRoot)
}
func validatePayloadConsistency(ctx context.Context, st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) error {
if envelope.Slot() != st.Slot() {
return errors.Errorf("envelope slot does not match state slot: envelope=%d, state=%d", envelope.Slot(), st.Slot())
}
@@ -287,6 +107,16 @@ func validatePayloadConsistency(st state.BeaconState, envelope interfaces.ROExec
return errors.Errorf("envelope builder index does not match committed bid builder index: envelope=%d, bid=%d", envelope.BuilderIndex(), latestBid.BuilderIndex())
}
// Verify execution_requests_root matches the bid commitment.
executionRequestsRoot, err := envelope.ExecutionRequests().HashTreeRoot()
if err != nil {
return errors.Wrap(err, "could not compute execution requests root")
}
bidExecutionRequestsRoot := latestBid.ExecutionRequestsRoot()
if executionRequestsRoot != bidExecutionRequestsRoot {
return errors.Errorf("execution requests root mismatch: envelope=%#x, bid=%#x", executionRequestsRoot, bidExecutionRequestsRoot)
}
payload, err := envelope.Execution()
if err != nil {
return errors.Wrap(err, "could not get execution payload from envelope")
@@ -337,47 +167,6 @@ func validatePayloadConsistency(st state.BeaconState, envelope interfaces.ROExec
return nil
}
// verifyPostStateRoot checks that the post-payload state root matches the
// envelope's declared state root.
func verifyPostStateRoot(ctx context.Context, st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) error {
r, err := st.HashTreeRoot(ctx)
if err != nil {
return errors.Wrap(err, "could not compute post-envelope state root")
}
if r != envelope.StateRoot() {
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
}
return nil
}
// applyExecutionPayloadStateMutations applies the state-changing operations
// from an execution payload: process execution requests, queue builder payment,
// set execution payload availability, and update the latest block hash.
func applyExecutionPayloadStateMutations(
ctx context.Context,
st state.BeaconState,
executionRequests *enginev1.ExecutionRequests,
blockHash [32]byte,
) error {
if err := processExecutionRequests(ctx, st, executionRequests); err != nil {
return errors.Wrap(err, "could not process execution requests")
}
if err := st.QueueBuilderPayment(); err != nil {
return errors.Wrap(err, "could not queue builder payment")
}
if err := st.SetExecutionPayloadAvailability(st.Slot(), true); err != nil {
return errors.Wrap(err, "could not set execution payload availability")
}
if err := st.SetLatestBlockHash(blockHash); err != nil {
return errors.Wrap(err, "could not set latest block hash")
}
return nil
}
// ExecutionPayloadEnvelopeSignatureBatch extracts the BLS signature from a signed execution payload
// envelope as a SignatureBatch for deferred batch verification.
func ExecutionPayloadEnvelopeSignatureBatch(
@@ -513,21 +302,3 @@ func builderPublicKey(st state.BeaconState, builderIdx primitives.BuilderIndex)
}
return publicKey, nil
}
// processExecutionRequests processes deposits, withdrawals, and consolidations from execution requests.
func processExecutionRequests(ctx context.Context, st state.BeaconState, rqs *enginev1.ExecutionRequests) error {
if err := processDepositRequests(ctx, st, rqs.Deposits); err != nil {
return errors.Wrap(err, "could not process deposit requests")
}
var err error
st, err = requests.ProcessWithdrawalRequests(ctx, st, rqs.Withdrawals)
if err != nil {
return errors.Wrap(err, "could not process withdrawal requests")
}
err = requests.ProcessConsolidationRequests(ctx, st, rqs.Consolidations)
if err != nil {
return errors.Wrap(err, "could not process consolidation requests")
}
return nil
}

View File

@@ -2,7 +2,6 @@ package gloas
import (
"bytes"
"context"
"testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
@@ -68,17 +67,20 @@ func buildPayloadFixture(t *testing.T, mutate func(payload *enginev1.ExecutionPa
ExcessBlobGas: 0,
}
emptyRequestsRoot, err := (&enginev1.ExecutionRequests{}).HashTreeRoot()
require.NoError(t, err)
bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: parentHash,
ParentBlockRoot: bytes.Repeat([]byte{0xDD}, 32),
BlockHash: blockHash,
PrevRandao: randao,
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 0,
ExecutionPayment: 0,
FeeRecipient: bytes.Repeat([]byte{0xEE}, 20),
ParentBlockHash: parentHash,
ParentBlockRoot: bytes.Repeat([]byte{0xDD}, 32),
BlockHash: blockHash,
PrevRandao: randao,
GasLimit: 1,
BuilderIndex: builderIdx,
Slot: slot,
Value: 0,
ExecutionPayment: 0,
FeeRecipient: bytes.Repeat([]byte{0xEE}, 20),
ExecutionRequestsRoot: emptyRequestsRoot[:],
}
header := &ethpb.BeaconBlockHeader{
@@ -187,18 +189,6 @@ func buildPayloadFixture(t *testing.T, mutate func(payload *enginev1.ExecutionPa
st, err := state_native.InitializeFromProtoGloas(stProto)
require.NoError(t, err)
expected := st.Copy()
ctx := context.Background()
require.NoError(t, processExecutionRequests(ctx, expected, envelope.ExecutionRequests))
require.NoError(t, expected.QueueBuilderPayment())
require.NoError(t, expected.SetExecutionPayloadAvailability(slot, true))
var blockHashArr [32]byte
copy(blockHashArr[:], payload.BlockHash)
require.NoError(t, expected.SetLatestBlockHash(blockHashArr))
expectedRoot, err := expected.HashTreeRoot(ctx)
require.NoError(t, err)
envelope.StateRoot = expectedRoot[:]
epoch := slots.ToEpoch(slot)
domain, err := signing.Domain(st.Fork(), epoch, cfg.DomainBeaconBuilder, st.GenesisValidatorsRoot())
require.NoError(t, err)
@@ -223,33 +213,15 @@ func buildPayloadFixture(t *testing.T, mutate func(payload *enginev1.ExecutionPa
}
}
func TestProcessExecutionPayload_Success(t *testing.T) {
func TestVerifyExecutionPayloadEnvelope_Success(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
require.NoError(t, ProcessExecutionPayload(t.Context(), fixture.state, fixture.signed))
latestHash, err := fixture.state.LatestBlockHash()
require.NoError(t, err)
var expectedHash [32]byte
copy(expectedHash[:], fixture.payload.BlockHash)
require.Equal(t, expectedHash, latestHash)
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
paymentIndex := slotsPerEpoch + (fixture.slot % slotsPerEpoch)
payments, err := fixture.state.BuilderPendingPayments()
require.NoError(t, err)
payment := payments[paymentIndex]
require.NotNil(t, payment)
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
require.NoError(t, VerifyExecutionPayloadEnvelope(t.Context(), fixture.state, fixture.signed))
}
func TestProcessExecutionPayloadWithDeferredSig_Success(t *testing.T) {
func TestVerifyExecutionPayloadEnvelopeWithDeferredSig_Success(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
header := fixture.state.LatestBlockHeader()
var previousStateRoot [32]byte
copy(previousStateRoot[:], header.StateRoot)
sigBatch, err := ProcessExecutionPayloadWithDeferredSig(t.Context(), fixture.state, previousStateRoot, fixture.signed)
sigBatch, err := VerifyExecutionPayloadEnvelopeWithDeferredSig(t.Context(), fixture.state, fixture.signed)
require.NoError(t, err)
require.NotNil(t, sigBatch)
require.Equal(t, 1, len(sigBatch.Signatures))
@@ -261,176 +233,17 @@ func TestProcessExecutionPayloadWithDeferredSig_Success(t *testing.T) {
valid, err := sigBatch.Verify()
require.NoError(t, err)
require.Equal(t, true, valid)
latestHash, err := fixture.state.LatestBlockHash()
require.NoError(t, err)
var expectedHash [32]byte
copy(expectedHash[:], fixture.payload.BlockHash)
require.Equal(t, expectedHash, latestHash)
available, err := fixture.state.ExecutionPayloadAvailability(fixture.slot)
require.NoError(t, err)
require.Equal(t, uint64(1), available)
updatedHeader := fixture.state.LatestBlockHeader()
require.DeepEqual(t, previousStateRoot[:], updatedHeader.StateRoot)
}
func TestProcessExecutionPayloadWithDeferredSig_PreviousStateRootMismatch(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
previousStateRoot := [32]byte{0x42}
_, err := ProcessExecutionPayloadWithDeferredSig(t.Context(), fixture.state, previousStateRoot, fixture.signed)
require.ErrorContains(t, "envelope beacon block root does not match state latest block header root", err)
}
func TestApplyExecutionPayload_Success(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
envelope, err := fixture.signed.Envelope()
require.NoError(t, err)
require.NoError(t, ApplyExecutionPayload(t.Context(), fixture.state, envelope))
latestHash, err := fixture.state.LatestBlockHash()
require.NoError(t, err)
var expectedHash [32]byte
copy(expectedHash[:], fixture.payload.BlockHash)
require.Equal(t, expectedHash, latestHash)
available, err := fixture.state.ExecutionPayloadAvailability(fixture.slot)
require.NoError(t, err)
require.Equal(t, uint64(1), available)
}
func TestApplyExecutionPayloadStateMutations_UpdatesAvailabilityAndLatestHash(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
newHash := [32]byte{}
newHash[0] = 0x99
require.NoError(t, applyExecutionPayloadStateMutations(t.Context(), fixture.state, fixture.envelope.ExecutionRequests, newHash))
latestHash, err := fixture.state.LatestBlockHash()
require.NoError(t, err)
require.Equal(t, newHash, latestHash)
available, err := fixture.state.ExecutionPayloadAvailability(fixture.slot)
require.NoError(t, err)
require.Equal(t, uint64(1), available)
}
func TestProcessExecutionPayload_PrevRandaoMismatch(t *testing.T) {
func TestVerifyExecutionPayloadEnvelope_PrevRandaoMismatch(t *testing.T) {
fixture := buildPayloadFixture(t, func(_ *enginev1.ExecutionPayloadDeneb, bid *ethpb.ExecutionPayloadBid, _ *ethpb.ExecutionPayloadEnvelope) {
bid.PrevRandao = bytes.Repeat([]byte{0xFF}, 32)
})
err := ProcessExecutionPayload(t.Context(), fixture.state, fixture.signed)
err := VerifyExecutionPayloadEnvelope(t.Context(), fixture.state, fixture.signed)
require.ErrorContains(t, "prev randao", err)
}
func TestQueueBuilderPayment_ZeroAmountClearsSlot(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
require.NoError(t, fixture.state.QueueBuilderPayment())
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
paymentIndex := slotsPerEpoch + (fixture.slot % slotsPerEpoch)
payments, err := fixture.state.BuilderPendingPayments()
require.NoError(t, err)
payment := payments[paymentIndex]
require.NotNil(t, payment)
require.Equal(t, primitives.Gwei(0), payment.Withdrawal.Amount)
}
func TestProcessBlindedExecutionPayload_NilEnvelope(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
require.NoError(t, ProcessBlindedExecutionPayload(t.Context(), fixture.state, [32]byte{}, nil))
}
func TestProcessBlindedExecutionPayload_Success(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
st := fixture.state
blockHash := [32]byte(fixture.payload.BlockHash)
stateRoot := [32]byte{0xAA}
envelope := &ethpb.SignedBlindedExecutionPayloadEnvelope{
Message: &ethpb.BlindedExecutionPayloadEnvelope{
Slot: fixture.slot,
BuilderIndex: fixture.envelope.BuilderIndex,
BlockHash: blockHash[:],
BeaconBlockRoot: make([]byte, 32),
ExecutionRequests: fixture.envelope.ExecutionRequests,
},
}
wrappedEnv, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(envelope.Message)
require.NoError(t, err)
require.NoError(t, ProcessBlindedExecutionPayload(t.Context(), st, stateRoot, wrappedEnv))
latestHash, err := st.LatestBlockHash()
require.NoError(t, err)
require.Equal(t, blockHash, latestHash)
available, err := st.ExecutionPayloadAvailability(fixture.slot)
require.NoError(t, err)
require.Equal(t, uint64(1), available)
header := st.LatestBlockHeader()
require.DeepEqual(t, stateRoot[:], header.StateRoot)
}
func TestProcessBlindedExecutionPayload_SlotMismatch(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
envelope := &ethpb.SignedBlindedExecutionPayloadEnvelope{
Message: &ethpb.BlindedExecutionPayloadEnvelope{
Slot: fixture.slot + 1,
BlockHash: make([]byte, 32),
BeaconBlockRoot: make([]byte, 32),
},
}
wrappedEnv, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(envelope.Message)
require.NoError(t, err)
err = ProcessBlindedExecutionPayload(t.Context(), fixture.state, [32]byte{}, wrappedEnv)
require.ErrorContains(t, "blinded envelope slot does not match state slot", err)
}
func TestProcessBlindedExecutionPayload_BuilderIndexMismatch(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
blockHash := [32]byte(fixture.payload.BlockHash)
envelope := &ethpb.SignedBlindedExecutionPayloadEnvelope{
Message: &ethpb.BlindedExecutionPayloadEnvelope{
Slot: fixture.slot,
BuilderIndex: 999,
BlockHash: blockHash[:],
BeaconBlockRoot: make([]byte, 32),
},
}
wrappedEnv, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(envelope.Message)
require.NoError(t, err)
err = ProcessBlindedExecutionPayload(t.Context(), fixture.state, [32]byte{}, wrappedEnv)
require.ErrorContains(t, "builder index does not match", err)
}
func TestProcessBlindedExecutionPayload_BlockHashMismatch(t *testing.T) {
fixture := buildPayloadFixture(t, nil)
wrongHash := bytes.Repeat([]byte{0xFF}, 32)
envelope := &ethpb.SignedBlindedExecutionPayloadEnvelope{
Message: &ethpb.BlindedExecutionPayloadEnvelope{
Slot: fixture.slot,
BuilderIndex: fixture.envelope.BuilderIndex,
BlockHash: wrongHash,
BeaconBlockRoot: make([]byte, 32),
},
}
wrappedEnv, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(envelope.Message)
require.NoError(t, err)
err = ProcessBlindedExecutionPayload(t.Context(), fixture.state, [32]byte{}, wrappedEnv)
require.ErrorContains(t, "block hash does not match", err)
}
func TestVerifyExecutionPayloadEnvelopeSignature(t *testing.T) {
fixture := buildPayloadFixture(t, nil)

View File

@@ -56,6 +56,7 @@ import (
// # [New in Gloas:EIP7732]
// latest_execution_payload_bid=ExecutionPayloadBid(
// block_hash=pre.latest_execution_payload_header.block_hash,
// execution_requests_root=hash_tree_root(ExecutionRequests()),
// ),
// next_withdrawal_index=pre.next_withdrawal_index,
// next_withdrawal_validator_index=pre.next_withdrawal_validator_index,
@@ -307,6 +308,11 @@ func upgradeToGloas(beaconState state.BeaconState) (state.BeaconState, error) {
}
}
emptyExecutionRequestsRoot, err := (&enginev1.ExecutionRequests{}).HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not compute empty execution requests root")
}
s := &ethpb.BeaconStateGloas{
GenesisTime: uint64(beaconState.GenesisTime().Unix()),
GenesisValidatorsRoot: beaconState.GenesisValidatorsRoot(),
@@ -337,11 +343,12 @@ func upgradeToGloas(beaconState state.BeaconState) (state.BeaconState, error) {
CurrentSyncCommittee: currentSyncCommittee,
NextSyncCommittee: nextSyncCommittee,
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{
BlockHash: payloadHeader.BlockHash(),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
ParentBlockHash: make([]byte, fieldparams.RootLength),
ParentBlockRoot: make([]byte, fieldparams.RootLength),
PrevRandao: make([]byte, fieldparams.RootLength),
BlockHash: payloadHeader.BlockHash(),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
ParentBlockHash: make([]byte, fieldparams.RootLength),
ParentBlockRoot: make([]byte, fieldparams.RootLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExecutionRequestsRoot: emptyExecutionRequestsRoot[:],
},
NextWithdrawalIndex: wi,
NextWithdrawalValidatorIndex: vi,

View File

@@ -43,7 +43,7 @@ import (
// </spec>
func ProcessWithdrawals(st state.BeaconState) error {
// Must be called before ProcessExecutionPayloadBid for the current block.
full, err := st.IsParentBlockFull()
full, err := st.LatestBlockHashMatchesBidBlockHash()
if err != nil {
return errors.Wrap(err, "could not get parent block full status")
}

View File

@@ -322,7 +322,7 @@ type withdrawalsState struct {
expectedResult state.ExpectedWithdrawalsGloasResult
}
func (w *withdrawalsState) IsParentBlockFull() (bool, error) {
func (w *withdrawalsState) LatestBlockHashMatchesBidBlockHash() (bool, error) {
return w.parentFull, w.parentErr
}

View File

@@ -14,34 +14,9 @@ import (
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/pkg/errors"
)
// ProcessSlotsForBlock advances the given state to the slot of the given block.
// This function assumes that the parent state is the latest state that has been processed before the given block.
// In particular, all that it is needed to get the blocks's prestate is to advance slots and possible epoch transitions.
func ProcessSlotsForBlock(
ctx context.Context,
st state.BeaconState,
b interfaces.ReadOnlyBeaconBlock) (state.BeaconState, error) {
accessRoot := b.ParentRoot()
if st.Version() < version.Gloas {
return ProcessSlotsUsingNextSlotCache(ctx, st, accessRoot[:], b.Slot())
}
full, err := st.IsParentBlockFull()
if err != nil {
return nil, errors.Wrap(err, "could not determine if parent block is full")
}
if full {
accessRoot, err = st.LatestBlockHash()
if err != nil {
return nil, errors.Wrap(err, "could not get latest block hash")
}
}
return ProcessSlotsUsingNextSlotCache(ctx, st, accessRoot[:], b.Slot())
}
// ProcessOperations
//
// Spec definition:

View File

@@ -160,12 +160,12 @@ func ProcessSlot(ctx context.Context, state state.BeaconState) (state.BeaconStat
}
// ProcessSlotsIfNeeded takes a ReadOnlyBeaconState and processes it only if its needed, it returns a ReadOnlyBeaconState
func ProcessSlotsIfNeeded(ctx context.Context, state state.ReadOnlyBeaconState, accessRoot []byte, slot primitives.Slot) (state.ReadOnlyBeaconState, error) {
func ProcessSlotsIfNeeded(ctx context.Context, state state.ReadOnlyBeaconState, parentRoot []byte, slot primitives.Slot) (state.ReadOnlyBeaconState, error) {
if slot <= state.Slot() {
return state, nil
}
copied := state.Copy()
return ProcessSlotsUsingNextSlotCache(ctx, copied, accessRoot, slot)
return ProcessSlotsUsingNextSlotCache(ctx, copied, parentRoot, slot)
}
// ProcessSlotsUsingNextSlotCache processes slots by using next slot cache for higher efficiency.

View File

@@ -10,9 +10,7 @@ import (
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
engine "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/stretchr/testify/require"
@@ -75,12 +73,13 @@ func newGloasState(t *testing.T, slot primitives.Slot, availability []byte) stat
ExecutionPayloadAvailability: availability,
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, int(cfg.SlotsPerEpoch*2)),
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
ExecutionRequestsRoot: make([]byte, 32),
},
Eth1Data: &ethpb.Eth1Data{
DepositRoot: make([]byte, 32),
@@ -141,217 +140,3 @@ func testBeaconBlockHeader() *ethpb.BeaconBlockHeader {
BodyRoot: make([]byte, 32),
}
}
// newGloasForkBoundaryState returns a Gloas BeaconState where IsParentBlockFull()==true
// because bid.BlockHash == latestBlockHash. The parentBlockRoot parameter controls
// whether the bid looks like an upgrade-seed (all-zeros) or a real committed bid (non-zero).
func newGloasForkBoundaryState(
t *testing.T,
slot primitives.Slot,
blockHash [32]byte,
parentBlockRoot [32]byte,
) state.BeaconState {
t.Helper()
cfg := params.BeaconConfig()
availability := bytes.Repeat([]byte{0xFF}, int(cfg.SlotsPerHistoricalRoot/8))
protoState := &ethpb.BeaconStateGloas{
Slot: slot,
LatestBlockHeader: testBeaconBlockHeader(),
BlockRoots: make([][]byte, cfg.SlotsPerHistoricalRoot),
StateRoots: make([][]byte, cfg.SlotsPerHistoricalRoot),
RandaoMixes: make([][]byte, fieldparams.RandaoMixesLength),
ExecutionPayloadAvailability: availability,
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, int(cfg.SlotsPerEpoch*2)),
// bid.BlockHash == LatestBlockHash so that IsParentBlockFull() returns true.
LatestBlockHash: blockHash[:],
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: parentBlockRoot[:],
BlockHash: blockHash[:],
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
},
Eth1Data: &ethpb.Eth1Data{
DepositRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
},
PreviousEpochParticipation: []byte{},
CurrentEpochParticipation: []byte{},
JustificationBits: []byte{0},
PreviousJustifiedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
CurrentJustifiedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
FinalizedCheckpoint: &ethpb.Checkpoint{Root: make([]byte, 32)},
PayloadExpectedWithdrawals: make([]*engine.Withdrawal, 0),
ProposerLookahead: make([]primitives.ValidatorIndex, 0),
Builders: make([]*ethpb.Builder, 0),
}
for i := range protoState.BlockRoots {
protoState.BlockRoots[i] = make([]byte, 32)
}
for i := range protoState.StateRoots {
protoState.StateRoots[i] = make([]byte, 32)
}
for i := range protoState.RandaoMixes {
protoState.RandaoMixes[i] = make([]byte, 32)
}
for i := range protoState.BuilderPendingPayments {
protoState.BuilderPendingPayments[i] = &ethpb.BuilderPendingPayment{
Withdrawal: &ethpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)},
}
}
pubkeys := make([][]byte, cfg.SyncCommitteeSize)
for i := range pubkeys {
pubkeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
aggPubkey := make([]byte, fieldparams.BLSPubkeyLength)
protoState.CurrentSyncCommittee = &ethpb.SyncCommittee{Pubkeys: pubkeys, AggregatePubkey: aggPubkey}
protoState.NextSyncCommittee = &ethpb.SyncCommittee{Pubkeys: pubkeys, AggregatePubkey: aggPubkey}
st, err := state_native.InitializeFromProtoGloas(protoState)
require.NoError(t, err)
return st
}
// newGloasTestBlock returns an ROBlock at the given slot with the given parentRoot.
func newGloasTestBlock(t *testing.T, slot primitives.Slot, parentRoot [32]byte) consensusblocks.ROBlock {
t.Helper()
blkProto := &ethpb.SignedBeaconBlockGloas{
Block: &ethpb.BeaconBlockGloas{
Slot: slot,
ParentRoot: parentRoot[:],
StateRoot: make([]byte, 32),
Body: &ethpb.BeaconBlockBodyGloas{
RandaoReveal: make([]byte, fieldparams.BLSSignatureLength),
Graffiti: make([]byte, 32),
Eth1Data: &ethpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: make([]byte, fieldparams.SyncAggregateSyncCommitteeBytesLength),
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
},
SignedExecutionPayloadBid: &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
Slot: slot,
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
PayloadAttestations: []*ethpb.PayloadAttestation{},
},
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
}
wsb, err := consensusblocks.NewSignedBeaconBlock(blkProto)
require.NoError(t, err)
rob, err := consensusblocks.NewROBlock(wsb)
require.NoError(t, err)
return rob
}
// TestProcessSlotsForBlock_UpgradeSeededBid verifies that ProcessSlotsForBlock uses
// b.ParentRoot() as the NSC access key when the state has an upgrade-seeded bid
// (bid.ParentBlockRoot == zero). This guards against the Fulu->Gloas fork-boundary
// false positive where UpgradeToGloas seeds bid.BlockHash == latestBlockHash while
// leaving bid.ParentBlockRoot as all-zeros.
func TestProcessSlotsForBlock_UpgradeSeededBid(t *testing.T) {
ctx := context.Background()
parentRoot := [32]byte{0x01, 0x02, 0x03}
blockHash := [32]byte{0xAA, 0xBB, 0xCC}
targetSlot := primitives.Slot(9)
// Build a Gloas state at slot 8 with IsParentBlockFull()==true but
// bid.ParentBlockRoot==zero (upgrade-seeded: not a real committed bid).
st := newGloasForkBoundaryState(t, targetSlot-1, blockHash, [32]byte{})
require.Equal(t, version.Gloas, st.Version())
// Verify preconditions.
full, err := st.IsParentBlockFull()
require.NoError(t, err)
require.True(t, full, "precondition: IsParentBlockFull must be true")
bid, err := st.LatestExecutionPayloadBid()
require.NoError(t, err)
require.Equal(t, [32]byte{}, bid.ParentBlockRoot(), "upgrade-seeded bid must have zero ParentBlockRoot")
// Prime NSC with parentRoot as the access key.
// With the guard in place (realBid==false), ProcessSlotsForBlock will use
// b.ParentRoot() as the NSC key and find this cached entry.
require.NoError(t, UpdateNextSlotCache(ctx, parentRoot[:], st))
blk := newGloasTestBlock(t, targetSlot, parentRoot)
out, err := ProcessSlotsForBlock(ctx, st, blk.Block())
require.NoError(t, err)
require.Equal(t, targetSlot, out.Slot())
// Verify that the NSC entry primed under parentRoot is still present,
// confirming it was used (read) rather than bypassed.
cached := NextSlotState(parentRoot[:], targetSlot)
require.NotNil(t, cached, "NSC entry under parentRoot should still be present after use")
}
// TestProcessSlotsForBlock_RealBid verifies that ProcessSlotsForBlock uses
// LatestBlockHash as the NSC access key when the state has a real committed bid
// (bid.ParentBlockRoot != zero). This is the normal post-fork case.
func TestProcessSlotsForBlock_RealBid(t *testing.T) {
ctx := context.Background()
parentRoot := [32]byte{0x01, 0x02, 0x03}
blockHash := [32]byte{0xAA, 0xBB, 0xCC}
realParentBlockRoot := [32]byte{0xDE, 0xAD, 0xBE, 0xEF}
targetSlot := primitives.Slot(9)
// Build a Gloas state at slot 8 with IsParentBlockFull()==true and
// bid.ParentBlockRoot!=zero (a real committed bid).
st := newGloasForkBoundaryState(t, targetSlot-1, blockHash, realParentBlockRoot)
require.Equal(t, version.Gloas, st.Version())
// Verify preconditions.
full, err := st.IsParentBlockFull()
require.NoError(t, err)
require.True(t, full, "precondition: IsParentBlockFull must be true")
bid, err := st.LatestExecutionPayloadBid()
require.NoError(t, err)
require.NotEqual(t, [32]byte{}, bid.ParentBlockRoot(), "real bid must have non-zero ParentBlockRoot")
// Prime NSC with the EL block hash as access key.
// With the guard in place (realBid==true), ProcessSlotsForBlock will use
// LatestBlockHash as the NSC key and find this cached entry.
require.NoError(t, UpdateNextSlotCache(ctx, blockHash[:], st))
blk := newGloasTestBlock(t, targetSlot, parentRoot)
out, err := ProcessSlotsForBlock(ctx, st, blk.Block())
require.NoError(t, err)
require.Equal(t, targetSlot, out.Slot())
// Verify that the NSC entry primed under blockHash is still present,
// confirming it was used (read) rather than bypassed.
cached := NextSlotState(blockHash[:], targetSlot)
require.NotNil(t, cached, "NSC entry under blockHash should still be present after use")
}
// TestProcessSlotsForBlock_PreGloas verifies that ProcessSlotsForBlock uses
// b.ParentRoot() as access key on pre-Gloas (Fulu) states, unchanged by the fix.
func TestProcessSlotsForBlock_PreGloas(t *testing.T) {
ctx := context.Background()
parentRoot := [32]byte{0x01, 0x02, 0x03}
targetSlot := primitives.Slot(5)
// newGloasState creates a Gloas-versioned state; we need a Fulu/pre-Gloas state.
// Use newGloasState as a base and just verify the slot advancement works.
// Note: version.Gloas is the version created by newGloasState; for pre-Gloas
// the function takes the version < Gloas path. We build a minimal Gloas state
// to test, but note ProcessSlotsForBlock has an explicit version check at top.
st := newGloasState(t, targetSlot-1, bytes.Repeat([]byte{0}, int(params.BeaconConfig().SlotsPerHistoricalRoot/8)))
blk := newGloasTestBlock(t, targetSlot, parentRoot)
out, err := ProcessSlotsForBlock(ctx, st, blk.Block())
require.NoError(t, err)
require.Equal(t, targetSlot, out.Slot())
}

View File

@@ -63,7 +63,8 @@ func ExecuteStateTransitionNoVerifyAnySig(
interop.WriteBlockToDisk(signed, false /* Has the block failed */)
interop.WriteStateToDisk(st)
st, err = ProcessSlotsForBlock(ctx, st, signed.Block())
parentRoot := signed.Block().ParentRoot()
st, err = ProcessSlotsUsingNextSlotCache(ctx, st, parentRoot[:], signed.Block().Slot())
if err != nil {
return nil, nil, errors.Wrap(err, "could not process slots")
}
@@ -116,17 +117,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.
@@ -134,24 +150,25 @@ func CalculateStateRoot(
// Execute per slots transition.
var err error
state, err = ProcessSlotsForBlock(ctx, state, signed.Block())
parentRoot := signed.Block().ParentRoot()
state, err = ProcessSlotsUsingNextSlotCache(ctx, state, parentRoot[:], signed.Block().Slot())
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.
@@ -396,6 +413,13 @@ func ProcessBlockForStateRoot(
blk := signed.Block()
body := blk.Body()
if state.Version() >= version.Gloas {
if err := gloas.ProcessParentExecutionPayload(ctx, state, blk); err != nil {
return nil, errors.Wrap(err, "could not process parent execution payload")
}
}
bodyRoot, err := body.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not hash tree root beacon block body")
@@ -408,18 +432,14 @@ func ProcessBlockForStateRoot(
}
if state.Version() >= version.Gloas {
// <spec fn="process_block" fork="gloas" hash="cc0f05ee">
// <spec fn="process_block" fork="gloas" hash="defer_payload">
// def process_block(state: BeaconState, block: BeaconBlock) -> None:
// process_block_header(state, block)
// # [Modified in Gloas:EIP7732]
// process_parent_execution_payload(state, block) # already called above
// process_block_header(state, block) # already called above
// process_withdrawals(state)
// # [Modified in Gloas:EIP7732]
// # Removed `process_execution_payload`
// # [New in Gloas:EIP7732]
// process_execution_payload_bid(state, block)
// process_randao(state, block.body)
// process_eth1_data(state, block.body)
// # [Modified in Gloas:EIP7732]
// process_operations(state, block.body)
// process_sync_aggregate(state, block.body.sync_aggregate)
// </spec>

View File

@@ -135,7 +135,6 @@ func blindEnvelope(env *ethpb.SignedExecutionPayloadEnvelope) *ethpb.SignedBlind
BuilderIndex: env.Message.BuilderIndex,
BeaconBlockRoot: env.Message.BeaconBlockRoot,
Slot: env.Message.Slot,
StateRoot: env.Message.StateRoot,
ParentBlockHash: env.Message.Payload.ParentHash,
},
Signature: env.Signature,

View File

@@ -40,7 +40,6 @@ func testEnvelope(t *testing.T) *ethpb.SignedExecutionPayloadEnvelope {
BuilderIndex: primitives.BuilderIndex(42),
BeaconBlockRoot: bytesutil.PadTo([]byte("beaconroot"), 32),
Slot: primitives.Slot(99),
StateRoot: bytesutil.PadTo([]byte("envelopestateroot"), 32),
},
Signature: bytesutil.PadTo([]byte("sig"), 96),
}
@@ -71,7 +70,6 @@ func TestStore_SaveAndRetrieveExecutionPayloadEnvelope(t *testing.T) {
assert.Equal(t, env.Message.Slot, loaded.Message.Slot)
assert.Equal(t, env.Message.BuilderIndex, loaded.Message.BuilderIndex)
assert.DeepEqual(t, env.Message.BeaconBlockRoot, loaded.Message.BeaconBlockRoot)
assert.DeepEqual(t, env.Message.StateRoot, loaded.Message.StateRoot)
assert.DeepEqual(t, env.Signature, loaded.Signature)
// BlockHash should be the payload's block hash (not a hash tree root).
@@ -168,6 +166,5 @@ func TestBlindEnvelope_PreservesBlockHash(t *testing.T) {
assert.Equal(t, env.Message.BuilderIndex, blinded.Message.BuilderIndex)
assert.Equal(t, env.Message.Slot, blinded.Message.Slot)
assert.DeepEqual(t, env.Message.BeaconBlockRoot, blinded.Message.BeaconBlockRoot)
assert.DeepEqual(t, env.Message.StateRoot, blinded.Message.StateRoot)
assert.DeepEqual(t, env.Signature, blinded.Signature)
}

View File

@@ -688,7 +688,6 @@ func (s *Service) ReconstructExecutionPayloadEnvelope(
BuilderIndex: envelope.Message.BuilderIndex,
BeaconBlockRoot: envelope.Message.BeaconBlockRoot,
Slot: envelope.Message.Slot,
StateRoot: envelope.Message.StateRoot,
},
Signature: envelope.Signature,
}, nil

View File

@@ -190,7 +190,6 @@ func (e *EngineClient) ReconstructExecutionPayloadEnvelope(
BuilderIndex: envelope.Message.BuilderIndex,
BeaconBlockRoot: envelope.Message.BeaconBlockRoot,
Slot: envelope.Message.Slot,
StateRoot: envelope.Message.StateRoot,
},
Signature: envelope.Signature,
}, nil

View File

@@ -40,13 +40,6 @@ func (f *ForkChoice) CanonicalNodeAtSlot(slot primitives.Slot) ([32]byte, bool)
return pn.node.root, pn.full
}
// PayloadContentLookup returns the preferred lookup key for a given beacon block root.
// If full payload content wins, it returns the block hash and true.
// If empty payload content wins, it returns the beacon block root and false.
func (f *ForkChoice) PayloadContentLookup(root [32]byte) ([32]byte, bool) {
return f.store.payloadContentLookup(root)
}
func (s *Store) resolveParentPayloadStatus(block interfaces.ReadOnlyBeaconBlock, parent **PayloadNode, blockHash *[32]byte) error {
sb, err := block.Body().SignedExecutionPayloadBid()
if err != nil {
@@ -300,21 +293,6 @@ func (s *Store) choosePayloadContent(n *Node) *PayloadNode {
return en
}
func (s *Store) payloadContentLookup(root [32]byte) ([32]byte, bool) {
en := s.emptyNodeByRoot[root]
if en == nil || en.node == nil {
return [32]byte{}, false
}
pn := s.choosePayloadContent(en.node)
if pn == nil || pn.node == nil {
return [32]byte{}, false
}
if pn.full {
return pn.node.blockHash, true
}
return pn.node.root, false
}
// nodeTreeDump appends to the given list all the nodes descending from this one
func (s *Store) nodeTreeDump(ctx context.Context, n *Node, nodes []*forkchoice2.Node) ([]*forkchoice2.Node, error) {
if ctx.Err() != nil {
@@ -482,6 +460,20 @@ func (f *ForkChoice) HasFullNode(root [32]byte) bool {
return ok
}
// IsFullNode returns whether fork choice would select the full payload variant
// for the given beacon block root. The caller MUST hold the forkchoice lock.
func (f *ForkChoice) IsFullNode(root [32]byte) bool {
en := f.store.emptyNodeByRoot[root]
if en == nil || en.node == nil {
return false
}
if slots.ToEpoch(en.node.slot) < params.BeaconConfig().GloasForkEpoch {
return false
}
pn := f.store.choosePayloadContent(en.node)
return pn != nil && pn.full
}
// BlockHash returns the hash committed in the given block
func (f *ForkChoice) BlockHash(root [32]byte) ([32]byte, error) {
s := f.store

View File

@@ -67,12 +67,13 @@ func prepareGloasForkchoiceState(
FinalizedCheckpoint: finalizedCheckpoint,
LatestBlockHeader: blockHeader,
LatestExecutionPayloadBid: &ethpb.ExecutionPayloadBid{
BlockHash: blockHash[:],
ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
BlockHash: blockHash[:],
ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
BlobKzgCommitments: [][]byte{make([]byte, 48)},
ExecutionRequestsRoot: make([]byte, 32),
},
Builders: make([]*ethpb.Builder, 0),
BuilderPendingPayments: builderPendingPayments,
@@ -907,46 +908,6 @@ func TestChoosePayloadContent(t *testing.T) {
})
}
func TestPayloadContentLookup(t *testing.T) {
f := setupGloas(t, 0, 0)
ctx := t.Context()
rootA := indexToHash(1)
blockHashA := indexToHash(100)
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, roblock))
t.Run("unknown root returns zero and false", func(t *testing.T) {
v, isBlockHash := f.PayloadContentLookup(indexToHash(999))
assert.Equal(t, [32]byte{}, v)
assert.Equal(t, false, isBlockHash)
})
t.Run("empty wins returns root", func(t *testing.T) {
v, isBlockHash := f.PayloadContentLookup(rootA)
assert.Equal(t, rootA, v)
assert.Equal(t, false, isBlockHash)
})
pe, err := prepareGloasForkchoicePayload(rootA)
require.NoError(t, err)
require.NoError(t, f.InsertPayload(pe))
t.Run("full wins returns block hash", func(t *testing.T) {
en := f.store.emptyNodeByRoot[rootA]
fn := f.store.fullNodeByRoot[rootA]
require.NotNil(t, en)
require.NotNil(t, fn)
en.weight = 1
fn.weight = 2
v, isBlockHash := f.PayloadContentLookup(rootA)
assert.Equal(t, blockHashA, v)
assert.Equal(t, true, isBlockHash)
})
}
func TestGloasForkedBranches(t *testing.T) {
f := setupGloas(t, 1, 1)
s := f.store

View File

@@ -74,6 +74,7 @@ type FastGetter interface {
FinalizedPayloadBlockHash() [32]byte
HasFullNode([32]byte) bool
HasNode([32]byte) bool
IsFullNode([32]byte) bool
HighestReceivedBlockSlot() primitives.Slot
HighestReceivedBlockRoot() [32]byte
IsCanonical(root [32]byte) bool
@@ -97,7 +98,6 @@ type FastGetter interface {
ParentRoot(root [32]byte) ([32]byte, error)
BlockHash(root [32]byte) ([32]byte, error)
CanonicalNodeAtSlot(slot primitives.Slot) ([32]byte, bool)
PayloadContentLookup(root [32]byte) ([32]byte, bool)
}
// Setter allows to set forkchoice information

View File

@@ -37,6 +37,13 @@ func (ro *ROForkChoice) HasFullNode(root [32]byte) bool {
return ro.getter.HasFullNode(root)
}
// IsFullNode delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) IsFullNode(root [32]byte) bool {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.IsFullNode(root)
}
// HasNode delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HasNode(root [32]byte) bool {
ro.l.RLock()
@@ -218,10 +225,3 @@ func (ro *ROForkChoice) CanonicalNodeAtSlot(slot primitives.Slot) ([32]byte, boo
defer ro.l.RUnlock()
return ro.getter.CanonicalNodeAtSlot(slot)
}
// PayloadContentLookup delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) PayloadContentLookup(root [32]byte) ([32]byte, bool) {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.PayloadContentLookup(root)
}

View File

@@ -18,6 +18,7 @@ const (
rlockCalled
runlockCalled
hasFullNodeCalled
isFullNodeCalled
hasNodeCalled
proposerBoostCalled
isCanonicalCalled
@@ -45,7 +46,6 @@ const (
dependentRootForEpochCalled
canonicalNodeAtSlotCalled
payloadWeightsCalled
payloadContentLookupCalled
)
func _discard(t *testing.T, e error) {
@@ -68,6 +68,11 @@ func TestROLocking(t *testing.T) {
call: hasFullNodeCalled,
cb: func(g FastGetter) { g.HasFullNode([32]byte{}) },
},
{
name: "isFullNodeCalled",
call: isFullNodeCalled,
cb: func(g FastGetter) { g.IsFullNode([32]byte{}) },
},
{
name: "hasNodeCalled",
call: hasNodeCalled,
@@ -173,11 +178,6 @@ func TestROLocking(t *testing.T) {
call: canonicalNodeAtSlotCalled,
cb: func(g FastGetter) { g.CanonicalNodeAtSlot(0) },
},
{
name: "payloadContentLookupCalled",
call: payloadContentLookupCalled,
cb: func(g FastGetter) { g.PayloadContentLookup([32]byte{}) },
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -220,6 +220,11 @@ func (ro *mockROForkchoice) HasFullNode(_ [32]byte) bool {
return false
}
func (ro *mockROForkchoice) IsFullNode(_ [32]byte) bool {
ro.calls = append(ro.calls, isFullNodeCalled)
return false
}
func (ro *mockROForkchoice) HasNode(_ [32]byte) bool {
ro.calls = append(ro.calls, hasNodeCalled)
return false
@@ -352,8 +357,3 @@ func (ro *mockROForkchoice) CanonicalNodeAtSlot(_ primitives.Slot) ([32]byte, bo
ro.calls = append(ro.calls, canonicalNodeAtSlotCalled)
return [32]byte{}, false
}
func (ro *mockROForkchoice) PayloadContentLookup(_ [32]byte) ([32]byte, bool) {
ro.calls = append(ro.calls, payloadContentLookupCalled)
return [32]byte{}, false
}

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},
},
}
}
@@ -910,6 +929,16 @@ func (s *Service) beaconEndpoints(
handler: server.GetExecutionPayloadEnvelope,
methods: []string{http.MethodGet},
},
{
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/v2/beacon/execution_payload/bid",
name: namespace + ".PublishSignedExecutionPayloadBid",

View File

@@ -34,6 +34,7 @@ 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/v1/beacon/execution_payload_envelope": {http.MethodPost},
"/eth/v2/beacon/execution_payload/bid": {http.MethodPost},
"/eth/v1/beacon/headers": {http.MethodGet},
"/eth/v1/beacon/headers/{block_id}": {http.MethodGet},
@@ -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

@@ -6,8 +6,8 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"net/http/httptest"
"strings"
"testing"
"github.com/OffchainLabs/prysm/v7/api"
@@ -27,17 +27,18 @@ func testJSONSignedBid() *structs.SignedExecutionPayloadBid {
hex96 := "0x" + strings.Repeat("00", 96)
return &structs.SignedExecutionPayloadBid{
Message: &structs.ExecutionPayloadBid{
ParentBlockHash: hex32,
ParentBlockRoot: hex32,
BlockHash: hex32,
PrevRandao: hex32,
FeeRecipient: hex20,
GasLimit: "30000000",
BuilderIndex: "1",
Slot: "100",
Value: "0",
ExecutionPayment: "0",
BlobKzgCommitments: []string{},
ParentBlockHash: hex32,
ParentBlockRoot: hex32,
BlockHash: hex32,
PrevRandao: hex32,
FeeRecipient: hex20,
GasLimit: "30000000",
BuilderIndex: "1",
Slot: "100",
Value: "0",
ExecutionPayment: "0",
BlobKzgCommitments: []string{},
ExecutionRequestsRoot: hex32,
},
Signature: hex96,
}
@@ -165,16 +166,17 @@ func TestPublishSignedExecutionPayloadBid_SSZ(t *testing.T) {
bid := &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
GasLimit: 30000000,
BuilderIndex: 1,
Slot: 100,
Value: 0,
ExecutionPayment: 0,
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
GasLimit: 30000000,
BuilderIndex: 1,
Slot: 100,
Value: 0,
ExecutionPayment: 0,
ExecutionRequestsRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}

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) {
@@ -45,7 +52,6 @@ func TestGetExecutionPayloadEnvelope_AcceptsSlotID(t *testing.T) {
BuilderIndex: primitives.BuilderIndex(42),
BeaconBlockRoot: root[:],
Slot: primitives.Slot(177),
StateRoot: bytesutil.PadTo([]byte("envelope-state"), 32),
},
Signature: bytesutil.PadTo([]byte("sig"), 96),
}
@@ -105,3 +111,83 @@ 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),
},
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

@@ -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,234 @@
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,
}
}
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

@@ -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

@@ -126,18 +126,12 @@ func (vs *Server) GetBeaconBlock(ctx context.Context, req *ethpb.BlockRequest) (
return resp, nil
}
func (vs *Server) handleSuccesfulReorgAttempt(ctx context.Context, slot primitives.Slot, parentRoot, _ [32]byte) (state.BeaconState, error) {
// Try to get the state from the NSC
accessRoot := parentRoot
if slots.ToEpoch(slot) >= params.BeaconConfig().GloasForkEpoch {
accessRoot, _ = vs.ForkchoiceFetcher.PayloadContentLookup(parentRoot)
}
head := transition.NextSlotState(accessRoot[:], slot)
func (vs *Server) handleSuccesfulReorgAttempt(ctx context.Context, slot primitives.Slot, parentRoot [32]byte) (state.BeaconState, error) {
head := transition.NextSlotState(parentRoot[:], slot)
if head != nil {
return head, nil
}
// cache miss
head, err := vs.StateGen.StateByRoot(ctx, accessRoot)
head, err := vs.StateGen.StateByRoot(ctx, parentRoot)
if err != nil {
return nil, status.Error(codes.Unavailable, "could not obtain head state")
}
@@ -154,12 +148,7 @@ func logFailedReorgAttempt(slot primitives.Slot, oldHeadRoot, headRoot [32]byte)
}
func (vs *Server) getHeadNoReorg(ctx context.Context, slot primitives.Slot, parentRoot [32]byte) (state.BeaconState, error) {
// Try to get the state from the NSC
accessRoot := parentRoot
if slots.ToEpoch(slot) >= params.BeaconConfig().GloasForkEpoch {
accessRoot, _ = vs.ForkchoiceFetcher.PayloadContentLookup(parentRoot)
}
head := transition.NextSlotState(accessRoot[:], slot)
head := transition.NextSlotState(parentRoot[:], slot)
if head != nil {
return head, nil
}
@@ -172,7 +161,7 @@ func (vs *Server) getHeadNoReorg(ctx context.Context, slot primitives.Slot, pare
func (vs *Server) getParentStateFromReorgData(ctx context.Context, slot primitives.Slot, oldHeadRoot, parentRoot, headRoot [32]byte) (head state.BeaconState, err error) {
if parentRoot != headRoot {
head, err = vs.handleSuccesfulReorgAttempt(ctx, slot, parentRoot, headRoot)
head, err = vs.handleSuccesfulReorgAttempt(ctx, slot, parentRoot)
} else {
if oldHeadRoot != headRoot {
logFailedReorgAttempt(slot, oldHeadRoot, headRoot)
@@ -206,7 +195,6 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
// Build consensus fields in background
var wg sync.WaitGroup
wg.Go(func() {
// Set eth1 data.
eth1Data, err := vs.eth1DataMajorityVote(ctx, head)
if err != nil {
@@ -251,6 +239,9 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
if err := sBlk.SetPayloadAttestations(vs.getPayloadAttestations(ctx, head, sBlk.Block().ParentRoot())); err != nil {
log.WithError(err).Error("Could not set payload attestations")
}
if err := vs.setParentExecutionRequests(ctx, sBlk, head); err != nil {
log.WithError(err).Error("Could not set parent execution requests")
}
}
})
@@ -296,17 +287,13 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
wg.Wait()
sr, err := vs.computeStateRoot(ctx, sBlk)
sr, _, 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.
// 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.
// For Gloas self-build, cache the execution payload envelope now that the block is fully built.
if sBlk.Version() >= version.Gloas && selfBuildEnvelope {
if err := vs.storeExecutionPayloadEnvelope(sBlk, local); err != nil {
return nil, status.Errorf(codes.Internal, "Could not build execution payload envelope: %v", err)
@@ -595,7 +582,6 @@ func (vs *Server) PrepareBeaconProposer(
if len(validatorIndices) == 0 {
return &emptypb.Empty{}, nil
}
log := log.WithField("validatorCount", len(validatorIndices))
@@ -645,37 +631,50 @@ 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")
}
beaconState, err := vs.BlockReceiver.GetPrestateToPropose(ctx, roblock)
if err != nil {
return nil, errors.Wrap(err, "could not retrieve beacon state")
}
root, err := transition.CalculateStateRoot(
ctx,
beaconState,
block,
)
if err != nil {
return vs.handleStateRootError(ctx, block, err)
}
log.WithField("beaconStateRoot", fmt.Sprintf("%#x", root)).Debugf("Computed state root")
return root[:], nil
beaconState, err := vs.StateGen.StateByRoot(ctx, roblock.Block().ParentRoot())
if err != nil {
return nil, errors.Wrapf(err, "could not get pre state for slot %d", roblock.Block().Slot())
}
st, err := transition.CalculatePostState(ctx, beaconState, block)
if err != nil {
return vs.handlePostBlockStateError(ctx, block, err)
}
return st, nil
}
type computeStateRootAttemptsKeyType string
const computeStateRootAttemptsKey = computeStateRootAttemptsKeyType("compute-state-root-attempts")
const maxComputeStateRootAttempts = 3
const (
computeStateRootAttemptsKey = computeStateRootAttemptsKeyType("compute-state-root-attempts")
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 +723,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

@@ -109,17 +109,22 @@ func (vs *Server) createSelfBuildExecutionPayloadBid(
}
parentBlockRoot := block.ParentRoot()
executionRequestsRoot, err := local.ExecutionRequests.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not compute execution requests root")
}
return &ethpb.ExecutionPayloadBid{
ParentBlockHash: ed.ParentHash(),
ParentBlockRoot: bytesutil.SafeCopyBytes(parentBlockRoot[:]),
BlockHash: ed.BlockHash(),
PrevRandao: ed.PrevRandao(),
FeeRecipient: ed.FeeRecipient(),
GasLimit: ed.GasLimit(),
BuilderIndex: params.BeaconConfig().BuilderIndexSelfBuild,
Slot: block.Slot(),
Value: 0,
ExecutionPayment: 0,
BlobKzgCommitments: local.BlobsBundler.GetKzgCommitments(),
ParentBlockHash: ed.ParentHash(),
ParentBlockRoot: bytesutil.SafeCopyBytes(parentBlockRoot[:]),
BlockHash: ed.BlockHash(),
PrevRandao: ed.PrevRandao(),
FeeRecipient: ed.FeeRecipient(),
GasLimit: ed.GasLimit(),
BuilderIndex: params.BeaconConfig().BuilderIndexSelfBuild,
Slot: block.Slot(),
Value: 0,
ExecutionPayment: 0,
BlobKzgCommitments: local.BlobsBundler.GetKzgCommitments(),
ExecutionRequestsRoot: executionRequestsRoot[:],
}, nil
}

View File

@@ -192,17 +192,18 @@ func TestSetExecutionPayloadBid_PrefersP2PBid(t *testing.T) {
// Populate the highest bid cache with a P2P bid.
p2pBid := &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
Slot: slot,
ParentBlockHash: parentHash[:],
ParentBlockRoot: parentRoot[:],
BlockHash: make([]byte, 32),
BuilderIndex: 5,
Value: 1000,
ExecutionPayment: 500,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
PrevRandao: make([]byte, 32),
BlobKzgCommitments: [][]byte{},
Slot: slot,
ParentBlockHash: parentHash[:],
ParentBlockRoot: parentRoot[:],
BlockHash: make([]byte, 32),
BuilderIndex: 5,
Value: 1000,
ExecutionPayment: 500,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
PrevRandao: make([]byte, 32),
BlobKzgCommitments: [][]byte{},
ExecutionRequestsRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}
@@ -265,17 +266,18 @@ func TestSetExecutionPayloadBid_PrefersLocalWhenHigherValue(t *testing.T) {
// P2P bid is only 1000 Gwei — local should win.
p2pBid := &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
Slot: slot,
ParentBlockHash: parentHash[:],
ParentBlockRoot: parentRoot[:],
BlockHash: make([]byte, 32),
BuilderIndex: 5,
Value: 1000,
ExecutionPayment: 500,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
PrevRandao: make([]byte, 32),
BlobKzgCommitments: [][]byte{},
Slot: slot,
ParentBlockHash: parentHash[:],
ParentBlockRoot: parentRoot[:],
BlockHash: make([]byte, 32),
BuilderIndex: 5,
Value: 1000,
ExecutionPayment: 500,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
PrevRandao: make([]byte, 32),
BlobKzgCommitments: [][]byte{},
ExecutionRequestsRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}
@@ -337,17 +339,18 @@ func TestSetExecutionPayloadBid_SelfBuildOnlyIgnoresCache(t *testing.T) {
// P2P bid has higher value, but selfBuildOnly=true should force self-build.
p2pBid := &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
Slot: slot,
ParentBlockHash: parentHash[:],
ParentBlockRoot: parentRoot[:],
BlockHash: make([]byte, 32),
BuilderIndex: 5,
Value: 1000,
ExecutionPayment: 500,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
PrevRandao: make([]byte, 32),
BlobKzgCommitments: [][]byte{},
Slot: slot,
ParentBlockHash: parentHash[:],
ParentBlockRoot: parentRoot[:],
BlockHash: make([]byte, 32),
BuilderIndex: 5,
Value: 1000,
ExecutionPayment: 500,
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
PrevRandao: make([]byte, 32),
BlobKzgCommitments: [][]byte{},
ExecutionRequestsRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/OffchainLabs/prysm/v7/api/client/builder"
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
coregloas "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
@@ -70,7 +71,8 @@ func (vs *Server) getLocalPayloadFromEngine(
st state.BeaconState,
parentRoot [32]byte,
slot primitives.Slot,
proposerId primitives.ValidatorIndex) (*consensusblocks.GetPayloadResponse, error) {
proposerId primitives.ValidatorIndex,
) (*consensusblocks.GetPayloadResponse, error) {
logFields := logrus.Fields{
"validatorIndex": proposerId,
"slot": slot,
@@ -102,7 +104,7 @@ func (vs *Server) getLocalPayloadFromEngine(
}
}
log.WithFields(logFields).Debug("Payload ID cache miss")
parentHash, err := vs.getParentBlockHash(ctx, st, slot)
parentHash, err := vs.getParentBlockHash(ctx, st, slot, parentRoot)
switch {
case errors.Is(err, errActivationNotReached) || errors.Is(err, errNoTerminalBlockHash):
return consensusblocks.NewGetPayloadResponse(emptyPayload())
@@ -137,7 +139,7 @@ func (vs *Server) getLocalPayloadFromEngine(
var attr payloadattribute.Attributer
switch {
case st.Version() >= version.Gloas:
withdrawals, err := st.WithdrawalsForPayload()
withdrawals, err := vs.computePayloadWithdrawals(ctx, st, parentRoot)
if err != nil {
return nil, err
}
@@ -255,7 +257,8 @@ func (vs *Server) getTerminalBlockHashIfExists(ctx context.Context, transitionTi
func (vs *Server) getBuilderPayloadAndBlobs(ctx context.Context,
slot primitives.Slot,
vIdx primitives.ValidatorIndex,
parentGasLimit uint64) (builder.Bid, error) {
parentGasLimit uint64,
) (builder.Bid, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.getBuilderPayloadAndBlobs")
defer span.End()
@@ -274,8 +277,41 @@ func (vs *Server) getBuilderPayloadAndBlobs(ctx context.Context,
return vs.getPayloadHeaderFromBuilder(ctx, slot, vIdx, parentGasLimit)
}
var errActivationNotReached = errors.New("activation epoch not reached")
var errNoTerminalBlockHash = errors.New("no terminal block hash")
var (
errActivationNotReached = errors.New("activation epoch not reached")
errNoTerminalBlockHash = errors.New("no terminal block hash")
)
// computePayloadWithdrawals returns the withdrawals for the next payload.
func (vs *Server) computePayloadWithdrawals(ctx context.Context, st state.BeaconState, parentRoot [32]byte) ([]*enginev1.Withdrawal, error) {
if !vs.HeadFetcher.HeadFull() {
return st.PayloadExpectedWithdrawals()
}
parentSlot, err := vs.ForkchoiceFetcher.RecentBlockSlot(parentRoot)
if err != nil {
return nil, errors.Wrap(err, "could not get parent block slot")
}
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch {
result, err := st.ExpectedWithdrawalsGloas()
if err != nil {
return nil, errors.Wrap(err, "could not compute expected withdrawals")
}
return result.Withdrawals, nil
}
// TODO: replace DB lookup with a single-entry cache (blockroot → envelope).
envelope, err := vs.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
if err != nil {
return nil, errors.Wrap(err, "could not get parent execution payload envelope")
}
if err := coregloas.ApplyParentExecutionPayload(ctx, st, envelope.Message.ExecutionRequests); err != nil {
return nil, errors.Wrap(err, "could not apply parent execution payload")
}
result, err := st.ExpectedWithdrawalsGloas()
if err != nil {
return nil, errors.Wrap(err, "could not compute expected withdrawals")
}
return result.Withdrawals, nil
}
// getParentBlockHash retrieves the parent block hash of the block at the given slot.
// The function's behavior varies depending on the state version and whether the merge has been completed.
@@ -287,13 +323,25 @@ var errNoTerminalBlockHash = errors.New("no terminal block hash")
// If the activation epoch has not been reached, an errActivationNotReached error is returned.
//
// Otherwise, the terminal block hash is fetched based on the slot's time, and an error is returned if it doesn't exist.
func (vs *Server) getParentBlockHash(ctx context.Context, st state.BeaconState, slot primitives.Slot) ([]byte, error) {
func (vs *Server) getParentBlockHash(ctx context.Context, st state.BeaconState, slot primitives.Slot, headRoot [32]byte) ([]byte, error) {
if st.Version() >= version.Gloas {
latestBlockHash, err := st.LatestBlockHash()
parentSlot, err := vs.ForkchoiceFetcher.RecentBlockSlot(headRoot)
if err != nil {
return nil, errors.Wrap(err, "could not get latest block hash")
return nil, errors.Wrap(err, "could not get parent block slot")
}
return latestBlockHash[:], nil
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch {
return getParentBlockHashPostCapella(st)
}
bid, err := st.LatestExecutionPayloadBid()
if err != nil {
return nil, errors.Wrap(err, "could not get latest execution payload bid")
}
if vs.HeadFetcher.HeadFull() {
bh := bid.BlockHash()
return bh[:], nil
}
pbh := bid.ParentBlockHash()
return pbh[:], nil
}
if st.Version() >= version.Capella {
return getParentBlockHashPostCapella(st)

View File

@@ -176,18 +176,56 @@ func TestServer_getExecutionPayload(t *testing.T) {
}
}
func TestServer_getParentBlockHash_Gloas(t *testing.T) {
want := bytesutil.ToBytes32([]byte("gloas-parent-hash"))
func TestServer_getParentBlockHash_Gloas_Full(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
blockHash := bytesutil.ToBytes32([]byte("block-hash"))
parentBlockHash := bytesutil.ToBytes32([]byte("parent-block-hash"))
headRoot := bytesutil.ToBytes32([]byte("head-root"))
st, err := util.NewBeaconStateGloas(func(state *ethpb.BeaconStateGloas) error {
state.LatestBlockHash = want[:]
state.LatestExecutionPayloadBid.BlockHash = blockHash[:]
state.LatestExecutionPayloadBid.ParentBlockHash = parentBlockHash[:]
return nil
})
require.NoError(t, err)
vs := &Server{}
got, err := vs.getParentBlockHash(context.Background(), st, 0)
chain := &chainMock.ChainService{FullHead: true}
vs := &Server{
ForkchoiceFetcher: chain,
HeadFetcher: chain,
}
got, err := vs.getParentBlockHash(context.Background(), st, 0, headRoot)
require.NoError(t, err)
require.DeepEqual(t, want[:], got)
require.DeepEqual(t, blockHash[:], got)
}
func TestServer_getParentBlockHash_Gloas_Empty(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
blockHash := bytesutil.ToBytes32([]byte("block-hash"))
parentBlockHash := bytesutil.ToBytes32([]byte("parent-block-hash"))
headRoot := bytesutil.ToBytes32([]byte("head-root"))
st, err := util.NewBeaconStateGloas(func(state *ethpb.BeaconStateGloas) error {
state.LatestExecutionPayloadBid.BlockHash = blockHash[:]
state.LatestExecutionPayloadBid.ParentBlockHash = parentBlockHash[:]
return nil
})
require.NoError(t, err)
chain := &chainMock.ChainService{}
vs := &Server{
ForkchoiceFetcher: chain,
HeadFetcher: chain,
}
got, err := vs.getParentBlockHash(context.Background(), st, 0, headRoot)
require.NoError(t, err)
require.DeepEqual(t, parentBlockHash[:], got)
}
func TestServer_getExecutionPayloadContextTimeout(t *testing.T) {

View File

@@ -1,12 +1,11 @@
package validator
import (
"bytes"
"context"
"fmt"
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"
@@ -15,6 +14,7 @@ import (
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
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/pkg/errors"
"github.com/sirupsen/logrus"
@@ -24,10 +24,7 @@ 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,
func (vs *Server) storeExecutionPayloadEnvelope(
sBlk interfaces.SignedBeaconBlock,
local *consensusblocks.GetPayloadResponse,
@@ -45,7 +42,6 @@ func (vs *Server) storeExecutionPayloadEnvelope(
BuilderIndex: params.BeaconConfig().BuilderIndexSelfBuild,
BeaconBlockRoot: blockRoot[:],
Slot: sBlk.Block().Slot(),
StateRoot: make([]byte, 32), // zeroed; computed lazily in GetExecutionPayloadEnvelope
}
// Precompute data column sidecars now (inside ProposeBeaconBlock) so the
@@ -107,7 +103,7 @@ func (vs *Server) GetExecutionPayloadEnvelope(
ctx context.Context,
req *ethpb.ExecutionPayloadEnvelopeRequest,
) (*ethpb.ExecutionPayloadEnvelopeResponse, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.GetExecutionPayloadEnvelope")
_, span := trace.StartSpan(ctx, "ProposerServer.GetExecutionPayloadEnvelope")
defer span.End()
if req == nil {
@@ -126,48 +122,11 @@ func (vs *Server) GetExecutionPayloadEnvelope(
"execution payload envelope not found for slot %d", req.Slot)
}
if bytes.Equal(envelope.StateRoot, make([]byte, 32)) {
// Lazily set the state root in the envelope by applying the payload evelope on the post block state
roEnvelope, err := consensusblocks.WrappedROExecutionPayloadEnvelope(envelope)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not wrap envelope: %v", err)
}
stateRoot, err := vs.computePostPayloadStateRoot(ctx, roEnvelope)
if err != nil {
return nil, status.Errorf(codes.Internal, "could not compute post-payload state root: %v", err)
}
vs.executionPayloadEnvelopeMu.Lock()
envelope.StateRoot = stateRoot
vs.executionPayloadEnvelopeMu.Unlock()
}
return &ethpb.ExecutionPayloadEnvelopeResponse{
Envelope: envelope,
}, nil
}
// computePostPayloadStateRoot retrieves the post-block state (after the block has
// been submitted and processed) and applies the execution payload state mutations
// to compute the post-payload state root for the envelope.
func (vs *Server) computePostPayloadStateRoot(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope) ([]byte, error) {
ctx, span := trace.StartSpan(ctx, "ProposerServer.computePostPayloadStateRoot")
defer span.End()
beaconState, err := vs.StateGen.StateByRoot(ctx, envelope.BeaconBlockRoot())
if err != nil {
return nil, errors.Wrap(err, "could not retrieve post-block state")
}
beaconState = beaconState.Copy()
if err := coregloas.ApplyExecutionPayload(ctx, beaconState, envelope); err != nil {
return nil, errors.Wrapf(err, "could not apply execution payload at slot %d", beaconState.Slot())
}
root, err := beaconState.HashTreeRoot(ctx)
if err != nil {
return nil, errors.Wrapf(err, "could not compute post-payload state root at slot %d", beaconState.Slot())
}
return root[:], nil
}
// PublishExecutionPayloadEnvelope validates and broadcasts a signed execution payload envelope.
// This is called by validators after signing the envelope retrieved from GetExecutionPayloadEnvelope.
//
@@ -250,3 +209,27 @@ func (vs *Server) broadcastGloasDataColumns(ctx context.Context) error {
return nil
}
// setParentExecutionRequests populates the parent_execution_requests field
// in the block body based on the parent's execution payload envelope.
func (vs *Server) setParentExecutionRequests(ctx context.Context, sBlk interfaces.SignedBeaconBlock, head state.BeaconState) error {
if head.Version() < version.Gloas {
return sBlk.SetParentExecutionRequests(&enginev1.ExecutionRequests{})
}
parentRoot := sBlk.Block().ParentRoot()
parentSlot, err := vs.ForkchoiceFetcher.RecentBlockSlot(parentRoot)
if err != nil {
return errors.Wrap(err, "could not get parent block slot")
}
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch || !vs.ForkchoiceFetcher.HasFullNode(parentRoot) {
return sBlk.SetParentExecutionRequests(&enginev1.ExecutionRequests{})
}
// TODO: replace DB lookup with a single-entry cache (blockroot → envelope).
signedEnvelope, err := vs.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
if err != nil {
return errors.Wrap(err, "could not get parent execution payload envelope")
}
return sBlk.SetParentExecutionRequests(signedEnvelope.Message.ExecutionRequests)
}

View File

@@ -6,7 +6,6 @@ import (
"testing"
mockp2p "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
mockstategen "github.com/OffchainLabs/prysm/v7/beacon-chain/state/stategen/mock"
"github.com/OffchainLabs/prysm/v7/config/params"
consensusblocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
@@ -17,6 +16,48 @@ 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(t *testing.T) {
local, sBlk := testGloasBlock(t)
vs := &Server{}
err := vs.storeExecutionPayloadEnvelope(sBlk, local)
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)
}
func TestExtractExecutionPayloadDeneb(t *testing.T) {
payload := &enginev1.ExecutionPayloadDeneb{
ParentHash: make([]byte, 32),
@@ -64,7 +105,6 @@ func TestSetGetExecutionPayloadEnvelope(t *testing.T) {
BuilderIndex: primitives.BuilderIndex(7),
BeaconBlockRoot: make([]byte, 32),
Slot: slot,
StateRoot: make([]byte, 32),
}
vs := &Server{}
@@ -90,7 +130,6 @@ func TestGetExecutionPayloadEnvelope_SlotMismatch(t *testing.T) {
BuilderIndex: primitives.BuilderIndex(7),
BeaconBlockRoot: make([]byte, 32),
Slot: 42,
StateRoot: make([]byte, 32),
}
vs := &Server{}
@@ -149,15 +188,12 @@ func TestPublishExecutionPayloadEnvelope_PreFork(t *testing.T) {
require.ErrorContains(t, "not supported before Gloas fork", err)
}
func TestGetExecutionPayloadEnvelopeRPC_StateRootAlreadySet(t *testing.T) {
func TestGetExecutionPayloadEnvelopeRPC_Success(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
stateRoot := make([]byte, 32)
stateRoot[0] = 0xAB // Non-zero state root
envelope := &ethpb.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: make([]byte, 32),
@@ -172,7 +208,6 @@ func TestGetExecutionPayloadEnvelopeRPC_StateRootAlreadySet(t *testing.T) {
BuilderIndex: primitives.BuilderIndex(0),
BeaconBlockRoot: make([]byte, 32),
Slot: 1,
StateRoot: stateRoot,
}
vs := &Server{}
@@ -184,50 +219,6 @@ func TestGetExecutionPayloadEnvelopeRPC_StateRootAlreadySet(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)
require.DeepEqual(t, envelope, resp.Envelope)
require.DeepEqual(t, stateRoot, resp.Envelope.StateRoot)
}
func TestGetExecutionPayloadEnvelopeRPC_ZeroStateRootComputesRoot(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.GloasForkEpoch = 0
params.OverrideBeaconConfig(cfg)
envelope := &ethpb.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: primitives.BuilderIndex(0),
BeaconBlockRoot: make([]byte, 32),
Slot: 1,
StateRoot: make([]byte, 32), // Zero state root triggers computation
}
// Set up a mock state gen with a Gloas state for the beacon block root.
sg := mockstategen.NewService()
st, err := util.NewBeaconStateGloas()
require.NoError(t, err)
sg.AddStateForRoot(st, [32]byte{}) // envelope.BeaconBlockRoot is all zeros
vs := &Server{
StateGen: sg,
}
vs.setExecutionPayloadEnvelope(envelope, nil)
// The call should enter the lazy computation path. It will fail during
// ApplyExecutionPayload because the mock state doesn't satisfy all consistency
// checks, but that proves we entered the zero-state-root branch.
_, err = vs.GetExecutionPayloadEnvelope(t.Context(), &ethpb.ExecutionPayloadEnvelopeRequest{
Slot: 1,
})
require.ErrorContains(t, "could not compute post-payload state root", err)
}
func TestPublishExecutionPayloadEnvelope_Success(t *testing.T) {
@@ -260,7 +251,6 @@ func TestPublishExecutionPayloadEnvelope_Success(t *testing.T) {
Slot: 1,
BuilderIndex: 0,
BeaconBlockRoot: make([]byte, 32),
StateRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}

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

@@ -26,15 +26,16 @@ func TestSubmitSignedExecutionPayloadBid_OK(t *testing.T) {
req := &ethpb.SignedExecutionPayloadBid{
Message: &ethpb.ExecutionPayloadBid{
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
BuilderIndex: 1,
Slot: 10,
Value: 100,
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20),
GasLimit: 30_000_000,
BuilderIndex: 1,
Slot: 10,
Value: 100,
ExecutionRequestsRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}

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)
@@ -3483,20 +3483,12 @@ func TestProposer_GetParentHeadState(t *testing.T) {
require.LogsContain(t, hook, "Late block attempted reorg failed")
})
t.Run("successful reorg uses payload content lookup access root", func(tt *testing.T) {
fullAccessRoot := bytesutil.ToBytes32([]byte("full-access-root"))
require.NoError(t, transition.UpdateNextSlotCache(ctx, fullAccessRoot[:], parentState))
t.Run("successful reorg uses parent root for NSC lookup", func(tt *testing.T) {
require.NoError(t, transition.UpdateNextSlotCache(ctx, parentRoot[:], parentState))
proposerServer := &Server{
ForkchoiceFetcher: &mock.ChainService{
MockPayloadContentLookup: map[[32]byte][32]byte{
parentRoot: fullAccessRoot,
},
MockPayloadContentIsFull: map[[32]byte]bool{
parentRoot: true,
},
},
StateGen: stategen.New(db, doublylinkedtree.New()),
ForkchoiceFetcher: &mock.ChainService{},
StateGen: stategen.New(db, doublylinkedtree.New()),
}
head, err := proposerServer.getParentStateFromReorgData(ctx, 1, parentRoot, parentRoot, headRoot)
@@ -3511,19 +3503,11 @@ func TestProposer_GetParentHeadState(t *testing.T) {
require.Equal(t, [32]byte(str), [32]byte(headStr))
})
t.Run("no reorg uses payload content lookup access root", func(tt *testing.T) {
fullAccessRoot := bytesutil.ToBytes32([]byte("full-access-root-no-reorg"))
require.NoError(t, transition.UpdateNextSlotCache(ctx, fullAccessRoot[:], parentState))
t.Run("no reorg uses parent root for NSC lookup", func(tt *testing.T) {
require.NoError(t, transition.UpdateNextSlotCache(ctx, headRoot[:], parentState))
proposerServer := &Server{
ForkchoiceFetcher: &mock.ChainService{
MockPayloadContentLookup: map[[32]byte][32]byte{
headRoot: fullAccessRoot,
},
MockPayloadContentIsFull: map[[32]byte]bool{
headRoot: true,
},
},
ForkchoiceFetcher: &mock.ChainService{},
HeadFetcher: &mock.ChainService{
State: headState,
Root: headRoot[:],

View File

@@ -17,7 +17,7 @@ type writeOnlyGloasFields interface {
// Builder pending payments / withdrawals.
SetBuilderPendingPayment(index primitives.Slot, payment *ethpb.BuilderPendingPayment) error
ClearBuilderPendingPayment(index primitives.Slot) error
QueueBuilderPayment() error
QueueBuilderPaymentForSlot(parentSlot primitives.Slot) error
RotateBuilderPendingPayments() error
AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal) error
@@ -71,7 +71,7 @@ type readOnlyGloasFields interface {
NextWithdrawalBuilderIndex() (primitives.BuilderIndex, error)
// Withdrawals
IsParentBlockFull() (bool, error)
LatestBlockHashMatchesBidBlockHash() (bool, error)
ExpectedWithdrawalsGloas() (ExpectedWithdrawalsGloasResult, error)
PayloadExpectedWithdrawals() ([]*enginev1.Withdrawal, error)
WithdrawalsForPayload() ([]*enginev1.Withdrawal, error)

View File

@@ -304,7 +304,7 @@ func withdrawalsEqual(a, b []*enginev1.Withdrawal) bool {
return true
}
// IsParentBlockFull returns true if the last committed payload bid was fulfilled with a payload,
// LatestBlockHashMatchesBidBlockHash returns true if the last committed payload bid was fulfilled with a payload,
// which can only happen when both beacon block and payload were present.
//
// WARNING: This must be called on a beacon state before processing the bid for the current block
@@ -315,9 +315,9 @@ func withdrawalsEqual(a, b []*enginev1.Withdrawal) bool {
// def is_parent_block_full(state: BeaconState) -> bool:
// return state.latest_execution_payload_bid.block_hash == state.latest_block_hash
// </spec>
func (b *BeaconState) IsParentBlockFull() (bool, error) {
func (b *BeaconState) LatestBlockHashMatchesBidBlockHash() (bool, error) {
if b.version < version.Gloas {
return false, errNotSupported("IsParentBlockFull", b.version)
return false, errNotSupported("LatestBlockHashMatchesBidBlockHash", b.version)
}
b.lock.RLock()
@@ -669,14 +669,14 @@ func (b *BeaconState) PayloadExpectedWithdrawals() ([]*enginev1.Withdrawal, erro
// fresh withdrawals are computed via ExpectedWithdrawalsGloas; otherwise
// the existing payload_expected_withdrawals from state are reused unchanged.
// This method does not acquire a lock directly; it delegates to
// IsParentBlockFull, ExpectedWithdrawalsGloas, and PayloadExpectedWithdrawals
// LatestBlockHashMatchesBidBlockHash, ExpectedWithdrawalsGloas, and PayloadExpectedWithdrawals
// which each acquire their own read lock.
func (b *BeaconState) WithdrawalsForPayload() ([]*enginev1.Withdrawal, error) {
if b.version < version.Gloas {
return nil, errNotSupported("WithdrawalsForPayload", b.version)
}
full, err := b.IsParentBlockFull()
full, err := b.LatestBlockHashMatchesBidBlockHash()
if err != nil {
return nil, err
}

View File

@@ -462,16 +462,16 @@ func TestExecutionPayloadAvailability(t *testing.T) {
})
}
func TestIsParentBlockFull(t *testing.T) {
func TestLatestBlockHashMatchesBidBlockHash(t *testing.T) {
t.Run("returns error before gloas", func(t *testing.T) {
st := &BeaconState{version: version.Fulu}
_, err := st.IsParentBlockFull()
_, err := st.LatestBlockHashMatchesBidBlockHash()
require.ErrorContains(t, "is not supported", err)
})
t.Run("returns false when bid is nil", func(t *testing.T) {
st := &BeaconState{version: version.Gloas}
got, err := st.IsParentBlockFull()
got, err := st.LatestBlockHashMatchesBidBlockHash()
require.NoError(t, err)
require.Equal(t, false, got)
})
@@ -486,7 +486,7 @@ func TestIsParentBlockFull(t *testing.T) {
latestBlockHash: hash,
}
got, err := st.IsParentBlockFull()
got, err := st.LatestBlockHashMatchesBidBlockHash()
require.NoError(t, err)
require.Equal(t, true, got)
})
@@ -502,7 +502,7 @@ func TestIsParentBlockFull(t *testing.T) {
latestBlockHash: other,
}
got, err := st.IsParentBlockFull()
got, err := st.LatestBlockHashMatchesBidBlockHash()
require.NoError(t, err)
require.Equal(t, false, got)
})

View File

@@ -91,18 +91,20 @@ func (b *BeaconState) SetExecutionPayloadBid(h interfaces.ROExecutionPayloadBid)
randao := h.PrevRandao()
blobKzgCommitments := h.BlobKzgCommitments()
feeRecipient := h.FeeRecipient()
executionRequestsRoot := h.ExecutionRequestsRoot()
b.latestExecutionPayloadBid = &ethpb.ExecutionPayloadBid{
ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: parentBlockRoot[:],
BlockHash: blockHash[:],
PrevRandao: randao[:],
GasLimit: h.GasLimit(),
BuilderIndex: h.BuilderIndex(),
Slot: h.Slot(),
Value: h.Value(),
ExecutionPayment: h.ExecutionPayment(),
BlobKzgCommitments: blobKzgCommitments,
FeeRecipient: feeRecipient[:],
ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: parentBlockRoot[:],
BlockHash: blockHash[:],
PrevRandao: randao[:],
GasLimit: h.GasLimit(),
BuilderIndex: h.BuilderIndex(),
Slot: h.Slot(),
Value: h.Value(),
ExecutionPayment: h.ExecutionPayment(),
BlobKzgCommitments: blobKzgCommitments,
FeeRecipient: feeRecipient[:],
ExecutionRequestsRoot: executionRequestsRoot[:],
}
b.markFieldAsDirty(types.LatestExecutionPayloadBid)
@@ -128,26 +130,35 @@ func (b *BeaconState) ClearBuilderPendingPayment(index primitives.Slot) error {
return nil
}
// QueueBuilderPayment implements the builder payment queuing logic for Gloas.
// Spec v1.7.0-alpha.0 (pseudocode):
// payment = state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH]
// amount = payment.withdrawal.amount
// if amount > 0:
//
// state.builder_pending_withdrawals.append(payment.withdrawal)
//
// state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH] = BuilderPendingPayment()
func (b *BeaconState) QueueBuilderPayment() error {
func (b *BeaconState) QueueBuilderPaymentForSlot(parentSlot primitives.Slot) error {
if b.version < version.Gloas {
return errNotSupported("QueueBuilderPayment", b.version)
return errNotSupported("QueueBuilderPaymentForSlot", b.version)
}
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
currentEpoch := slots.ToEpoch(b.slot)
parentEpoch := slots.ToEpoch(parentSlot)
if parentEpoch == currentEpoch {
return b.queueBuilderPaymentAtIndex(slotsPerEpoch + (parentSlot % slotsPerEpoch))
}
if parentEpoch+1 == currentEpoch {
return b.queueBuilderPaymentAtIndex(parentSlot % slotsPerEpoch)
}
bid := b.latestExecutionPayloadBid
if bid == nil || bid.Value == 0 {
return nil
}
return b.AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal{{
FeeRecipient: bytesutil.SafeCopyBytes(bid.FeeRecipient),
Amount: bid.Value,
BuilderIndex: bid.BuilderIndex,
}})
}
func (b *BeaconState) queueBuilderPaymentAtIndex(paymentIndex primitives.Slot) error {
b.lock.Lock()
defer b.lock.Unlock()
slot := b.slot
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
paymentIndex := slotsPerEpoch + (slot % slotsPerEpoch)
if uint64(paymentIndex) >= uint64(len(b.builderPendingPayments)) {
return fmt.Errorf("builder pending payments index %d out of range (len=%d)", paymentIndex, len(b.builderPendingPayments))
}

View File

@@ -49,8 +49,9 @@ func (t testExecutionPayloadBid) BlobKzgCommitments() [][]byte { return t.blobKz
func (t testExecutionPayloadBid) BlobKzgCommitmentCount() uint64 {
return uint64(len(t.blobKzgCommitments))
}
func (t testExecutionPayloadBid) FeeRecipient() [20]byte { return t.feeRecipient }
func (t testExecutionPayloadBid) IsNil() bool { return false }
func (t testExecutionPayloadBid) FeeRecipient() [20]byte { return t.feeRecipient }
func (t testExecutionPayloadBid) ExecutionRequestsRoot() [32]byte { return [32]byte{} }
func (t testExecutionPayloadBid) IsNil() bool { return false }
func TestSetExecutionPayloadBid(t *testing.T) {
t.Run("previous fork returns expected error", func(t *testing.T) {
@@ -189,80 +190,6 @@ func TestClearBuilderPendingPayment(t *testing.T) {
})
}
func TestQueueBuilderPayment(t *testing.T) {
t.Run("previous fork returns expected error", func(t *testing.T) {
st := &BeaconState{version: version.Fulu}
err := st.QueueBuilderPayment()
require.ErrorContains(t, "is not supported", err)
})
t.Run("appends withdrawal, clears payment, and marks dirty", func(t *testing.T) {
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
slot := primitives.Slot(3)
paymentIndex := slotsPerEpoch + (slot % slotsPerEpoch)
st := &BeaconState{
version: version.Gloas,
slot: slot,
dirtyFields: make(map[types.FieldIndex]bool),
rebuildTrie: make(map[types.FieldIndex]bool),
sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference),
builderPendingPayments: make([]*ethpb.BuilderPendingPayment, slotsPerEpoch*2),
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
}
st.builderPendingPayments[paymentIndex] = &ethpb.BuilderPendingPayment{
Weight: 1,
Withdrawal: &ethpb.BuilderPendingWithdrawal{
FeeRecipient: bytes.Repeat([]byte{0xAB}, 20),
Amount: 99,
BuilderIndex: 1,
},
}
require.NoError(t, st.QueueBuilderPayment())
require.DeepEqual(t, emptyBuilderPendingPayment, st.builderPendingPayments[paymentIndex])
require.Equal(t, true, st.dirtyFields[types.BuilderPendingPayments])
require.Equal(t, true, st.dirtyFields[types.BuilderPendingWithdrawals])
require.Equal(t, 1, len(st.builderPendingWithdrawals))
require.DeepEqual(t, bytes.Repeat([]byte{0xAB}, 20), st.builderPendingWithdrawals[0].FeeRecipient)
require.Equal(t, primitives.Gwei(99), st.builderPendingWithdrawals[0].Amount)
// Ensure copied withdrawal is not aliased.
st.builderPendingPayments[paymentIndex].Withdrawal.FeeRecipient[0] = 0x01
require.Equal(t, byte(0xAB), st.builderPendingWithdrawals[0].FeeRecipient[0])
})
t.Run("zero amount does not append withdrawal", func(t *testing.T) {
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
slot := primitives.Slot(3)
paymentIndex := slotsPerEpoch + (slot % slotsPerEpoch)
st := &BeaconState{
version: version.Gloas,
slot: slot,
dirtyFields: make(map[types.FieldIndex]bool),
rebuildTrie: make(map[types.FieldIndex]bool),
sharedFieldReferences: make(map[types.FieldIndex]*stateutil.Reference),
builderPendingPayments: make([]*ethpb.BuilderPendingPayment, slotsPerEpoch*2),
builderPendingWithdrawals: []*ethpb.BuilderPendingWithdrawal{},
}
st.builderPendingPayments[paymentIndex] = &ethpb.BuilderPendingPayment{
Weight: 1,
Withdrawal: &ethpb.BuilderPendingWithdrawal{
FeeRecipient: bytes.Repeat([]byte{0xAB}, 20),
Amount: 0,
BuilderIndex: 1,
},
}
require.NoError(t, st.QueueBuilderPayment())
require.DeepEqual(t, emptyBuilderPendingPayment, st.builderPendingPayments[paymentIndex])
require.Equal(t, true, st.dirtyFields[types.BuilderPendingPayments])
require.Equal(t, false, st.dirtyFields[types.BuilderPendingWithdrawals])
require.Equal(t, 0, len(st.builderPendingWithdrawals))
})
}
func TestUpdatePendingPaymentWeight(t *testing.T) {
cfg := params.BeaconConfig()
slotsPerEpoch := cfg.SlotsPerEpoch

View File

@@ -20,7 +20,6 @@ go_library(
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/state/stategen",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/gloas:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/core/transition:go_default_library",

View File

@@ -5,11 +5,9 @@ import (
stderrors "errors"
"fmt"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/filters"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
@@ -17,7 +15,6 @@ import (
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/pkg/errors"
)
@@ -229,34 +226,6 @@ func (s *State) DeleteStateFromCaches(_ context.Context, blockRoot [32]byte) err
return s.epochBoundaryStateCache.delete(blockRoot)
}
// This function is a wrapper that loads the state by block hash, this function would error if the block hash is not in DB.
func (s *State) loadStateByBlockHash(ctx context.Context, blockHash [32]byte, slot primitives.Slot) (state.BeaconState, error) {
blockRoot, err := s.blockRootForExecHash(ctx, blockHash, slot)
if err != nil {
return nil, errors.Wrapf(err, "could not resolve block hash %#x to beacon block root", blockHash)
}
blockState, err := s.loadStateByRoot(ctx, blockRoot)
if err != nil {
return nil, errors.Wrapf(err, "could not load state by resolved beacon block root %#x for execution block hash %#x", blockRoot, blockHash)
}
blk, err := s.beaconDB.Block(ctx, blockRoot)
if err != nil {
return nil, errors.Wrapf(err, "could not load block by resolved beacon block root %#x for execution block hash %#x", blockRoot, blockHash)
}
signedEnvelope, err := s.beaconDB.ExecutionPayloadEnvelope(ctx, blockRoot)
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve execution payload envelope for block with root %#x at slot %d", blockRoot, slot)
}
envelope, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(signedEnvelope.GetMessage())
if err != nil {
return nil, errors.Wrapf(err, "could not wrap blinded execution payload envelope for block with root %#x at slot %d", blockRoot, slot)
}
if err := gloas.ProcessBlindedExecutionPayload(ctx, blockState, blk.Block().StateRoot(), envelope); err != nil {
return nil, errors.Wrapf(err, "could not apply execution payload envelope for block with root %#x at slot %d", blockRoot, slot)
}
return blockState, nil
}
// This loads a beacon state from either the cache or DB, then replays blocks up the slot of the requested block root.
func (s *State) loadStateByRoot(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) {
ctx, span := trace.StartSpan(ctx, "stateGen.loadStateByRoot")
@@ -322,13 +291,6 @@ func (s *State) loadStateByRootFromDBOrReplay(ctx context.Context, blockRoot [32
}
targetSlot := summary.Slot
// If blockRoot is not a beacon block root (e.g. it's a Gloas execution block hash
// used as state key after envelope processing), resolve it to the beacon block root
// so that latestAncestor and loadBlocks can walk the block chain.
if !s.beaconDB.HasBlock(ctx, blockRoot) {
return s.loadStateByBlockHash(ctx, blockRoot, targetSlot)
}
// Since the requested state is not in caches or DB, start replaying using the last
// available ancestor state which is retrieved using input block's root.
startState, err := s.latestAncestor(ctx, blockRoot)
@@ -352,29 +314,6 @@ func (s *State) loadStateByRootFromDBOrReplay(ctx context.Context, blockRoot [32
return s.replayBlocks(ctx, startState, blks, targetSlot)
}
// blockRootForExecHash resolves a Gloas execution block hash to the corresponding
// beacon block root by finding the block at the given slot whose bid commits to that hash.
func (s *State) blockRootForExecHash(ctx context.Context, execHash [32]byte, slot primitives.Slot) ([32]byte, error) {
f := filters.NewFilter().SetStartSlot(slot).SetEndSlot(slot)
blks, roots, err := s.beaconDB.Blocks(ctx, f)
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not query blocks by slot")
}
for i, blk := range blks {
if blk.Block().Version() < version.Gloas {
continue
}
bid, err := blk.Block().Body().SignedExecutionPayloadBid()
if err != nil || bid == nil || bid.Message == nil || len(bid.Message.BlockHash) != 32 {
continue
}
if [32]byte(bid.Message.BlockHash) == execHash {
return roots[i], nil
}
}
return [32]byte{}, fmt.Errorf("no block at slot %d with execution block hash %#x", slot, execHash)
}
// latestAncestor returns the highest available ancestor state of the input block root.
// It recursively looks up block's parent until a corresponding state of the block root
// is found in the caches or DB.

View File

@@ -553,58 +553,6 @@ func TestLoadStateByRoot(t *testing.T) {
}
}
func TestBlockRootForExecHash_Found(t *testing.T) {
ctx := t.Context()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
blockHash := bytesutil.PadTo([]byte{0xCC}, 32)
b := util.NewBeaconBlockGloas()
b.Block.Slot = 10
b.Block.Body.SignedExecutionPayloadBid.Message.BlockHash = blockHash
wsb, err := blt.NewSignedBeaconBlock(b)
require.NoError(t, err)
require.NoError(t, beaconDB.SaveBlock(ctx, wsb))
expectedRoot, err := b.Block.HashTreeRoot()
require.NoError(t, err)
root, err := service.blockRootForExecHash(ctx, bytesutil.ToBytes32(blockHash), 10)
require.NoError(t, err)
require.Equal(t, expectedRoot, root)
}
func TestBlockRootForExecHash_NotFound(t *testing.T) {
ctx := t.Context()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
b := util.NewBeaconBlockGloas()
b.Block.Slot = 10
b.Block.Body.SignedExecutionPayloadBid.Message.BlockHash = bytesutil.PadTo([]byte{0xAA}, 32)
wsb, err := blt.NewSignedBeaconBlock(b)
require.NoError(t, err)
require.NoError(t, beaconDB.SaveBlock(ctx, wsb))
wrongHash := bytesutil.ToBytes32(bytesutil.PadTo([]byte{0xBB}, 32))
_, err = service.blockRootForExecHash(ctx, wrongHash, 10)
require.ErrorContains(t, "no block at slot", err)
}
func TestBlockRootForExecHash_SkipsPreGloas(t *testing.T) {
ctx := t.Context()
beaconDB := testDB.SetupDB(t)
service := New(beaconDB, doublylinkedtree.New())
b := util.NewBeaconBlock()
b.Block.Slot = 10
wsb, err := blt.NewSignedBeaconBlock(b)
require.NoError(t, err)
require.NoError(t, beaconDB.SaveBlock(ctx, wsb))
_, err = service.blockRootForExecHash(ctx, [32]byte{}, 10)
require.ErrorContains(t, "no block at slot", err)
}
func TestLastAncestorState_CanGetUsingDB(t *testing.T) {
ctx := t.Context()
beaconDB := testDB.SetupDB(t)

View File

@@ -1,12 +1,10 @@
package stategen
import (
"bytes"
"context"
"fmt"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/filters"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
@@ -14,7 +12,6 @@ import (
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -39,17 +36,7 @@ func (s *State) replayBlocks(
})
rLog.Debug("Replaying state")
// For Gloas states: if the first replay block's bid parentBlockHash doesn't
// match the ancestor state's latestBlockHash, the ancestor block's execution
// payload was delivered but the state is post-CL (EL not yet applied). Apply
// the ancestor's envelope to bring the state to post-EL before replaying.
if len(signed) > 0 && state.Version() >= version.Gloas && signed[0].Block().Version() >= version.Gloas {
if err := s.maybeApplyAncestorEnvelope(ctx, state, signed[0]); err != nil {
return nil, errors.Wrap(err, "could not apply ancestor execution payload envelope")
}
}
for i, blk := range signed {
for _, blk := range signed {
if ctx.Err() != nil {
return nil, ctx.Err()
}
@@ -62,44 +49,10 @@ func (s *State) replayBlocks(
continue
}
var envelope *ethpb.SignedBlindedExecutionPayloadEnvelope
if i < len(signed)-1 && blk.Block().Version() >= version.Gloas {
bid, err := blk.Block().Body().SignedExecutionPayloadBid()
if err != nil {
return nil, errors.Wrapf(err, "could not get execution payload bid for block at slot %d", blk.Block().Slot())
}
if bid == nil || bid.Message == nil {
return nil, fmt.Errorf("missing execution payload bid for block at slot %d", blk.Block().Slot())
}
child := signed[i+1].Block()
childBid, err := child.Body().SignedExecutionPayloadBid()
if err != nil {
return nil, errors.Wrapf(err, "could not get execution payload bid for block at slot %d", child.Slot())
}
if childBid == nil || childBid.Message == nil {
return nil, fmt.Errorf("missing execution payload bid for block at slot %d", child.Slot())
}
if bytes.Equal(childBid.Message.ParentBlockHash, bid.Message.BlockHash) {
root := child.ParentRoot()
envelope, err = s.beaconDB.ExecutionPayloadEnvelope(ctx, root)
if err != nil {
return nil, errors.Wrapf(err, "could not retrieve execution payload envelope for block with root %#x at slot %d", root, slot)
}
}
}
state, err = executeStateTransitionStateGen(ctx, state, blk)
if err != nil {
return nil, errors.Wrapf(err, "could not execute state transition for block at slot %d", slot)
}
if envelope != nil && envelope.Message != nil {
wrappedEnvelope, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(envelope.Message)
if err != nil {
return nil, errors.Wrapf(err, "could not wrap blinded execution payload envelope for block at slot %d", slot)
}
if err := gloas.ProcessBlindedExecutionPayload(ctx, state, blk.Block().StateRoot(), wrappedEnvelope); err != nil {
return nil, errors.Wrapf(err, "could not apply execution payload envelope for block at slot %d", slot)
}
}
}
duration := time.Since(start)
@@ -112,50 +65,6 @@ func (s *State) replayBlocks(
return state, nil
}
// maybeApplyAncestorEnvelope checks whether the ancestor state needs its
// execution payload envelope applied before block replay can proceed. This is
// needed when the ancestor state is post-CL (latestBlockHash not yet updated
// with the delivered payload). The check compares the first replay block's bid
// parentBlockHash with the state's latestBlockHash: a mismatch means the
// ancestor's payload was delivered but the state doesn't reflect it.
func (s *State) maybeApplyAncestorEnvelope(
ctx context.Context,
st state.BeaconState,
firstBlock interfaces.ReadOnlySignedBeaconBlock,
) error {
firstBid, err := firstBlock.Block().Body().SignedExecutionPayloadBid()
if err != nil || firstBid == nil || firstBid.Message == nil {
return nil
}
latestHash, err := st.LatestBlockHash()
if err != nil {
return err
}
if bytes.Equal(firstBid.Message.ParentBlockHash, latestHash[:]) {
return nil
}
// The first block expects a different latestBlockHash than what the state
// has: the ancestor's execution payload was delivered but the state is
// post-CL. Apply the ancestor's envelope.
ancestorRoot := firstBlock.Block().ParentRoot()
envelope, err := s.beaconDB.ExecutionPayloadEnvelope(ctx, ancestorRoot)
if err != nil {
return errors.Wrapf(err, "could not retrieve execution payload envelope for ancestor root %#x", ancestorRoot)
}
if envelope == nil || envelope.Message == nil {
return errors.Errorf("Received nil execution payload envelope for ancestor root %#x", ancestorRoot)
}
ancestorBlock, err := s.beaconDB.Block(ctx, ancestorRoot)
if err != nil {
return errors.Wrapf(err, "could not retrieve ancestor block for root %#x", ancestorRoot)
}
wrappedEnvelope, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(envelope.Message)
if err != nil {
return errors.Wrap(err, "could not wrap ancestor blinded execution payload envelope")
}
return gloas.ProcessBlindedExecutionPayload(ctx, st, ancestorBlock.Block().StateRoot(), wrappedEnvelope)
}
// loadBlocks loads the blocks between start slot and end slot by recursively fetching from end block root.
// The Blocks are returned in slot-descending order.
func (s *State) loadBlocks(ctx context.Context, startSlot, endSlot primitives.Slot, endBlockRoot [32]byte) ([]interfaces.ReadOnlySignedBeaconBlock, error) {

View File

@@ -5,15 +5,10 @@ import (
"fmt"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"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/monitoring/tracing/trace"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -67,10 +62,6 @@ type chainer interface {
chainForSlot(ctx context.Context, target primitives.Slot) (state.BeaconState, []interfaces.ReadOnlySignedBeaconBlock, error)
}
type executionPayloadEnvelopeProvider interface {
executionPayloadEnvelope(ctx context.Context, blockRoot [32]byte) (*ethpb.SignedBlindedExecutionPayloadEnvelope, error)
}
type stateReplayer struct {
target primitives.Slot
method retrievalMethod
@@ -112,7 +103,7 @@ func (rs *stateReplayer) ReplayBlocks(ctx context.Context) (state.BeaconState, e
"diff": diff,
}).Debug("Replaying canonical blocks from most recent state")
for i, b := range descendants {
for _, b := range descendants {
if ctx.Err() != nil {
return nil, ctx.Err()
}
@@ -122,29 +113,6 @@ func (rs *stateReplayer) ReplayBlocks(ctx context.Context) (state.BeaconState, e
return nil, errors.Wrap(err, "could not execute state transition")
}
// Apply the envelope for all blocks except the last one.
// The caller is responsible for applying the envelope on the last block if needed.
if i < len(descendants)-1 && b.Version() >= version.Gloas {
if p, ok := rs.chainer.(executionPayloadEnvelopeProvider); ok {
root, err := b.Block().HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not compute block root for execution payload envelope lookup")
}
signedEnvelope, err := p.executionPayloadEnvelope(ctx, root)
if err != nil && !errors.Is(err, db.ErrNotFound) {
return nil, errors.Wrap(err, "could not retrieve execution payload envelope")
}
if signedEnvelope != nil && signedEnvelope.Message != nil {
envelope, err := blocks.WrappedROBlindedExecutionPayloadEnvelope(signedEnvelope.Message)
if err != nil {
return nil, errors.Wrap(err, "could not wrap blinded execution payload envelope")
}
if err := gloas.ProcessBlindedExecutionPayload(ctx, s, b.Block().StateRoot(), envelope); err != nil {
return nil, errors.Wrap(err, "could not apply execution payload envelope")
}
}
}
}
}
if rs.target > s.Slot() {
s, err = ReplayProcessSlots(ctx, s, rs.target)

View File

@@ -153,7 +153,7 @@ func DownloadFinalizedData(ctx context.Context, client *beacon.Client) (*OriginD
WithField("blockRoot", hexutil.Encode(br[:])).
Info("Downloaded checkpoint sync state and block.")
if s.Version() >= version.Gloas {
if full, err := s.IsParentBlockFull(); err == nil && full {
if full, err := s.LatestBlockHashMatchesBidBlockHash(); err == nil && full {
log.Warn("Checkpoint sync state has payload already applied")
}
}

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

@@ -34,7 +34,6 @@ func makeEnvelope(t *testing.T, slot primitives.Slot, blockHash [32]byte, parent
Message: &ethpb.ExecutionPayloadEnvelope{
Slot: slot,
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
StateRoot: make([]byte, fieldparams.RootLength),
ExecutionRequests: &enginev1.ExecutionRequests{},
Payload: &enginev1.ExecutionPayloadDeneb{
ParentHash: parentHash[:],

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.

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